系统移植【总结】
📖 二、什么是 BSP
BSP = Board Support Package(板级支持包)。
硬件做出来之后,要让操作系统在上面跑起来,需要一整套底层软件。这套软件就叫 BSP。
BSP 包
├── Bootloader → 系统移植的 U-Boot
├── Linux Kernel → 系统移植的内核
└── Root File System → 系统移植的根文件系统
🗺️ 四、BSP 源码包结构预览
public_sources/
├── kernel/ # Linux 内核源码(改得最多的地方)
├── bootloader/ # U-Boot 源码
├── nvidia/ # NVIDIA 专有驱动和模块
└── ...
交叉编译工具链
# 编辑启动脚本,在末尾追加一行
vim ~/.bashrc
# 添加这一行(路径换成你 pwd 出来的结果):
export PATH=/home/yhai/store/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/bin:$PATH
# 立即生效
source ~/.bashrc
内核编译
阶段一:解压搭建 阶段二:内核编译 阶段三:安装+构建
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ tar BSP框架 │ │ 改 Makefile │ │ cp Image │
│ tar public源码 │ ──→ │ 设置 LOCALVERSION│ ──→ │ cp dtb │
│ tar 内核源码 │ │ make defconfig│ │ make modules │
│ tar rootfs │ │ make menuconfig│ │ apply_binaries│
└──────────────┘ │ make -j4 │ └──────────────┘
└──────────────┘
BSP框架(空壳) + public_sources + rootfs镜像
↓ ↓ ↓
[解压] [解压] [解压到rootfs/]
↓ ↓ ↓
Linux_for_Tegra/ kernel源码 rootfs文件系统
│ │ │
│ [阶段二:编译] │
│ make -j4 │
│ │ │
│ Image + dtb + .ko │
│ │ │
└───────┬───────┴────────┬─────────┘
│ │
[阶段三:拷贝到对应目录]
│
sudo ./apply_binaries.sh
│
最终刷机镜像 ✅
目录结构总览
~/bsp/ ← 老师自建的根目录
├── Jetson-210_Linux_R32.6.1_aarch64.tbz2 ← BSP 开发框架(空壳)
├── public_sources.tbz2 ← BSP 源码离线包
├── Tegra_Linux_Sample-Root-Filesystem_* ← rootfs 镜像
└── Linux_for_Tegra/ ← 解压后生成的框架目录(核心)
├── kernel/ ← 刷机时从此处取 Image 和 dtb
│ ├── Image ← 内核镜像(原厂预编译)
│ └── dtb/ ← 设备树文件(原厂预编译)
├── rootfs/ ← 根文件系统内容
└── source/ ← 源码目录(public_sources 解压到这里)
└── public/
├── kernel/ ← 内核源码(kernel-4.9)
├── hardware/ ← 硬件相关(设备树 + 头文件)
├── bootloader/ ← U-Boot 源码
└── ...
刷机
拔 SD 卡 → 插跳线帽 → micro USB 连电脑 → 板子上电
│
├── lsusb 看到 NVIDIA Corp → ✅ 恢复模式
│
└── sudo ./flash.sh jetson-nano-qspi-sd mmcblk0p1
│
└── The target t210ref has been flashed successfully.
│
└── 拔跳线帽 → 重启 → 初始设置 → 完成 ✅
📖 一、U-Boot 是什么
板子上电后运行的第一个程序。此时没有任何操作系统,U-Boot 是一段裸机代码,直接操作硬件。
上电 → ROM Boot → U-Boot (SPL) → U-Boot (完整版) → Linux Kernel → Rootfs
↑
这节课学的
# 先进入恢复模式(跳线帽 + micro USB + 上电 + lsusb 确认)
cd ~/Linux_for_Tegra
sudo ./flash.sh -k LNX jetson-nano-qspi-sd mmcblk0p1
| 参数 | 含义 |
|---|---|
-k LNX | 只刷 LNX 分区(即 U-Boot 所在分区),不刷其他分区 |
jetson-nano-qspi-sd | 板型 + SD 卡启动 |
mmcblk0p1 | 目标分区 |
对比:
flash.sh jetson-nano-qspi-sd(无-k)= 刷整机 ;-k LNX= 只刷 U-Boot。
两个编译对比:内核 vs U-Boot
| 内核编译 | U-Boot 编译 | |
|---|---|---|
| Makefile 写法 | ARCH = arm64(直接赋值) | CROSS_COMPILE ?= aarch64-linux-gnu-(条件赋值) |
| 导入配置 | make tegra_defconfig | make p3450-0000_defconfig |
| 产物 | Image(未压缩) | u-boot.bin |
| 刷机命令 | flash.sh(整机) | flash.sh -k LNX(只刷分区) |
| 验证方式 | 启动后 uname -r | 串口看 U-Boot 启动日志 |
① 为什么 U-Boot 配置叫 p3450-0000,内核配置叫 tegra?
配置粒度对比:
U-Boot defconfig: p3450-0000_defconfig
└─ 精确到"某一块具体的板子"
因为 U-Boot 需要精确知道:DDR 型号、上电时序、
板子上的具体外设
内核 defconfig: tegra_defconfig
└─ 精确到"Tegra 这一系列 SoC"
因为内核用设备树(dtb)来描述具体板子差异,
defconfig 只需要管 SoC 级通用配置
U-Boot 是"一板一配置",内核是"一平台一配置 + 设备树适配具体板子"。
② 为什么内核产物是三件套,U-Boot 只有一个文件?
U-Boot: u-boot.bin → 一段裸机代码,自包含,直接跑
内核: Image → 内核本体
*.dtb → 设备树(描述具体硬件连接)
*.ko(数百个) → 内核模块(外设驱动,按需加载)
内核为了支持"一套代码跑所有平台",把硬件描述抽离成设备树(dtb),把可选驱动抽离成模块(.ko)。U-Boot 没这个必要——它只负责引导,跑完就把控制权交给内核。
③ 为什么 U-Boot 能单独刷,内核不能?
SD 卡分区布局(简化):
┌─────────────┬──────────────┬────────────────────┐
│ LNX 分区 │ 其他分区 │ rootfs 分区 │
│ (U-Boot) │ (dtb/其他) │ /boot/Image ←内核 │
└─────────────┴──────────────┴────────────────────┘
↑ 独立分区 ↑ 和 rootfs 混在一起
flash.sh -k LNX 单独刷 没有独立分区,只能刷整机
NVIDIA 把 U-Boot 放在独立的 LNX 分区,所以能 -k LNX 单独刷;而内核 Image 塞在 rootfs/boot/ 里,跟着根文件系统一起打包,只能整机刷。
4.4 为什么 Jetson Nano 没有独立内核分区?
传统板子分区: [bootloader] [kernel] [rootfs] ← 内核独立分区
Jetson Nano: [U-Boot(LNX)] [dtb(DTB)] [rootfs(APP,内核在/boot里)]
↑ ↑ ↑
U-Boot 设备树 内核在 rootfs/boot/Image
NVIDIA 把内核 Image 放在 rootfs/boot/Image,作为根文件系统的一部分,所以:
- 没有
kernel分区 → 不能-k单独刷内核 - 刷内核 = 刷整机(或 SCP 替换
/boot/Image,见第 9 节)
刷机日志
###############################################################################
# L4T BSP Information:
# R32 , REVISION: 6.1
###############################################################################
# Target Board Information:
# Name: jetson-nano-qspi-sd, Board Family: t210ref, SoC: Tegra 210,
# OpMode: production, Boot Authentication: ,
# Disk encryption: disabled ,
###############################################################################
./tegraflash.py --chip 0x21 --applet "/home/yhai/bsp/Linux_for_Tegra/bootloader/nvtboot_recovery.bin" --skipuid --cmd "dump eeprom boardinfo cvm.bin"
Welcome to Tegra Flash
version 1.0.0
Type ? or help for help and q or quit to exit
Use ! to execute system commands
[ 0.0049 ] Generating RCM messages
[ 0.0104 ] tegrarcm --listrcm rcm_list.xml --chip 0x21 0 --download rcm /home/yhai/bsp/Linux_for_Tegra/bootloader/nvtboot_recovery.bin 0 0
[ 0.0120 ] RCM 0 is saved as rcm_0.rcm
[ 0.0203 ] RCM 1 is saved as rcm_1.rcm
[ 0.0218 ] List of rcm files are saved in rcm_list.xml
[ 0.0288 ]
[ 0.0292 ] Signing RCM messages
[ 0.0446 ] tegrasign --key None --list rcm_list.xml --pubkeyhash pub_key.key
[ 0.0465 ] Assuming zero filled SBK key
[ 0.0876 ]
[ 0.0877 ] Copying signature to RCM mesages
[ 0.0917 ] tegrarcm --chip 0x21 0 --updatesig rcm_list_signed.xml
[ 0.1123 ]
[ 0.1125 ] Boot Rom communication
[ 0.1173 ] tegrarcm --chip 0x21 0 --rcm rcm_list_signed.xml --skipuid
[ 0.1198 ] RCM version 0X210001
[ 0.1676 ] Boot Rom communication completed
[ 1.1883 ]
[ 1.1888 ] dump EEPROM info
[ 1.2043 ] tegrarcm --oem platformdetails eeprom /home/yhai/bsp/Linux_for_Tegra/bootloader/cvm.bin
[ 1.2284 ] Applet version 00.01.0000
[ 1.2629 ] Saved platform info in /home/yhai/bsp/Linux_for_Tegra/bootloader/cvm.bin
[ 1.3566 ]
[ 1.3618 ] tegrarcm --reboot recovery
[ 1.3650 ] Applet version 00.01.0000
[ 1.4036 ]
Board ID(3448) version(401)
copying bctfile(/home/yhai/bsp/Linux_for_Tegra/bootloader/t210ref/BCT/P3448_A00_lpddr4_204Mhz_P987.cfg)... done.
copying bootloader(/home/yhai/bsp/Linux_for_Tegra/bootloader/t210ref/cboot.bin)... done.
copying initrd(/home/yhai/bsp/Linux_for_Tegra/bootloader/l4t_initrd.img)... done.
Making Boot image... done.
Existing sosfile(/home/yhai/bsp/Linux_for_Tegra/bootloader/nvtboot_recovery.bin) reused.
copying tegraboot(/home/yhai/bsp/Linux_for_Tegra/bootloader/t210ref/nvtboot.bin)... done.
copying cpu_bootloader(/home/yhai/bsp/Linux_for_Tegra/bootloader/t210ref/cboot.bin)... done.
copying bpffile(/home/yhai/bsp/Linux_for_Tegra/bootloader/t210ref/sc7entry-firmware.bin)... done.
copying wb0boot(/home/yhai/bsp/Linux_for_Tegra/bootloader/t210ref/warmboot.bin)... done.
Existing tosfile(/home/yhai/bsp/Linux_for_Tegra/bootloader/tos-mon-only.img) reused.
Existing eksfile(/home/yhai/bsp/Linux_for_Tegra/bootloader/eks.img) reused.
copying dtbfile(/home/yhai/bsp/Linux_for_Tegra/kernel/dtb/tegra210-p3448-0000-p3449-0000-b00.dtb)... done.
Copying nv_boot_control.conf to rootfs
populating kernel to rootfs... done.
populating initrd to rootfs... done.
populating kernel_tegra210-p3448-0000-p3449-0000-b00.dtb to rootfs... done.
Making system.img... //制作整机镜像
populating rootfs from /home/yhai/bsp/Linux_for_Tegra/rootfs ... populating /boot/extlinux/extlinux.conf ... done.
Syncing system.img ... done.
Converting RAW image to Sparse image... done.
system.img built successfully. //整机镜像制作成功
Existing tbcfile(/home/yhai/bsp/Linux_for_Tegra/bootloader/nvtboot_cpu.bin) reused.
copying tbcdtbfile(/home/yhai/bsp/Linux_for_Tegra/kernel/dtb/tegra210-p3448-0000-p3449-0000-b00.dtb)... done.
copying cfgfile(/home/yhai/bsp/Linux_for_Tegra/bootloader/t210ref/cfg/flash_l4t_t210_max-spi_sd_p3448.xml) to flash.xml... done.
copying flasher(/home/yhai/bsp/Linux_for_Tegra/bootloader/t210ref/cboot.bin)... done.
Existing flashapp(/home/yhai/bsp/Linux_for_Tegra/bootloader/tegraflash.py) reused.
./tegraflash.py --bl cboot.bin --bct P3448_A00_lpddr4_204Mhz_P987.cfg --odmdata 0xa4000 --bldtb kernel_tegra210-p3448-0000-p3449-0000-b00.dtb --applet nvtboot_recovery.bin --cmd "flash; reboot" --cfg flash.xml --chip 0x21 --bins "EBT cboot.bin; DTB tegra210-p3448-0000-p3449-0000-b00.dtb"
saving flash command in /home/yhai/bsp/Linux_for_Tegra/bootloader/flashcmd.txt
saving Windows flash command to /home/yhai/bsp/Linux_for_Tegra/bootloader/flash_win.bat
assign_value: crc-flash.xml.bin 1 65520 1
printf '\x1' | dd of=crc-flash.xml.bin bs=1 seek=65520 count=1 conv=notrunc
记录了1+0 的读入
记录了1+0 的写出
1字节已复制,0.000290227 s,3.4 kB/s
assign_value: crc-flash.xml.bin 0 65521 1
printf '\x0' | dd of=crc-flash.xml.bin bs=1 seek=65521 count=1 conv=notrunc
记录了1+0 的读入
记录了1+0 的写出
1字节已复制,0.000310866 s,3.2 kB/s
assign_string: crc-flash.xml.bin PTHD 65528 4
echo PTHD | dd of=crc-flash.xml.bin bs=1 seek=65528 count=4 conv=notrunc
记录了4+0 的读入
记录了4+0 的写出
4字节已复制,0.000874877 s,4.6 kB/s
*** Flashing target device started. ***
Welcome to Tegra Flash
version 1.0.0
Type ? or help for help and q or quit to exit
Use ! to execute system commands
[ 0.0073 ] tegrasign --getmode mode.txt --key None
[ 0.0094 ] Assuming zero filled SBK key
[ 0.0262 ]
[ 0.0315 ] Generating RCM messages
[ 0.0408 ] tegrarcm --listrcm rcm_list.xml --chip 0x21 0 --download rcm nvtboot_recovery.bin 0 0
[ 0.0427 ] RCM 0 is saved as rcm_0.rcm
[ 0.0513 ] RCM 1 is saved as rcm_1.rcm
[ 0.0527 ] List of rcm files are saved in rcm_list.xml
[ 0.0636 ]
[ 0.0645 ] Signing RCM messages
[ 0.0713 ] tegrasign --key None --list rcm_list.xml --pubkeyhash pub_key.key
[ 0.0758 ] Assuming zero filled SBK key
[ 0.0907 ]
[ 0.0907 ] Copying signature to RCM mesages
[ 0.1008 ] tegrarcm --chip 0x21 0 --updatesig rcm_list_signed.xml
[ 0.1102 ]
[ 0.1104 ] Parsing partition layout
[ 0.1164 ] tegraparser --pt flash.xml.tmp
[ 0.1302 ]
[ 0.1305 ] Using default ramcode: 0
[ 0.1306 ] Disable BPMP dtb trim, using default dtb
[ 0.1306 ]
[ 0.1307 ] Creating list of images to be signed
[ 0.1347 ] tegrahost --chip 0x21 0 --partitionlayout flash.xml.bin --list images_list.xml
[ 0.1631 ]
[ 0.1632 ] Generating signatures
[ 0.1673 ] tegrasign --key None --list images_list.xml --pubkeyhash pub_key.key
[ 0.1722 ] Assuming zero filled SBK key
[ 0.2667 ]
[ 0.2669 ] Generating br-bct
[ 0.2743 ] tegrabct --bct P3448_A00_lpddr4_204Mhz_P987.cfg --chip 0x21 0
[ 0.3041 ]
[ 0.3043 ] Updating boot device parameters
[ 0.3121 ] tegrabct --bct P3448_A00_lpddr4_204Mhz_P987.bct --chip 0x21 0 --updatedevparam flash.xml.bin
[ 0.3144 ] Warning: No sdram params
[ 0.3227 ]
[ 0.3229 ] Updating bl info
[ 0.3270 ] tegrabct --bct P3448_A00_lpddr4_204Mhz_P987.bct --chip 0x21 0 --updateblinfo flash.xml.bin --updatesig images_list_signed.xml
[ 0.3427 ]
[ 0.3429 ] Updating secondary storage information into bct
[ 0.3472 ] tegraparser --pt flash.xml.bin --chip 0x21 0 --updatecustinfo P3448_A00_lpddr4_204Mhz_P987.bct
[ 0.3547 ]
[ 0.3549 ] Updating Odmdata
[ 0.3636 ] tegrabct --bct P3448_A00_lpddr4_204Mhz_P987.bct --chip 0x21 0 --updatefields Odmdata =0xa4000
[ 0.3662 ] Warning: No sdram params
[ 0.3739 ]
[ 0.3741 ] Get Signed section of bct
[ 0.3781 ] tegrabct --bct P3448_A00_lpddr4_204Mhz_P987.bct --chip 0x21 0 --listbct bct_list.xml
[ 0.3867 ]
[ 0.3869 ] Signing BCT
[ 0.4000 ] tegrasign --key None --list bct_list.xml --pubkeyhash pub_key.key
[ 0.4037 ] Assuming zero filled SBK key
[ 0.4147 ]
[ 0.4149 ] Updating BCT with signature
[ 0.4193 ] tegrabct --bct P3448_A00_lpddr4_204Mhz_P987.bct --chip 0x21 0 --updatesig bct_list_signed.xml
[ 0.4321 ]
[ 0.4323 ] Copying signatures
[ 0.4387 ] tegrahost --chip 0x21 0 --partitionlayout flash.xml.bin --updatesig images_list_signed.xml
[ 0.4542 ]
[ 0.4543 ] Updating BFS information on BCT
[ 0.4642 ] tegrabct --bct P3448_A00_lpddr4_204Mhz_P987.bct --chip 0x21 0 --updatebfsinfo flash.xml.bin
[ 0.4716 ]
[ 0.4718 ] Boot Rom communication
[ 0.4771 ] tegrarcm --chip 0x21 0 --rcm rcm_list_signed.xml
[ 0.4794 ] BR_CID: 0x321010016430b746140000000aff01c0
[ 0.4985 ] RCM version 0X210001
[ 0.5016 ] Boot Rom communication completed
[ 1.5264 ]
[ 1.5268 ] Sending BCTs
[ 1.5526 ] tegrarcm --download bct P3448_A00_lpddr4_204Mhz_P987.bct
[ 1.5565 ] Applet version 00.01.0000
[ 1.5941 ] Sending bct
[ 1.6023 ] [................................................] 100%
[ 1.8669 ]
[ 1.8749 ] tegrahost --chip 0x21 --align cboot.bin
[ 1.9068 ]
[ 1.9148 ] tegrahost --magicid EBT --appendsigheader cboot.bin cboot.bin_blheader
[ 1.9452 ]
[ 1.9508 ] tegrasign --key None --list cboot.bin_list.xml
[ 1.9546 ] Assuming zero filled SBK key
[ 2.0115 ]
[ 2.0190 ] tegrahost --updatesigheader cboot.bin_blheader.encrypt cboot.bin_blheader.hash zerosbk
[ 2.0304 ]
[ 2.0374 ] tegrahost --chip 0x21 --align tegra210-p3448-0000-p3449-0000-b00.dtb
[ 2.0464 ]
[ 2.0509 ] tegrahost --magicid DTB --appendsigheader tegra210-p3448-0000-p3449-0000-b00.dtb tegra210-p3448-0000-p3449-0000-b00.dtb_blheader
[ 2.0723 ]
[ 2.0791 ] tegrasign --key None --list tegra210-p3448-0000-p3449-0000-b00.dtb_list.xml
[ 2.0827 ] Assuming zero filled SBK key
[ 2.1046 ]
[ 2.1130 ] tegrahost --updatesigheader tegra210-p3448-0000-p3449-0000-b00.dtb_blheader.encrypt tegra210-p3448-0000-p3449-0000-b00.dtb_blheader.hash zerosbk
[ 2.1226 ]
[ 2.1233 ] Sending bootloader and pre-requisite binaries
[ 2.1330 ] tegrarcm --download ebt cboot.bin.encrypt 0 0 --download rp1 tegra210-p3448-0000-p3449-0000-b00.dtb.encrypt 0
[ 2.1419 ] Applet version 00.01.0000
[ 2.1716 ] Sending ebt
[ 2.1743 ] [................................................] 100%
[ 2.3103 ] Sending rp1
[ 2.3257 ] [................................................] 100%
[ 2.4120 ]
[ 2.4174 ] tegrarcm --boot recovery
[ 2.4227 ] Applet version 00.01.0000
[ 2.4587 ]
[ 2.4632 ] Retrieving storage infomation
[ 2.4725 ] tegrarcm --oem platformdetails storage storage_info.bin
[ 2.4774 ] Applet is not running on device. Continue with Bootloader
[ 3.2283 ]
[ 3.2624 ] tegradevflash --oem platformdetails storage storage_info.bin
[ 3.2657 ] Cboot version 00.01.0000
[ 3.2951 ] Saved platform info in storage_info.bin
[ 3.3261 ]
[ 3.3262 ] Flashing the device
[ 3.3321 ] tegradevflash --pt flash.xml.bin --storageinfo storage_info.bin --create
[ 3.3351 ] Cboot version 00.01.0000
[ 3.3512 ] Writing partition GPT with gpt.bin
[ 3.3545 ] [................................................] 100%
[ 3.3692 ] Writing partition PT with crc-flash.xml.bin
[ 17.8846 ] [................................................] 100%
[ 17.9228 ] Writing partition NVC with nvtboot.bin.encrypt
[ 21.8266 ] [................................................] 100%
[ 21.8670 ] Writing partition NVC_R with nvtboot.bin.encrypt
[ 31.8801 ] [................................................] 100%
[ 31.9127 ] Writing partition TBC with nvtboot_cpu.bin.encrypt
[ 41.9290 ] [................................................] 100%
[ 41.9497 ] Writing partition RP1 with kernel_tegra210-p3448-0000-p3449-0000-b00.dtb.encrypt
[ 46.1327 ] [................................................] 100%
[ 46.1667 ] Writing partition EBT with cboot.bin.encrypt
[ 60.5792 ] [................................................] 100%
[ 60.6378 ] Writing partition WB0 with warmboot.bin.encrypt
[ 89.6937 ] [................................................] 100%
[ 89.7098 ] Writing partition BPF with sc7entry-firmware.bin.encrypt
[ 89.9800 ] [................................................] 100%
[ 89.9978 ] Writing partition TOS with tos-mon-only.img.encrypt
[ 90.2653 ] [................................................] 100%
[ 90.2899 ] Writing partition DTB with kernel_tegra210-p3448-0000-p3449-0000-b00.dtb.encrypt
[ 93.7258 ] [................................................] 100%
[ 93.7527 ] Writing partition LNX with boot.img.encrypt
[ 108.1705 ] [................................................] 100%
[ 108.2204 ] Warning: EKS partition magic header mismatch!
[ 148.0177 ] Writing partition EKS with eks.img
[ 148.0194 ] [................................................] 100%
[ 148.0339 ] Writing partition BMP with bmp.blob
[ 148.3008 ] [................................................] 100%
[ 148.3293 ] Writing partition RP4 with rp4.blob
[ 157.3764 ] [................................................] 100%
[ 157.4030 ] Writing partition VER_b with qspi_bootblob_ver.txt
[ 165.2354 ] [................................................] 100%
[ 165.2744 ] Writing partition VER with qspi_bootblob_ver.txt
[ 165.5363 ] [................................................] 100%
[ 165.5530 ] Writing partition APP with system.img
[ 165.8227 ] [................................................] 100%
[ 788.8329 ]
[ 788.8488 ] tegradevflash --write BCT P3448_A00_lpddr4_204Mhz_P987.bct
[ 788.8523 ] Cboot version 00.01.0000
[ 788.8627 ] Writing partition BCT with P3448_A00_lpddr4_204Mhz_P987.bct
[ 788.8697 ] [................................................] 100%
[ 795.5282 ]
[ 795.5283 ] Flashing completed
[ 795.5284 ] Coldbooting the device
[ 795.5320 ] tegradevflash --reboot coldboot
[ 795.5343 ] Cboot version 00.01.0000
[ 795.5460 ]
*** The target t210ref has been flashed successfully. ***
Reset the board to boot from internal eMMC.
UBOOT启动流程分析
颜色含义:
- 🟫 灰色 = 厂商固件,不能改(BL1/BL2/BL3)
- 🟪 紫色 = 用户可定制(BL4 U-Boot)
- 🟦 蓝色 = Linux 内核
- 🟩 绿色 = 应用层

