Linux啓動分析——init進程與app啓動

概述

本文經過簡要分析init進程源碼,梳理其處理流程,重點關注init進程如何啓動應用程序,總結啓動腳本文件的編寫思路linux

init進程源碼分析

init進程是linux內核啓動的第一個進程,怎麼知道的?從內核源碼linux-2.6.xxx/init/main.c代碼的kernel_init()函數分析,能夠發現,內核會根據uboot傳入的參數來啓動第一個進程,通常都是initshell

怎麼啓動的呢,調用kernel_execve()函數完成的,猜想是從根文件系統的/sbin/init來啓動的,linux的任何應用程序都是基於文件系統的,啓動應用程序前提是根文件系統已經掛載好了。好,那麼根文件系統又是從哪裏來的呢,是由busybox這個工具配置編譯生成的,因此要分析init源碼,要去busybox裏找init的源碼數組

源碼位置:/busybox/init/init.c,在其中查找main()函數,發現只有init_main(),沒有main(),能夠猜想busybox是經過一些方法將init進程的入口修改成init_main(),實際上全部busybox的命令工具都是一個到busybox程序的連接,網絡

cd /sbin
ls -l init
lrwxrwxrwx 1 root 0 14 Nov 16 2016 init -> ../bin/busybox

能夠看到,init進程實際上是到busybox的連接,不用管它,知道init進程的入口是init_main()函數就好了app

#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進程最開始要作的事情,設置一些信號相關的東西,初始化console,而後設置環境變量,跟啓動app彷佛沒有什麼關係,不用管,繼續往下看less

/* 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();
}

這一段代碼是一個if判斷,註釋說若是是single user mode,則走上半段代碼,若是不是single user mode,則調用parse_inittab() 函數,由於內核啓動init進程沒有傳入附加參數,因此argv[1]不存在,程序走parse_inittab()函數

註釋還說若是沒有定義CONFIG_FEATURE_USE_INITTAB 這個宏,程序會執行一些默認的action,那怎麼知道這個宏定義了沒有呢,猜想這個宏應該是對busybox配置時的選項,好,怎麼查看busybox配置呢,和linux內核配置同樣的道理,結合make menuconfig和各級config文件來看工具

是否認義了宏CONFIG_FEATURE_USE_INITTAB?

在busybox中執行make meunconfig,進入熟悉的配置界面
這裏寫圖片描述
大概瀏覽一下,和init有關係的好像有個Init Utilities項,進去
這裏寫圖片描述
這裏面有一項「Support reading an inittab file」,這個配置項是選中的,描述中有「inittab」這個單詞,和init源碼中說到的parse_inittab()很類似,好,make menuconfig先放到一邊,來看看配置文件,打開頂層目錄的Config.in,全局搜一下"init",發現只有最下面有:oop

source init/Config.in

進入init文件夾,打開其中的Config.in文件,發現配置項源碼分析

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.

猜想沒錯,CONFIG_FEATURE_USE_INITTAB這個宏確實定義了,回到init源碼分析,進入parse_inittab()函數。首先看到這個函數前有一大段註釋,看看它說什麼

/* 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這個宏,可是/etc/inittab這個文件沒有,也會走默認的action,好,大概猜測一下,parse_inttab()函數好像和要分析的app啓動有點關係了,若是定義了XXX宏,就去解析/etc/inittab這個文件,執行裏面的東西,若是沒有定義XXX宏或者/etc/inittab文件不存在,執行一些默認的東西

好,搞清楚一件事,/etc/inittab這個文件很重要,可能須要本身來建立這個文件,往裏面寫東西,可是寫什麼內容呢?目前還不知道。那若是不走/etc/inittab這一條路呢,默認會執行的action又是什麼意思?來分析一下parse_inittab()這個函數

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這個文件,若是不存在,執行了不少new_init_action() ,若是存在,就走了一個while()循環,猜想應該是解析/etc/inittab文件的內容,根據文件的內容執行new_init_action() 。好,那麼inittab文件到底寫什麼格式,什麼東西呢,while()循環裏面有一個static const char actions[]數組看起來像是和inittab的內容有關係,裏面有「sysinit」等字符串,可是仍是沒辦法搞清楚怎麼寫inittab文件

inittab文件怎麼寫

/busybox/examples/下面找到一個inittab腳本的例子,打開,看到一個相似格式說明的句子:

Format for each entry: <id>:<runlevels>:<action>:<process>

猜想inittab文件裏能夠有多條entry,每條entry格式中有id、runlevels、action和process這四項內容,這裏也出現了action,和代碼裏的action數組很像。文件裏又說id和runlevels可有可無,好,要搞清楚inittab怎麼寫,關鍵在於理解action和process,繼續看說明

action

action包括:sysinit、respawn、askfirst、wait、once、restart、ctrlaltdel、和shutdown共八種,

process

指定要運行的程序和它的參數

而後還說了若是沒有inittab文件,則運行如下內容

::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

這應該就是代碼中若是讀不到inittab文件,則執行的一系列net_init_action的內容

再往下看,出現的第一條示例entry

::sysinit:/etc/init.d/rcS

是否是很熟悉,linux系統嵌入式設備裏一般會有/etc/init.d/rcS這個文件,它是一個shell腳本,根據前面的格式,分析一下,id和runlevel爲空,action爲sysinit,process爲/etc/init.d/rcS,因此第一件要乾的事情是去執行rcS腳本,而rcS腳本里能夠作本身想作的任何事情了

下一條示例是

::askfirst:-/bin/sh

註釋說的是啓動shell到console口,無論,繼續看

tty4::respawn:/sbin/getty 38400 tty5
tty5::respawn:/sbin/getty 38400 tty6

開啓getty

::restart:/sbin/init

指定init進程的重啓位置

::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a

在重啓以前要作的事情

再回到代碼上,這個while()循環遍歷了inittab文件的每個entry,解析出entry的四個部分:id、runlevel、action和process,放到一個指針數組char *token[4]中,則token[2]token[3]表明action和process,程序裏調用index_in_strings()函數將token[2]轉成字符串,即「sysinit」等值,再調用net_init_action(),分析net_init_action()源碼能夠看出,其實只是把這些action和process添加到一個鏈表中,並無作實際的處理,真正的處理在後續的代碼中,parse_inittab()函數返回,

......
    /* 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) {
        ......

這裏調用run_action()運行鏈表中每個entry,而且首先運行的是action爲sysinit的動做

總結

到這裏,大體搞清楚了init進程是怎麼啓動app的了,上流程圖
這裏寫圖片描述

簡單來講,init進程首先分析/etc/inittab文件,固然,能夠本身修改busybox源碼,讓它從任意文件開始分析,若是不存在inittab文件,則執行默認的action;若是inittab文件存在,則根據inittab文件中的條目執行,一般是去/etc/init.d/rcS文件中執行腳本命令,固然,修改源碼,你也可讓它執行別的腳本

rcS腳本是以shell腳本語言編寫,通常的套路是

  1. 加載驅動模塊
  2. 配置網絡,建橋、配網卡地址
  3. 啓動app
相關文章
相關標籤/搜索