Linux三板斧
U-Boot
编译uboot
MC6830
export PATH=/home/meian/work/toolChain/molchipv500-armgcc-uclibc/bin:$PATH
make boot myboard=mc6830/demoboard
产物

A1

产物
u-boot.bin只包含 U-Boot proper(第二阶段引导程序)
u-boot-with-spl.bin = SPL(第一阶段) + U-Boot proper(第二阶段) + 可能的对齐/头部/校验/填充。
BootROM
下面是常见的 BootROM → SPL → U-Boot proper 流程,具体初始化职责和加载格式由 SoC/SDK 决定,不能套用于所有芯片。
存放在Soc内部的ROM,这个ROM的size很小。
作用:
- 确定启动源 ,根据Boot 引脚,决定从SPI NOR 还是SPI NAND ,eMMC、SD 卡启动。
- 初始化最最基础的硬件,加载 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的命令
查看启动参数
bootcmd: 告诉 “U-Boot“怎么启动 Linux”,uboot上电倒计时结束运行的就是这个(run bootcmd)。
在 U-Boot 交互命令行中查看
printenv bootcmd
bootargs:告诉 Linux 内核 “启动之后怎么运行’’ ,里面的内容是传递给Linux内核
- 查看
/proc/cmdline
cat /proc/cmdline
mem=160M earlycon console=ttyS0,115200 root=/dev/mtdblock3 rootfstype=jffs2 rw mtdparts=spi-nor:512K(uboot),128K(logo),2816K(kernel),8192K(rootfs),21120K(app)
sf read
用来读取flash的内存数据
sf read <RAM地址> <SPI Flash偏移> <长度>
sf read 0x80008000 0xA0000 2C0000
编译内核
君正A1N
make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- distclean
make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- clean
#设置
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删除
cp my_config_backup .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 clean
- 它只删除编译生成的中间文件(
.o文件)和最终的内核镜像。 - 它不会删除
.config文件,也不会删除编译过程中自动生成的一些依赖文件和配置缓存。 - 适用场景:你只是修改了某个驱动代码,想快速重新编译一下。
- 它只删除编译生成的中间文件(
- make mrproper(彻底清理):
- 它会删除所有编译生成的文件,包括
.o、内核镜像、.config配置文件,以及各种自动生成的依赖(如.depend、.version等)。它会将源码树恢复到刚解压时的“最干净”状态。 - 适用场景:“环境乱掉了”,比如你切换了交叉编译器、修改了架构(ARCH),或者遇到了一些莫名其妙的链接错误、头文件找不到的错误。
- 它会删除所有编译生成的文件,包括
- make distclean(最彻底):
- 比
mrproper还要狠,它不仅清理编译产物,还会删除编辑器产生的备份文件(如*~)和补丁文件(*.rej)。一般用于准备打包发布源码。
- 比
添加新选项
#添加新选项
make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- menuconfig
#编译内核
make ARCH=mips CROSS_COMPILE=mips-v720s229-linux-uclibc-gnu- -j8 uImage
内核的产物
一般放在arch/架构/boot里
uImage
- legacy uImage 是 64 字节 U-Boot image header + 镜像载荷;载荷由架构和构建目标决定,不一定是 zImage
- 可用 mkimage 为内核载荷添加头部,其中记录实际采用的压缩方式、加载地址、入口地址等信息
- 一般由bootm启动
uImage
┌────────────────────────────┐
│ U-Boot legacy header 64B │
├────────────────────────────┤
│ zImage / compressed kernel │
└────────────────────────────┘
这个U-Boot 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
编译得到的dtb可以有两种方式添加到内核
内置方式
在.config里
CONFIG_BUILTIN_DTB=y
CONFIG_MIPS_NO_APPENDED_DTB=y
设备树专题见:dts.md。
制作根文件系统
成功安装 SDK 后,在 resource/rootfs 目录中存放已制作好的根文件系统,可以跳过文件系统的制作,直接使用 SDK 中提供的文件系统。如果用户如果有特殊需求,可在 busybox 的基础上制作根文件系统。制作根文件系统的具体操作步骤如下
第一步:建立根文件系统文件 rootfs
#mkdir rootfs
#cd rootfs
#cp -a opensource/busybox/_install/. ./
#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 -a);不能把开发主机的 /dev 节点不加区分地搬到目标板。
lib 目录是存放应用程序所需要的库文件,根据应用程序需要拷贝相应的库文件。
文件系统的选择
对于 NorFlash Based 系统,存储空间一般较小,因此会选用压缩文件系统。推荐以下两种文件系统:
A. squashfs:只读文件系统,压缩率高
B. jffs2:可读写文件系统,可选择压缩方式
推荐的系统搭建的方案是系统 rootfs 以及不需要经常修改的系统分区采用squashfs 文件系统,而配置分区 system 等需要经常读写的分区采用 jffs2 文件系统。
Squashfs 文件系统
squashfs 文件系统是一套基于 Linux 内核使用的压缩只读文件系统,压缩率高。squashfs 具有如下特点:
优点:压缩率高,数据(data),节点(inode)和目录(directories)都被压缩,可以节省flash 空间,Squashfs 4.0 支持 64 位文件及文件系统大小,不限于 4 GiB;实际还受工具版本、内核和设备限制。
缺点:无法写操作
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 在 JFFS 基础上改进的文件系统,是用于微型嵌入式设备的原始闪存芯片的实际文件系统。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会由用户态 mdev 扫描 sysfs 中已存在的设备信息,把已经存在的硬件信息找出来,批量在/dev下生成节点。- 如果不执行:仅在 /dev 没有静态节点、devtmpfs 或其他设备管理器负责填充时,才可能缺少节点;使用 devtmpfs 的系统并非必须靠 mdev -s 创建设备节点。
它是如何工作的?(原理)
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,则代表该用户不允许登录系统(常用于系统服务账号)。 |
常用
自定义函数
在uboot阶段,自定义函数并调用
在include/command.h
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
U_BOOT_CMD(
sata_power, 2, 2, do_sata_power,
"sata_power 0/1.{######### add by lifan@2022.06.22 ########}\n",
"\t- sata_power 0/1 ;(0 - ON , 1 - OFF) \n"
);
| 位置 | 值 | 含义 |
|---|---|---|
_name | sata_power | 命令名 |
_maxargs | 2 | 最大参数个数,包含命令名本身 |
_rep | 2 | 是否可重复;非 0 即可。惯例写 0/1,写 2 效果等同 1 |
_cmd | do_sata_power | 处理函数 |
_usage | 短帮助 | 敲 help(无参数)列出全部命令时 |
_help | 长帮助 | 敲 help sata_power,或命令返回 CMD_RET_USAGE(-1)时 |
操作寄存器
md= memory display,读内存mw= memory write,写内存.l= long word,每次按 32 bit / 4 字节 操作
md.l <address> <count>
配置gpio
uboot
- PIN Mux
把物理的PAD 切换到GPIO。PAD(焊盘)可以理解为芯片的的"物理引脚(管脚)控制器"
引脚对应Linux gpio编号
例如 PE16对应gpio144
PA = 0 × 32
PB = 1 × 32
PC = 2 × 32
PD = 3 × 32
PE = 4 × 32
PE16 = 4 × 32 + 16 = 144