Linux三板斧
U-Boot
编译uboot

**u-boot.bin**只包含 U-Boot proper(第二阶段引导程序)
**u-boot-with-spl.bin **= SPL(第一阶段) + U-Boot proper(第二阶段) + 可能的对齐/头部/校验/填充。
BootROM
存放在Soc内部的ROM,这个ROM的size很小。
作用:
- **确定启动源 **,根据Boot 引脚,决定从SPI NOR 还是SPI NAND ,eMMC,SDK启动。
- **初始化最最基础的硬件,**加载 SPL 所必需的最少部分
- 极少量时钟
- SPI / NAND / eMMC 的 只读能力
- 内部 SRAM(供 SPL 使用)
- 不初始化 DRAM
- 把 SPL 读到 SRAM
- (可选)做安全校验(Secure Boot)
- 校验签名
- 校验 hash
- 校验证书链
- 跳转执行 SPL
SPL (Secondary Program Loader)
在CPU刚开始上电的时候,没有 DRAM,没有 C 运行环境,栈都没有。你如果想跑u-boot程序,前提就是DRAM初始化完成。
作用:
- 初始化最基本的硬件尤其是 DRAM
- 提供“最小运行环境
- 设置栈(stack)
- 设置 BSS
- 关闭/简单配置 cache
- 让 C 代码能跑起来
- 从启动介质加载 U-Boot proper
- 如果启动介质:SPI NOR
- 初始化 SPI
- 从固定 offset 读取
u-boot.bin - 把它搬到 DRAM
- 如果启动介质:SPI NOR

