yocto inittab initialization processing

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/”下的脚本文件。









你可能感兴趣的:(技术)