Linux系统移植全流程解析
系统移植
系统:操作系统,一个程序,一堆代码(管理对应资源和任务)
包含:Linux,window,uinx,freertos Linux操作系统
移植:将源代码(Linux操作系统源码)从一种环境(开发环境)下放到另一种环境(产品环境)下运行也可以称为移植。
一.启动流程
pc机
(1)启动BIOS(英文"Basic Input Output System"的缩略词,直译过来后中文名称就是"基本输入输出系统"。是一组固化到计算机内主板上一个ROM芯片上的程序,它保存着计算机最重要的基本输入输出的程序、开机后自检程序和系统自启动程序,它可从CMOS中读写系统设置的具体信息。其主要功能是为计算机提供最底层的、最直接的硬件设置和控制。)
1、初始化时钟,初始化内存,基本硬件的初始化
2、判断启动方式(usb/硬盘/光驱)
(2)引导程序
固化在硬盘(存储数据)最前面的部分
识别对应的操作系统
加载对应操作系统

(3)操作系统:(windows,linux,Mac OS,没有图形界面程序)
(4)文件系统(NTFS,FAT(16/32),window文件系统格式 日志文件系统)
(5)应用程序(桌面应用程序)
启动的流程:
首先pc机启动先启动的BIOS(系统时钟和内存)、选择对应的启动方式(usb、硬盘、光驱)。对应启动盘的最开始的程序就是引导程序(内存中),引导程序是首先识别有多少系统(内存中),如果只有一个系统直接加载系统,当有多个操作系系统时,会让你选择系统启动,系统起来之后,挂载文件系统,启动我们程序。操作系统是个非图形化界面的程序,桌面是对应的应用程序。
嵌入式设备启动流程
(1)厂家固化在IROM代码
1.基本硬件初始化(它需要的时钟和设备)
2.判断对应的启动方式(eMMC(Flash的一个变种)/USB/SD卡)
3.把Bootloader第一个阶段的代码加载IRAM(芯片内部运行内存(缺点芯片贵))
(2)在IRAM里面去运行对应的bootloader第一阶段的代码(SPL(Secondary Program Loader,二级程序加载器)第二阶段程序的加载)
1.初始化整个系统时钟
2.初始化内存
3.把完整的bootloader搬移到内存中
Bootloader分两个阶段运行的原因:芯片内部存储空间有限,如果内存空间做大,成本提高。
不在IROM中初始化内存的原因:因为芯片厂家不知道开发板采用什么样的内存,没有办法进行初始化。
(3)在内存运行bootloader第二个阶段的代码
1.初始化一下硬件设备(根据开发板去定义(网卡串口usb))
2.加载对应操作系统(内存)
(4)运行操作系统(没有图形界面的)
(5)挂载文件系统(EXT(2,3,4))
(6)启动应用程序
Bootloader=BIOS+引导程序。

二.U-boot和Linux 内核之间的参数传递
Linux内核是被U-Boot被动加载到内存中运行的,没有初始化过内存等硬件,对当前硬件环境一无所知,如果U-Boot不传递参数,内核无法正常运行。
(u_boot和Linux之间在传递参数的时候,是按照双方约定的格式传递,格式包含:1.struct parm__struct结构体,2.tag列表,3.设备树)
参数传递的规则
| 寄存器 | |
| r0 | 规定为0,不传递有效信息 |
| r1 | 开发板的ID |
| r2 | 其他参数在内存中开始地址 |
r1的参数,是让Linux内核自己去判断,它是否支持当前的开发板
r2是其他参数在内存中的开始地址,单个寄存器只有4个字节,无法存放大量参数,因此大量参数存放在内存中,将内存起始地址记录在r2。
r2存放的信息:
(1)内存的信息
内存的开始地址和内存的大小
(2)命令行参数:bootargs的内容
高诉Linux内核启动后,挂载文件系统的方式(例如:NFS方式)
(3)其他信息
bootcmd
u-boot启动完成读秒后,自动执行从参数(命令)
把文件从 TFTP 服务器加载到内存(RAM)(临时的),然后从内存启动
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:自己的系统映象文件。
bootargs
setenv bootargs root=/dev/nfs nfsroot=192.168.2.5:/home/hqyj/fs4412/rootfs,proto=tcp,nfsvers=3,
console=ttySAC2,115200 init=/linuxrc,ip=192.168.2.29
| root=/dev/nfs | 指定根文件系统(rootfs)不是从本地块设备(如 emmc/SD 卡)挂载,而是使用 NFS 网络文件系统。内核会因此启用 NFS 根文件系统支持。 |
| nfsroot=192.168.2.5:/home/hqyj/fs4412/rootfs |
|
proto=tcp | NFS 使用 TCP 协议 |
| nfsvers=3 | 使用 NFS 版本 3 |
| no_lock | 禁用 NFS 文件锁(lockd)。避免在无锁服务时出现警告或延迟,常用于简单开发环境。 |
rw | 根文件系统以 读写(read-write) 方式挂载,允许在开发过程中修改文件。 |
console=ttySAC2,115200 | 内核控制台输出到 串口 2(ttySAC2),波特率 115200。这是开发板与主机间调试信息的输出通道 |
init=/linuxrc | 内核启动后执行的第一个用户态程序是 /linuxrc(通常是 BusyBox 提供的初始化脚本或软链接到 /sbin/init)。 |
| ip=192.168.2.29 | 给开发板自身指定静态 IP 地址(192.168.2.29),用于 NFS 挂载及网络通信。 |
三.Kconfig Makefile .config menuconfig之间的关系
menuconfig是Linux内核提供的菜单选项,用于表示Linux内核有哪些功能,类比餐馆的菜单,汇总了当前内核所有支持的功能选项供用户选择。
.config是Linux当前内核选择了哪些功能的记录,类比点菜的凭证,记录了用户最终选定的内核功能。
Makefile给Linux内核提供对应的功能实现,类比餐馆所有厨师会做的菜,只有Makefile中支持的功能才能出现在menuconfig的菜单选项里。
Kconfig用于配置对应的宏,将用户选择的功能配置项通过宏的形式确定下来,比如配置为Y代表将对应功能直接编译进内核。M代表编译进模块。n表示不编译。
配置项核心逻辑
内核菜单的选项来源是Kconfig,Kconfig的内容是一级一级包含的,从最内层的硬件相关配置(如DM9000)开始,先被物理网卡相关配置包含,再被网络相关配置包含,继续向上被更上层的功能配置包含,最终汇总成menuconfig的完整菜单。
将功能编译进内核依靠make流程中定义的宏配置,部分功能没有默认宏配置,需要手动配置宏,将宏设置为Y代表将该功能直接编译进内核。

