嵌入式启动流程

1.厂家固化在IROM内的代码

{

1>基本硬件的初始化

2>判断对应的启动方式(emmc启动还是sd卡启动)

3>把bootloader第一阶段代码加载到IRAM(运存)中运行

}

2.在IRAM里运行bootloader的第一阶段代码

{

1>初始化整个系统的时钟

2>初始化内存

3>把完整的bootloader搬移到内存中去

}

3.在内存中运行bootloader的第二段代码

{

1>初始化硬件设备(根据开发板初始化对应的硬盘、网卡、串口等)

2>加载对应的操作系统(将操作系统加载到内存中)

}

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

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

6.启动应用程序

参数bootargs和bootcmd解析

u-boot 会将包含硬件描述信息的 DTB 文件加载到内存中,并将参数(如 bootargs)写入 DTB 的 /chosen 节点中

在 ARM 架构下,U-Boot 启动内核时,会通过特定的 CPU 寄存器向内核传递上述数据结构的内存地址:
r0 寄存器:固定设置为 0,作为标识。
r1 寄存器:传递机器 ID(Machine ID)
r2 寄存器:存放的是设备树二进制文件(DTB)在系统 RAM 中的物理内存地址

(3)其他信息

网络启动的bootargs参数配置

bootargs root=/dev/nfs nfsroot=192.168.2.57:/home/hqyj/fs4412/rootfs,proto=tcp,nfsvers=3, console=ttySAC2,115200 ip=192.168.2.223

root=/dev/nfs:指定根文件系统通过网络文件系统(NFS)挂载。
nfsroot=192.168.2.57:/home/hqyj/fs4412/rootfs,proto=tcp,nfsvers=3:NFS 服务器 IP 为 192.168.2.57,共享目录为 /home/hqyj/fs4412/rootfs,使用 TCP 协议和 NFSv3 版本。
console=ttySAC2,115200:同上,串口控制台配置。
ip=192.168.2.223:为开发板分配静态 IP 地址 192.168.2.223,确保能与 NFS 服务器通信。

bootcmd=tftp 0x41000000 uImage; tftp 0x42000000 exynos4412-fs4412.dtb;tftp 0x43000000 ramdisk.img; bootm 0x41000000 0x43000000 0x42000000

通过 TFTP 协议从服务器下载Linux镜像文件,设备树文件和文件系统。

本地启动:

tftp 41000000 uImage 

movi write kernel 41000000

tftp 41000000 exynos4412-fs4412.dtb

movi write dtb 41000000

tftp 41000000 ramdisk.img

movi write rootfs 41000000 300000

先通过tftp下载文件,再将下载的文件写入MMU

本地启动bootargs参数配置:

bootargs=root=/dev/ram0 rw console=ttySAC2,115200 init=/linuxrc

root=/dev/ram0:指定根文件系统位于内存盘,通常用于临时系统或调试。
rw:以读写模式挂载根文件系统。
console=ttySAC2,115200:设置串口控制台为 ttySAC2,波特率 115200,用于输出内核日志和接收用户输入。
init=/linuxrc:指定系统启动后第一个运行的进程为 /linuxrc,通常是 BusyBox 的 init 程序。

bootcmd=movi read kernel 41000000; movi read dtb 42000000; movi read rootfs 43000000 300000; bootm 41000000 43000000 42000000

读取内存中的Linux镜像文件,设备树文件和文件系统

Linux内核编译驱动原理

menuconfig是Linux内核提供的菜单选项,用于表示Linux内核有哪些功能

.config是Linux当前内核选择了哪些功能的记录

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

内核菜单的选项来源是Kconfig,Kconfig的内容是一级一级包含的,从最内层的硬件相关配置(如DM9000)开始,先被物理网卡相关配置包含,再被网络相关配置包含,继续向上被更上层的功能配置包含,最终汇总成menuconfig的完整菜单。
将功能编译进内核依靠make流程中定义的宏配置,部分功能没有默认宏配置,需要手动配置宏,将宏设置为Y代表将该功能直接编译进内核。

