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:
posted @ 2022-01-03 17:01  人民广场的二道贩子  阅读(1437)  评论(1编辑  收藏  举报