Linux三板斧

U-Boot

编译uboot

MC6830

export PATH=/home/meian/work/toolChain/molchipv500-armgcc-uclibc/bin:$PATH
make boot myboard=mc6830/demoboard

产物

image-20260819103939845

A1

image-20260105185419377

产物

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很小。

作用:

  1. 确定启动源 ,根据Boot 引脚,决定从SPI NOR 还是SPI NAND ,eMMC、SD 卡启动。
  2. 初始化最最基础的硬件,加载 SPL 所必需的最少部分
    1. 极少量时钟
    2. SPI / NAND / eMMC 的 只读能力
    3. 内部 SRAM(供 SPL 使用)
    4. 不初始化 DRAM
  3. 把 SPL 读到 SRAM
  4. (可选)做安全校验(Secure Boot)
    1. 校验签名
    2. 校验 hash
    3. 校验证书链
  5. 跳转执行 SPL

SPL (Secondary Program Loader)

在CPU刚开始上电的时候,没有 DRAM,没有 C 运行环境,栈都没有。你如果想跑u-boot程序,前提就是DRAM初始化完成。

作用:

  1. 初始化最基本的硬件尤其是 DRAM
  2. 提供“最小运行环境
    1. 设置栈(stack)
    2. 设置 BSS
    3. 关闭/简单配置 cache
    4. 让 C 代码能跑起来
  3. 从启动介质加载 U-Boot proper
    1. 如果启动介质:SPI NOR
      1. 初始化 SPI
      2. 从固定 offset 读取 u-boot.bin
      3. 把它搬到 DRAM

img

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校验
Typekernel / 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(访问控制列表)或某些特定功能的校验值。
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 文件系统:

  1. 内核启动后,会将所有设备信息导出到 /sys 目录下。
  2. 执行 mdev -s 时,它会递归扫描 /sys/class 和 /sys/block。
  3. 它会寻找每个设备目录下的 dev 文件(这个文件记录了设备的主次设备号)。
  4. 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 前做不了,或者做起来不方便的事。

  1. 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。

  1. 需要先加载驱动或创建设备节点

直接挂载 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 时执行
restartinit自身重启时执行

/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"
);
位置值含义
_namesata_power命令名
_maxargs2最大参数个数,包含命令名本身
_rep2是否可重复;非 0 即可。惯例写 0/1,写 2 效果等同 1
_cmddo_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

  1. 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

参考:Squashfs 4.0;U-Boot mkimage。