03 openwrt的啟動過程


引用博客:https://clockworkbird9.wordpress.com/2016/09/

[ 2.824545] VFS: Mounted root (ext4 filesystem) readonly on device 179:1.
[ 2.833446] Freeing unused kernel memory: 244K (84733000 - 84770000)
[ 3.006884] init: Console is alive
[ 3.011436] init: - watchdog -
[ 3.329383] init: - preinit -
[ 6.570976] mount_root: mounting /dev/root
[ 6.579281] EXT4-fs (mmcblk0p1): re-mounted. Opts: (null)
[ 6.596450] procd: - early -
[ 6.599817] procd: - watchdog -
[ 7.301153] procd: - ubus -
[ 7.362047] procd: - init -

可以明顯的看到procd進程接管了init進程

1 啟動流程

  • u-boot
    它配置低級硬件,加載Linux內核image和設備樹blob,最后使用內核cmdline跳轉到RAM中的Linux內核映像;
  • Kernel -> Hareware
    Linux Kernel初始化Hareware
  • Kernel -> filesystem
    將掛載根文件系統
  • Kernel -> Init Process (PID 1)
    內核初始化進程
  • Openwrt -> Preinit
    openwrt初始化進程,注意這里是Preinit函數,不是腳本
  • Openwrt -> Procd、perinit(腳本)
    procd回去調用/etc/rc.d
    preinit初始化完成之后。初始化過程就結束了

2 Preinit

2.1 /etc/preinit

OpenWRT會將OpenWRT初始化進程preinit注入到內核初始化進程列表中(kernel_init)。


此時設備會去執行/etc/preinit位於package/base-files/etc/

#!/bin/sh
# Copyright (C) 2006-2016 OpenWrt.org
# Copyright (C) 2010 Vertical Communications

### 設備第一次執行到這里PREINIT參數未定義,因此/sbin/init會被執行
### 因此/sbin/init為設備的第一次初始化過程
[ -z "$PREINIT" ] && exec /sbin/init

export PATH="%PATH%"

. /lib/functions.sh
. /lib/functions/preinit.sh
. /lib/functions/system.sh

### boot_hook_init在 /lib/functions/preinit.sh中定義
boot_hook_init preinit_essential
boot_hook_init preinit_main
boot_hook_init failsafe
boot_hook_init initramfs
boot_hook_init preinit_mount_root

### 執行/lib/preinit/下所有腳本
for pi_source_file in /lib/preinit/*; do
	. $pi_source_file
done

boot_run_hook preinit_essential

pi_mount_skip_next=false
pi_jffs2_mount_success=false
pi_failsafe_net_message=false

boot_run_hook preinit_main

/sbin/initprocd/init.d/init.c編譯的可執行文件。

int
main(int argc, char **argv)
{
    pid_t pid;

    //打開日志
    ulog_open(ULOG_KMSG, LOG_DAEMON, "init");

    //設置信號
    sigaction(SIGTERM, &sa_shutdown, NULL);
    sigaction(SIGUSR1, &sa_shutdown, NULL);
    sigaction(SIGUSR2, &sa_shutdown, NULL);

    /*  early
     *   |->early_mounts
     *   |      |-> mount
     *   |      |->early_dev 設置環境變量
     *   |->LOG("Console is alive")
     */
	early();
    
    /*  cmdline
     *      |-> get init_debug 獲取init_debug等級
     */
	cmdline();
    
    /*  watchdog_init
     *      |->LOG("- watchdog -")
     */
	watchdog_init(1);

	pid = fork();
    if (!pid) {
        /*  /sbin/kmodloader
         *      |-> /etc/modules-boot.d 加載驅動
         */
        char *kmod[] = { "/sbin/kmodloader", "/etc/modules-boot.d/", NULL };

        if (debug < 3)
            patch_stdio("/dev/null");

        execvp(kmod[0], kmod);
        ERROR("Failed to start kmodloader\n");
        exit(-1);
    }
    if (pid <= 0) {
        ERROR("Failed to start kmodloader instance\n");
    } else {
        int i;

        for (i = 0; i < 1200; i++) {
            if (waitpid(pid, NULL, WNOHANG) > 0)
                break;
            usleep(10 * 1000);
            watchdog_ping();
        }
    }
    
	uloop_init();
    /*  preinit
     *      |-> LOG("- preinit -")
     *      |-> fork->procd
     *      |-> setenv("PREINIT", "1", 1)
     *      |-> fork->sh /etc/preinit
     */
	preinit();
    uloop_run();

    return 0;
}

initd/preinit.c

