openwrt启动流程(一)

Linux在start_kernel函数执行的最后会调用kernel_init函数来启动用户空间的一号进程,之前都是我们熟悉的init进程,但在openwrt里面会执行/etc/preinit脚本

其中标记红框的代码为给kernel打的patch

下面来看/etc/preinit脚本

[ -z "$PREINIT" ] && exec /sbin/init 

使用exec,表明init替换进程上下文,pid保持不变,应该为1

PREINIT变量当前没有值,所以执行init,下面来看init.c函数

    early();
    cmdline();
    watchdog_init(1);

首先做一些文件系统挂载,环境变量设置,watchdog初试化等操作

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

        if (debug < 3) {
            int fd = open("/dev/null", O_RDWR);

            if (fd > -1) {
                dup2(fd, STDIN_FILENO);
                dup2(fd, STDOUT_FILENO);
                dup2(fd, STDERR_FILENO);
                if (fd > STDERR_FILENO)
                    close(fd);
            }
        }
        execvp(kmod[0], kmod);
        ERROR("Failed to start kmodloader\n");
        exit(-1);
    }

然后fork一个子进程执行kmodloader,下面来看kmodloader.c函数

    if (scan_loaded_modules())
        return -1;

    if (scan_module_folders())
        return -1;

从/proc/modules里面遍历所有已经安装的模块,包括模块名称和大小等信息,插入到avl树中,同时将节点状态设置为LOADED

