Linux啟動(dòng)分析——init進(jìn)程與app啟動(dòng)
概述
本文通過簡要分析init進(jìn)程源碼,梳理其處理流程,重點(diǎn)關(guān)注init進(jìn)程如何啟動(dòng)應(yīng)用程序,總結(jié)啟動(dòng)腳本文件的編寫思路
init進(jìn)程源碼分析
init進(jìn)程是linux內(nèi)核啟動(dòng)的第一個(gè)進(jìn)程,怎么知道的?從內(nèi)核源碼linux-2.6.xxx/init/main.c代碼的kernel_init()函數(shù)分析,可以發(fā)現(xiàn),內(nèi)核會(huì)根據(jù)uboot傳入的參數(shù)來啟動(dòng)第一個(gè)進(jìn)程,一般都是init
怎么啟動(dòng)的呢,調(diào)用kernel_execve()函數(shù)完成的,猜測是從根文件系統(tǒng)的/sbin/init來啟動(dòng)的,linux的任何應(yīng)用程序都是基于文件系統(tǒng)的,啟動(dòng)應(yīng)用程序前提是根文件系統(tǒng)已經(jīng)掛載好了。好,那么根文件系統(tǒng)又是從哪里來的呢,是由busybox這個(gè)工具配置編譯生成的,所以要分析init源碼,要去busybox里找init的源碼
源碼位置:/busybox/init/init.c,在其中查找main()函數(shù),發(fā)現(xiàn)只有init_main(),沒有main(),可以猜測busybox是通過一些方法將init進(jìn)程的入口修改為init_main(),實(shí)際上所有busybox的命令工具都是一個(gè)到busybox程序的鏈接,
- cd /sbin
- ls -l init
- lrwxrwxrwx 1 root 0 14 Nov 16 2016 init -> ../bin/busybox
可以看到,init進(jìn)程其實(shí)是到busybox的鏈接,不用管它,知道init進(jìn)程的入口是init_main()函數(shù)就行了
- #if DEBUG_SEGV_HANDLER
- {
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
- sa.sa_sigaction = handle_sigsegv;
- sa.sa_flags = SA_SIGINFO;
- sigaction(SIGSEGV, &sa, NULL);
- ......
- }
- #endif
- ......
- console_init();
- set_sane_term();
- ......
- /* Make sure environs is set to something sane */
- putenv((char *) "HOME=/");
- putenv((char *) bb_PATH_root_path);
- putenv((char *) "SHELL=/bin/sh");
- putenv((char *) "USER=root"); /* needed? why? */
這一段是init進(jìn)程最開始要做的事情,設(shè)置一些信號(hào)相關(guān)的東西,初始化console,然后設(shè)置環(huán)境變量,跟啟動(dòng)app似乎沒有什么關(guān)系,不用管,繼續(xù)往下看
- /* Check if we are supposed to be in single user mode */
- if (argv[1]
- && (strcmp(argv[1], "single") == 0 || strcmp(argv[1], "-s") == 0 || LONE_CHAR(argv[1], '1'))
- ) {
- /* ??? shouldn't we set RUNLEVEL="b" here? */
- /* Start a shell on console */
- new_init_action(RESPAWN, bb_default_login_shell, "");
- } else {
- /* Not in single user mode - see what inittab says */
- /* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
- * then parse_inittab() simply adds in some default
- * actions (i.e., INIT_SCRIPT and a pair
- * of "askfirst" shells) */
- parse_inittab();
- }
這一段代碼是一個(gè)if判斷,注釋說如果是single user mode,則走上半段代碼,如果不是single user mode,則調(diào)用parse_inittab() 函數(shù),因?yàn)閮?nèi)核啟動(dòng)init進(jìn)程沒有傳入附加參數(shù),所以argv[1]不存在,程序走parse_inittab()
注釋還說如果沒有定義CONFIG_FEATURE_USE_INITTAB 這個(gè)宏,程序會(huì)執(zhí)行一些默認(rèn)的action,那怎么知道這個(gè)宏定義了沒有呢,猜測這個(gè)宏應(yīng)該是對busybox配置時(shí)的選項(xiàng),好,怎么查看busybox配置呢,和linux內(nèi)核配置一樣的道理,結(jié)合make menuconfig和各級config文件來看
是否定義了宏CONFIG_FEATURE_USE_INITTAB?
在busybox中執(zhí)行make meunconfig,進(jìn)入熟悉的配置界面
大概瀏覽一下,和init有關(guān)系的好像有個(gè)Init Utilities項(xiàng),進(jìn)去
這里面有一項(xiàng)“Support reading an inittab file”,這個(gè)配置項(xiàng)是選中的,描述中有“inittab”這個(gè)單詞,和init源碼中說到的parse_inittab()很相似,好,make menuconfig先放到一邊,來看看配置文件,打開頂層目錄的Config.in,全局搜一下"init",發(fā)現(xiàn)只有最下面有:
- source init/Config.in
進(jìn)入init文件夾,打開其中的Config.in文件,發(fā)現(xiàn)配置項(xiàng)
- config FEATURE_USE_INITTAB
- bool "Support reading an inittab file"
- default y
- depends on INIT
- help
- Allow init to read an inittab file when the system boot.
猜測沒錯(cuò),CONFIG_FEATURE_USE_INITTAB這個(gè)宏確實(shí)定義了,回到init源碼分析,進(jìn)入parse_inittab()函數(shù)。首先看到這個(gè)函數(shù)前有一大段注釋,看看它說什么
- /* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
- * then parse_inittab() simply adds in some default
- * actions (i.e., runs INIT_SCRIPT and then starts a pair
- * of "askfirst" shells). If CONFIG_FEATURE_USE_INITTAB
- * _is_ defined, but /etc/inittab is missing, this
- * results in the same set of default behaviors.
- */
前面的話和之前的if判斷意思差不多,如果定義了XXX這個(gè)宏,但是/etc/inittab這個(gè)文件沒有,也會(huì)走默認(rèn)的action,好,大概猜想一下,parse_inttab()函數(shù)好像和要分析的app啟動(dòng)有點(diǎn)關(guān)系了,如果定義了XXX宏,就去解析/etc/inittab這個(gè)文件,執(zhí)行里面的東西,如果沒有定義XXX宏或者/etc/inittab文件不存在,執(zhí)行一些默認(rèn)的東西
好,搞清楚一件事,/etc/inittab這個(gè)文件很重要,可能需要自己來創(chuàng)建這個(gè)文件,往里面寫東西,但是寫什么內(nèi)容呢?目前還不知道。那如果不走/etc/inittab這一條路呢,默認(rèn)會(huì)執(zhí)行的action又是什么意思?來分析一下parse_inittab()這個(gè)函數(shù)
- static void parse_inittab(void)
- {
- #if ENABLE_FEATURE_USE_INITTAB
- char *token[4];
- parser_t *parser = config_open2("/etc/inittab", fopen_for_read);
- if (parser == NULL)
- #endif
- {
- /* No inittab file - set up some default behavior */
- /* Sysinit */
- new_init_action(SYSINIT, INIT_SCRIPT, "");
- /* Askfirst shell on tty1-4 */
- new_init_action(ASKFIRST, bb_default_login_shell, "");
- //TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users
- new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
- new_init_action(ASKFIRST, bb_default_login_shell, VC_3);
- new_init_action(ASKFIRST, bb_default_login_shell, VC_4);
- /* Reboot on Ctrl-Alt-Del */
- new_init_action(CTRLALTDEL, "reboot", "");
- /* Umount all filesystems on halt/reboot */
- new_init_action(SHUTDOWN, "umount -a -r", "");
- /* Swapoff on halt/reboot */
- new_init_action(SHUTDOWN, "swapoff -a", "");
- /* Restart init when a QUIT is received */
- new_init_action(RESTART, "init", "");
- return;
- }
- #if ENABLE_FEATURE_USE_INITTAB
- /* optional_tty:ignored_runlevel:action:command
- * Delims are not to be collapsed and need exactly 4 tokens
- */
- while (config_read(parser, token, 4, 0, "#:",
- PARSE_NORMAL & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
- /* order must correspond to SYSINIT..RESTART constants */
- static const char actions[] ALIGN1 =
- "sysinit\0""wait\0""once\0""respawn\0""askfirst\0"
- "ctrlaltdel\0""shutdown\0""restart\0";
- int action;
- char *tty = token[0];
- if (!token[3]) /* less than 4 tokens */
- goto bad_entry;
- action = index_in_strings(actions, token[2]);
- if (action < 0 || !token[3][0]) /* token[3]: command */
- goto bad_entry;
- /* turn .*TTY -> /dev/TTY */
- if (tty[0]) {
- tty = concat_path_file("/dev/", skip_dev_pfx(tty));
- }
- new_init_action(1 << action, token[3], tty);
- if (tty[0])
- free(tty);
- continue;
- bad_entry:
- message(L_LOG | L_CONSOLE, "Bad inittab entry at line %d",
- parser->lineno);
- }
- config_close(parser);
- #endif
- }
首先去讀了/etc/inittab這個(gè)文件,如果不存在,執(zhí)行了很多new_init_action() ,如果存在,就走了一個(gè)while()循環(huán),猜測應(yīng)該是解析/etc/inittab文件的內(nèi)容,根據(jù)文件的內(nèi)容執(zhí)行new_init_action() 。好,那么inittab文件到底寫什么格式,什么東西呢,while()循環(huán)里面有一個(gè)static const char actions[]數(shù)組看起來像是和inittab的內(nèi)容有關(guān)系,里面有“sysinit”等字符串,但是還是沒辦法搞清楚怎么寫inittab文件
inittab文件怎么寫
/busybox/examples/下面找到一個(gè)inittab腳本的例子,打開,看到一個(gè)類似格式說明的句子:
- Format for each entry: <id>:<runlevels>:<action>:<process>
猜測inittab文件里可以有多條entry,每條entry格式中有id、runlevels、action和process這四項(xiàng)內(nèi)容,這里也出現(xiàn)了action,和代碼里的action數(shù)組很像。文件里又說id和runlevels無關(guān)緊要,好,要搞清楚inittab怎么寫,關(guān)鍵在于理解action和process,繼續(xù)看說明
action
action包括:sysinit、respawn、askfirst、wait、once、restart、ctrlaltdel、和shutdown共八種,
process
指定要運(yùn)行的程序和它的參數(shù)
然后還說了如果沒有inittab文件,則運(yùn)行以下內(nèi)容
- ::sysinit:/etc/init.d/rcS
- ::askfirst:/bin/sh
- ::ctrlaltdel:/sbin/reboot
- ::shutdown:/sbin/swapoff -a
- ::shutdown:/bin/umount -a -r
- ::restart:/sbin/init
- tty2::askfirst:/bin/sh
- tty3::askfirst:/bin/sh
- tty4::askfirst:/bin/sh
這應(yīng)該就是代碼中如果讀不到inittab文件,則執(zhí)行的一系列net_init_action的內(nèi)容
再往下看,出現(xiàn)的第一條示例entry
- ::sysinit:/etc/init.d/rcS
是不是很熟悉,linux系統(tǒng)嵌入式設(shè)備里通常會(huì)有/etc/init.d/rcS這個(gè)文件,它是一個(gè)shell腳本,根據(jù)前面的格式,分析一下,id和runlevel為空,action為sysinit,process為/etc/init.d/rcS,所以第一件要干的事情是去執(zhí)行rcS腳本,而rcS腳本里可以做自己想做的任何事情了
下一條示例是
- ::askfirst:-/bin/sh
注釋說的是啟動(dòng)shell到console口,不管,繼續(xù)看
- tty4::respawn:/sbin/getty 38400 tty5
- tty5::respawn:/sbin/getty 38400 tty6
開啟getty
- ::restart:/sbin/init
指定init進(jìn)程的重啟位置
- ::ctrlaltdel:/sbin/reboot
- ::shutdown:/bin/umount -a -r
- ::shutdown:/sbin/swapoff -a
在重啟之前要做的事情
再回到代碼上,這個(gè)while()循環(huán)遍歷了inittab文件的每一個(gè)entry,解析出entry的四個(gè)部分:id、runlevel、action和process,放到一個(gè)指針數(shù)組char *token[4]中,則token[2]和token[3]代表action和process,程序里調(diào)用index_in_strings()函數(shù)將token[2]轉(zhuǎn)成字符串,即“sysinit”等值,再調(diào)用net_init_action(),分析net_init_action()源碼可以看出,其實(shí)只是把這些action和process添加到一個(gè)鏈表中,并沒有做實(shí)際的處理,真正的處理在后續(xù)的代碼中,parse_inittab()函數(shù)返回,
- ......
- /* Now run everything that needs to be run */
- /* First run the sysinit command */
- run_actions(SYSINIT);
- check_delayed_sigs();
- /* Next run anything that wants to block */
- run_actions(WAIT);
- check_delayed_sigs();
- /* Next run anything to be run only once */
- run_actions(ONCE);
- /* Now run the looping stuff for the rest of forever.
- */
- while (1) {
- ......
這里調(diào)用run_action()運(yùn)行鏈表中每一個(gè)entry,并且首先運(yùn)行的是action為sysinit的動(dòng)作
總結(jié)
到這里,大致搞清楚了init進(jìn)程是怎么啟動(dòng)app的了,上流程圖
簡單來說,init進(jìn)程首先分析/etc/inittab文件,當(dāng)然,可以自己修改busybox源碼,讓它從任意文件開始分析,如果不存在inittab文件,則執(zhí)行默認(rèn)的action;如果inittab文件存在,則根據(jù)inittab文件中的條目執(zhí)行,通常是去/etc/init.d/rcS文件中執(zhí)行腳本命令,當(dāng)然,修改源碼,你也可以讓它執(zhí)行別的腳本
rcS腳本是以shell腳本語言編寫,一般的套路是
- 加載驅(qū)動(dòng)模塊
- 配置網(wǎng)絡(luò),建橋、配網(wǎng)卡地址
- 啟動(dòng)app