ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux驱动初始化

Linux驱动初始化

一、嵌入式设备启动流程

1.ROM Boot(第一阶段,芯片内置ROM,出厂固化,运行厂家固化在IROM里的代码)

初始化基础硬件(需要的时钟和设备):PLL、最小RAM

判断启动方式:emmc,SD,USB

把bootloader第一阶段代码加载到IRAM(芯片运行内存)

2.U-Boot(在IRAM里运行对应的bootloader第一阶段的代码)

SPL(Secondary Program Loader(二级程序加载器)):u-boot-spl.bin

初始化整个系统的时钟

初始化内存

把完整的bootloader搬移到内存中

3.在内存里运行bootloader第二个阶段的代码

初始化了一些硬件设备(根据开发板 去定义(网卡;串口;USB))

加载对应的操作系统(内存)

4.运行操作系统(无图形界面)

5.挂载文件系统(EXT(2,3,4))

6.启动应用程序

不在IROM里初始化内存的原因:因为芯片厂家不知道开发板采用什么样的内存,没有办法进行初始化

为啥bootloader分两个阶段?

运行芯片内部存储空间有限,如果内部空间做大,成本提高

二、U-boot和Linux 内核之间的参数传递
内核自身上电不会主动扫描、探测全部硬件资源,依赖 U-Boot 传递基础信息才能正常初始化系统。根文件系统路径、串口 控制台、网卡 IP、NFS 挂载配置这类运行参数,完全依靠 U-Boot 的bootargs传递给内核之后才能正确启动硬件系统。

数据流向:

  1. U-Boot(setenv bootargs) → bootm启动内核时,把bootargs放入内存固定区域 → Linux内核

  2. 启动解析该字符串

参数传递方式(通过寄存器)

r1 : 存放机器 ID(Machine ID),内核根据该 ID 匹配内核中注册的单板平台代码,区分不同开发板硬件平台

r2 : 存放内存这片区域存放内存大小、告诉Linux内核启动后,挂载文件系统的方式、bootargs 命令行、串口、时钟等全部系统参数

U-boot只是告诉了数据在什么位置,没有告诉内核可以访问的内存大小,数据什么格式,什么时候结束,Linux内核如何知道它访问的参数结束的地方在哪里?

u_boot和Linux之间在传递参数的时候,是按照双方约定的格式传递,格式包含:1.struct parm__struct结构体,2.tag列表,3.设备树.

bootargs

内核刚启动阶段只会初始化最基础的 CPU、MMU,不会自己自动扫描挂载根文件系统、匹配串口输出、不会配置网卡 IP,全部依靠 bootargs 告知内核该怎么工作。

root=/dev/nfs nfsroot=192.168.2.62:/home/hqyj/fs4412/rootfs,proto=tcp,nfsvers=3 rw

console=ttySAC2,115200 init=/linuxrc ip=192.168.2.34

参数片段具体作用补充说明
root=/dev/nfs指定根文件系统的介质类型为 NFS 网络文件系统告知内核去哪里寻找系统最顶层的根目录,是系统能否启动的核心
nfsroot=IP: 目录路径填写 PC 主机 IP + Ubuntu 上 rootfs 的绝对路径内核通过网线挂载电脑里的根文件系统,开发调试常用
proto=tcp,nfsvers=3NFS 传输使用 TCP 协议、协议版本为 3提升传输稳定性,适配 Ubuntu 默认 NFS 服务
rw根文件系统以读写模式挂载不加该参数默认只读,板子无法创建设备节点、保存文件,会卡死启动
console=ttySAC2,115200指定调试串口:设备名 ttySAC2,波特率 115200内核打印日志、命令行交互的串口,配置错误则串口无打印
ip=192.168.2.234开发板开机自动配置静态 IP 地址和电脑 IP 处于同一网段,保证 NFS 网络通信正常
init=/linuxrc指定用户空间第一个运行的程序内核启动后执行的第一个用户态程序是 /linuxrc(通常是 BusyBox 提供的初始化脚本或软链接到 /sbin/init)

bootcmd

bootcmd 是 U-Boot 的环境变量,存放一串 U-Boot 可执行命令.

作用:开发板上电倒计时结束后,U-Boot 自动执行这组命令,完成加载内核、dtb、启动 Linux 系统整套流程

bootcmd=tftp 41000000 uImage;tftp 42000000 exynos4412-fs4412.dtb;

tftp 43000000 ramdisk.img;bootm 41000000 43000000 42000000

uImage:是 U-Boot 引导加载程序专用的、经过特殊格式封装的 Linux 内核镜像文件。

exynos4412-fs4412.dtb:设备树文件。

ramdisk.img:自己的系统映象文件。

tftp 0x41000000 uImage

通过 TFTP 从电脑下载内核 uImage,存到板子内存地址 0x41000000

tftp 0x42000000 exynos4412-fs4412.dtb
下载设备树 dtb 文件,存入内存 0x42000000

bootm 41000000 43000000 42000000

正式启动 Linux 内核
格式:bootm 内核地址 ramdisk地址 dtb地址

三、Kconfig Makefile .config menuconfig之间的关系
总关系:Kconfig 定义选项规则 → menuconfig 让你可视化选择 → 选择结果存成 .config → Makefile 读 .config 决定编译哪些 .c 文件 → 最终生成 uImage。

1.Kconfig--"菜单的菜谱"

相当于配置对应的宏 ,将用户选择的功能配置项通过宏的形式确定下来。比如配置为Y代表将对应功能直接编译进内核,M代表编译进模块,n表示不编译。

