OpenWRT系统启动过程

OpenWrt系统启动脚本调用流程如下

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

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

/etc/preinit脚本如下: 

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

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

export PATH=/bin:/sbin:/usr/bin:/usr/sbin

pi_ifname=
pi_ip=192.168.1.1
pi_broadcast=192.168.1.255
pi_netmask=255.255.255.0

fs_failsafe_ifname=
fs_failsafe_ip=192.168.2.20
fs_failsafe_broadcast=192.168.2.255
fs_failsafe_netmask=255.255.255.0

fs_failsafe_wait_timeout=2

pi_suppress_stderr="y"
pi_init_suppress_stderr="y"
pi_init_path="/bin:/sbin:/usr/bin:/usr/sbin"
pi_init_cmd="/sbin/init"

. /lib/functions.sh
. /lib/functions/preinit.sh
. /lib/functions/system.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

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

PREINIT变量当前没有值,所以执行init;使用exec,表明init替换进程上下文,pid保持不变,应该为1;

下面来看init.c函数:

int  main(int argc, char **argv)  
{  
    pid_t pid;  
  
    sigaction(SIGTERM, &sa_shutdown, NULL);  
    sigaction(SIGUSR1, &sa_shutdown, NULL);  
    sigaction(SIGUSR2, &sa_shutdown, NULL);  
  
    early();//-------->early.c  
    cmdline();  
    watchdog_init(1); //------->../watchdog.c  
  
    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);  
    }  
    if (pid <= 0)  
        ERROR("Failed to start kmodloader instance\n");  
    uloop_init();  
    preinit();    //-------------->watchdog.c  
    uloop_run();  
  
    return 0;  
} 

首先做一些文件系统挂载,环境变量设置,watchdog初试化等操作,然后fork一个子进程执行kmodloader,下面来看kmodloader.c函数: 

    if (scan_loaded_modules())
        return -1;

    if (scan_module_folders())
        return -1;
        
    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();

    }

/proc/modules里面遍历所有已经安装的模块,包括模块名称和大小等信息,插入到avl树中,同时将节点状态设置为LOADED
;将/lib/modules/x.x.x路径保存到二维指针module_folders,遍历/lib/modules/x.x.x/*.ko,查看是否在avl树中,如果不在,添加到avl树中,同时将节点状态设置为SCANNED;遍历/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,procd内容较多,此处暂不讨论了,可参考第一个链接的末尾部分;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值