一、整体架构概览
ubuntu(开发主机)
/home/linux/nfs/ ← 通过 NFS 共享到开发板
|
NFS(网络文件系统)
|
ARM 开发板 /mnt/ ← 挂载点
开发板存储介质:
- 内存(RAM,512MB) ← 断电丢失,系统在此运行
- eMMC(8GB,芯片形式SD卡) ← 产品最终部署介质
- SD 卡 ← 开发阶段推荐,可反复擦写
开发板连接方式:
- 串口(底层调试,内核启动早期唯一调试手段)
- 有线网络(NFS 文件共享 / TFTP 内核下载)
- WiFi
三大核心任务:
- 移植 Linux 系统到 ARM 平台(烧写 bootloader + kernel + rootfs)
- 在 ubuntu 上交叉编译应用程序(在A平台编译,B平台运行)
- 将编译好的应用程序通过 NFS 部署到 ARM 上运行
⚠️ 关键原则:必须使用与目标平台匹配的交叉编译器,不匹配则程序无法执行。
二、应用开发 vs 内核开发
| 对比项 | 应用程序开发 | 内核/驱动开发 |
|---|---|---|
| 调试接口 | 网络(telnet)/ NFS 挂载目录 | 串口(唯一底层调试手段) |
| 可用时机 | 操作系统启动完成后 | 内核启动早期即需介入 |
| 编译工具 | 交叉编译器(arm-linux-gnueabi-gcc) | 同上,但需内核源码配合 |
| 运行环境 | 用户空间,依赖 OS | 内核空间,直接操作硬件 |
| 出错后果 | 进程崩溃,系统不受影响 | 整机宕机 / 内核 panic |
⚠️ 串口是内核开发的生命线,网络协议(telnet 等)仅在操作系统启动后才可用,无法用于内核早期调试。
三、Linux 系统三大组成部分
Linux 系统 = bootloader + kernel + rootfs,三者缺一不可:
存储介质(SD卡 / eMMC)
├── bootloader 分区 ← 裸机程序,引导内核
├── kernel 分区 ← 内核镜像(zImage + dtb)
└── rootfs 分区 ← 根文件系统(所有文件的集合)
重要认知: 烧写到开发板的是一个完整的操作系统镜像(类似 Windows 安装盘),而非仅仅是内核。根文件系统(rootfs)成功挂载才标志着系统启动完成,而不是内核运行结束。
三者关系简述
| 组件 | 本质 | 职责 |
|---|---|---|
| bootloader(uboot) | 裸机程序 | 初始化硬件,搬移内核,引导启动 |
| kernel(内核) | 操作系统核心 | 管理硬件资源,提供系统服务 |
| rootfs(根文件系统) | 文件集合 | 提供命令、库、配置、用户程序 |
四、Linux 完整启动流程(核心重点)
4.1 三阶段启动概览
系统上电
↓
① bootloader(uboot)------ 裸机程序,为内核启动准备环境
↓ CPU 控制权移交给内核
② kernel(内核)------ 操作系统核心程序
↓ 挂载根文件系统
③ rootfs 挂载 + init 进程启动 ------ 进入用户空间
4.2 bootloader(uboot)详解
作用: 裸机程序,是内核运行前的"搬运工"和"环境准备者"。任何具备"为内核准备环境并引导启动"功能的程序都可称为 bootloader,uboot 是其中最常见的实现。
完整初始化顺序(重要!必背):
1. 初始化异常向量表
2. 初始化 CPU(工作模式)
3. 初始化栈
4. 初始化时钟
5. 关看门狗
6. 关闭 Cache ← ⚠️ 数据Cache必须关闭(否则DDR初始化结果不一致)
指令Cache 可选
7. 关 MMU ← ⚠️ 页表未建立,开MMU会导致地址翻译出错
8. 关中断
9. 初始化内存(DDR)
10. 初始化相关设备(串口、网口)
11. 集成相关协议(tftp 等)
12. 搬移内核到内存
13. 向内核传参 ← ⚠️ 传递:根文件系统类型 / init进程路径 / 控制台 / IP
14. 引导内核启动
↓
bootloader 不再控制 CPU,CPU 控制权移交给内核
4.3 kernel(内核)详解
内核管理的六大模块:
| 模块 | 说明 |
|---|---|
| 文件管理 | 文件系统读写 |
| 进程管理 | 进程创建、调度 |
| 内存管理 | 虚拟内存、页管理 |
| 网络管理 | 网络协议栈 |
| 设备管理 | 驱动框架 |
| IPC | 进程间通信 |
内核启动最后阶段:
挂载根文件系统(rootfs) ← 系统启动完成的标志
↓
启动 init 进程(由内核 init 退化/exec 而来)
↓
init(PID=1)→ 其他进程 → shell → 用户应用程序(userapp)
⚠️ init 进程是用户空间的第一个进程(PID=1),所有其他进程都是它的子孙。
4.4 rootfs(根文件系统)详解
作用: 第一个被挂载的文件系统,挂载点为 /,其成功挂载标志着系统启动完成。
rootfs/
├── 配置文件 (系统启动配置)
├── 系统命令 (ls, cp, mount 等)
├── 库 (libc 等共享库)
├── 用户程序 (应用程序)
└── 普通文件 (文本、mp3、jpg 等)
五、系统上电物理流程(以 SD 卡启动为例)
系统上电
↓
芯片 Boot ROM 读取拨码开关,选择启动介质(SD卡)
↓
① CPU 拷贝 bootloader【前半部分】到 OCRAM(片上RAM)执行
(此时 DDR 未初始化,只能用片上 OCRAM)
↓
② bootloader 前半部分初始化 DDR 内存
↓
③ bootloader 将自身【后半部分】搬移到 DDR 中执行
↓
④ bootloader 搬移内核到 DDR 指定地址(0x80800000)
↓
⑤ bootloader 设置 PC = 0x80800000,跳转至内核入口
↓
⑥ 内核运行,最终挂载 rootfs,启动 init
内核来源(两种情况):
| 场景 | bootloader 需要做的事 |
|---|---|
| 内核在 SD 卡中 | 初始化SD卡控制器,读 kernel 分区写入 DDR |
| 内核在 ubuntu 上(开发阶段) | 初始化有线网卡 + 集成tftp协议,下载内核到 DDR |
根文件系统来源(两种情况):
| 场景 | 挂载方式 |
|---|---|
| rootfs 在 SD 卡中(量产) | 内核直接挂载 SD 卡的 rootfs 分区 |
| rootfs 在 ubuntu 上(开发阶段) | 内核通过 NFS 挂载 ubuntu 的 rootfs 目录,bootloader 传参需包含 IP |
六、内存地址映射与代码类型(重点)
6.1 内存地址映射
DDR 物理地址空间:
0x80000000 ← DDR 起始地址
...
0x80800000 ← 内核默认加载地址(zImage 解压运行地址)
...
0x83000000 ← 设备树(dtb)加载地址
...
⚠️ 内核加载地址 0x80800000 由硬件内存布局决定,不可随意更改 ,因内核内部使用绝对跳转指令(
ldr pc, =0x80800000),属于地址相关代码。
6.2 两种代码类型
| 类型 | 说明 | 使用场景 |
|---|---|---|
| 地址无关代码 | 加载地址和链接地址无关,放任意地址都能运行 | bootloader 前半部分(在 OCRAM 运行) |
| 地址相关代码 | 加载地址和链接地址必须保持一致,否则跑飞 | bootloader 后半部分、内核(在 DDR 运行) |
6.3 两种跳转指令
| 类型 | 指令 | 原理 | 特点 |
|---|---|---|---|
| 相对跳转(短跳转) | bl |
靠 PC 的偏移量跳转 | 地址无关,适合前半段 |
| 绝对跳转(长跳转) | ldr pc, =addr |
向 PC 赋一个绝对地址 | 地址相关,适合搬移后执行 |
在 bootloader 中的体现:
bootloader 前半部分(OCRAM 运行)
→ 加载地址(OCRAM)≠ 链接地址(DDR)
→ 必须使用相对跳转 bl,即地址无关代码
bootloader 将自身搬移到 DDR 后
→ 加载地址 = 链接地址
→ 可以使用绝对跳转 ldr pc, =addr
七、网络配置
7.1 ubuntu 有线连接开发板(静态 IP)
1. VMware → 设置 → 网络适配器 → 更改为【桥接模式】
2. VMware → 编辑 → 虚拟网络编辑器 → Vmnet0 桥接到【有线网卡】
3. 修改网络配置文件:sudo vim /etc/network/interfaces
4. 重启网络服务
bash
# /etc/network/interfaces
auto ens33
iface ens33 inet static
address 192.168.1.3
netmask 255.255.255.0
gateway 192.168.1.1
7.2 ubuntu 无线上网(DHCP)
1. VMware → 设置 → 网络适配器 → 更改为【NAT 模式】
2. VMware → 编辑 → 虚拟网络编辑器 → Vmnet0 桥接到【无线网卡】
3. 修改网络配置文件:sudo vim /etc/network/interfaces
4. 重启网络服务
bash
# /etc/network/interfaces
auto ens33
iface ens33 inet dhcp
7.3 开发板 IP 配置
bash
# 在开发板 Linux 命令行执行(与主机同网段,IP不同)
ifconfig eth0 192.168.1.101 netmask 255.255.255.0
# 验证与主机连通性
ping 192.168.1.3
八、NFS 配置与挂载
8.1 ubuntu 端配置 NFS 服务
bash
# 编辑导出配置文件
sudo vim /etc/exports
# 添加以下内容(允许所有客户端读写挂载)
/home/linux/nfs *(rw,sync,no_root_squash)
# 重启 NFS 服务
sudo /etc/init.d/nfs-kernel-server restart
⚠️
no_root_squash表示客户端 root 用户在服务端也具有 root 权限,开发阶段必须加此选项,否则开发板上操作文件会遇到权限问题。
8.2 开发板端挂载 NFS
bash
# 挂载 ubuntu 共享目录到开发板 /mnt
mount -t nfs -o nolock,nfsvers=3 192.168.1.3:/home/linux/nfs /mnt
# 验证挂载(能看到 ubuntu 上的文件即成功)
ls /mnt
# 验证双向同步
touch /mnt/test_file # 开发板创建文件
# 在 ubuntu 上 ls /home/linux/nfs 能看到 test_file ✓
# 卸载(使用完毕后必须卸载,否则原目录文件会被隐藏)
umount /mnt
⚠️ 必须指定
nfsvers=3,否则可能因版本不兼容导致挂载失败。
九、系统烧写
9.1 存储介质选择原则
| 介质 | 适用阶段 | 原因 |
|---|---|---|
| SD 卡 | 开发阶段(推荐) | 可反复擦写,避免耗尽 eMMC 寿命 |
| eMMC | 产品最终部署 | 稳定后才进行烧写,速度快 |
⚠️ 操作系统必须烧写到非易失性存储器(eMMC/SD卡),不能烧写到内存(DDR),因为内存断电后数据丢失。
9.2 烧写流程(MFG 工具 + USB OTG)
1. 开发板断电
2. 拨码开关拨至 USB 模式
3. 插入 SD 卡,连接 USB 线到主机
4. 上电,MFG 工具识别到设备
5. 点击"开始"烧写镜像
6. 烧写完成后,拨码开关拨回 SD 卡启动模式
7. 重新上电,系统从 SD 卡启动 ✓
十、交叉编译开发流程
10.1 交叉编译概念
裸机开发: 下载(Download)程序到目标板
Linux开发:移植(Porting)/ 交叉编译
交叉编译 = 在 A 平台(x86 Ubuntu)编译,在 B 平台(ARM)运行
10.2 工具链安装
bash
# 解压交叉编译工具链(以 arm-linux-gnueabi-gcc 为例)
tar -xvf arm-linux-gnueabihf-gcc-xxx.tar.bz2 -C /usr/local/
# 配置环境变量
vim ~/.bashrc
# 添加:
export PATH=$PATH:/usr/local/arm/bin
# 使配置生效
source ~/.bashrc
# 验证安装
arm-linux-gnueabihf-gcc -v
10.3 完整交叉开发闭环
Ubuntu 主机
│
├─ 1. 用交叉编译器编译程序
│ arm-linux-gnueabihf-gcc hello.c -o arm-app
│
├─ 2. 程序自动出现在 NFS 共享目录
│ /home/linux/nfs/arm-app
│
└─ 3. 开发板通过 NFS 挂载后直接运行
/mnt/arm-app ← 直接执行,无需手动拷贝
ARM 开发板
├─ 挂载 NFS:mount -t nfs ... /mnt
├─ 运行程序:/mnt/arm-app
└─ 如需离线运行:cp /mnt/arm-app /usr/bin/ 再 umount
十一、完整开发流程总结
ubuntu 开发主机
│
├── 交叉编译内核(zImage + dtb)→ 放入 TFTP 目录
├── 交叉编译应用程序 → 放入 NFS 目录
├── 配置 NFS 服务(共享 rootfs)
└── 配置 TFTP 服务(提供内核下载)
│
│ 有线网络
▼
ARM 开发板
├── uboot 通过 TFTP 下载内核 → bootz 启动
├── 内核通过 NFS 挂载 rootfs
└── 用户通过 NFS 目录开发调试应用程序
十二、关键命令速查
bash
# ===== Ubuntu 主机端 =====
# 修改网络配置
sudo vim /etc/network/interfaces
# 重启网络服务
sudo /etc/init.d/networking restart
sudo systemctl restart networking
# 重启 NFS 服务
sudo /etc/init.d/nfs-kernel-server restart
# 交叉编译程序
arm-linux-gnueabihf-gcc hello.c -o arm-app
# ===== 开发板端 =====
# 设置 IP
ifconfig eth0 192.168.1.101 netmask 255.255.255.0
# 挂载 NFS(完整格式)
mount -t nfs -o nolock,nfsvers=3 192.168.1.3:/home/linux/nfs /mnt
# 卸载 NFS
umount /mnt
# 验证网络
ping 192.168.1.3
十三、常见概念速查
| 概念 | 说明 |
|---|---|
| 交叉编译 | 在 x86 ubuntu 上编译出能在 ARM 上运行的程序 |
| NFS | 网络文件系统,ubuntu 共享目录给开发板,双向实时同步 |
| TFTP | 简单文件传输协议,uboot 阶段通过它下载内核 |
| OCRAM | 片上 RAM,CPU 上电后可直接访问,用于加载 bootloader 前半部分 |
| eMMC | 芯片形式的SD卡,焊接在板上,容量大(8GB) |
| dtb | 设备树二进制文件,描述硬件信息,内核启动时读取 |
| init 进程 | 用户空间第一个进程,PID=1,所有进程的祖先 |
| zImage | 压缩后的内核镜像,默认加载地址 0x80800000 |
| MFG 工具 | NXP 官方烧写工具,通过 USB OTG 烧写镜像到 SD卡/eMMC |
| bootloader | 不特指 uboot,泛指一切"为内核准备环境并引导启动"的程序 |
| 地址无关代码 | 加载地址与链接地址无关,可放任意位置运行 |
| 地址相关代码 | 加载地址必须等于链接地址,否则跑飞 |