void
preinit(void)
{
        // perinit腳本
        char *init[] = { "/bin/sh", "/etc/preinit", NULL };
        // procd
        char *plug[] = { "/sbin/procd", "-h", "/etc/hotplug-preinit.json", NULL };
        int fd;

        LOG("- preinit -\n");

        /*  注意這個是回調函數
         */
        plugd_proc.cb = plugd_proc_cb;
        plugd_proc.pid = fork();
        if (!plugd_proc.pid) {
                /*  plug "/sbin/procd", "-h", "/etc/hotplug-preinit.json"
                 *  先執行procd 入參為 -h /etc/hotplug-preinit.json
                 */
                execvp(plug[0], plug);
                ERROR("Failed to start plugd: %m\n");
                exit(EXIT_FAILURE);
        }
        if (plugd_proc.pid <= 0) {
                ERROR("Failed to start new plugd instance: %m\n");
                return;
        }
        uloop_process_add(&plugd_proc);

        setenv("PREINIT", "1", 1);

        fd = creat("/tmp/.preinit", 0600);

        if (fd < 0)
                ERROR("Failed to create sentinel file: %m\n");
        else
                close(fd);

        preinit_proc.cb = spawn_procd;
        preinit_proc.pid = fork();
        if (!preinit_proc.pid) {
                /*  init "/bin/sh", "/etc/preinit
                 */
                execvp(init[0], init);
                ERROR("Failed to start preinit: %m\n");
                exit(EXIT_FAILURE);
        }
        if (preinit_proc.pid <= 0) {
                ERROR("Failed to start new preinit instance: %m\n");
                return;
        }
        uloop_process_add(&preinit_proc);

        DEBUG(4, "Launched preinit instance, pid=%d\n", (int) preinit_proc.pid);
}

回調函數:回調函數與普通函數的區別在於在回調函數中主程序會把回調函數像參數一樣傳入庫函數

fork procd進程,指定了hotplug-preinit.json,所以會執行hotplug_run

int main(int argc, char **argv)
{
    int ch;
    char *dbglvl = getenv("DBGLVL");
    int ulog_channels = ULOG_KMSG;

    if (dbglvl) {
        debug = atoi(dbglvl);
        unsetenv("DBGLVL");
    }

    while ((ch = getopt(argc, argv, "d:s:h:S")) != -1) {
        switch (ch) {
        case 'h':
            /*  建立netlink通訊機制,完成內核的交互,監聽uevent事件
             */
            return hotplug_run(optarg);
        case 's':
            ubus_socket = optarg;
            break;
        case 'd':
            debug = atoi(optarg);
            break;
        case 'S':
            ulog_channels = ULOG_STDIO;
            break;
        default:
            return usage(argv[0]);
        }
    }

    ulog_open(ulog_channels, LOG_DAEMON, "procd");

    setsid();
    uloop_init();
    procd_signal();
    if (getpid() != 1)
        procd_connect_ubus();
    else
        /* 狀態機處理,實際效果如下
         * [ 6.596450] procd: - early -
         * [ 6.599817] procd: - watchdog -
         * [ 7.301153] procd: - ubus -
         * [ 7.362047] procd: - init -
         */
        procd_state_next();
    uloop_run();
    uloop_done();

    return 0;
}

procd_state_next處理完狀態之后設備設備會執行到rcS.c

int rcS(char *pattern, char *param, void (*q_empty)(struct runqueue *))
{
    runqueue_init(&q);
    q.empty_cb = q_empty;
    q.max_running_tasks = 1;

    // 這里便是我們常見的自啟動腳本的地方
    // 需要知道的是K開頭的文件為stop,S開頭的文件為start
    return _rc(&q, "/etc/rc.d", pattern, "*", param);
}

這里引用別人的圖解

1、early()init 中的第一個函數。它有四個主要任務:

  • early_mounts(): mount /proc, /sysfs, /dev, /tmp;
  • early_env(): 使用/usr/sbin:/sbin:/usr/bin:/bin 設置 PATH參數;
  • 初始化/dev/console;
  • init打印第一條消息:"控制台是一個活的",如上所示;

2、cmdline() 是第二個函數,它從/proc/cmdline讀取內核引導命令行並解析init_debug參數;
3、watchdog_init() 初始化監視程序 /dev/watchdog 並打印第二條消息 "- 監視程序 -" 如上所示;
4、fork 一個新線程,讓/sbin/kmodloader加載有關/etc/modules-boot.d/ 的設備驅動程序;
5、uloop_init() 初始化uloop,這是一個事件循環實現。后來的procdsh /etc/preinit將由uloop管理;
6、preinit()有四個主要任務:

  • 打印第三條消息:"- preinit -",如上所示;
  • fork()一個新的線程來執行sh /etc/preinit。這將是第二次執行此初始化腳本。一個名為spawn_procd()的回調函數將在sh /etc/preinit完成后執行。
    注意:spawn_procd()將從/tmp/debuglevel讀取系統調試級別,並將其設置為env DBGLVL。它還將看門狗fd設置為env WDTFD。最后,它將分叉真正的/sbin/procd作為deamon
  • set env 變量 PREINIT with setenv("PREINIT", "1", 1);
  • fork() 一個新的線程,用於執行/sbin/procd程序,參數為-h /etc/hotplug-preinit.json
  • 注意:這個新線程將通過uloop_process_add()以及一個callbakc函數添加到uloop中,作為當/sbin/procd – h完成時,回調函數plugd_proc_cb()

3 啟動腳本示例

#!/bin/sh /etc/rc.common            ### 這里/etc/common表明使用了此文件中的提供的一些基本函數

START=15        ### 執行順序,改變之后需要執行/etc/init.d/hello enable才能生效
STOP=85

start() {
    
}

stop() {
    
}

restart() {
    
}

3.1 rc.common

  • start:啟動服務
  • stop :關閉服務
  • restart:重啟服務
  • reload :重新讀取配置
  • enable :打開服務自啟動,即將文件軟鏈接到/etc/rc.d
  • disable:關閉服務自啟動。
  • enabled:查詢當前自啟動狀態
  • boot :調用start
  • shutdown:調用stop
  • help:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM