procd系列一:内核启动流程

内核的初始化过程由start_kernel函数开始,至第一个用户进程init结束,调用了一系列的初始化函数对所有的内核组件进行初始化。其中,start_kernel、rest_init、kernel_init、init_post等4个函数构成了整个初始化过程的主线。

start_kernel函数

从start_kernel函数开始,内核即进入了C语言部分,它完成了内核的大部分初始化工作。实际上,可以将start_kernel函数看做内核的main函数。

asmlinkage void __init start_kernel(void)

rest_init函数

在start_kernel函数的最后调用了rest_init函数进行后续的初始化。

(1)rest_init中调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd

(2)调用schedule函数开启了内核的调度系统,从此linux系统开始转起来了。

rest_init最终调用cpu_idle函数结束了整个内核的启动。

进程0 idle进程, 叫空闲进程,也就是死循环。
进程1 kernel_init 函数就是进程1,这个进程被称为init进程。
进程2 kthreadd 函数就是进程2,这个进程是linux内核的守护进程。

kernel_init函数

kernel_init函数将完成设备驱动程序的初始化,并调用init_post函数启动用户空间的init进程。

init_post函数

到init_post函数为止,内核的初始化已经进入尾声,第一个用户空间进程init将姗姗来迟

如果内核命令行中给出了到init进程的直接路径(或者别的可替代的程序),这里就试图执行init。

init 进程

init:开始是内核态,后来转变为用户态】

init进程完成了从内核态向用户态的转变
init进程在内核态下面时,通过一个函数kernel_execve来执行一个用户空间编译连接的应用程序就跳跃到用户态了。

在init/main.c中最后会通过kernel_execve()来调用用户空间的init进程(如/sbin/init, /etc/init, /bin/init等)

/sbin/init从哪里来呢,答案就是 procd,看一下 /sbin/init 的源码:

int
main(int argc, char **argv)
{
pid_t pid;
 
ulog_open(ULOG_KMSG, LOG_DAEMON, "init");
// 挂载了 /proc 、/sys 、/dev 、/tmp …等目录,并设置 PATH 环境变量
early();
// 从cmd line中获取debug log等级
cmdline();
// 开启看门狗
watchdog_init(1);
 
pid = fork();
if (!pid) {
///etc/modules-boot.d/ 目录下保存了需要开机自动加载的kernel模块信息。
char *kmod[] = { "/sbin/kmodloader", "/etc/modules-boot.d/", NULL };
 
if (debug < 3)
patch_stdio("/dev/null");
//加载kernel模块
execvp(kmod[0], kmod);
ERROR("Failed to start kmodloader: %m\n");
exit(EXIT_FAILURE);
}
if (pid <= 0) {
ERROR("Failed to start kmodloader instance: %m\n");
} else {
const struct timespec req = {0, 10 * 1000 * 1000};
int i;
 
for (i = 0; i < 1200; i++) {
if (waitpid(pid, NULL, WNOHANG) > 0)
break;
nanosleep(&req, NULL);
watchdog_ping();
}
}
uloop_init();
// 执行/sbin/procd -h /etc/hotplug-preinit.json, 然后是 /bin/sh /etc/preinit ,并在/etc/preinit 执行结束的回调函数中调用 /sbin/procd
preinit();
uloop_run();
 
return 0;
}

1.early() 主要是挂载了 /proc 、/sys 、/dev 、/tmp …等目录,并设置 PATH 环境变量

early()
    early_mounts();
    early_env();

2.cmdline() 是设置了 debug 全局变量

cmdline()
    debug = (int) r;

3.watchdog_init(1) 开启看门狗

4./sbin/kmodloader /etc/modules-boot.d/ 加载模块

5.preinit() 启动/sbin/procd进程 ,执行/etc/preinit 脚本

大概看一下preinit的函数内部实现:

