1. 前言
前面我们已经了解了内核的初始化,还是比较复杂的,具体的三言两语也解释不清,只能好好分析源码;这节我们该往上层走了,这一节我们来研究init进程的初始化和启动,其实事实上前面的内核初始化已经进入init初始化了。
2. 源码解析
注:本系列源码都是基于kernel4.9的
前面我们已经讲到rest_init函数很重要,我们init进程的初始化启动也在这里面。
下面我们依次进入以下文件夹或函数:
kernel4.9->init->main.c->start_kernel->rest_init:
在rest_init函数里面有这样一行代码:
kernel_thread(kernel_init, NULL, CLONE_FS);
这行代码就是创建init进程的关键,kernel_init
就是我们的init进程,kernel_thread就是用来创建进程的,这里返回值应该是1,代表了进程号。在这行代码以下有这样一行代码:
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
这行代码就是用来创建pid为2的kthreadd进程,用来做内核进程的处理。
而我们的init进程主要用来做用户态进程的处理的。
现在我们进入kernel_init
函数
它位于kernel4.9/init/
目录下,源码如下:
static int __ref kernel_init(void *unused)
{
int ret;
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_readonly();
system_state = SYSTEM_RUNNING;
numa_default_policy();
rcu_end_inkernel_boot();
#ifdef CONFIG_MTPROF
// 到这里代表内核初始化完成,同时init也初始化完毕,下面就会进行运行
log_boot("Kernel_init_done");
#endif
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* 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) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
先不管下面的代码,我们先进入kernel_init_freeable();
函数看看,我们看到在前两节分析过的do_basic_setup();
代码的下面是这样的:
/* Open the /dev/console on the rootfs, this should never fail */
// 这里主要用于打开命令控制行程序
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
pr_err("Warning: unable to open an initial console.\n");
(void) sys_dup(0);
(void) sys_dup(0);
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
// 准备命名空间,其实就是挂载初始化root文件系统,加载运存等等
prepare_namespace();
}
下面是prepare_namespace();
的源码,有兴趣可以看看根文件系统挂载流程:
/*
* Prepare the namespace - decide what/where to mount, load ramdisks, etc.
*/
void __init prepare_namespace(void)
{
int is_floppy;
if (root_delay) {
printk(KERN_INFO "Waiting %d sec before mounting root device...\n",
root_delay);
ssleep(root_delay);
}
/*
* wait for the known devices to complete their probing
*
* Note: this is a potential source of long boot delays.
* For example, it is not atypical to wait 5 seconds here
* for the touchpad of a laptop to initialize.
*/
wait_for_device_probe();
md_run_setup();
dm_run_setup();
if (saved_root_name[0]) {
root_device_name = saved_root_name;
if (!strncmp(root_device_name, "mtd", 3) ||
!strncmp(root_device_name, "ubi", 3)) {
mount_block_root(root_device_name, root_mountflags);
goto out;
}
ROOT_DEV = name_to_dev_t(root_device_name);
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}
if (initrd_load())
goto out;
/* wait for any asynchronous scanning to complete */
if ((ROOT_DEV == 0) && root_wait) {
printk(KERN_INFO "Waiting for root device %s...\n",
saved_root_name);
while (driver_probe_done() != 0 ||
(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
msleep(100);
async_synchronize_full();
}
is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
if (is_floppy && rd_doload && rd_load_disk(0))
ROOT_DEV = Root_RAM0;
mount_root();
out:
devtmpfs_mount("dev");
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");
}
当以上工作做完,init初始化就已经差不多了,下面就是运行init进程:
在kernel_init
函数里面,有以下部分代码:
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* 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) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
// 如果在前面的启动init进程程序中未能成功启动init,
//就依次在以下路径查找并运行init
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh")) // 试图建立/bin/sh 来代替init程序
return 0;
// 如果没有找到任何一个init程序就执行panic,最后可能会重启计算机
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
这些代码就是用来启动init进程的,至此init进程已经成功启动,init启动后就可以使用用户态的命令程序了。
3. 总结
以上我们描述了ini进程的启动,init的源码我们在以后的总结中会继续研究,其实吧,上面的叙述不够详细;应当在源码了解更多的细节,本系列主要用途是带领大家研究源码,致力于大概了解安卓系统启动过程中要做哪些事情。