u-boot的启动流程

.设置cpu为SVC模式

(SVC模式:超级管理员模式,CPU刚启动时,必须切换到超级管理员模式才能对底层硬件执行任何操作)

2.设置异常向量表

(异常向量表:在ARM架构中,异常向量表是一个固定地址的数组,每个元素存放的都是一条跳转指令)

3.关闭Cache和MMU

(Cache:高速缓存;MMU:内存管理单元;在u-boot刚启动时,内存还没有被正确配置,如果不关闭,虚拟地址和物理地址之间的映射会出错,导致系统直接崩溃)

4.处理唤醒条件

(检测自己是按键唤醒还是通电唤醒;按键唤醒会跳过硬件初始化,直接恢复到休眠前的状态)

5.判断运行位置

(判断是在SRAM中运行还是在内存中运行;在SRAM运行:初始化系统时钟,初始化内存;在内存中运行:直接跳转到u-boot重定向)

6.初始化串口(UART)

(把启动过程中的相关信息打印到电脑屏幕上,方便调试)

7.设置SP(栈指针)

(给CPU分配一块临时的栈内存,用于存放函数调度的局部变量)

8.调用board_init_f

(f表示从MMU/Flash中运行;这个函数的主要功能是把完整的u-boot代码从慢速的存储器(MMU/Flash)搬移到快速的DDR内存中去)

9.u-boot重定向

(代码搬移完成后,CPU的指针必须从SRAM跳转到DDR内存中继续执行)

10.清除.bss段

(.bss段存放的是未初始化的全局变量和静态变量,在跳转后必须把这段内存全部置零,防止里面残留的脏数据导致系统异常)

11.调用board_init_r

r表示重定向后运行;

该函数的作用主要是:

1>外围硬件初始化:初始化网卡,EMMC,USB等所有外设;

2>把串口正式设置为标准IO设备,后续的打印信息都从这里输出;

3>进入主循环(main_loop):进入主循环是u-boot的最终形态,在这个形态下,他会检测是否有按键按下,如按下enter键会打断自启动:

(1)如果没有被打断,u-boot会自动执行bootcmd里的命令

(2)如果打断u-boot会停留在命令行交互界面

Linux的启动流程

1.读取 CPU 内部 ID:

CPU 刚启动时,内核首先要确认:“我是谁?” 通过读取特定的寄存器(如 ARM 的 MIDR),获取CPU的ID(当前处理器的型号信息)。

__lookup_processor_type (检查内核是否支持该 CPU):

内核里编译了很多种 CPU 的支持代码。这里会拿着刚才读到的 ID 去内核的“支持列表”里查。

如果不支持:直接报错打印 unrecognized/unsupported processor variant,然后死循环停机。

这说明你编译的内核版本和手里的芯片不匹配。

2.获取 Bootloader 传递的板子 ID:
U-Boot在跳转给内核前,会把板子的型号 ID 放在寄存器里传给内核。

__lookup_machine_type (检查内核是否支持该主板):内核拿着 R1 里的 ID 去查自己的“主板支持列表”。

如果不支持:报错 

3.__create_page_tables (建立一级页表):

为什么要建表?

此时 CPU 还在用物理地址(比如 0x30000000)访问内存。但 Linux 内核设计是基于虚拟地的。为了让内核跑起来,必须先画一张“地图”,告诉 CPU:“当你看到虚拟地址 A 时,其实要去物理地址 B 找数据”。

这段代码会在内存中开辟一块区域,填入映射关系。

4.__enable_mmu (使能 MMU):

MMU (内存管理单元) 是 CPU 里的一个硬件模块。

执行这条指令后,CPU 正式开启“虚拟地址模式”。从此以后,代码里写的地址都是虚拟地址,MMU 会自动帮你翻译为物理地址。
注意:开启 MMU 后,程序的执行流必须无缝切换到虚拟地址上,否则立马崩溃。