2.menuconfig--"点菜界面"

读取所有 Kconfig 文件,生成一个图形化的菜单界面。在里面用空格键勾选/取消选项。它本身不修改 Kconfig,只是可以让使用者方便地选择。

3.config - -"点好的菜单"

.config 是人可读的纯文本,记录了用户最终选定的内核功能。

4.Makefile--"后厨按菜单做菜"

管理Linux内核的编译过程,根据.config里面的宏配置决定编译哪些代码

四、U-boot启动过程
ARM核的设置

1.设置CPU 为SVC模式 2.设置异常向量表 3.关闭Cache和MMU(内存管理单元:A.内存访问权限设置 B.物理地址和虚地址的映射);

SOC芯片的设置

4.处理唤醒的条件 5.判断是否在内存中运行 6.如果不在内存中运行,则初始化系统时钟和内存控制器(第一阶段) 如果在内存中运行,则不需要初始化系统时钟和内存控制器(第二阶段) 7.初始化uart 8.设置sp;

9.调用board_init_f

第一阶段代码: board_init_f 功能将u_boot从存储器搬移到内存,跳到内存中运行

第二阶段代码: board_init_f (1)执行了初始化序列表 (板子相关的初始化操作) (2)为u_boot重定向预留内存

10.对u_boot重定向 (从低端内存搬移到高端内存)

11.清bss段 12.board_init_r

[1]外围硬件初始化 (emmc 、网卡) [2]让串口作为标准输入输出设备 [3]main_loop;

探测自动启动是否被打断,如果没有被打断,则执行bootcmd指定的命令 如果被打断了,则进入u_boot交互界面

五、Linux启动过程

1.CPU 合法性校验
读取 CPU 标识,内核检索自身配置,判断是否支持当前 CPU;不支持则直接打印错误并终止启动。
2.单板机器 ID 合法性校验
读取 U‑Boot 通过r1寄存器传递的板子 Machine ID,和内核内置支持的单板列表比对;ID 不匹配打印报错,大概率是内核配置与开发板不匹配。
3.内存管理基础搭建
校验全部通过后,调用函数创建一级页表 → 开启 MMU 虚拟内存机制,内核正式进入虚拟地址寻址模式。
4.基础环境初始化
清空 BSS 段、设置栈指针、保存 CPU 编号、单板 ID、ATAGS 参数基地址等全局数据。
5.汇编跳转 C 代码
使用汇编指令跳转至start_kernel(),从汇编环境进入 C 语言的内核主体运行。

六、busybox init启动过程

1. 启动 init 进程

Linux 内核完成根文件系统挂载后,执行 init 进程(进程 ID 为 1)。在 Busybox 中,这个 init 就是 /sbin/init,由 Busybox 提供。

2. 读取并解析 /etc/inittab

init 启动后,首先读取并解析 /etc/inittab 文件,然后按照该文件的配置去启动相应的应用程序和脚本。

#!/bin/sh /bin/mount -a # 根据 /etc/fstab 挂载所有文件系统 mkdir /dev/pts # 创建伪终端设备目录 mount -t devpts devpts /dev/pts # 挂载 devpts 文件系统(支持 SSH 等需要终端的程序) echo /sbin/mdev>/proc/sys/kernel/hotplug # 告诉内核:有热插拔事件时调用 mdev mdev -s # 扫描 /sys 创建所有设备节点(/dev/ttySAC2 等)

3.执行 /etc/init.d/rcS 脚本
#!/bin/sh /bin/mount -a # 根据 /etc/fstab 挂载所有文件系统 mkdir /dev/pts # 创建伪终端设备目录 mount -t devpts devpts /dev/pts # 挂载 devpts 文件系统(支持 SSH 等需要终端的程序) echo /sbin/mdev>/proc/sys/kernel/hotplug # 告诉内核:有热插拔事件时调用 mdev mdev -s # 扫描 /sys 创建所有设备节点(/dev/ttySAC2 等)

4.根据 /etc/fstab 批量挂载各类文件系统
device:要挂载的设备;mount-point:挂载点;type:文件系统类型;options:挂载参数;dump程序根据这个字段来决定这个文件系统是否需要备份;fsck order:fsck(file system check)程序根据这个字段来决定是否对磁盘做检查。

/proc:内核运行信息虚拟文件系统
/sys:硬件设备树、驱动信息文件系统
/tmp:临时目录(内存文件系统)
/dev:设备节点目录

5.运行 shell 程序

硬件、文件系统全部就绪后,依据 inittab 配置启动/bin/sh,也就是在串口看到的命令交互终端。

6. 执行 /etc/profile 脚本
#!/bin/sh export HOSTNAME=farsight # 设置主机名 export USER=root export HOME=root # root 用户的家目录 export PS1="[${USER}@${HOSTNAME} \W]\# " # 命令提示符格式:[root@farsight ~]# PATH=/bin:/sbin:/usr/bin:/usr/sbin # 命令搜索路径 LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH # 库搜索路径 export PATH LD_LIBRARY_PATH

四个文件之间的关系

Linux 开机自启程序的四种方案

方案启动时机进程自愈运行环境适用场景
bootargs 指定 init最早(根文件挂载完立刻运行)无残缺,无系统服务极简专用固件
/etc/inittab respawn系统初始化完成后崩溃自动重启完整系统环境必须永久常驻的后台服务
rcS 内部调用系统初始化中期无基础环境就绪需要提前启动的硬件检测程序
/etc/profile 调用所有系统服务、shell 环境就绪无环境最全,变量全部生效绝大多数普通业务程序(首选)
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进