内核的初始化过程由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通信流程。