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 '' &

這樣我們的自定義的應用程序就啓動起來了!
系統的啓動過程,相信通過這三課大家已經有了一個比較粗略的認識,
接下來,後續會分析的細緻一點,敬請期待

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