5.汇编收尾工作

1>清除 BSS 段:把未初始化的全局变量清零(C 语言标准规定全局变量默认为 0)。
2>设置 SP (栈指针):为 C 语言函数调用准备栈空间。
3>保存参数:把之前查到的 cpu_id、machine_id 以及 U-Boot 传来的参数指针 (atags_pointer) 存到全局变量里,方便后面 C 代码使用。

6.b start_kernel (跳到 C 语言部分执行):

跳转到 init/main.c 中的 start_kernel 函数。

1>打印版本信息
2>处理 Tag 参数列表:解析 U-Boot 传过来的内存大小、命令行参数(bootargs)。如果是新式内核,这里会解析 设备树 。
3>初始化控制台:让 printk 能往串口输出东西,这时候你才能看到后面的打印信息。
4>内存管理初始化:建立更复杂的二级、三级页表,把内存分成一个个页面进行管理。

5>初始化异常处理
6>加载驱动模块:扫描代码里的 module_init 宏,把显卡、网卡、USB 等驱动程序初始化一遍。
7>挂载文件系统:找到根文件系统(Rootfs),通常在 Flash 或 SD 卡上。
8>启动 init 进程:这是内核做的最后一件事。它会在根文件系统里找到 /sbin/init (或 /linuxrc) 并运行它。
结果:init 进程是所有用户态进程的祖先,它启动了 Shell、GUI 界面,你就看到了操作系统的桌面或命令行。

busybox init启动过程

1、挂载根文件系统

这是 init 进程启动的前提。内核在完成自身初始化后,会挂载根文件系统(通常是 /),为后续的用户空间程序提供运行环境。如果根文件系统无法挂载,系统将无法继续启动。
2、启动 init 进程

内核通过 execve("/sbin/init", ...) 启动 init 进程。init 是所有用户进程的“祖先”,它负责:

读取并解析配置文件 /etc/inittab

根据配置启动其他系统服务和终端

管理系统的运行级别(Runlevel)

3、读取并解析 /etc/inittab 文件

etc/inittab 是 init 进程的“行动指南”,定义了系统在不同运行级别下应执行的操作。图中显示的典型条目包括:

::sysinit:/etc/init.d/rcS:系统初始化时执行的脚本

::askfirst:-/bin/sh:在控制台启动一个交互式 shell(常用于单用户模式或调试)

::restart:/sbin/init:当 init 进程被重启时执行的操作

::ctrlaltdel:/sbin/reboot:按下 Ctrl+Alt+Del 时触发重启

4、执行 /etc/init.d/rcS 脚本

这是系统初始化的核心脚本,通常由 inittab 中的 sysinit 条目触发。它的主要任务包括:

挂载必要的文件系统(如 /dev/pts)

创建设备节点(如 /dev/pts)

设置热插拔事件处理程序(如 echo /sbin/mdev > /proc/sys/kernel/hotplug)

启动基础服务(如网络、日志等)

5、挂载 /etc/fstab 文件中指定的设备

/etc/fstab 是一个静态文件系统表,定义了系统在启动时应自动挂载的文件系统。常见的挂载项包括:

proc:虚拟文件系统,提供内核和进程信息

tmpfs:临时文件系统,用于 /tmp、/dev 等目录

sysfs:虚拟文件系统,提供设备和驱动信息

这些文件系统的挂载为系统提供了必要的运行时环境。

6、运行 Shell 程序并执行 /etc/profile

在完成所有系统初始化后,init 会启动一个登录 shell(如 /bin/sh),并执行 /etc/profile 脚本。该脚本用于设置用户环境变量,例如:

HOSTNAME:主机名

USER:当前用户

HOME:用户主目录

PATH:可执行文件搜索路径

LD_LIBRARY_PATH:动态库搜索路径

这些环境变量确保用户登录后能正常使用系统命令和应用程序。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