yocto是用来制作发行版image、BSP的一个很强大的工具,其文件系统的结构跟Ubuntu、redhat等很像,几乎如出一辙,这也能看出,yocto的成长也是能够整合多方平台(系统)的。好久没写博客,最近研究了一下yocto文件系统的启动流程,觉得挺有意思,在这里分享一下。废话少说,正式进入主题。
内核起来,启动第一号进程(init进程,/sbin/init)后,就把控制权交给了init进程,init进程会寻找/etc/inittab配置文件,然后根据/etc/inittab配置文件的内容,对系统进行初始化(跟android init.rc类似)。
用ps –e 可以查看所有的进程状态,其中可以看到,一号进程为 init进程
1 ? 00:00:03 init
整个inittab的初始化过程可以归结为:
1、 内核启动完毕,把控制权交给init进程,init进程会寻找”/etc/inittab”,根据”/etc/inittab”来初始化剩下的启动流程,inittab的角色跟androidinit.rc有点类似。
2、 分别启动”/etc/default/rcS”和”/etc/init.d/rc”,”/etc/default/rcS”是用来配置系统环境变量;”/etc/init.d/rc”里面是一些列的逻辑处理,首先会用”/etc/init.d/rc”来轮询和执行”/etc/rcS.d/”下的脚本文件。
3、 启动完”/etc/rcS.d/”下的脚本文件后,接着会再使用”/etc/init.d/rc”来轮询和执行”/etc/rc$runlevel.d/”下的脚本,由于inittab定义当前的$runlevel=5,也就是会执行”/etc/rc5.d/”下的脚本。
4、 最后是绑定串口(ttymxc)和tty,启动登陆程序getty,配置gpu运行环境。
以下是根据inittab的脚本代码,具体分析,inittab的初始化流程。
# The default runlevel.
id:5:initdefault:
这个语句的格式是:label:runlevel:action:process。看到,系统把当前的$runlevel设置为5.
参考网上资料,上面四个符号其相关的代表的意义:
1.label
登记项标志符,是一个任意指定的、4个字符以内的序列标号,在本文件内必须唯一。
label是1到4个字符的标签,用来标示输入的值。一些系统只支持2个字符的标签。鉴于此原因,多数人都将标签字符的个数限制在2个以内。该标签可以是任意字符构成的字符串,但实际上,某些特定的标签是常用的,在Red Hat Linux中使用的标签是:
id 用来定义缺省的init运行的级别
si 是系统初始化的进程
ln 其中的n从1~6,指明该进程可以使用的runlevel的级别
ud 是升级进程
ca 指明当按下Ctrl+Alt+Del是运行的进程
pf 指当UPS表明断电时运行的进程
pr 是在系统真正关闭之前,UPS发出电源恢复的信号时需要运行的进程
x 是将系统转入X终端时需要运行的进程
2.runlevels
系统运行级,即执行登记项的init级别。用于指定相应的登记项适用于哪一个运行级,即在哪一个运行级中被处理。如果该字段为空,那么相应的登记项将适用于所有的运行级。在该字段中,可以同时指定一个或多个运行级,其中各运行级分别以数字0, 1, 2, 3, 4, 5, 6或字母a, b, c表示,且无须对其进行分隔。
0-->Halt,关闭系统.
1-->单用户,在grub启动时加上为kernel加上参数single即可进入此运行等级
2-->无网络多用户模式.
3-->有网络多用户模式.
4-->有网络多用户模式.
5-->X模式
6-->reboot重启系统
S/s-->同运行等级1
a,b,c-->自定义等级,通常不使用.
但yocto这里没有细分,把2—5都归纳为多用户模式:
# Runlevel 0 is halt.
# Runlevel 1 is single-user.
# Runlevels 2-5 are multi-user.
# Runlevel 6 is reboot.
3.action
表示进入对应的runlevel时,init应该运行process字段的命令的方式,有效的action值如下。
boot:只有在引导过程中,才执行该进程,但不等待该进程的结束。当该进程死亡时,也不重新启动该进程。
bootwait:只有在引导过程中,才执行该进程,并等待进程的结束。当该进程死亡时,也不重新启动该进程。实际上,只有在系统被引导后,并从单用户模式进入多用户模式时,这些登记项才被处理;如果系统的默认运行级设置为2(即多用户模式),那么这些登记项在系统引导后将马上被处理。
initdefault:指定系统的默认运行级。系统启动时,init将首先查找该登记项,如果存在,init将依据此决定系统最初要进入的运行级。具体来说,init将指定登记项"run_level"字段中的最大数字(即最高运行级)为当前系统的默认运行级;如果该字段为空,那么将其解释为"0123456",并以"6"作为默认运行级。如果不存在该登记项,那么init将要求用户在系统启动时指定一个最初的运行级。
off:如果相应的进程正在运行,那么就发出一个告警信号,等待20秒后,再通过关闭信号强行终止该进程。如果相应的进程并不存在,那么就忽略该登记项。
once:启动相应的进程,但不等待该进程结束便继续处理/etc/inittab文件中的下一个登记项;当该进程终止时,init也不重新启动该进程。在从一个运行级进入另一个运行级时,如果相应的进程仍然在运行,那么init就不重新启动该进程。
ondemand:与"respawn"的功能完全相同,但只用于运行级为a、b或c的登记项。
powerfail:只在init接收到电源失败信号时,才执行该进程,但不等待该进程结束。
powerwait:只在init接收到电源失败信号时,才执行该进程,并在继续对/etc/inittab文件进行任何处理前等待该进程结束。
respawn:如果相应的进程还不存在,那么init就启动该进程,同时不等待该进程的结束就继续扫描/etc/inittab文件;当该进程终止时,init将重新启动该进程。如果相应的进程已经存在,那么init将忽略该登记项并继续扫描/etc/inittab文件。
sysinit:只有在启动或重新启动系统并首先进入单用户模式时,init才执行这些登记项。而在系统从运行级1~6进入单用户模式时,init并不执行这些登记项。"action"字段为"sysinit"的登记项在"run_level"字段不指定任何运行级。
wait:启动进程并等待其结束,然后再处理/etc/inittab文件中的下一个登记项。
ctrlaltdel:用户在控制台键盘上按下Ctrl+Alt+Del组合键时,允许init重新启动系统。注意,如果该系统放在一个公共场所,系统管理员可将Ctrl+Alt+Del组合键配置为其他行为,比如忽略等。
4.process
具体应该执行的命令。并负责在退出运行级时将其终止(当然在进入的runlevel中仍要运行的程序除外)。当运行级别改变,并且正在运行的程序并没有在新的运行级别中指定需要运行时,那么init会先发送一个SIGTERM 信号终止,然后是SIGKILL。
其中,用telinit可以切换用户态。如:telinit 1 。一看下来,还是很有趣:telinit 0 关机,telinit 6 重启。
所以,看到inittab default 的level是5,目前系统是运行在多用户态下的。
继续往下看:
# Boot-time system configuration/initialization script.
# This is run first except when booting in emergency (-b) mode.
si::sysinit:/etc/init.d/rcS
这里说明了,在非紧急模式下,首先会运行“/etc/init.d/rcS”脚本,因为yocto是嵌入式系统,一般我们默认都是在非紧急模式下启动的。根据这里的描述,是接“-b”就是紧急模式下启动。
但这里传递进了一个空的“runlevel”,这里很重要,因为在rcS里面会自定义runlevel。
跟进到“/etc/init.d/rcS”脚本:
PATH=/sbin:/bin:/usr/sbin:/usr/bin
runlevel=S
prevlevel=N
umask 022
export PATH runlevel prevlevel
# Make sure proc is mounted
[ -d "/proc/1" ] || mount /proc
# Source defaults.
. /etc/default/rcS
# Trap CTRL-C &c only in this shell so we can interrupt subprocesses.
trap ":" INT QUIT TSTP
# Call all parts in order.
exec /etc/init.d/rc S
rcS自己定义了两个变量:runlevel=S,prevlevel=N ,也就是后面会用到的。这个文件前面的内容都在配置环境,其中,看看“/etc/default/rcS”。其实也在配置环境,看看里面可能用到的一些变量:
•••
UTC=yes
VOLATILE_ENABLE_CACHE=yes
ROOTFS_READ_ONLY=no
•••
其中,看到第一个,这里把系统的时区设置成了UTC。
“/etc/init.d/rcS”最后exec 了“/etc/init.d/rc”脚本,并传递了“S”参数进去,接下来是分析“/etc/init.d/rc”
先来看看rc文件的说明:# rc This file is responsible for starting/stopping
# services when the runlevel changes.
#
# Optimization feature:
# A startup script is _not_ run when the service was
# running in the previous runlevel and it wasn't stopped
# in the runlevel transition (most Debian services don't
# have K?? links in rc{1,2,3,4,5} )
rc文件是当runlevel 改变的时候,用来相应starting/stoppingservice的,也就说明了,starting/stopping service的动作是由rc文件来主导。接着是说,如果一个服务正在运行,那么它的启动脚本就不会被运行(因为所有的服务,都是通过脚本来运行的);同时当runlevel过渡到另一个runlevel的时候,在run的服务不会被停止。
由于“/etc/init.d/rcS”传递了runlevel=S,prevlevel=N 两个参数,所以接下来会被执行的是/etc/rcS.d/下的脚本文件(下面代码中有分析到)
看/etc/init.d/rc的相关内容:
//用来启动脚本部分的内容跳过(startup方法的内容)
# First, run the KILL scripts. //首先检查K文件,并先运行,kill掉一些在启动过程中需要kill掉的service
//所以K开头的文件在启动,重启,关机的时候都会被执行。
if [ $previous != N ] //如动态logo的service
then //由于这里previous = N ,所以执行/etc/rcS.d/目录文件的时候会跳过这一步
for i in /etc/rc$runlevel.d/K[0-9][0-9]*
do
# Check if the script is there.
[ ! -f $i ] && continue
# Stop the service.
startup $i stop
done
fi
# Now run the START scripts for this runlevel.
for i in /etc/rc$runlevel.d/S* //逐一执行S开头的脚本。
do //由于这里$runlevel=S
[ ! -f $i ] && continue //也就是说执行的是/etc/rcS.d/目录下的文件
if [ $previous != N ] && [ $previous != S ] //执行/etc/rcS.d/目录文件的时候会跳过这一步
then //因为previous = N
# //这里判断当前runlevel下已启动的service就不会重新启动
# Find start script in previous runlevel and
# stop script in this runlevel.
#
suffix=${i#/etc/rc$runlevel.d/S[0-9][0-9]}
stop=/etc/rc$runlevel.d/K[0-9][0-9]$suffix
previous_start=/etc/rc$previous.d/S[0-9][0-9]$suffix
#
# If there is a start script in the previous level
# and _no_ stop script in this level, we don't
# have to re-start the service.
#
[ -f $previous_start ] && [ ! -f $stop ] && continue
fi
case "$runlevel" in
0|6) //如果是0或者是6 的runlevel,那么就传递stop参数
//系统是通过runlevel来区分是正常启动,重启,还是关机
//所以,case 0在关机的时候才执行,case 6在reboot的时候才执行。
startup $i stop
;;
*)
startup $i start //如果是0或6之外的runlevel,则传递start参数,启动service
;; // startup函数(方法)也是在”/etc/default/rcS”里面定义的
Esac //startup的作业就是直接启动各种各样的service
done
fi
执行完“/etc/init.d/rc”和“/etc/rcS.d/”下的脚本文件(服务)后,跳回/etc/inittab,继续往下执行:
# What to do in single-user mode.
~~:S:wait:/sbin/sulogin
此语句可以让系统在重新启动、进入单用户模式的时候提示输入超级用户密码。S同运行等级1,并等待其结束,然后再处理/etc/inittab文件中的下一个登记项。(此两句解释来源于网上参考资料)
由于当前inittab指定当前的runlevel为5,所以以下语句中,系统启动过程中只有“l5:5:wait:/etc/init.d/rc5”会被执行,也就是通过“/etc/init.d/rc”文件,逐一执行“/etc/rc5.d/”里面的文件。跟上面的过程一样。
l0:0:wait:/etc/init.d/rc 0
l1:1:wait:/etc/init.d/rc 1
l2:2:wait:/etc/init.d/rc 2
l3:3:wait:/etc/init.d/rc 3
l4:4:wait:/etc/init.d/rc 4
l5:5:wait:/etc/init.d/rc 5 //当前runlevel为5,只会执行此语句
l6:6:wait:/etc/init.d/rc 6
继续往下看:
z6:6:respawn:/sbin/sulogin
mxc::respawn:/etc/init.d/rc_mxc.S
#mxc0:12345:respawn:/sbin/getty 115200 ttymxc0
# /sbin/getty invocations for the runlevels.
#
# The "id" field MUST be the same as the last
# characters of the device (after "tty").
#
# Format:
# :::
#
1:2345:respawn:/sbin/getty 38400 tty1 //这里是1,2,3,4,5的runlevel都会被执行这条语句
这几句是系统输出中断,与对应的硬件串口。可以通过修改getty来实现autologin功能。
到此,yocto启动完毕。
关机或者重启的流程也大同小异,不一样的地方是不会执行“/etc/rc5.d/”下的脚本文件了。变成了:关机执行“/etc/rc0.d/”下的脚本文件;重启执行“/etc/rc6.d/”下的脚本文件。