📖 二、什么是 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_defconfigmake 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 · BootROM
CPU 内部 iROM,只读

BL2 · TegraBoot
厂商固件,刷机在这实现

BL3 · CBoot
微内核,USB/显示/logo

BL4 · U-Boot ★
用户可定制

Linux Kernel
Image 内核镜像

Rootfs 根文件系统
挂载

/sbin/init
PID 1 第一个进程

启动脚本 rc.local

APP 应用程序
用户登录运行

颜色含义:

  • 🟫 灰色 = 厂商固件,不能改(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-bootELF 格式(带符号、调试信息)gdb 调试、objdump 反汇编
u-boot.lds链接脚本看代码/数据段地址布局
u-boot.map链接后符号地址映射查某个函数被链接到哪个地址
System.map符号表内核/U-Boot 符号查询
u-boot.dtb / u-boot-dtb.bin设备树二进制设备树相关内容

USB 和 PCIe 架构基本概念

名词一句话解释
EHCIEnhanced Host Controller Interface,USB 2.0 主流主机控制器
OHCI / UHCIUSB 1.1 时代的两套主机控制器规范(OHCI 偏开放,UHCI 是 Intel)
XHCIeXtensible Host Controller Interface,USB 3.0 主机控制器,向下兼容 2.0
Companion 模式EHCI 兼容老 USB 1.1 设备的方式——把 OHCI/UHCI 集成进来
USB 主机侧 vs 设备侧主机侧 = 板子(Host),设备侧 = U 盘 / 摄像头(Device/Gadget)
PCIePeripheral Component Interconnect Express,高速串行总线
差分信号用两根线传"反向"的信号,接收端做差,抗干扰
lane(通道)PCIe 一对差分线(TX+/TX- 或 RX+/RX-)= 1 lane
GT/sGiga 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.cdrivers/usb/net/ 下对应驱动
总线匹配U_BOOT_PCI_DEVICEU_BOOT_USB_DEVICE / usb 设备匹配
设备树匹配compatible = "realtek,rtl8169"compatible = "realtek,rtl8152"
KconfigCONFIG_RTL8169CONFIG_RTL8152 等
Makefileobj-$(CONFIG_RTL8169) += rtl8169.oobj-$(CONFIG_XXX) += xxx.o
  1. 搜索驱动: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 差异非常大,移植难度极大
  1. 置: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   ← 关键!
  1. 编译 + 拷贝
make
cp u-boot.bin /tftpboot
  1. 切换到 USB 网卡测试
usb start                        # 启动(扫描)USB 控制器
# 把网线从有线网口拔出,插入 USB 网卡
ping 192.168.9.119
环节有线网卡(PCIe)USB 网卡(本课)
激活总线pci enumusb start
命令源码cmd/pci.ccmd/usb.c
总线驱动Tegra PCI supportxHCI + EHCI + Tegra T210
设备驱动drivers/net/rtl8169.cdrivers/usb/eth/r8152.c
驱动 KconfigCONFIG_RTL8169CONFIG_RTL8152
ping 标志eth_rtl8169r8152_eth

U-Boot 启动盘优先顺序与环境变量

概念一句话解释
boot_targetsU-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 加载内核
cbootargsC-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                           # 复位验证自动启动

最小系统基本概念

存储

寄存器

RAM 内存
掉电丢失

ROM / Flash
掉电可永久保存

CPU 内部
速度最快
空间最小

DRAM
靠电容存储
电脑内存条 / 手机 4G 8G

SRAM
靠门电路存储
CPU 一级/二级/三级缓存
更快更贵

ROM 演进历史

Flash 现代主流

ROM 只读

PROM
programmable ROM
熔丝技术:熔断为0、不熔断为1
只有一次写入机会

EPROM
Electrically Programmable
紫外线擦除(曝光一段时间)

EEPROM
Electrically Erasable Programmable
电擦除(高压电场)

NorFlash
晶体管并联
每一个 bit 都可寻址
可直接 XIP 运行程序

NandFlash
晶体管串联
按块访问(不能按位寻址)
不能直接运行程序

EMMC
NandFlash + 控制器 + 标准封装接口
BGA 焊在板上

TF 卡 / SD 卡 / U 盘
可移动 / 可热插拔

SSD 固态硬盘

2.2 核心区分表

类别典型代表掉电后速度用途
寄存器CPU 内部通用寄存器丢失最快CPU 暂存数据
RAM 内存DRAM(电脑内存条)、SRAM(CPU 缓存)丢失快运行中程序/数据
NorFlash小容量 flash永久快(可 XIP)存放启动代码、小容量固件
NandFlash大容量 flash永久中(按块)存放大量数据
EMMCNandFlash + 控制器 + 接口永久中内嵌多媒体卡(Jetson Nano 内部存储)
SD 卡 / TF 卡 / U 盘可移动 NandFlash永久中移动存储(板子用读卡器转 USB 也算)
SSD大容量 NandFlash永久快电脑硬盘

/ 根目录
最顶层文件系统

/boot
启动引导

/bin
普通命令

/sbin
管理员命令

/etc
配置和启动脚本

/lib /lib64
库文件

/dev
设备文件

/media
可移动设备挂载点

/proc
伪文件系统
CPU/mem/进程

/sys
伪文件系统
设备模型

/root /home
用户目录

/mnt
挂载点

Image 内核镜像
initrd 临时 rootfs

ls / cd / pwd / vi

insmod 等管理员命令

启动脚本、配置文件

内核核心模块 /lib/modules

一切皆文件的设备抽象

U 盘插入自动挂到这里

查看系统信息(cpuinfo 等)

替代旧 /dev 管理设备

管理员/普通用户家目录

临时挂载别的设备

目录作用典型内容
/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 类型介质功用典型场景
JFFS2NorFlash / NandFlash闪存文件系统(日志型防掉电);缺点:快满时垃圾回收慢嵌入式设备常用
YAFFS2NandFlash专用于 Nand 的闪存文件系统;改进了 JFFS2 的压缩(不压缩 → 更快)手机资料存储
CRAMFSNorFlash / NandFlash只读的压缩文件系统;安全性高(不被篡改)不需要用户改的场景
EXT2 / EXT4DISK(硬盘、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

busybox 一个二进制文件

cp(软链接)

mv(软链接)

ls(软链接)

vi(软链接)

cat(软链接)

... 上百个命令

linuxrc 第一个应用

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/nullc 1 3空设备——写进去的数据直接消失(黑洞)
dev/consolec 5 1控制台——没有它,内核起来可能没有串口信息输出

why 必须手动创建这两个:/dev 里大多数设备文件是内核启动后自动创建的;但 dev/null 和 dev/console 是内核启动早期就要用到的(控制台输出、init 进程读 null),必须提前手动创建,否则启动会出问题。

mknod dev/null c 1 3 里:c = 字符设备,1 = 主设备号,3 = 次设备号。

NFS 的解法

NFS 服务
网线

实时读写(不是拷贝)

虚拟机
/nfs/rootfs
(rootfs 真实存放)

板子
挂载到 /mnt
(内存映射)

  • 板子不烧写,通过网线把虚拟机的 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 启动设置

概念一句话解释
inittabinit 进程读的启动配置文件,指定初始化脚本、终端 shell
rcS系统初始化脚本,启动时执行的第一个脚本
fstab自动挂载的文件系统列表(介质 / 挂载点 / 类型)
profile系统环境变量设置(PATH、LD_LIBRARY_PATH 等)
mdevbusybox 的设备管理器,热插拔/启动时自动创建设备节点
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/nfsrootfs 在 NFS 上
nfsroot=192.168.9.119:/nfs/rootfsNFS 服务器 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

二、系统配置脚本:启动流程总览

内核启动 → 挂载 rootfs

运行 init 进程

读 /etc/inittab

sysinit: 执行 /etc/init.d/rcS

rcS 里 mount -a
(读 /etc/fstab 挂载文件系统)

mdev 创建设备节点

ifconfig 配 IP / 跑自己程序

askfirst: 启动 shell
(读 /etc/profile 设环境变量)

进入终端,能敲命令

四个关键文件各司其职:**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/initinit 重启时执行
ctrlaltdel:/sbin/rebootCtrl+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 = 默认)
挂载类型作用
/procproc伪文件系统,暴露内核信息(CPU/mem/进程)
/tmptmpfs内存临时文件系统(掉电丢失)
/syssysfs设备模型信息
/devtmpfs设备文件(内存里,配合 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 传感器)
NandFlashdrivers/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/(网卡)等
显卡 / GPUdrivers/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 对比图

Linux 内核源码

平台无关代码
(换板子不动)

平台相关代码
(换板子要改)

kernel/
调度算法

mm/
内存管理算法

net/
TCP/IP 协议

fs/
VFS 框架

block/
块设备上层

ipc/
进程间通讯

include/linux/
通用头文件

arch/
CPU 体系结构

include/asm/
平台头文件

drivers/
设备驱动(部分)

arch/arm64/boot/dts/
设备树

一句话总结:内核用「平台无关 = 跨板通用 + 平台相关 = 改这块就行」的设计,把「硬件变化时软件修改范围」压到最小。这正是嵌入式移植的核心。

内核启动分析

在这里插入图片描述

这张图清晰展示了三个阶段:

  • 左:引导阶段(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 / BL4Tegra 的 4 级 BootLoader:BootROM → TegraBoot → CBoot → U-Boot
head.S内核启动的第一个汇编文件,做硬件环境准备后跳转到 C 语言的 start_kernel
start_kernelinit/main.c 中的函数,内核初始化的「总入口」,打印大部分启动日志
rest_initinit/main.c 中的函数,创建 0/1/2 号进程、初始化驱动、运行第一个应用
MMUMemory 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 提供 │    │ 我们移植的  │
│ 不可修改    │    │ 不可修改    │    │ 不可修改    │    │ 可自定义    │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘
层级名称来源我们能改吗?
BL1BootROM芯片硬件固化❌ 不能
BL2TegraBootNVIDIA 提供❌ 不能
BL3CBootNVIDIA 提供❌ 不能
BL4U-Boot开源 / 我们移植✅ 可以

BL1
BootROM
芯片固化

BL2
TegraBoot

BL3
CBoot

BL4
U-Boot
我们移植 ⭐

Linux Kernel
Image

Rootfs
根文件系统

APP
应用程序


三、内核启动三阶段总览

┌─────────────────────────────────────────────────────────────────────────────┐
│  第一阶段:引导阶段(汇编)                                                    │
│  文件: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_kernelb 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 sSymbol:cs f s attach搜索所有引用该符号的位置
:cs f gGlobal definition:cs f g start_kernel查找函数/变量的定义处
:cs f cCallers:cs f c start_kernel查找谁调用了这个函数
:cs f fFile:cs f f driver.c按文件名搜索
:cs f iIncluded files:cs f i types.h查找哪些文件 include 了这个头文件
:cs f tText: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 函数名、汇编里的标号,本质一样:链接完成后每个符号都绑定一个虚拟地址
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