内核启动阶段kernel_init(init)进程分析
在kernel进入c语言阶段后,会开始执行start_kernel函数,它负责进行kernel正式运行前各个功能的初始化:打印了一些信息、内核工作需要的模块的初始化被依次调用(譬如内存管理、调度系统、异常处理···),最后末尾调用了一个rest_init函数启动了三个进程(idle、kernel_init、kthreadd),来开启操作系统的正式运行。如下图所示:
- idle是操作系统的空闲进程,当cpu空闲的时候会去运行它
- kernel_init最开始只是一个函数,这个函数作为进程被启动,但是之后它将读取根文件系统下的init程序,这个操作将完成从内核态到用户态的转变,而这个init进程是所有用户态进程的父进程,它生了大量的子进程,所以init进程将永远存在,其PID是1
- kthreadd是内核守护进程,其PID为2
接下来就开始分析kernel_init进程(函数)
1.打开控制台设备
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup(0);
(void) sys_dup(0);
- 首先打开了/dev/console文件,在Linux中一切皆是文件,所以/dev/console文件代表的是控制台设备(其实就是串口)
- 在Linux中,进程打开文件后会获得该文件的文件描述符。这里kernel_init进程就得到了控制台的文件描述符,然后又使用sys_dup复制了2次….一共得到了3个文件描述符。这三个文件描述符分别是0、1、2.这三个文件描述符就是所谓的:标准输入、标准输出、标准错误
- 之后kernel_init所有的子进程都将继承这3个文件描述符,也就是说后面所有的进程一生出来,就默认拥有标准输入、标准输出、标准错误的文件描述符
- 其实这一步的根本目的就是传承这3个文件描述符、
2.挂载根文件系统
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
- prepare_namespace这个函数负责挂载根文件系统
- 如果内核挂载根文件系统成功,则会打印出:VFS: Mounted root (xxxx filesystem) on device xxxx.
如果挂载根文件系统失败,则会打印:No filesystem could mount root, tried: xxxx - 一般来说,挂载根文件系统失败的原因有:1.U-boot的环境变量bootargs设置不对,导致传递了错误的参数给kernel;2.还有一定几率是烧录根文件系统的时候出错,这个纯粹是运气问题;3.根文件系统本身制作有问题
3.启动init进程
init_post();
- 调用了init_post来启动init进程,这个函数主要内容为
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
- 这个函数会去根文件系统中寻找init程序,具体路径由U-boot的环境变量bootargs提供,一旦init程序被找到,就会启动init进程(该可执行程序的文件名不一定叫init),然后操作系统正式运行,至此一去不复返
- 如果U-boot的环境变量bootargs没有传过来路径,或者路径中找不到,或者执行出错,那么kernel还留了一手以防万一。init_post函数尾部有四个run_init_process函数,它们会去4个地方看看有没有init程序,如果以上都不成功,就启动失败了
- 其实这个init程序一般来说是根文件目录下的linuxrc,linuxrc是一个符号链接,指向了busybox。说到底init进程就是busybox