U-Boot 启动步骤详解(汇编 + C 两阶段)
板子上电的那一瞬间,发生了什么?
第 0 步|CPU 内部的固化代码先动起来。 你按下的电源键,最先醒来的不是 U-Boot,而是 CPU 片内 iROM 里出厂就烧死的 BootROM(BL1)。它干了一件不起眼但决定生死的事——把 Flash 的时钟初始化好。因为 Flash 是个需要时钟才能工作的芯片,没这一步,后面的 U-Boot 根本读不出来。
第 1 步|U-Boot 从 Flash 被读出。 Flash 能用了,CPU 从 Flash 的起始地址读出 U-Boot 的第一条汇编指令,开始执行。
第 2 步|先"穿上盔甲"再干活。 汇编阶段的前几步不是急着初始化硬件,而是先切换 CPU 到权限最高的 SVC 模式,然后关中断、关看门狗、关 MMU、关 cache。为什么要关?一句话:“越单纯越稳定”。U-Boot 是板子上电第一个用户程序,容不得半点闪失——不能被中断打断、不能初始化到一半被看门狗复位、不能让 cache 读到旧数据。
第 3 步|按"先后顺序"初始化硬件。 顺序很有讲究:时钟(心脏)→ 串口(调试)→ Flash(读内核)→ 内存(跑 C 代码)。规律就一条——后面要用的,先初始化。
第 4 步|第一次"搬家"。 初始化完基本硬件,U-Boot 干了一件聪明事:把自己从 Flash 搬到内存。因为内存比 Flash 快几十倍,从此刻起 U-Boot 就在"高速公路"上跑了,这叫第一次重定位。
第 5 步|准备栈,进入 C 世界。 汇编干完了 C 做不了的脏活累活,接下来准备栈(C 函数调用的前提),然后跳进 C 代码——U-Boot 90% 以上的代码都在这里。
第 6 步|初始化"非关键"硬件。 C 阶段里,把 USB、网卡、摄像头这些"锦上添花"的设备初始化好。它们失败也不致命,所以放后面。
第 7 步|第二次"搬家",迎接内核。 从 Flash / SD 卡读出内核 Image,搬到内存——第二次重定位。
第 8 步|交棒。 跳转到内核入口,U-Boot 使命完成,Linux 内核接管,开始挂载根文件系统、跑第一个进程 /sbin/init。
| 序号 | 阶段 | 动作 | 归属 |
|---|---|---|---|
| 0 | 上电 | BootROM 初始化 Flash 时钟 | BL1(CPU 内) |
| 1 | 取指 | 从 Flash 读 U-Boot 第一条汇编指令 | 汇编 |
| 2 | 保护 | 切 SVC + 关中断/看门狗/MMU/cache | 汇编 |
| 3 | 基础初始化 | 时钟 → 串口 → Flash → 内存 | 汇编/C |
| 4 | 自搬移 | U-Boot:Flash → 内存(重定位 ①) | 汇编+C |
| 5 | 准备栈 | 为 C 函数调用铺路 | 汇编 |
| 6 | 外设初始化 | USB / 网卡 / 摄像头 | C |
| 7 | 加载内核 | 内核:Flash → 内存(重定位 ②) | C |
| 8 | 跳转 | 交棒给 Linux 内核 | 汇编 |
Flash 内存(DDR)
(乡间小路) (高速公路)
┌──────────────┐ ┌──────────────┐
│ │ ① U-Boot 自搬移 │ │
│ U-Boot │ ─────────────────> │ U-Boot │
│ (慢) │ │ (快) │
│ │ │ │
│ Kernel │ ② 内核搬移 │ Kernel │
│ (慢) │ ─────────────────> │ (快) │
└──────────────┘ └──────────────┘
为什么两次搬移?
→ 内存比 Flash 快几十倍
→ U-Boot 在慢速设备跑完基本动作后,立刻搬到快速设备
→ 内核也是这样
→ 启动速度 = 缩短慢速设备的运行时间
U-Boot 源码目录结构
u-boot-v2026.04/
├── arch/ ★ CPU 体系结构(arm/x86/mips/riscv...)
├── board/ ★ 开发板配置(各厂商开发板)
├── drivers/ ★ 驱动(block/spi/i2c/gpio/net/video...)
├── configs/ ★ 官方 defconfig 配置
├── cmd/ U-Boot 命令(tftp/gpio/usb...)
├── dts/ 设备树
├── net/ 上层网络协议栈(TFTP/NFS/bootp/ping)
├── fs/ 文件系统
├── common/ 通用代码(与具体板子无关)
├── lib/ 通用库
├── include/ 头文件
├── tools/ 编译辅助工具
├── scripts/ 构建脚本
├── doc/ 文档
├── api/ examples/ test/ post/ 少用
├── Makefile 顶层构建入口
├── Kbuild / Kconfig / config.mk 构建系统
├── COPYING / README / MAINTAINERS / Licenses 版权与说明
│
└── 【编译产物】
├── u-boot.bin ← 最终烧到板子上的二进制(★最重要)
├── u-boot ← ELF 格式(带调试信息,gdb 用)
├── u-boot.lds ← 链接脚本(决定代码/数据放哪)
├── u-boot.map ← 链接后的符号地址映射表
├── System.map ← 符号表
├── u-boot.dtb / u-boot-dtb.bin / u-boot-nodtb.bin ← 设备树相关
├── u-boot.cfg ← 编译配置快照
└── u-boot.srec / u-boot.sym 其它格式镜像/符号
2.1 arch/ —— CPU 体系结构(“整个板子的心脏”)
为什么最重要:整个板子最核心的是 CPU,而 CPU 架构 + 各厂商 SoC 的紧密相关代码都在这。
arch/
├── arm/ ← 我们最常打交道的
├── x86/ ← PC、工控机
├── mips/ riscv/ powerpc/ sh/ xtensa/ ...
└── arc/ microblaze/ nios2/ sandbox/ ...
进到 arch/arm/,核心是两层:
arch/arm/
├── mach-xxx/ ← 各厂商 SoC 平台(machine,原厂维护)
│ ├── mach-tegra ★英伟达(Jetson Nano 在这!)
│ ├── mach-sunxi 全志
│ ├── mach-rockchip 瑞芯微
│ ├── mach-exynos 三星
│ ├── mach-imx NXP
│ └── mach-... (几十个)
│
└── cpu/ ← CPU 内核(按 ARM 版本分)
├── arm11 / arm720t / arm920t / arm926ejs / arm1176 ...
├── armv7 ← 32 位 Cortex-A 系列
└── armv8 ← 64 位(Jetson Nano 的 Cortex-A57 就在这)
2.2 board/ —— 开发板配置(“CPU 外面的世界”)
为什么有它:CPU 外面还挂着内存、Flash、网卡、摄像头等一大堆外围硬件。这些"板子级"的东西放 board/。
board/
├── nvidia/ ★ 英伟达官方开发板(Jetson 系列在这)
├── samsung/ 三星
├── huawei/ 华为
├── rockchip/ 瑞芯微
├── xilinx/ Xilinx
├── ti/ TI
├── friendlyarm/ 友善之臂
├── tq/ 天嵌
├── sunxi/ 全志
└── ...(上百个厂商)
2.3 drivers/ —— 驱动(“借鉴内核,但精简得多”)
为什么叫"借鉴内核":U-Boot 的驱动框架是从 Linux 内核搬过来的思路,但大幅简化——内核里各种模块极多,U-Boot 是"裸机层",只保留了常用驱动。
drivers/
├── block/ 块设备(硬盘、SD 卡、eMMC)
├── spi/ SPI 总线 + 各厂商 SPI 控制器
├── i2c/ I2C 总线
├── gpio/ GPIO(点灯、按键)
├── pwm/ PWM(脉冲宽度调制)
├── mmc/ SD 卡 / eMMC
├── usb/ USB
├── net/ ★ 网卡硬件驱动(注意!和顶层 net/ 区分)
├── video/ 视频/摄像头(如 tegra124 的视频驱动)
├── clk/ 时钟
├── pinctrl/ 引脚复用
├── serial/ 串口
├── watchdog/ 看门狗
├── rtc/ 实时时钟
└── ...
重点区分(易混):
| 目录 | 层 | 例子 |
|---|---|---|
顶层 net/ | 上层协议栈(与硬件无关) | TFTP、NFS、bootp、ping |
drivers/net/ | 网卡硬件驱动(与芯片绑定) | rtl8169(老师板子的网卡) |
2.4 configs/ —— 官方默认配置(make xxx_defconfig)
为什么存在:每块板子的硬件都不一样——用哪款 CPU、配哪块网卡、挂哪个 Flash、内存多大。厂商在发布板子时,会把这套默认配置一并放进 configs/。
configs/
├── xxx_defconfig 各厂商的官方配置(英伟达、三星、Xilinx 等)
└── ...
2.5 cmd/ —— U-Boot 命令(“敲的那些命令的代码在这”)
在 U-Boot 命令行敲的 tftp、usb、gpio、mmc 等命令,源码都在这。
cmd/
├── tftp.c TFTP 下载命令
├── gpio.c GPIO 点灯命令
├── usb.c USB 命令
├── mmc.c MMC/SD 命令
└── ...
2.6 net/(顶层)—— 上层网络协议栈
net/
├── tftp.c TFTP 协议
├── nfs.c NFS 协议
├── bootp.c / dhcpv6.c 启动地址分配
├── ping.c / ping6.c ping 命令
├── arp.c 地址解析
└── ...
2.7 其余目录(“看看就行”)
| 目录 | 作用 | 移植要改吗 |
|---|---|---|
fs/ | 文件系统(ext2/ext4、FAT32、NFS 等) | ❌ 稳定,基本不动 |
common/ | 通用代码(与具体板子无关) | ❌ 不动 |
lib/ | 通用库(字符串、设备树解析 fdtdec 等) | ❌ 不动 |
include/ | 头文件(驱动/程序的公共头) | ❌ 基本不动 |
dts/ | 设备树(编译生成 dtb) | ⚠️ 设备树章节再细讲 |
tools/ scripts/ | 编译辅助工具 / 构建脚本 | ❌ 不动 |
🔍 三、编译产物速查
| 文件 | 是什么 | 什么时候用 |
|---|---|---|
| u-boot.bin | 最终烧到板子上的二进制 | 刷机 / 烧写 |
| u-boot | ELF 格式(带符号、调试信息) | gdb 调试、objdump 反汇编 |
| u-boot.lds | 链接脚本 | 看代码/数据段地址布局 |
| u-boot.map | 链接后符号地址映射 | 查某个函数被链接到哪个地址 |
| System.map | 符号表 | 内核/U-Boot 符号查询 |
| u-boot.dtb / u-boot-dtb.bin | 设备树二进制 | 设备树相关内容 |
USB 和 PCIe 架构基本概念
| 名词 | 一句话解释 |
|---|---|
| EHCI | Enhanced Host Controller Interface,USB 2.0 主流主机控制器 |
| OHCI / UHCI | USB 1.1 时代的两套主机控制器规范(OHCI 偏开放,UHCI 是 Intel) |
| XHCI | eXtensible Host Controller Interface,USB 3.0 主机控制器,向下兼容 2.0 |
| Companion 模式 | EHCI 兼容老 USB 1.1 设备的方式——把 OHCI/UHCI 集成进来 |
| USB 主机侧 vs 设备侧 | 主机侧 = 板子(Host),设备侧 = U 盘 / 摄像头(Device/Gadget) |
| PCIe | Peripheral Component Interconnect Express,高速串行总线 |
| 差分信号 | 用两根线传"反向"的信号,接收端做差,抗干扰 |
| lane(通道) | PCIe 一对差分线(TX+/TX- 或 RX+/RX-)= 1 lane |
| GT/s | Giga Transfers per second,每秒十亿次传输(PCIe 速度单位) |
| 8b/10b 编码 | PCIe Gen1/Gen2 用,每 10 bit 里只有 8 bit 是数据(用于时钟恢复) |