将/lib/modules/x.x.x路径保存到二维指针module_folders,遍历/lib/modules/x.x.x/*.ko,查看是否在avl树中,如果不在,添加到avl树中,同时将节点状态设置为SCANNED

        while (getline(&mod, &mod_len, fp) > 0) {
            char *nl = strchr(mod, '\n');
            struct module *m;
            char *opts;

            if (nl)
                *nl = '\0';

            opts = strchr(mod, ' ');
            if (opts)
                *opts++ = '\0';

            m = find_module(get_module_name(mod));
            if (!m || (m->state == LOADED))
                continue;

            if (opts)
                m->opts = strdup(opts);
            m->state = PROBE;
            if (basename(gl.gl_pathv[j])[0] - '0' <= 9)
                load_modprobe();

        }

遍历/etc/modules-boot.d/下所有文件并打开,判断当前模块是否在avl树中,如果存在且节点状态不为LOADED,则将此节点状态设置为PROBE,如果文件名第一个字符是0~9之间,则装载此模块,同时将此节点状态设置为LOADED

回到init.c,来看preinit函数

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

    LOG("- preinit -\n");

    plugd_proc.cb = plugd_proc_cb;
    plugd_proc.pid = fork();
    if (!plugd_proc.pid) {
        execvp(plug[0], plug);
        ERROR("Failed to start plugd\n");
        exit(-1);
    }
    if (plugd_proc.pid <= 0) {
        ERROR("Failed to start new plugd instance\n");
        return;
    }
    uloop_process_add(&plugd_proc);

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

    preinit_proc.cb = spawn_procd;
    preinit_proc.pid = fork();
    if (!preinit_proc.pid) {
        execvp(init[0], init);
        ERROR("Failed to start preinit\n");
        exit(-1);
    }
    if (preinit_proc.pid <= 0) {
        ERROR("Failed to start new preinit instance\n");
        return;
    }
    uloop_process_add(&preinit_proc);

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

fork两个子进程分别执行procd和/etc/preinit,来看procd.c,因携带参数-h /etc/hotplug-preinit.json,所以进入hotplug_run

void hotplug(char *rules)
{
    struct sockaddr_nl nls;
    int nlbufsize = 512 * 1024;

    rule_file = strdup(rules);
    memset(&nls,0,sizeof(struct sockaddr_nl));
    nls.nl_family = AF_NETLINK;
    nls.nl_pid = getpid();
    nls.nl_groups = -1;

    if ((hotplug_fd.fd = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT)) == -1) {
        ERROR("Failed to open hotplug socket: %s\n", strerror(errno));
        exit(1);
    }
    
    if (bind(hotplug_fd.fd, (void *)&nls, sizeof(struct sockaddr_nl))) {
        ERROR("Failed to bind hotplug socket: %s\n", strerror(errno));
        exit(1);
    }

    if (setsockopt(hotplug_fd.fd, SOL_SOCKET, SO_RCVBUFFORCE, &nlbufsize, sizeof(nlbufsize)))
        ERROR("Failed to resize receive buffer: %s\n", strerror(errno));

    json_script_init(&jctx);
    queue_proc.cb = queue_proc_cb;
    uloop_fd_add(&hotplug_fd, ULOOP_READ);
}

这里是建立netlink监听内核的uevent事件,具体的实现之后再单独分析,回到/etc/preinit

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

在当前shell环境下调用三个脚本,里面包含一些函数定义

boot_hook_init preinit_essential
boot_hook_init preinit_main
boot_hook_init failsafe
boot_hook_init initramfs
boot_hook_init preinit_mount_root

初试化hook链

for pi_source_file in /lib/preinit/*; do
    . $pi_source_file
done

依次执行/lib/preinit/下面的脚本,都是将函数调用添加到hook链上面

boot_run_hook preinit_essential

执行preinit_essential注册的hook链的所有函数

boot_run_hook preinit_main

执行preinit_main注册的hook链的所有函数

/etc/preinit执行完毕后,init进程调用spawn_procd

static void
spawn_procd(struct uloop_process *proc, int ret)
{
    char *wdt_fd = watchdog_fd();
    char *argv[] = { "/sbin/procd", NULL};
    struct stat s;
    char dbg[2];

    if (plugd_proc.pid > 0)
        kill(plugd_proc.pid, SIGKILL);

    if (!stat("/tmp/sysupgrade", &s))
        while (true)
            sleep(1);

    unsetenv("INITRAMFS");
    unsetenv("PREINIT");
    DEBUG(2, "Exec to real procd now\n");
    if (wdt_fd)
        setenv("WDTFD", wdt_fd, 1);
    check_dbglvl();
    if (debug > 0) {
        snprintf(dbg, 2, "%d", debug);
        setenv("DBGLVL", dbg, 1);
    }

    execvp(argv[0], argv);
}

首先kill掉刚才创建的子进程procd

    if (plugd_proc.pid > 0)
        kill(plugd_proc.pid, SIGKILL);

最后调用procd,来看procd.c

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

    ulog_open(ULOG_KMSG, LOG_DAEMON, "procd");

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

    while ((ch = getopt(argc, argv, "d:s:h:")) != -1) {
        switch (ch) {
        case 'h':
            return hotplug_run(optarg);
        case 's':
            ubus_socket = optarg;
            break;
        case 'd':
            debug = atoi(optarg);
            break;
        default:
            return usage(argv[0]);
        }
    }
    setsid();
    uloop_init();
    procd_signal();
    trigger_init();
    if (getpid() != 1) {
        procd_connect_ubus();
        
    } else {
        procd_state_next();
    }    
    uloop_run();
    uloop_done();

    return 0;
}

因pid为1,进入procd_state_next()

 

{
    char ubus_cmd[] = "/sbin/ubusd";

    switch (state) {
    case STATE_EARLY:
        LOG("- early -\n");
        watchdog_init(0);
        hotplug("/etc/hotplug.json");
        procd_coldplug();
        break;

    case STATE_UBUS:
        // try to reopen incase the wdt was not available before coldplug
        watchdog_init(0);
        set_stdio("console");
        LOG("- ubus -\n");
        procd_connect_ubus();
        service_init();
        service_start_early("ubus", ubus_cmd);
        break;

    case STATE_INIT:
        LOG("- init -\n");
        procd_inittab();
        procd_inittab_run("respawn");
        procd_inittab_run("askconsole");
        procd_inittab_run("askfirst");
        procd_inittab_run("sysinit");

        // switch to syslog log channel
        ulog_open(ULOG_SYSLOG, LOG_DAEMON, "procd");
        break;

    case STATE_RUNNING:
        LOG("- init complete -\n");
        break;

    case STATE_SHUTDOWN:
        /* Redirect output to the console for the users' benefit */
        set_console();
        LOG("- shutdown -\n");
        procd_inittab_run("shutdown");
        sync();
        break;

    case STATE_HALT:
        // To prevent killed processes from interrupting the sleep
        signal(SIGCHLD, SIG_IGN);
        LOG("- SIGTERM processes -\n");
        kill(-1, SIGTERM);
        sync();
        sleep(1);
        LOG("- SIGKILL processes -\n");
        kill(-1, SIGKILL);
        sync();
        sleep(1);
        if (reboot_event == RB_POWER_OFF)
            LOG("- power down -\n");
        else
            LOG("- reboot -\n");

        /* Allow time for last message to reach serial console, etc */
        sleep(1);

        /* We have to fork here, since the kernel calls do_exit(EXIT_SUCCESS)
         * in linux/kernel/sys.c, which can cause the machine to panic when
         * the init process exits... */
        if (!vfork( )) { /* child */
            reboot(reboot_event);
            _exit(EXIT_SUCCESS);
        }

        while (1)
            sleep(1);
        break;

    default:
        ERROR("Unhandled state %d\n", state);
        return;
    };
}

 

顺序跳转到每个state执行,来看procd_inittab_run("sysinit"),其他以后再分析

static void runrc(struct init_action *a)
{
    if (!a->argv[1] || !a->argv[2]) {
        ERROR("valid format is rcS <S|K> <param>\n");
        return;
    }
    rcS(a->argv[1], a->argv[2], rcdone);
}

第一个参数为S或者K,第二个参数为boot

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

    return _rc(&q, "/etc/rc.d", pattern, "*", param);
}

然后依次执行/etc/rc.d中S开头的脚本,参数为boot

 

 

 

 

 

posted @ 2017-08-30 18:53  5分  阅读(3055)  评论(0)    收藏  举报