void
preinit(void)
{
char *init[] = { "/bin/sh", "/etc/preinit", NULL };
char *plug[] = { "/sbin/procd", "-h", "/etc/hotplug-preinit.json", NULL };
int fd;
 
LOG("- preinit -\n");
//执行 /sbin/procd -h /etc/hotplug-preinit.json
plugd_proc.cb = plugd_proc_cb; // 回调函数中将pid设置为0
plugd_proc.pid = fork();
if (!plugd_proc.pid) {
execvp(plug[0], plug);
ERROR("Failed to start plugd: %m\n");
exit(EXIT_FAILURE);
}
uloop_process_add(&plugd_proc);
// 设置环境变量
setenv("PREINIT", "1", 1);
 
fd = creat("/tmp/.preinit", 0600);
 
//执行 /etc/preinit
preinit_proc.cb = spawn_procd; //回调函数中调用 /sbin/procd
preinit_proc.pid = fork();
if (!preinit_proc.pid) {
execvp(init[0], init);
ERROR("Failed to start preinit: %m\n");
exit(EXIT_FAILURE);
}
uloop_process_add(&preinit_proc);
}

用流程图梳理一下上面 /sbin/init 的流程如下:

6.执行完 /etc/preinit 脚本 之后,启动 procd

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

跟踪代码:

main
  procd_state_next
    state_enter
      

这里主要来看 state_enter

static void state_enter(void)
{
    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_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");
        procd_inittab_run("respawnlate");
        procd_inittab_run("askconsolelate");
        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);
#ifndef DISABLE_INIT
        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);
#else
        exit(0);
#endif
        break;
 
    default:
        ERROR("Unhandled state %d\n", state);
        return;
    };
}

case STATE_EARLY
这里主要是 调用 hotplug procd_coldplug ,自动创建设备节点,生成/dev/xxx

case STATE_UBUS
初始化 ubus 连接

case STATE_INIT
读取 /etc/inittab

::sysinit:/etc/init.d/rcS S boot
::shutdown:/etc/init.d/rcS K shutdown
tts/0::askfirst:/usr/libexec/login.sh
ttyS0::askfirst:/usr/libexec/login.sh
tty1::askfirst:/usr/libexec/login.sh
void procd_inittab(void)
  if (add_action(a, tags[TAG_ACTION]))
      for (i = 0; i < ARRAY_SIZE(handlers); i++)   // 见 handlers 定义
        if (!strcmp(handlers[i].name, name)) {
        a->handler = &handlers[i];
        list_add_tail(&a->list, &actions);
        return 0;
       }
// handlers 定义     
static struct init_handler handlers[] = {
{
   .name = "sysinit",
   .cb = runrc,
}, {
   .name = "shutdown",
   .cb = runrc,
}, {
   .name = "askfirst",
   .cb = askfirst,
   .multi = 1,
}
   //...
}
static void runrc(struct init_action *a)
{
   if (!a->argv[1] || !a->argv[2]) {
    ERROR("valid format is rcS <S|K> <param>\n");
    return;
   }
 
   /* proceed even if no init or shutdown scripts run */
   if (rcS(a->argv[1], a->argv[2], rcdone))
    rcdone(NULL);
}
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/ 下的启动脚本,逐个执行。 /etc/rc.d/ 目录文件实例:

root@172:/etc/rc.d# ls |sort
K10gpio_switch
K10mysqld
K10openvpn
K50dropbear
K50lighttpd
K81redis
K85odhcpd
K89log
K90network
K90sysfixtime
K98boot
K99umount
S00sysfixtime
S10boot
S10system
S11sysctl
S12log
S19dnsmasq
S19firewall
S20network
S35odhcpd
S36asterisk
S50cron
S50dropbear
S50lighttpd
S50odbc
S50php5-fastcgi
S50php5-fpm
S50yate
S90openvpn
S94gpio_switch
S95done
S95mysqld
S96led
S98redis
S98sysntpd
S99urandom_seed

case STATE_RUNNING:
初始化完成

case STATE_SHUTDOWN:
关闭

case STATE_HALT:
关闭

。。。。

后续为ubusd初始化及client和server通信流程。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值