驱动第一部分
一、嵌入式设备启动流程

1、运行厂家固化在IROM里面的代码
a.基本硬件初始化(需要的时钟和设备)
b. 判断对应的启动方式(emmc;sd卡;usb)
c. 把bootloader第一阶段的代码加载到IRAM(芯片内运行内存)
2.在IRAM里运行对应的bootloader第一阶段的代码
SPL(Secondary Program Loader(二级程序加载器)):u-boot-spl.bin
a.初始化整个系统的时钟;
b.初始化内存
c.把完整的bootloader搬移到内存中
3.在内存里运行bootloader第二个阶段的代码
a.初始化了一些硬件设备(根据开发板去定义(网卡;串口;USB))
b.加载对应的操作系统(内存)
4.运行操作系统(没有图形界面的)
5.挂载文件系统(EXT(2,3,4))
6.启动应用程序
不在IROM里初始化内存的原因:因为芯片厂家不知道开发板采用什么样的内存,没有办法进行初始化
为啥bootloader分两个阶段?
运行芯片内部存储空间有限,如果内部空间做大,成本提高
二、U-boot和Linux 内核之间的参数传递
内核自身上电不会主动扫描、探测全部硬件资源,依赖 U-Boot 传递基础信息才能正常初始化系统。根文件系统路径、串口控制台、网卡 IP、NFS 挂载配置这类运行参数,完全依靠 U-Boot 的bootargs传递给内核之后才能正确启动硬件系统。
数据流向:
U-Boot(setenv bootargs) → bootm启动内核时,把bootargs放入内存固定区域 → Linux内核
启动解析该字符串
参数传递方式(通过寄存器)
| 寄存器 | |
| r0 | 规定为0,不传递有效信息 |
| r1 | 开发板的ID |
| r2 | 其他参数在内存中开始地址 |
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.128:/home/hqyj/fs4412/rootfs,proto=tcp,nfsvers=3 rw
console=ttySAC2,115200 init=/linuxrc ip=192.168.2.234
| 参数片段 | 具体作用 | 补充说明 |
|---|---|---|
root=/dev/nfs |
指定根文件系统的介质类型为 NFS 网络文件系统 | 告知内核去哪里寻找系统最顶层的根目录,是系统能否启动的核心 |
nfsroot=IP:目录路径 |
填写 PC 主机 IP + Ubuntu 上 rootfs 的绝对路径 | 内核通过网线挂载电脑里的根文件系统,开发调试常用 |
proto=tcp,nfsvers=3 |
NFS 传输使用 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 文件,然后按照该文件的配置去启动相应的应用程序和脚本。
::sysinit:/etc/init.d/rcS # 系统初始化最先执行rcS脚本(最关键)
::askfirst:-/bin/sh # 串口控制台拉起一个shell交互终端
::restart:/sbin/init # init异常重启的处理
::ctrlaltdel:/sbin/reboot # 按键重启系统

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 环境就绪 | 无 | 环境最全,变量全部生效 | 绝大多数普通业务程序(首选) |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)