嵌入式Linux一般性启动流程
1 | graph LR |
上面是一般嵌入式Linux的启动流程
第一阶段:内部ROM
在系统刚上电时从内部的ROM中读取程序进行执行,内部ROM程序的作用就是从多种存储设备中加载启动程序并执行,一般加载的就是第二阶段的uboot。例如IMX6ULL的SD卡启动,规定从SD卡分区表后的的第三个扇区开始存放uboot。当使用SD卡启动时,就会驱动SD卡的指定扇区获取uboot程序存储到内存中开始执行。
第二阶段:uboot
当uboot启动之后,uboot就会根据启动的配置进行后续的启动。uboot也是为了查找下一阶段的代码,但是它相较与内部ROM中的启动程序来说占用的空间更大,也能提供更灵活的加载方式,甚至配置启动脚本,启动参数等。uboot可以初始化网卡、U盘、EMMC、NAND Flash等接口、并从中获取内核程序存入内存。需要注意的是uboot是单线程执行的裸机程序,也不存在内存管理,内核加载到内存的什么位置需要自行配置。
到uboot阶段,程序的存储位置已经不是问题了,基本上可以以任何方式,从任何接口获取数据。包括网络下载,串口传输、从文件系统中解析等方式。
除此之外uboot还支持向内核传递启动参数,启动参数一般会配置当前系统支持的内存大小、启动后使用的控制台、挂载到根目录的设备、根目录的文件系统类型等。如果需要将NFS挂载为根目录,则还需要配置本机IP,服务器IP,网关,子网掩码等网络以及nfs相关信息。
等准备好数据以后,跳转到内核开始执行
第三阶段:kernel
内核启动后会做一下几件事:
根据启动参数配置控制台,保证日志输出
根据启动参数配置网络
解析设备树,将设备树中的设备节点转化为系统中描述设备信息 的设备结构体
挂载内核内部的驱动并进行匹配
根据启动参数挂载根目录
查找根目录中指定位置是否包含启动程序(可以是脚本)常见的启动程序可以是:
- /bin/sh
- /bin/init
- /sbin/init
- /init
- /linuxrc
不同的内核版本使用的启动程序不同,一般使用的是/sbin/init 基本上都可以支持。内核在找到任意一个启动程序后就会使用1号进程执行启动程序。1号进程启动后会根据配置文件执行其它脚本、程序等。初始化程序完成后就在后台执行。1号进程是所有进程的直接或间接父进程。
第四阶段:rootfs
Linux各种发行版的不同,就是根文件系统的不同。从1号进程启动、完成初始化后就正式进入系统。而嵌入是开发关注的较多的还是在启动时执行自己的启动脚本。busybox完全使用配置文件和脚本进行初始化,而ubuntu则使用systemd管理系统中的服务、启动、关机等操作。
下面介绍busybox的启动流程。busybox的init开始执行后会分析/etc/inittab文件,该文件描述了什么时间执行什么程序。例如哪些程序在启动时执行,哪些程序在关机时执行,哪些程序启动后不允许关闭,意外关闭后需要重启等。
一般在inittab文件中会启动/etc/init.d/rcS,而rcS脚本则会调用/etc/init.d/下的所有S加两位数字开头的脚本,例如:S00dev、S90Autorun。这些脚本会按编号顺序执行
关于自动挂载:一般在/etc/init.d下的一系列脚本中会使用mount -a
命令,该命令会读取/etc/fstab文件,并挂载配置文件中的所有待挂载的文件系统。
【注】:initramfs
initramfs是一种特殊的根文件系统,在内核开启之后会在最终进入主文件系统之前先进入initramfs文件系统。随后可以切换为主文件系统。
1 | graph LR |
这个系统可以在uboot阶段放到内存中,并在启动参数中指定存放的地址,可以绑定在内核中。在kernel启动时由内核解压并在内存中构建出可用的根文件系统,这个根文件系统由于是在每次启动时重新解压,所以在该文件系统中的所有操作都不会保存。用这种方式可以作为恢复系统,在主文件系统损坏时可以在该模式下进行修复,也可以进行一些驱动加载的工作。