查看uboot的启动参数
在 U-Boot 交互命令行中查看
printenv bootcmd查看
/proc/cmdline
cat /proc/cmdline
Kernel
编译内核
君正A1N
make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- distclean
make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- isvp_a1_calf_defconfig
make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- menuconfig
make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- uImage -j8
富瀚MC6830
make ARCH=arm CROSS_COMPILE=arm-mol-linux-uclibcgnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-mol-linux-uclibcgnueabihf- -j8
#编译出现问题
cp .config my_config_backup
make ARCH=arm CROSS_COMPILE=arm-mol-linux-uclibcgnueabihf- mrproper #会把.config删除
make ARCH=arm CROSS_COMPILE=arm-mol-linux-uclibcgnueabihf- olddefconfig
当环境乱掉了
# 1️⃣ 先备份配置
cp .config my_config_backup
# 2️⃣ 彻底清理
make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- mrproper
# 或 distclean(更彻底,但通常 mrproper 就够)
# 3️⃣ 恢复配置
cp my_config_backup .config
# 4️⃣ 让内核“消化”你的配置
#读取当前的.config,对比Kconfig定义
#找出.config中缺失的新选项
#缺失的选项全部填入默认值(不询问,静默处理
#更新 .config 后退出
make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- olddefconfig
# 5️⃣ 再编
make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- uImage -j$(nproc)
添加新选项
#添加新选项
make menuconfig
#编译内核
make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- -j8 uImage
内核的产物
一般放在arch/架构/boot里
uImage
- uImage的本质是zImage + U-Boot image header
- 通过
mkimage对普通的压缩内核映像文件(zImage)加工而得 - 一般由bootm启动
+-----------------------+
| U-Boot Image Header | 64 bytes
+-----------------------+
| zImage / Image data |
+-----------------------+
header 里包含:
| 字段 | 作用 |
|---|---|
| Magic | 识别是不是 uImage |
| Load Addr | 加载地址 |
| Entry Addr | 跳转地址 |
| Size | 内核大小 |
| CRC | 校验 |
| Type | kernel / ramdisk |
zImage
解压器 + 压缩后的内核
可自解压的压缩内核镜像
一般由bootz启动
zImage通常包含:
| 内容 | 说明 |
|---|---|
| 解压启动代码 | 一小段架构相关的汇编/C代码,负责准备环境、解压内核 |
| 压缩后的内核数据 | 由 vmlinux 转换、压缩后得到 |
| 入口地址信息 | 供 bootloader 或自身解压逻辑跳转使用 |
| 可选 initramfs | 如果内核配置了 CONFIG_INITRAMFS_SOURCE,可能会被打进内核 |
| 通常不包含 U-Boot header | 这个是 uImage 才有的 |
bootz:
bootz <kernel_addr> <initrd_addr> <fdt_addr>
- 第1个参数:Linux 内核 zImage 在 RAM 中的地址
- 第2个参数:initrd/initramfs 地址,没有则用
-表示没有 - 第3个参数:Device Tree Blob(DTB) 地址
Device Tree
设备树专题见:dts.md。
制作根文件系统
成功安装 SDK 后,在 resource/rootfs 目录中存放已制作好的根文件系统,可以跳过文件系统的制作,直接使用 SDK 中提供的文件系统。如果用户如果有特殊需求,可在 busybox 的基础上制作根文件系统。制作根文件系统的具体操作步骤如下
**第一步:**建立根文件系统文件 rootfs
#mkdir rootfs
#cd rootfs
#cp –R opensource/busybox/_intsall/* ./
#mkdir etc dev lib tmp var mnt system proc
**第二步:**配置 etc、lib、dev 等目录的必需文件(可以参考 SDK 中的文件系统)。
etc 目录可参考系统 /etc 下的文件。其中最主要的文件包括 inittab、fstab、init.d/rcS 文件等,这些文件最好从 busybox 的 examples 目录下拷贝过来,根据需求自行修改。
dev 目录下的设备文件,可以直接从系统中拷贝过来或者使用 mknod 命令生成需要的设备文件。拷贝文件时请使用 cp –R filename。
lib 目录是存放应用程序所需要的库文件,根据应用程序需要拷贝相应的库文件。
文件系统的选择
对于 NorFlash Based 系统,存储空间一般较小,因此会选用压缩文件系统。推荐以下两种文件系统:
A. squashfs:只读文件系统,压缩率高
B. jffs2:可读写文件系统,可选择压缩方式
推荐的系统搭建的方案是系统 rootfs 以及不需要经常修改的系统分区采用squashfs 文件系统,而配置分区 system 等需要经常读写的分区采用 jffs2 文件系统。
Squashfs 文件系统
squashfs 文件系统是一套基于 Linux 内核使用的压缩只读文件系统,压缩率高。squashfs 具有如下特点:
**优点:**压缩率高,数据(data),节点(inode)和目录(directories)都被压缩,可以节省flash 空间,最大可以支持 4G 文件系统。
**缺点:**无法写操作
squashfs 制作方法为:
mksquashfs <源目录> <输出镜像> [选项]
-b 64K
- 指定squashfs block size
-comp (压缩算法)
- xz
-noappend(不追加)
- 强制覆盖现有的输出文件,而不是在旧文件后面追加数据。
-no-xattrs(不包含扩展属性))
- 禁止在镜像中保存文件的“扩展属性”(Extended Attributes)。
- 什么是扩展属性:它类似于 Windows 下的文件元数据,通常用于存储 SELinux 安全标签、文件 ACL(访问控制列表)或某些特定功能的校验值。
- 禁止在镜像中保存文件的“扩展属性”(Extended Attributes)。
mksquashfs rootfs_jz_nvr_36 rootfs_36_v1 -b 64K -comp xz -noappend -no-xattrs
其中,mksquashfs 为制作 squashfs 文件系统工具可网上下载;rootfs 是之前已经制作好的根文件系统; rootfs.squashfs.img 是生成输出的 squashfs 文件系统映像文件。 -b 64K 指定 squashfs 文件系统的块大小为 64K(决定于实际 spi flash 块大小)。-comp 指定文件系统压缩方式为 xz。请根据实际情况修改参数。
Jffs2 文件系统
jffs2 是 RedHat 的 David Woodhouse 在 jffs2 基础上改进的文件系统,是用于微型嵌入式设备的原始闪存芯片的实际文件系统。jffs2 文件系统是日志结构化的**可读写**的文件系统。jffs2 的优缺点如下:
优点: 使用了压缩的文件格式。最重要的特性是可读写操作。
缺点: jffs2 文件系统挂载时需要扫描整个 jffs2 文件系统,因此当 jffs2 文件系统分区增大时,挂载时间也会相应的变长。使用 jffs2 格式可能带来少量的 Flash 空间的浪费。这主要是由于日志文件的过度开销和用于回收系统的无用存储单元,浪费的空间大小大致是若干个数据段。jffs2 的另一缺点是当文件系统已满或接近满时,jffs2运行速度会迅速降低。这是因为垃圾收集的问题。
jffs2 的制作方法为:
# mkfs.jffs2 -o rootfs.jffs2 -r ../rootfs -e 0x10000 -s 0x1000 -n -l -X zlib --pad=0x20000
其中,mkfs.jffs2 工具可以从互联网中下载。rootfs 为之前已经制作好的根文件系统;rootfs.jffs2 为输出的镜像文件。
参数说明:
-d 指定根文件系统;
-l little-endian 小端模式;
-e flash 的块大小;
-o 输出镜像文件;
-s 页大小;
–pad 输出文件大小
注意
jffs2 制作某个分区为 jffs2 文件,分区大小必须为 nor erase_size 的整数倍,这个是官方驱动的要求。
这是因为JFFS2
/app是squashfs文件系统, 它的大小以实际 squashfs 镜像的大小为准,而不是mtd4分区16MB
JFFS2 是可读写的,它会用满整个 mtd5 分区, 大小略小于mtd5分区,是因为JFFS2 自身的元数据开销
[root@Ingenic-uc1_1:smart]# df -h
Filesystem Size Used Available Use% Mounted on
/dev/root 1.8M 1.8M 0 100% /
devtmpfs 122.3M 40.0K 122.2M 0% /dev
tmpfs 122.3M 40.0K 122.2M 0% /dev
tmpfs 122.3M 4.0K 122.3M 0% /tmp
tmpfs 122.3M 0 122.3M 0% /run
/dev/mtdblock4 13.0M 13.0M 0 100% /app
/dev/mtdblock5 11.1M 388.0K 10.7M 3% /var
tmpfs 5.0M 0 5.0M 0% /var/run
mdev
在 Linux 启动过程中,内核会先检测硬件(如 Flash、网卡、串口、I2C 设备等)。虽然内核知道了这些硬件的存在,但在用户空间(也就是你的程序里),需要通过 /dev/ttyS0 或 /dev/sda 这样的文件才能访问硬件。
mdev的静态扫描(-s):在系统刚启动时,/dev目录下通常是空的(如果是挂载的tmpfs)。执行mdev -s会强制内核重新遍历整个/sys/class和/sys/block目录,把已经存在的硬件信息找出来,批量在/dev下生成节点。- 如果不执行:你会发现
/dev目录下空空如也,你的 NVR 程序可能会报错“找不到设备”。
它是如何工作的?(原理)
mdev 并不是凭空想象设备名字的,它依赖于 sysfs 文件系统:
- 内核启动后,会将所有设备信息导出到
/sys目录下。 - 执行
mdev -s时,它会递归扫描/sys/class和/sys/block。 - 它会寻找每个设备目录下的
dev文件(这个文件记录了设备的主次设备号)。 mdev读取这些编号,结合/etc/mdev.conf中的规则,在/dev下调用mknod创建对应的设备节点。
启动流程
上电
↓
Boot ROM
↓
Bootloader,例如 U-Boot
↓
加载并启动 Linux kernel
↓
内核初始化
↓
挂载根文件系统 rootfs
↓
启动 PID 1:/sbin/init
↓
init 读取 /etc/inittab
↓
执行 /etc/init.d/rcS 等初始化脚本
↓
启动 getty、业务程序和后台服务
↓
进入可登录、可工作的系统
挂载根文件系统
flash上的根文件系统
root=/dev/mtdblock3 rootfstype=squashfs
内核启动后会直接尝试挂载这个设备作为 /,然后执行:/sbin/init
这种流程是:U-Boot – > kernel –> 直接挂载 flash 上的 rootfs –> 执行 /sbin/init
适合:
flash 分区固定
rootfs 类型固定
驱动全部内建进 kernel
不需要升级判断
不需要解密
不需要校验
不需要选择 A/B 分区
内存里的initramfs
initramfs 的作用是“先有一个能跑脚本的最小系统”,initramfs.cpio.gz 会被 kernel 解压到 RAM 里,形成一个临时 /
里面通常有
/init
/bin/busybox
/bin/sh
/dev
/proc
/sys
/etc/init.d/rcS
它可以做很多 kernel 直接 mount rootfs 前做不了,或者做起来不方便的事。
- flash/rootfs 设备还没准备好
NAND flash、SPI-NOR、eMMC、SATA 硬盘、SD 卡有些设备不是 kernel 一启动就能立刻稳定可用。如果 kernel 直接 mount,设备还没出来,就会报:VFS: Cannot open root device
initramfs 可以先循环等待:
while [ ! -e /dev/mmcblk0p2 ]; do
sleep 1
done
然后再挂载真正 rootfs。
- 需要先加载驱动或创建设备节点
直接挂载 rootfs 要求相关驱动已经编进 kernel:
MTD 驱动
init进程
init 是内核启动的第一个用户态进程。它负责把系统从 “内核已经启动” 推进到"业务可以运行"。既是用户空间初始化管理者,也是PID 1进程。
在 BusyBox 系统里,主要职责通常是:
读取
/etc/inittab;执行系统初始化脚本;
启动登录终端;
启动需要常驻的进程;
回收孤儿进程;
在系统重启、关机时运行相应命令;
对
respawn类型的进程进行重启。
/etc/inittab文件
/etc/inittab 是 init的配置文件。
它告诉 init:
- 系统初始化时执行什么;
- 哪些程序只运行一次;
- 哪些程序退出后要重新启动;
- 哪个终端需要启动登录程序;
- 按下特定组合键时执行什么;
- 重启和关机时执行什么。
inittab的内容:
# Startup the system
::sysinit:/sbin/swapoff -a
::sysinit:/bin/mount -t tmpfs tmpfs /dev
::sysinit:/bin/mkdir -p /dev/pts
::sysinit:/bin/mkdir -p /dev/shm
::sysinit:/bin/mount -a
::sysinit:/bin/hostname -F /etc/hostname
# now run any rc scripts
::sysinit:/etc/init.d/rcS
# Put a getty on the serial port
console::respawn:/sbin/getty -L console 115200 vt100 # GENERIC_SERIAL
常见的action
| action | 作用 |
|---|---|
sysinit | 系统初始化时优先执行 |
wait | 执行并等待结束 |
once | 执行一次,不等待永久常驻 |
respawn | 退出后重新启动 |
askfirst | 按 Enter 后启动,退出后再次等待 |
shutdown | 关机或重启时执行 |
ctrlaltdel | 收到 Ctrl+Alt+Delete 时执行 |
restart | init自身重启时执行 |
/etc/inittab里的格式:
<id>:<runlevels>:<action>:<process>
例子:
console : 空 : respawn : /sbin/getty -L console 115200 vt100
│ │ │ │
│ │ │ └─ 要启动的程序
│ │ └─ 退出后重新启动
│ └─ runlevels,BusyBox 中通常忽略
└─ 标识符,同时常用于指定终端
getty:打开 TTY,提示输入登录名,然后调用 /bin/login。
/bin/login 主要负责:
- 查找用户信息,例如
/etc/passwd - 检查密码,例如
/etc/shadow - 检查用户是否允许登录
- 设置 UID、GID
- 设置 HOME、USER、SHELL、PATH 等环境变量
- 切换到用户主目录
- 启动用户 shell
那么认证成功后,login 会启动:/bin/sh ,此时你才真正看到 shell 提示符:
[root@Ingenic-uc1_1:~]#
/etc/passwd
格式
#用户名:密码字段:UID:GID:描述:家目录:登录Shell
root:x:0:0:root:/root:/bin/sh
| 字段名称 | 示例值 | 详细含义 |
|---|---|---|
| 用户名 (Username) | root | 用户登录系统时输入的名称。 |
| 密码占位符 (Password) | x | 早期这里存放加密后的密码。出于安全考虑,现代 Linux 将密码加密串移到了 /etc/shadow 文件中,这里仅用 x 占位。 |
| 用户 ID (UID) | 0 | 系统的唯一数字标识。0 永远代表超级用户 root。通常系统用户的 UID 为 1~999,普通用户从 1000 开始。 |
| 组 ID (GID) | 0 | 用户所属**主组(Primary Group)**的数字标识,对应 /etc/group 中的记录。 |
| 用户信息说明 (GECOS) | root | 备注字段,通常写用户的真实姓名、办公地址或电话。一般可以为空。 |
| 家目录 (Home Dir) | /root | 用户登录成功后,默认进入的初始工作目录。普通用户通常是 /home/用户名。 |
| 登录 Shell (Shell) | /bin/bash | 用户登录后由哪个程序来解析命令。如果是 /sbin/nologin 或 /usr/sbin/nologin,则代表该用户不允许登录系统(常用于系统服务账号)。 |