四.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内部ID:芯片厂商生产芯片时会为其生成唯一ID,类似身份证,内核读取该ID后与内部保存的ID匹配,确认当前运行的CPU是内核支持的款型,匹配失败会打印错误信息,该阶段代码为汇编实现,还未进入C语言阶段。
(2)接着获取bootloader传递的板子ID:每块开发板也有独立ID,与CPU内部ID相互独立,开发板ID需要向Linux内核官方申请
(3)MMU使能与页表映射阶段
1.两个ID验证完成后,内核打开MMU,MMU的功能是完成物理地址和虚拟地址的映射,这是操作系统中地址为虚拟地址的原因。
2.初期采用一级页表映射,物理地址和虚拟地址一一对应,例如物理地址4000万对应虚拟地址4000万,此时仅使能MMU,还未完全接管整个内存,后续会重新建立页表映射。
(4)C语言运行条件准备阶段
1.执行清空BSS段、设置SP指针两步操作,是C语言程序可以正常运行的必要条件:清空BSS段后,未初始化的全局变量和static修饰的未初始化静态变量默认值为0,若未做该操作这类变量值为随机值。
2.该阶段还会保存板子ID、CPU ID及其他相关参数。
(3)start_kernel阶段(C语言运行阶段)
1.完成C语言运行条件准备后,进入start_kernel函数,正式切换到C语言执行内核代码,此时终端开始输出大量打印信息。
2.该阶段具体操作为:打印Linux内核版本、处理bootnode传递给Linux内核的参数、初始化控制台(默认控制台为窗口,用于输入输出)、获取内存信息、重新建立页表,完成物理内存和虚拟内存的完整映射,后续可使用虚拟地址,支持多个虚拟地址映射到同一物理内存,32位机器的每个进程可拥有4G独立虚拟地址空间,实际占用物理内存远小于该数值,依赖物理地址和虚拟地址的映射实现。
3.之后依次初始化异常处理、加载所有驱动模块、挂载根文件系统、执行根文件系统中的init程序,启动init进程,完成整个内核启动流程。

六.Busybox init启动过程分析

1. 启动 init 进程
Linux 内核完成根文件系统挂载后,执行 init 进程(进程 ID 为 1)。在 Busybox 中,这个 init 就是 /sbin/init,由 Busybox 提供。
2.读取并解析 /etc/inittab
inittab文件格式
id编号:权限等级:动作:进程
id编号和权限等级一般忽略
| 动作 | |
| sysinit | 表示进程在系统启动后最先执行,只执行一次,init 进程等待它结束才继续执行其他动作 |
| wait | 表示进程在执行 sysinit 后执行,只执行一次,init 进程等待它结束才继续执行其他动作 |
| once | 表示进程在执行 wait 后执行,只执行一次,init 进程不等待它结束 |
| respawn | 表示进程在启动 once 后执行,init 进程一旦发现该进程死掉,就会重新启动它 |
| askfirst | 表示进程在启动完 respawn 后执行,与 respawn 类似,不过 init 进程会先输出提示 "Please press Enter to activate this console",等用户输入回车后才启动子进程 |
| restart | 如果 BusyBox 中配置了 CONFIG_FEATURE_USE_INITTAB,并且 init 进程收到 SIGHUP 信号时,先重新读取/解析 /etc/inittab 文件,再执行该进程 |
| shutdown | 表示进程在系统收到重启、关闭系统命令时运行 |
| ctrlaltdel | 表示进程在按下 Ctrl+Alt+Del 组合键时运行 |
3. 执行 /etc/init.d/rcS 脚本
这是由 init 进程第一个调用的脚本,作为系统启动时的初始化脚本。

4. 挂载 /etc/fstab 文件指定的设备

device:要挂载的设备,mount-point:挂载点,type:文件系统类型,options:挂载参数,dump程序根据这个字段来决定这个文件系统是否需要备份。fsck order:fsck程序根据这个字段来决定是否对磁盘做检查
proc — Linux内核的资源(进程资源) → /proc 这个目录
tmpfs — 临时存储的空间 → /tmp 这个目录
sysfs — Linux子系统(GPIO、PWM、I2C) → /sys 这个目录
tmpfs — Linux的设备挂载 → /dev 目录
5. 运行 shell 程序
当 init 按照 inittab 配置启动到 askfirst 或 respawn 的 shell 进程时,用户会看到一个命令行提示符。此时 shell 已经运行,但环境变量尚未完全设置好,因此接下来需要执行 /etc/profile 来初始化环境。
6. 执行 /etc/profile 脚本


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


所有评论(0)