linux 系统启动 第三课

linux系统从上电到系统运行起来(三)

通过前两课的分析,我们已经跟踪到了关键的一个函数kernel_init,它最终也就是1号进程,是从内核态到用户态转变的关键点,我们在这里对这个函数进行分析

static int __ref kernel_init(void *unused)
{
	kernel_init_freeable();
	/* need to finish all async __init code before freeing the memory */
	async_synchronize_full();
	free_initmem();
	mark_rodata_ro();
	system_state = SYSTEM_RUNNING;
	numa_default_policy();

	flush_delayed_fput();

	if (ramdisk_execute_command) {
		if (!run_init_process(ramdisk_execute_command))
			return 0;
		pr_err("Failed to execute %s\n", ramdisk_execute_command);
	}

	/*
	 * We try each of these until one succeeds.
	 *
	 * The Bourne shell can be used instead of init if we are
	 * trying to recover a really broken machine.
	 */
	if (execute_command) {
		if (!run_init_process(execute_command))
			return 0;
		pr_err("Failed to execute %s.  Attempting defaults...\n",
			execute_command);
	}
	if (!run_init_process("/sbin/init") ||
	    !run_init_process("/etc/init") ||
	    !run_init_process("/bin/init") ||
	    !run_init_process("/bin/sh"))
		return 0;

	panic("No init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/init.txt for guidance.");
}

static int run_init_process(const char *init_filename)
{
	argv_init[0] = init_filename;
	return do_execve(init_filename,
		(const char __user *const __user *)argv_init,
		(const char __user *const __user *)envp_init);
}

从内核源码kernel_init最后调用了exec族函数,进行了进程替换,如果uboot没有传相关启动参数进来,则默认依次到根文件系统下寻找init进程(就是busybox的init进程)
下面我们来分析下init进程的一些流程
首先init_main会调用parse_inittab来解析/etc/inittab的内容,在parse_inittab的最后会执行new_init_action(SYSINIT, INIT_SCRIPT, “”) ,
#define INIT_SCRIPT “/etc/init.d/rcS” /* Default sysinit script. */
默认的启动脚本即是/etc/init.d/rcS,然后通过该启动脚本来启动相应的进程
现我们来看一个具体的例子
首先看下inittab文件

::sysinit:/etc/init.d/rcS S boot
::shutdown:/etc/init.d/rcS K stop

init程序需要读取配置文件/etc/inittab。inittab是一个不可执行的文本文件,它有若干行指令所组成,inittab文件中的值都是如下格式:label:runlevel:action:process
label是1~4个字符的标签,用来标示输入的值
runlevel字段指定runlevel的级别。可以指定多个runlevel级别,也可以不为runlevel字段指定特定的值
runlevel用来表示在init进程结束之后的系统状态,在系统的硬件中没有固定的信息来表示runlevel,它纯粹是一种软件结构。init和inittab是runlevel影响系统状态的唯一原因。runlevel是init所处于的运行级别的标识,一般使用0-6以及S或s。0、1、6运行级别被系统保留:其中0作为shutdown动作,1作为重启至单用户模式,6为重启;S和s意义相同,表示单用户模式

runlevel: 
  Runlevel=0 是让init关闭所有进程并终止系统。 
  Runlevel=1 是用来将系统转到单用户模式,单用户模式只能有系统管理员进入,在该模式下处理那些在有登录用户的情况下不能进行更改的文件,改runlevel的编号1也可以用S代替。 
  Runlevel=2 是允许系统进入多用户的模式,但并不支持文件共享,这种模式很少应用。 
  Runlevel=3 是最常用的运行模式,主要用来提供真正的多用户模式,也是多数服务器的缺省模式。 
  Runlevel=4 一般不被系统使用,用户可以设计自己的系统状态并将其应用到runlevel 4阶段,尽管很少使用,但使用该系统可以实现一些特定的登录请求。 
  Runlevel=5 是将系统初始化为专用的X Window终端。对功能强大的Linux系统来说,这并不是好的选择,但用户如果需要这样,也可以通过在runlevel启动来实现该方案。 
  Runlevel=6 是关闭所有运行的进程并重新启动系统

action字段定义了该进程应该运行在何种状态下:

boot 在系统启动时运行,忽略runlevel
bootwait 在系统启动时运行,等待init进程完成。忽略runlevel
ctrlaltdel 当Ctrl+Alt+Del三个键同时按下时运行,把SIGINT信号发送给init。忽略runlevel
initdefault 不要执行这个进程,它用于设置默认runlevel
kbrequest 当init从键盘中收到信号时运行。这里要求键盘组合符合KeyBoardSigral(参见/usr/share/doc/kbd-*关于键盘组合的文档)
off 禁止进入,因此该进程不运行
once 每一个runlevel级别运行一次
ondemand 当系统指定特定的运行级别A、B、C时运行
powerfail 当init收到SIGPWR信号时运行
powerokwait 当收到SIGPWD信号且/etc/文件中的电源状态包含OK时运行
powerwait 当收到SIGPWD信号,并且init等待进程结束时运行
respawn 不管何时终止都重新启动进程
sysinit 在运行boot或bootwait进程之前运行
wait 运行进程等待输入运行模式
sysinit、boot、bootwait等action将在系统启动时无条件运行,而忽略其中的runlevel。

process字段包含init执行的进程,该进程采用的格式与在命令行下运行该进程的格式一样,因此process字段都以该进程的名字开头,紧跟着是运行时要传递给该进程的参数

然后其他自定义的都是通过/etc/init.d/rcS来启动,参考格式如下

{
        for i in /etc/rc.d/$1*; do
                [ -x $i ] && $i $2 2>&1
        done
} | logger -s -p 6 -t '' &

这样我们的自定义的应用程序就启动起来了!
系统的启动过程,相信通过这三课大家已经有了一个比较粗略的认识,
接下来,后续会分析的细致一点,敬请期待

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章