Linux三板斧

U-Boot

编译uboot

image-20260105185419377

**u-boot.bin**只包含 U-Boot proper(第二阶段引导程序)

**u-boot-with-spl.bin **= SPL(第一阶段) + U-Boot proper(第二阶段) + 可能的对齐/头部/校验/填充。

BootROM

存放在Soc内部的ROM,这个ROM的size很小。

作用:

  1. **确定启动源 **,根据Boot 引脚,决定从SPI NOR 还是SPI NAND ,eMMC,SDK启动。
  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的启动参数

  • 在 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校验
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

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

  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/inittabinit的配置文件

它告诉 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,则代表该用户不允许登录系统(常用于系统服务账号)。