CPU 内部
├── USB2 Controller #1 (OTG) ← USB 2.0 控制器,OTG=可主可从
│ └── HSIC P0/P1/P2/P3 + USB2 P0/P1/P2/P3
├── USB2 Controller #2 ← USB 2.0 控制器
├── XUSB Controller ← USB 3.0 控制器(XHCI)
│ ├── xHCI Controller(含 Device Controller)
│ ├── HSIC P0/P1
│ ├── SS P0/P1(SuperSpeed,USB 3.0 高速)
│ ├── SBU P0/P1
│ └── Device Controller
│
└── MUX(开关) ← 复用选择器
├── 选 USB2 PAD
├── 选 USB3 PAD
└── 选 PCIE PAD ← 注意:USB 接口和 PCIe 接口可以共享!
重点观察:
- MUX(开关):一个物理接口可以切换到 USB 2.0、USB 3.0 或 PCIe。这就是"USB / PCIe 复用"——同一个金手指,根据寄存器配置走不同协议。
- OTG:On-The-Go,既能做主机也能做设备(手机常见,插电脑时是设备,插 U 盘时是主机)。
- USB Device Controller:USB 设备侧控制器(板子作为 U 盘/设备时用)。
2.4 USB 主机侧软件分层
- USB 控制器(USB2 Controller #1 OTG / USB2 Controller #2)+ XUSB 控制器(USB 3.0,xHCI)都集成在 CPU 内部
- PCIe 控制器也在 CPU 内部,连出去到金手指 → 接 Wi-Fi / 显卡等
- 还有 SATA、eMMC、UART、I2C、SPI 等各种接口控制器
- 所有控制器通过内部总线(APB / AHB)连到 CPU 核心
CPU 通过 USB 总线跟外部 USB 设备(U 盘、摄像头、USB 网卡)通信,软件上分成 4 层:
┌─────────────────────────────────────┐
│ USB 设备驱动 ← 我们要写这一层! │ 如:U 盘驱动、UVC 摄像头驱动、AX88772 USB 网卡驱动
├─────────────────────────────────────┤
│ USB Core(内核提供的通用协议层) │ 内核已实现,不用我们写
├─────────────────────────────────────┤
│ USB 主机控制器驱动(OHCI/UHCI/EHCI/XHCI)│ 内核已实现
├─────────────────────────────────────┤
│ USB 主机控制器(硬件) │ CPU 厂商(英伟达)已设计好
├─────────────────────────────────────┤
│ USB 总线(物理线缆) │
└─────────────────────────────────────┘

差分信号
PCIe 一条 lane = 4 根线:发送一对 + 接收一对。
PCIE0_RX0_P ← 接收端正极
PCIE0_RX0_N ← 接收端负极
PCIE0_TX0_P ← 发送端正极
PCIE0_TX0_N ← 发送端正负
差分信号原理(老师重点讲):
普通信号:单根线,高电平 1 / 低电平 0
差分信号:两根线,TX+ 和 TX- 波形相反(一个高,另一个就低)
TX+ ──┐ ┌──┐ ┌──
│ │ │ │
TX- ──┘────┘ └────┘
↑ ↑
两信号相减 = 2 × TX+(差值)
接收端:TX+ - TX- = 抗干扰后的真实信号
识别设备型号 + 网卡工作原理
| 步骤 | 命令 | 解决什么问题 |
|---|---|---|
| ① | lsusb | 是什么芯片?(厂商 + 产品 ID) |
| ② | lsusb -t | 拓扑 + 当前用什么驱动 + 速度 |
| ③ | lsmod | 驱动有没有装 / 模块关系 |
| 方式 | 物理接口 | 适用场景 |
|---|---|---|
| 有线网卡(PCIe 网卡) | RJ45 网口 | 网线接路由器/电脑 |
| 无线网卡(Wi-Fi) | 板载 Wi-Fi 模块 | 无线接 AP |
| USB 网卡 | USB 口 | 有线/无线网卡都坏时的备选 |

CPU 内部
├── CPU 0 (Cortex-A57) ← 应用处理核心
│ ├── I-cache 48K
│ └── D-cache 32K
│
├── NEON + FPU ← SIMD/浮点
│
├── AXI 总线(高速) ← ARM AMBA 高速互联协议
│
├── Memory Fabric ← 片上网络,连接各控制器
│
├── APB 总线(低速外设) ← ARM AMBA 低速外设协议
│
├── XUSB Controller (XHCI) ← USB 3.0 控制器(上一节讲过)
│ ├── xHCI Controller
│ └── Device Controller
│
├── SATA Controller ← SATA 控制器
│
└── PCIE Controller ← PCIe 控制器
├── AFI (Adapter Fabric Interface)
├── TM80 / TM81 ← Transaction Manager
└── PCIE Lane 0/1/2/3/4 ← 5 个通道可配置
外部接口(MUX 复用)
├── MUX 1: USB3 / SATA / PCIE ← 三选一
├── MUX 2: SS / SATA / PCIE ← 三选一(SS = SuperSpeed = USB 3.0)
└── MUX 3: USB3 PCIE ×7 ← 七通道 PCIE(连金手指)
外部设备
├── WIFI 无线网卡 ← 走 PCIe
├── 有线网卡 (RJ45) ← 走 PCIe
└── USB 网卡 (USB 转 RJ45) ← 走 USB(板载网卡坏时的备选)
CPU 内部
├── CPU 0 (Cortex-A57) ← 应用处理核心
│ ├── I-cache 48K
│ └── D-cache 32K
│
├── NEON + FPU ← SIMD/浮点
│
├── AXI 总线(高速) ← ARM AMBA 高速互联协议
│
├── Memory Fabric ← 片上网络,连接各控制器
│
├── APB 总线(低速外设) ← ARM AMBA 低速外设协议
│
├── XUSB Controller (XHCI) ← USB 3.0 控制器(上一节讲过)
│ ├── xHCI Controller
│ └── Device Controller
│
├── SATA Controller ← SATA 控制器
│
└── PCIE Controller ← PCIe 控制器
├── AFI (Adapter Fabric Interface)
├── TM80 / TM81 ← Transaction Manager
└── PCIE Lane 0/1/2/3/4 ← 5 个通道可配置
外部接口(MUX 复用)
├── MUX 1: USB3 / SATA / PCIE ← 三选一
├── MUX 2: SS / SATA / PCIE ← 三选一(SS = SuperSpeed = USB 3.0)
└── MUX 3: USB3 PCIE ×7 ← 七通道 PCIE(连金手指)
外部设备
├── WIFI 无线网卡 ← 走 PCIe
├── 有线网卡 (RJ45) ← 走 PCIe
└── USB 网卡 (USB 转 RJ45) ← 走 USB(板载网卡坏时的备选)
2.3 三条数据通路对比
通路 ①:有线网卡(PCIe)—— 高速首选
CPU 核心
│ (AXI 高速总线)
↓
Memory Fabric(片上网络)
│ (AXI)
↓
PCIe 控制器(TM80/TM81 + AFI)
│ (PCIe Lane,差分信号,Gen2 500 MB/s)
↓
MUX 复用选择
↓
PCIe PHY(金手指引脚)
↓
有线网卡(RJ45)
↓
网线 → 路由器 / 电脑
通路 ②:无线网卡(Wi-Fi)—— 同样走 PCIe
CPU 核心 → Memory Fabric → PCIe 控制器 → Wi-Fi 模块 → 无线信号 → Wi-Fi AP
老师原话:“有线网卡、Wi-Fi 都是 PCIe 接口,只是协议层不同。” Wi-Fi 模块也通过 PCIe 连接到 CPU。
通路 ③:USB 网卡(备选方案)—— 走 USB 总线
CPU 核心
│ (AXI)
↓
Memory Fabric
│ (APB 桥)
↓
XUSB Controller(XHCI)
│ (USB 3.0 SuperSpeed 或 USB 2.0 HighSpeed)
↓
USB PHY → USB 网口
↓
USB 网卡内部(有协议转换芯片)
│ USB 数据包 ↔ 网线数据包
↓
RJ45 → 网线 → 外部
4.1 实战路径-移植实战路径
① 拿到 USB 网卡
↓
② 插到 Ubuntu 虚拟机(不插板子,板子还没驱动)
↓
③ lsusb / lsusb -t / lsmod 三连 → 识别芯片型号
↓
④ 复制 VendorID:ProductID 搜索 → 确认芯片型号 + 对应内核驱动
↓
⑤ 在 U-Boot / 内核 Kconfig 里勾选对应驱动 → 编译 → 板子验证
4.2 Jetson Nano 上的对应位置
| 主题 | Jetson Nano 对应 |
|---|---|
| PCIe 控制器驱动 | arch/arm/mach-tegra/pcie.c |
| USB XHCI 驱动 | drivers/usb/host/xhci-tegra.c |
| USB 网卡驱动(r8152) | drivers/net/usb/r8152.c |
| USB 网络通用层 | drivers/net/usb/usbnet.c |
| PHY 接口 | drivers/net/mii.c |
| 芯片手册 | Nvidia TRM(Technical Reference Manual) |
USB 网卡移植
| 步骤 | 有线网卡(已 OK,参考) | USB 网卡(待移植) |
|---|---|---|
| 命令 | cmd/pci.c(pci enum) | cmd/usb.c(usb start) |
| 驱动源码 | drivers/net/rtl8169.c | drivers/usb/net/ 下对应驱动 |
| 总线匹配 | U_BOOT_PCI_DEVICE | U_BOOT_USB_DEVICE / usb 设备匹配 |
| 设备树匹配 | compatible = "realtek,rtl8169" | compatible = "realtek,rtl8152" |
| Kconfig | CONFIG_RTL8169 | CONFIG_RTL8152 等 |
| Makefile | obj-$(CONFIG_RTL8169) += rtl8169.o | obj-$(CONFIG_XXX) += xxx.o |
- 搜索驱动:8152 在哪
# 在 u-boot 源码里搜 8152
grep -rn "8152" drivers/
结果:drivers/usb/eth/r8152.c
① 当前 u-boot 源码里搜(grep 芯片型号)
↓ 没有
② 最新版 u-boot 官网源码里找
↓ 还没有
③ Linux 内核里找(drivers/net/usb/r8152.c)
⚠️ 但内核框架和 u-boot 差异非常大,移植难度极大
- 置:
make menuconfig两层选配
2.1 完整配置树
make menuconfig
Device Drivers
USB support
├── [*] Enable driver model for USB ← USB 驱动模型(DM)
│
├── 总线支持(官方已默认选好)
│ ├── [*] xHCI HCD (USB 3.0) support
│ │ └── [*] Support for NVIDIA Tegra T210 on-chip XHCI USB controller
│ └── [*] EHCI HCD (USB 2.0) support
│ └── [*] Support for NVIDIA Tegra on-chip EHCI USB controller
│
└── 驱动支持(★ 要手动选的)
USB to Ethernet Controller Drivers
└── [*] Realtek RTL8152B/RTL8153 support ← 关键!
- 编译 + 拷贝
make
cp u-boot.bin /tftpboot
- 切换到 USB 网卡测试
usb start # 启动(扫描)USB 控制器
# 把网线从有线网口拔出,插入 USB 网卡
ping 192.168.9.119
| 环节 | 有线网卡(PCIe) | USB 网卡(本课) |
|---|---|---|
| 激活总线 | pci enum | usb start |
| 命令源码 | cmd/pci.c | cmd/usb.c |
| 总线驱动 | Tegra PCI support | xHCI + EHCI + Tegra T210 |
| 设备驱动 | drivers/net/rtl8169.c | drivers/usb/eth/r8152.c |
| 驱动 Kconfig | CONFIG_RTL8169 | CONFIG_RTL8152 |
| ping 标志 | eth_rtl8169 | r8152_eth |
U-Boot 启动盘优先顺序与环境变量
| 概念 | 一句话解释 |
|---|---|
boot_targets | U-Boot 依次尝试的启动设备列表(按优先级) |
distro_bootcmd | 遍历 boot_targets,逐个执行 bootcmd_${target} 的"万能启动命令" |
bootcmd_${target} | 每个 target 的具体启动命令(如 bootcmd_usb0、bootcmd_mmc1) |
bootcmd | 上电自动执行的第一条命令(默认 run distro_bootcmd) |
preboot | 自动启动前执行的钩子,可自动加载脚本(如 /u-boot-preboot.scr) |
extlinux | 一种"内核放文件系统"的启动机制,U-Boot 读 /boot/extlinux/extlinux.conf 加载内核 |
cbootargs | C-Boot 传给 kernel 的启动参数(控制台、根文件系统位置等) |
rootfs | 根文件系统,即启动后看到的完整目录树 |
启动盘优先顺序
boot_targets = mmc1 mmc0 usb0 nvme0 pxe dhcp
SD卡 内部 USB NVMe PXE DHCP
↑ eMMC 设备 设备 网络 网络
优先 ↑
启动顺序:SD 卡 (mmc1) → 内部 eMMC (mmc0) → USB 设备 (usb0) → NVMe (仅 TX2) → PXE 网络 → DHCP 网络。
# run distro_bootcmd # 按启动盘优先顺序选择启动
# setenv bootcmd run distro_bootcmd # 设置自启动命令为"按优先顺序"
# saveenv # 持久化到 SPI Flash
extlinux 实现原理
一种「内核放文件系统」的启动机制,u-boot 读 /boot/extlinux/extlinux.conf 加载内核
传统方式的局限(先回顾)
传统方式:
u-boot → 读 flash 某分区起始地址 → 加载内核到内存
→ 内核挂载根文件系统 → 才能访问应用程序
- 内核是 flash 里某个分区的二进制,按「起始地址」去读。
- 根文件系统没加载、没进入应用层之前,看不到文件系统。
- 改内核要刷机:进恢复模式(短接两脚)、烧写分区,很麻烦。
sudo ./flash.sh -k LNX jetson-nano-qspi-sd mmcblk0p1 # 刷 LNX 分区 = 换内核
extlinux 的核心思想:把内核搬进文件系统
把内核从「裸机层原始存储空间的某分区二进制」,搬到根文件系统的一个目录(如 /boot/Image),放到应用层:
flash 里只剩两个逻辑分区:
├── u-boot
└── 根文件系统(rootfs)
└── /boot/Image ← 内核就放这,是个普通文件
最大好处:
| 场景 | 传统方式 | extlinux(应用层) |
|---|---|---|
| 改内核 | 刷机、进恢复模式、烧写 | 远程登录,scp/覆盖 /boot/Image,重启即可 |
| 编译内核 | 只能在宿主机交叉编译 | 板子够强(Jetson),甚至可直接在板子上编译替换 |
extlinux.conf 三段式 + ${cbootargs}
/boot/extlinux/extlinux.conf:
LABEL primary
MENU LABEL primary kernel
LINUX /boot/Image # 指定启动的内核文件
INITRD /boot/initrd # 指定初始 ramdisk(加载内核前挂载的过渡文件系统)
APPEND ${cbootargs} rootfstype=ext4 root=/dev/mmcblk0p1 rw rootwait
| 字段 | 作用 |
|---|---|
LINUX | 内核文件路径(相对 extlinux.conf 所在目录) |
INITRD | 初始 ramdisk(就是上面说的「过渡性小文件系统」) |
APPEND | 追加给内核的命令行参数 |
板子设置从 U 盘启动
从 U 盘启动其实主要就改两个地方:虚拟机里把配置文件改一下、板子上把自启动命令改一下。
虚拟机里:接 U 盘 + 查看分区表
# 1. 把 SD 卡通过读卡器接入虚拟机(或直接用 U 盘),自动挂载
cd /media/yhai/b1c100cd-cc74-4e7f-acb8-f0d34c931ee8/
# 2. 查看 U 盘被识别成哪个设备、有哪些分区
sudo parted /dev/sdc print
改 extlinux.conf:root 从 mmcblk0p1 → sda1
sudo vim /media/yhai/b1c100cd-cc74-4e7f-acb8-f0d34c931ee8/boot/extlinux/extlinux.conf
# 把这一行
APPEND ${cbootargs} rootfstype=ext4 root=/dev/mmcblk0p1 rw rootwait
# 改成
APPEND ${cbootargs} rootfstype=ext4 root=/dev/sda1 rw rootwait
板子上:run usb_boot 试启动
# 把改好的 U 盘插到板子 USB 口,重新上电进入 u-boot 命令行
run usb_boot # 手动执行一次「从 USB 启动」
usb_boot是环境变量里现成的启动命令(run boot_pci_enum; usb start; if usb dev ...),对应boot_targets里的usb0。- 执行后从 USB 挂载、加载内核、进入系统 = 成功。
固化:setenv bootcmd + saveenv
临时用 run usb_boot 可以,但想让每次上电都自动从 U 盘启动:
setenv bootcmd run usb_boot # 把自启动命令改成「从 USB 启动」
saveenv # 写回 SPI Flash 持久化
reset # 复位验证自动启动
最小系统基本概念
2.2 核心区分表
| 类别 | 典型代表 | 掉电后 | 速度 | 用途 |
|---|---|---|---|---|
| 寄存器 | CPU 内部通用寄存器 | 丢失 | 最快 | CPU 暂存数据 |
| RAM 内存 | DRAM(电脑内存条)、SRAM(CPU 缓存) | 丢失 | 快 | 运行中程序/数据 |
| NorFlash | 小容量 flash | 永久 | 快(可 XIP) | 存放启动代码、小容量固件 |
| NandFlash | 大容量 flash | 永久 | 中(按块) | 存放大量数据 |
| EMMC | NandFlash + 控制器 + 接口 | 永久 | 中 | 内嵌多媒体卡(Jetson Nano 内部存储) |
| SD 卡 / TF 卡 / U 盘 | 可移动 NandFlash | 永久 | 中 | 移动存储(板子用读卡器转 USB 也算) |
| SSD | 大容量 NandFlash | 永久 | 快 | 电脑硬盘 |
| 目录 | 作用 | 典型内容 |
|---|---|---|
| /boot | 启动引导文件 | Image(内核镜像)、initrd(ramdisk 临时 rootfs) |
| /bin | 普通命令 | ls、cd、pwd、vi 等所有用户可用 |
| /sbin | 管理员命令 | insmod(加载内核模块)等需 root 权限的命令 |
| /etc | 配置和启动脚本 | 系统配置、网络配置、各种启动脚本 |
| /lib、/lib64 | 库文件 | 内核核心模块在 /lib/modules |
| /dev | 设备文件 | 一切设备抽象成文件 |
| /media | 可移动设备挂载点 | U 盘插入自动挂到这里 |
| /proc | 伪文件系统 | 查看 CPU/内存/进程/中断信息(重启消失) |
| /sys | 伪文件系统 | 设备模型(替代旧的 /dev 管理方式) |
| /root、/home | 用户目录 | 管理员/普通用户的家目录 |
| /mnt | 挂载点 | 临时挂载别的设备 |
| FS 类型 | 介质 | 功用 | 典型场景 |
|---|---|---|---|
| JFFS2 | NorFlash / NandFlash | 闪存文件系统(日志型防掉电);缺点:快满时垃圾回收慢 | 嵌入式设备常用 |
| YAFFS2 | NandFlash | 专用于 Nand 的闪存文件系统;改进了 JFFS2 的压缩(不压缩 → 更快) | 手机资料存储 |
| CRAMFS | NorFlash / NandFlash | 只读的压缩文件系统;安全性高(不被篡改) | 不需要用户改的场景 |
| EXT2 / EXT4 | DISK(硬盘、U 盘) | Linux 扩展文件系统(大容量、高效) | 电脑硬盘、U 盘、SD 卡(TF 卡也可格成 ext4) |
| NFS | 网络 | 网络文件系统,把远程文件夹映射到本地 | 开发调试(不用每次刷 flash,改代码即时生效) |
| RAMDISK | 内存 | 把内存模拟成硬盘 | 初始引导的临时 rootfs(/boot/initrd) |
| procfs / tmpfs | 内存(虚拟) | 伪文件系统 | /proc(CPU/mem/进程)、/tmp(临时文件) |
| sysfs / devfs | 内存(虚拟) | 伪文件系统(设备模型) | /sys(替代旧 /dev 的混乱管理) |
一、为什么需要「最简根文件系统」
| 对比 | Ubuntu 类系统 | 最简根文件系统(busybox) |
|---|---|---|
| 大小 | 6.6G 起 | 8M 左右 |
| 适用 | 第三代嵌入式(Jetson 这种资源强的) | 第一/二代嵌入式(硬件以 MB 计) |
| 命令 | 全套 GNU 工具 | busybox 一个盒子搞定 |
三、编译 busybox
make menuconfig
只设置 两项(其他不动):
Settings
[*] Build static binary (no shared libs) ← ① 静态编译
(aarch64-linux-gnu-) Cross compiler prefix ← ② 交叉编译工具链前缀
编译 + 验证
make
file busybox # 确认编出来的是 ARM 平台
busybox: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV),
statically linked, for GNU/Linux 3.7.0, BuildID[sha1]=..., stripped
安装
make install # 生成 _install/ 目录
cd _install
ls # bin linuxrc sbin usr
why 要 make install:编译只生成 busybox 二进制;
make install会把 busybox 及其各种命令的软链接整理到_install/目录——这就是根文件系统的雏形。
busybox 的「瑞士军刀」原理
看 _install/bin 里:
ls -l cp
# cp -> busybox (cp 是个软链接,指向 busybox)
ls -l mv
# mv -> busybox
ls -l linuxrc
# linuxrc -> bin/busybox
why 体积小:ls、cp、mv、vi… 这些命令不是独立的程序,而是软链接,全部指向 busybox 这一个二进制文件。busybox 运行时根据「自己是被哪个名字调用的」(argv[0])决定执行哪个命令的功能。所以几百个命令 = 1 个文件,体积自然小——这就是「瑞士军刀」。
五、填充 rootfs 内容
_install 只有 bin/linuxrc/sbin/usr,太简陋,补齐成熟悉的 Linux 目录结构:
mkdir dev etc var proc tmp home root mnt sys # 创建需要的目录
cp -rf ../examples/bootfloppy/etc/* etc/ # 拷贝 busybox 最简示例配置
拷贝并裁剪库(核心难点)
6.1 拷贝交叉编译工具链的库
cp /home/yhai/store/gcc-linaro-7.3.1-2018.05-x86_64_aarch64-linux-gnu/aarch64-linux-gnu/libc/lib/ . -a
du -mh lib
# 45M lib/debug
# 228M lib ← 太大了,要裁
6.2 三步裁剪
rm lib/*.a # ① 删静态库
aarch64-linux-gnu-strip lib/* # ② 去掉调试信息
rm -rf lib/debug/ # ③ 删调试目录
du -mh lib # 8.5M ← 从 228M 裁到 8.5M
| 步骤 | 作用 | 大小变化 |
|---|---|---|
| 原始 | 拷进来的完整库 | 228M |
rm lib/*.a | 删静态库(后缀 .a) | → 133M(减半) |
strip lib/* | 去掉调试信息 | → 54M |
rm -rf lib/debug/ | 删调试目录 | → 8.5M |
创建设备文件 dev/null 和 dev/console
sudo mknod dev/null c 1 3 # 空设备(黑洞设备)
sudo mknod dev/console c 5 1 # 控制台(串口输出)
| 设备文件 | 主次设备号 | 作用 |
|---|---|---|
dev/null | c 1 3 | 空设备——写进去的数据直接消失(黑洞) |
dev/console | c 5 1 | 控制台——没有它,内核起来可能没有串口信息输出 |
why 必须手动创建这两个:
/dev里大多数设备文件是内核启动后自动创建的;但dev/null和dev/console是内核启动早期就要用到的(控制台输出、init 进程读 null),必须提前手动创建,否则启动会出问题。
mknod dev/null c 1 3里:c= 字符设备,1= 主设备号,3= 次设备号。
NFS 的解法
- 板子不烧写,通过网线把虚拟机的 rootfs 目录挂载过来。
- 你在虚拟机里改文件(创建/删除/修改),板子那边自动同步。
板子从 NFS 启动
板子从 NFS 启动的完整命令
# 1. 设置启动参数(告诉内核:rootfs 在 NFS 上)
setenv bootargs root=/dev/nfs rw nfsroot=192.168.9.119:/nfs/rootfs \
console=ttyS0,115200 init=/linuxrc ip=192.168.9.9
# 2. 激活网卡(NFS 走网络,必须先让网卡工作)
pci enum;pci
# 3. 从 SD 卡加载内核 + 设备树到内存
fstype mmc 1:1 # 查看 mmc 1:1 的文件系统类型
ext4ls mmc 1:1 # 查看目录
ext4load mmc 1:1 0x84000000 /boot/Image # 读内核到 0x84000000
ext4load mmc 1:1 83100000 /boot/tegra210-p3448-0002-p3449-0000-b00.dtb # 读设备树
# 4. 启动内核
booti 0x84000000 - 83100000 # 内核地址 - 设备树地址
booti 0x84000000 - 83100000
# 内核地址 ↑ 设备树地址
# "-" 表示没有 initrd(ramdisk)
成功的 NFS 启动设置
| 概念 | 一句话解释 |
|---|---|
| inittab | init 进程读的启动配置文件,指定初始化脚本、终端 shell |
| rcS | 系统初始化脚本,启动时执行的第一个脚本 |
| fstab | 自动挂载的文件系统列表(介质 / 挂载点 / 类型) |
| profile | 系统环境变量设置(PATH、LD_LIBRARY_PATH 等) |
| mdev | busybox 的设备管理器,热插拔/启动时自动创建设备节点 |
| PATH | 命令搜索路径 |
| LD_LIBRARY_PATH | 动态库搜索路径 |
1.1 最终成功的 bootargs
setenv bootargs root=/dev/nfs rw nfsroot=192.168.9.119:/nfs/rootfs,v3 \
console=ttyS0,115200 init=/linuxrc ip=192.168.9.9
| 参数 | 含义 |
|---|---|
root=/dev/nfs | rootfs 在 NFS 上 |
nfsroot=192.168.9.119:/nfs/rootfs | NFS 服务器 IP + rootfs 目录 |
,v3 | 强制用 NFSv3 协议(关键!) |
console=ttyS0,115200 | 串口输出 |
init=/linuxrc | 第一个应用程序 |
ip=192.168.9.9 | 板子 IP |
| 成功的 bootcmd(一键启动) |
setenv bootcmd pci enum \; pci \; \
ext4load mmc 1:1 0x84000000 /boot/Image \; \
ext4load mmc 1:1 83100000 /boot/tegra210-p3448-0002-p3449-0000-b00.dtb \; \
booti 0x84000000 - 83100000
二、系统配置脚本:启动流程总览
四个关键文件各司其职:**inittab 指定「启动时跑 rcS」,rcS 里 mount -a 读 fstab 挂载文件系统,进入 shell 时读 profile 设环境变量
/etc/inittab(启动配置文件)
#this is run first except when booting in single-user mode.
:: sysinit:/etc/init.d/rcS # 指定系统初始化脚本是 rcS
::askfirst:-/bin/sh # 需按确认键才能进入 shell
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
| 行 | 含义 |
|---|---|
sysinit:/etc/init.d/rcS | 系统初始化时执行 rcS 脚本(最关键的一行) |
askfirst:-/bin/sh | 启动 shell 前先询问,按确认键才进终端 |
restart:/sbin/init | init 重启时执行 |
ctrlaltdel:/sbin/reboot | Ctrl+Alt+Del 软启动 → 重启 |
/etc/init.d/rcS(启动脚本)
#!/bin/sh
/bin/mount -a # 挂载 /etc/fstab 指定的所有文件系统
echo /sbin/mdev > /proc/sys/kernel/hotplug # 热插拔时调用 mdev
/sbin/mdev -s # mdev 自动产生设备节点
#./app.exe # 可添加自己的程序,上电自动运行
/sbin/ifconfig eth0 192.168.9.18 # 启动后自动配置 IP
| 行 | 含义 |
|---|---|
mount -a | 挂载 /etc/fstab 里列的所有文件系统(配合 fstab 用) |
echo /sbin/mdev > ...hotplug | 设置热插拔时调用 mdev(U 盘插拔等) |
mdev -s | 启动时自动创建设备节点(/dev 下的文件) |
./app.exe | 你自己写的程序,上电自动运行(去掉注释即可) |
ifconfig eth0 192.168.9.18 | 启动后自动配 IP |
自己写程序上电自运行
想在板子上电时自动跑自己的程序(比如你说的 yolo 程序):
# 1. 把编译好的程序(ARM 交叉编译的)放到根文件系统,如 /bin/myapp
# 2. 在 rcS 里加一行:
./myapp
# 或者
/bin/myapp
/etc/fstab(自动挂载的文件系统列表)
#device mount-point type options dump fsck order
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
| 列 | 含义 |
|---|---|
| device | 设备/文件系统(proc/tmpfs/sysfs 是虚拟的,没有真实设备) |
| mount-point | 挂载点 |
| type | 文件系统类型 |
| options | 挂载选项(defaults = 默认) |
| 挂载 | 类型 | 作用 |
|---|---|---|
/proc | proc | 伪文件系统,暴露内核信息(CPU/mem/进程) |
/tmp | tmpfs | 内存临时文件系统(掉电丢失) |
/sys | sysfs | 设备模型信息 |
/dev | tmpfs | 设备文件(内存里,配合 mdev) |
/etc/profile(环境变量设置)
#!/bin/sh
export HOSTNAME=yhai
export USER=root
export HOME=root
export PS1="[$USER@$HOSTNAME \W]\# " # 终端提示符
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH
| 变量 | 作用 |
|---|---|
HOSTNAME / USER / HOME | 主机名 / 用户名 / 家目录 |
PS1 | 终端提示符([root@yhai ~]# 这种) |
PATH | 命令搜索路径 |
LD_LIBRARY_PATH | 动态库搜索路径 |
内核分析
Linux 系统(OS) = 内核(kernel) + 文件系统(fs)
+ 工具集 + 各种库 + 桌面管理器 + 应用程序
整个 Linux 软件系统从顶到底分三层:
应用层(rootfs)
│ ↑↓
│ 系统调用(syscall)← 唯一通道
│
内核层(Linux kernel)
│ ↑↓
│
硬件层(CPU / 内存 / Flash / 摄像头 / 网卡)

| # | 模块 | 上层(与硬件无关) | 下层(与硬件相关) | 类比 |
|---|---|---|---|---|
| 1 | 进程管理 | 并发多任务(调度算法) | 体系架构相关代码(ARM/x86/MIPS) | CPU 调度 |
| 2 | 内存管理 | 虚拟存储(统一地址空间) | 内存管理(具体 RAM 操作) | 内存分配 |
| 3 | 文件系统 | VFS(统一文件接口) | 块设备(Flash/SD/硬盘) | 数据存储 |
| 4 | 设备管理 | 设备控制(统一设备模型) | 驱动(具体硬件驱动代码) | ⭐ 嵌入式重点 |
| 5 | 网络协议 | 网络通讯(TCP/IP 等) | 网络子系统(网卡驱动) | 联网通信 |
Linux 内核特性
Linux 5 . 13 . 0 - 30 -generic
│ │ │ │ │
│ │ │ │ └─ 通用 / 服务器 / 桌面 等变体标识
│ │ │ └───── 在该发布版本上打的**第 30 次微调 patch**
│ │ └───────── **修订版本号**(bug fix)
│ └───────────── **次版本号**(新功能)
└───────────────── **主版本号**(重大架构变更)
| 目录 | 功能 | 对应第 35 节模块 |
|---|---|---|
arch/ | CPU 体系结构(arm/powerpc/mips/x86/avr/arm64) | 进程管理下层、内存管理下层 |
block/ | 块设备的上层代码(硬盘 / nandflash / SD 卡) | 文件系统下层 |
crypto/ | 加密功能(压缩、CRC 校验、SHA1 加密算法) | (独立子系统) |
Document/ | 内核帮助手册(含设备树文档) | (文档) |
drivers/ | 驱动(网卡 / 摄像头 / U 盘 / flash / i2c 等) | 设备管理下层 ⭐ |
fs/ | 文件系统(ext2 / jffs2 / yaffs2 / sysfs) | 文件系统上层 |
include/ | 头文件(include/asm/ 平台特有、include/linux/ 通用) | 所有模块 |
Init/ | 内核初始化(init/main.c) | 启动入口 |
ipc/ | 进程间通讯(共享内存、信号量) | 进程管理上层 |
kernel/ | 内核调度算法 | 进程管理上层 ⭐ |
mm/ | 内存管理 | 内存管理 ⭐ |
net/ | 网络协议(TCP/IP 等) | 网络协议上层 |
lib/ | 通用库(strlen / memcpy) | 所有模块 |
firmware/ | 固件(二进制程序,只读不写) | (设备管理辅助) |
sound/ | 声卡驱动 | 设备管理下层 |
3.3 drivers/ 子目录速查(按设备类型)
| 设备类型 | 路径 | 例子 |
|---|---|---|
| 摄像头 | drivers/media/i2c/ | imx219.c(索尼 IMX219 传感器) |
| NandFlash | drivers/mtd/ | (Jetson Nano 没焊 NandFlash) |
| SD 卡 | drivers/mmc/ | sdhci-tegra.c 等 |
| 有线网卡 | drivers/net/ethernet/realtek/ | r8169.c(Realtek 8169/8168) |
| I2C 设备(OLED 屏) | drivers/i2c/ | i2c-dev.c 等 |
| USB 设备 | drivers/usb/ | host/(主控)、eth/(网卡)等 |
| 显卡 / GPU | drivers/gpu/ | (Tegra GPU 驱动) |
四、arch/ 目录(CPU 架构专属)
arch/ 是最关键的平台相关目录——所有 CPU 架构相关代码都在这里。
4.1 一级 arch/ 子目录
yhai@yhai:~/bsp/linux-5.16.11/arch$ ls
alpha arm64 csky hexagon ia64 m68k microblaze nds32 nios2 openrisc parisc powerpc riscv s390 sh sparc um x86 xtensa
arm arc arm64 avr ...
每种 CPU 架构一个子目录(arm、arm64、x86、powerpc、mips、riscv…)。
4.2 arch/arm64/ 关键子目录(你做移植主要动这里)
| 路径 | 功能 | 移植时怎么用 |
|---|---|---|
arch/arm64/boot/ | 启动引导(生成 Image) | 编译产物的根目录 |
arch/arm64/boot/dts/ | 设备树(含各厂商 DTS:nvidia/、rockchip/、amlogic/…) | 改硬件信息就改 .dts |
arch/arm64/boot/configs/ | 官方板子配置(defconfig 文件) | 编译前用 make xxx_defconfig 加载 |
arch/arm64/kernel / lib / mm | 平台特有的内核调度、库、内存管理 | 极底层代码,一般不改 |
4.3 arch/arm64/boot/dts/ 下面的厂商子目录
$ ls arch/arm64/boot/dts/
actions/ altera/ amazon/ amd/ amlogic/ apm/ apple/ arm/ bitmain/ broadcom/
cavium/ exynos/ freescale/ hisilicon/ intel/ lg/ marcell/ marvell/ mediatek/
microchip/ nvidia/ qcom/ realtek/ renesas/ rockchip/ socionext/ sprd/ synaptics/
ti/ toshiba/ xilinx/
所以内核的核心设计思想是:
- 把「通用逻辑」(进程调度、TCP/IP 协议、内存管理算法)和「具体硬件细节」(CPU 指令、寄存器地址、中断号)严格分离
- 上层(平台无关)保持稳定,跨平台通用
- 下层(平台相关)按需修改
5.4 平台相关 vs 平台无关 mermaid 对比图
一句话总结:内核用「平台无关 = 跨板通用 + 平台相关 = 改这块就行」的设计,把「硬件变化时软件修改范围」压到最小。这正是嵌入式移植的核心。
内核启动分析

这张图清晰展示了三个阶段:
- 左:引导阶段(head.S)——自解压、保存参数、异常级别、CPU初始化、MMU、重定位、0号进程、异常向量表 → 跳转 start_kernel
- 中:内核初始化(start_kernel)——版本信息、设备树、bootargs、系统调用、内存、调度、中断、控制台 → 跳转 rest_init
- 右:过渡到 rootfs(rest_init)——创建内核线程(1号init/2号守护/0号空转)、驱动初始化、驱动模块初始化 → 运行第一个应用(init=/linuxrc)
| 概念 | 一句话解释 |
|---|---|
| BootLoader | 引导加载程序,负责上电后把内核从存储介质加载到内存并跳转执行 |
| BL1 / BL2 / BL3 / BL4 | Tegra 的 4 级 BootLoader:BootROM → TegraBoot → CBoot → U-Boot |
| head.S | 内核启动的第一个汇编文件,做硬件环境准备后跳转到 C 语言的 start_kernel |
| start_kernel | init/main.c 中的函数,内核初始化的「总入口」,打印大部分启动日志 |
| rest_init | init/main.c 中的函数,创建 0/1/2 号进程、初始化驱动、运行第一个应用 |
| MMU | Memory Management Unit,把物理地址映射成虚拟地址,让应用层看到线性地址空间 |
| 异常级别(EL) | ARMv8 的特权分层:EL0(应用)< EL1(OS)< EL2(虚拟化)< EL3(安全监控) |
| 异常向量表 | 内核预设的「异常处理函数地址表」,中断/同步异常/致命异常发生时自动跳转对应处理函数 |
| 0 号进程 | 内核线程(空闲任务),CPU 没事干时它空转待命 |
| 1 号进程(init) | 初始化进程,负责驱动初始化、驱动模块加载、运行第一个用户态应用 |
| 2 号进程 | 守护进程(kthreadd),负责管理和创建其他内核线程 |
Tegra 的 4 级 BootLoader
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ BL1 │ → │ BL2 │ → │ BL3 │ → │ BL4 │
│ (BootROM) │ │ (TegraBoot) │ │ (CBoot) │ │ (U-Boot) │
│ 芯片固化 │ │ NVIDIA 提供 │ │ NVIDIA 提供 │ │ 我们移植的 │
│ 不可修改 │ │ 不可修改 │ │ 不可修改 │ │ 可自定义 │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
| 层级 | 名称 | 来源 | 我们能改吗? |
|---|---|---|---|
| BL1 | BootROM | 芯片硬件固化 | ❌ 不能 |
| BL2 | TegraBoot | NVIDIA 提供 | ❌ 不能 |
| BL3 | CBoot | NVIDIA 提供 | ❌ 不能 |
| BL4 | U-Boot | 开源 / 我们移植 | ✅ 可以 |
三、内核启动三阶段总览
┌─────────────────────────────────────────────────────────────────────────────┐
│ 第一阶段:引导阶段(汇编) │
│ 文件:arch/arm64/kernel/head.S │
│ 任务:保存U-Boot参数、设置异常级别、CPU初始化、使能MMU、准备0号进程、设置异常向量表 │
│ 终点:跳转到 start_kernel(C语言) │
├─────────────────────────────────────────────────────────────────────────────┤
│ 第二阶段:内核初始化(C语言) │
│ 文件:init/main.c → start_kernel() │
│ 任务:版本信息、设备树安装、bootargs解析、系统调用/内存/调度/中断/控制台初始化 │
│ 终点:跳转到 rest_init() │
├─────────────────────────────────────────────────────────────────────────────┤
│ 第三阶段:过渡到 rootfs(C语言) │
│ 文件:init/main.c → rest_init() │
│ 任务:创建0/1/2号进程、驱动初始化、驱动模块加载、运行第一个应用(init=/linuxrc) │
│ 终点:进入应用层(用户态) │
└─────────────────────────────────────────────────────────────────────────────┘
内核被 U-Boot 加载到内存后,第一条指令就在 arch/arm64/kernel/head.S 里。这个阶段全是汇编代码,完成从「裸机环境」到「能跑 C 语言」的过渡。
4.2 引导阶段步骤详解
| 步骤 | 做什么 | 为什么重要 |
|---|---|---|
| 自解压 / 无 | 32位内核可能需要解压,64位内核一般不压缩直接运行 | 节省上电时间;64位硬件资源足,不需要压缩 |
| 保存 U-Boot 传入参数 | 把 U-Boot 通过寄存器/内存传过来的参数(bootargs、设备树地址等)保存到内核变量 | 后面 start_kernel 要解析这些参数 |
| 异常级别设置 | 设置 ARMv8 的异常级别(一般进入 EL2) | 决定内核的特权级,影响能访问哪些硬件资源 |
| CPU 初始化 | 初始化 CPU 相关寄存器、缓存等 | 让 CPU 进入内核可运行的状态 |
| primary_switch | 从汇编切换到 C 代码的「准备工作」 | 汇编和 C 的调用约定不同(栈、寄存器),需要过渡 |
| 使能 MMU | 打开内存管理单元,开启虚拟地址模式 | 后面内核和应用都运行在虚拟地址空间 |
| 内核重定位 | 根据 MMU 映射关系,调整内核在内存中的位置 | 确保内核代码/数据在虚拟地址空间正确 |
| 准备 0 号进程 | 设置 0 号进程(空闲任务)的栈、任务结构 | CPU 没事干时有个「默认任务」可以运行 |
| 设置异常向量表 | 把中断/同步异常/致命异常的处理函数地址写入向量表 | 内核能响应外部事件(按键、网卡中断、系统调用) |
| 跳转到 start_kernel | b start_kernel 进入 C 语言初始化 | 汇编阶段结束,开始复杂的 C 语言初始化 |
7.2 start_kernel 初始化步骤
| 步骤 | 初始化内容 | 对应内核子系统 | 为什么重要 |
|---|---|---|---|
| 内核版本信息 | 打印 Linux version 4.9.253-tegra ... | (日志输出) | 确认编译的版本、工具链、时间戳 |
| 体系结构和设备树安装 | 解析 CPU 类型、安装设备树(DTB) | arch/ + 设备树 | 设备树是驱动移植的关键——后面会深入 |
| 安装命令行参数(bootargs) | 解析 U-Boot 传入的 bootargs | 启动参数 | root=、console=、init= 都在这里解析 |
| 系统调用初始化 | 设置系统调用入口表 | 进程管理 | 应用层进入内核的唯一合法通道 |
| 内存管理初始化 | 初始化页表、内存分配器(buddy/slab) | mm/ | 进程内存分配的基础 |
| 内核调度初始化 | 初始化调度器(CFS)、创建 init 任务 | kernel/ | 多进程并发的基础 |
| 中断、时钟、定时器初始化 | 注册中断控制器、启动系统时钟 | 中断子系统 | 驱动中断、CPU 时间片都依赖这里 |
| 控制台(console)初始化 | 初始化串口/帧缓冲输出 | 设备管理 | 后面才有 printk 输出到屏幕/串口 |
| 跳转到 rest_init | 调用 rest_init() | 进程管理 | 初始化结束,进入「过渡到 rootfs」阶段 |
源码跟踪
生成 ctags 符号索引
$ ctags -R
生成 cscope 索引库
$ cscope -Rbkq
打开 vim 开始编辑
$ vim .
跳转到函数定义:Ctrl + ]
返回上一个位置:Ctrl + o
当前在 A 文件 → Ctrl+] 跳到 B 文件函数定义 → Ctrl+o 回到 A 文件
→ Ctrl+i 再跳回 B 文件
跳到头文件:gf
当前文件内查找:gd + n
删除和撤销
| 快捷键 | 作用 |
|---|---|
dw | 删除从光标到下一个词开头的内容(删一个单词) |
u | 撤销上一次操作(可无限撤销) |
搜索文件名::cs f f
:cs f f driver.c
| Shift + g | 跳到末尾 |
全局搜索字符串::cs f s
:cs f s attach
快捷搜索光标处的字符串:Ctrl + Shift + - + s
cscope 的 :cs f 后面可以接不同字母,表示不同搜索类型:
| 命令 | 搜索类型 | 例子 | 作用 |
|---|---|---|---|
:cs f s | Symbol | :cs f s attach | 搜索所有引用该符号的位置 |
:cs f g | Global definition | :cs f g start_kernel | 查找函数/变量的定义处 |
:cs f c | Callers | :cs f c start_kernel | 查找谁调用了这个函数 |
:cs f f | File | :cs f f driver.c | 按文件名搜索 |
:cs f i | Included files | :cs f i types.h | 查找哪些文件 include 了这个头文件 |
:cs f t | Text | :cs f t "hello" | 搜索文本字符串 |
垂直分屏:Ctrl + w v
窗口间切换:Ctrl + w w
回到当前目录浏览器::e .
:e .
跟踪线索——用 System.map + addr2line 定位内核第一条指令
| 概念 | 一句话解释 |
|---|---|
| System.map | 内核符号表:make 编译内核后自动生成,记录每个符号(C 函数名 / 汇编标号)↔ 链接后的最终虚拟地址 |
| vmlinux | 编译产出的未压缩、带完整调试信息的内核 ELF 镜像;板上启动用的 Image 是它再加工(strip 掉调试信息等)后的产物 |
| addr2line | 交叉工具链自带的反查工具:输入「地址 + ELF 镜像」,输出函数名 + 源文件:行号 |
| 连接脚本 vmlinux.lds.S | 告诉链接器:几千个 .o 的代码段/数据段在最终镜像里按什么顺序、放在哪里;ENTRY(_text) 指定程序入口 |
| 符号(symbol) | C 函数名、汇编里的标号,本质一样:链接完成后每个符号都绑定一个虚拟地址 |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)