【linux kernel】linux内核的init线程

linux内核的init线程

(注)

  • 本文代码出自linux内核版本:4.1.15(小生的文章若无特别说明,都将基于此版本)

  • 本文代码的出处文件以(/)开头,代表linux内核的安装目录。(小生的文章若无特别说明,都有这样约定)


一、概述

rest_init()函数中会调用kthread_init()函数创建init线程,如下代码:

kernel_thread(kernel_init, NULL, CLONE_FS);

在linux内核中,由kernel_init代表的是1号init线程,当linux内核启动完成后,将启动用户空间的init程序,使linux内核从内核态向用户态转变。因此,本文将分析init线程的线程函数和调用机制。

二、init线程的入口函数kernel_init()

init线程的入口函数是kernel_init,如下所示(/init/main.c):

static int __ref kernel_init(void *unused)
{
	int ret;

	kernel_init_freeable();

	async_synchronize_full();
	free_initmem();
	mark_rodata_ro();
	system_state = SYSTEM_RUNNING;
	numa_default_policy();

	flush_delayed_fput();

	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()分析

kernel_init()init线程函数中将调用kernel_init_freeable()函数:

static noinline void __init kernel_init_freeable(void)
{
    /* 等待kthreadd线程全部启动完毕 */
	wait_for_completion(&kthreadd_done);

    /* 代码运行在此处,scheduler调度器已经完全启动了,现在开始进行阻塞分配 */
	gfp_allowed_mask = __GFP_BITS_MASK;

    /* 设置init线程可以在任何node上分配pages */
	set_mems_allowed(node_states[N_MEMORY]);

    /* 设置init线程可以在任何cpu上运行 */
	set_cpus_allowed_ptr(current, cpu_all_mask);
	
    /* 获取当前线程的pid */
	cad_pid = task_pid(current);
	
    //在smp下,执行启动其他cpu的准备操作
	smp_prepare_cpus(setup_max_cpus);
	
    //执行smp之前的初始化调用
	do_pre_smp_initcalls();
	lockup_detector_init();
	
    //SMP初始化。由引导处理器调用以激活剩余部分
	smp_init();
    //SMP调度初始化。
	sched_init_smp();
	
    //执行一些基础的启动操作。
	do_basic_setup();

    //在rootfs中打开/dev/console。这个地方不允许失败 
	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);

    /* 检查是否设置了早期用户空间的init程序,如果设置了,就让其执行。ramdisk_execute_command可以由“rdinit=”指定 */
	if (!ramdisk_execute_command)
		ramdisk_execute_command = "/init";

	if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
		ramdisk_execute_command = NULL;
		prepare_namespace();
	}

	integrity_load_keys();
	load_default_modules();
}

上述代码31行:调用do_basic_setup()执行一些基础的启动操作。该函数在linux内核启动过程中,比较重要,主要涉及了linux内核重要组件的初始化,例如:驱动模型的初始化等。

第37-38行,使用sys_dup()系统调用函数将标准输入的文件描述符复制2次,一个作为标准输出,一个作为标准错误。通过该操作,将标准输入、输出、错误设置成/dev/console。在开发中,console参数可以通过u-boot的bootargs环境变量设置,从而指定console参数值!!!

第41-42行代码:检查是否设置了早期用户空间的init程序,如果设置了,就让其执行 。ramdisk_execute_command可以由“rdinit=”指定 。如果ramdisk_execute_command没有被执行,则linux将其设置为/init

第44-47行代码:通过sys_access系统调用检查进程是否可以访问该文件(由ramdisk_execute_command指定)的路径名。调用prepare_namespace();来挂载根文件系统。根文件系统也可由u-boot的命令行下环境变量参数指定。例如:root=/dev/mmcblk1p2 rootwait rw”就表示根文件系统在/dev/mmcblk1p2 中,也就是 EMMC 的分区 2 中。

第49-50行代码:代码运行到此处,linux内核已经完成了初始启动流程,实际上已经启动并运行了。接下来需要释放initmem段占用的内存空间,并进入用户空间模式。这部分由kernel_init_freeable()函数的后续代码完成。至此,rootfs根文件系统是可用的,故调用integrity_load_keys()尝试加载公钥和调用load_default_modules()加载默认模块 。


下文将回到kernel_init()函数,继续后续的分析。

四、kernel_init()余下部分的分析

kernel_init_freeable()函数执行完成后,linux内核的一些基础组件:例如设备驱动模型等、根文件系统已经准备完毕,接下来就将启动用户空间程序了。从而使linux从内核态向用户态转变。

(4-1)一些基础的操作

如下代码片段:

	async_synchronize_full();
	free_initmem();
	//(该函数似乎与调试相关,小生暂也不知道有啥作用。haha)
	mark_rodata_ro();   
	system_state = SYSTEM_RUNNING;
	numa_default_policy();

	flush_delayed_fput();

(1)async_synchronize_full()函数:用于同步所有的异步调用。

(2)free_initmem()函数:用于释放初始化时的内存空间。

(3)使用system_state = SYSTEM_RUNNING;设置系统状态为SYSTEM_RUNNING

(4)numa_default_policy()函数(/mm/mempolicy.c):将当前线程的NUMA存储管理策略重置为默认值。

(5)flush_delayed_fput()函数:如果内核线程确实需要完成它已经完成的最后一个fput(),才调用这个函数。在kernel_init()函数中现在唯一的操作则是引导。因此需要确保在initramfs上对二进制文件的写入操作,不会导致打开的struct file等待__fput()。如果没有这个flush_delayed_fput()execve()将无法工作。

(4-2)启动ramdisk_execute_command指定的程序

(注:以下代码片段出自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);
	}

ramdisk_execute_command是一个全局的 char 指针变量,值为“/init”,也就是根目录下的 init 程序。ramdisk_execute_command 可以通过 uboot 在环境变量中指定,在 u-boot的bootargs中使用“rdinit=xxx”即可指定,其中xxx 为具体的 init 程序名字。

如果ramdisk_execute_command制定了(即不为NULL),则调用run_init_process()函数启动它,启动成功则退出。

(4-3)启动execute_command指定的程序

(注:以下代码片段出自kernel_init()函数)

	if (execute_command) {
		ret = run_init_process(execute_command);
		if (!ret)
			return 0;
		panic("Requested init %s failed (error %d).",
		      execute_command, ret);
	}

同样的,execute_command也是一个全局的 char 指针变量,值可以通过u-boot 传递,如下代码片段(/init/main.c):

static int __init init_setup(char *str)
{
	unsigned int i;

	execute_command = str;
	for (i = 1; i < MAX_INIT_ARGS; i++)
		argv_init[i] = NULL;
	return 1;
}
__setup("init=", init_setup);

由以上代码可见,在u-boot的启动参数bootargs中使用“init=xxxx”就可以指定execute_command的值,比如“init=/linuxrc”表示根文件系统中的linuxrc 就是要执行的用户空间的 init 程序。

同样的,如果execute_command指定了,则调用run_init_process()启动程序,启动成功则退出。

(4-4)启动linux内核默认的用户空间程序

当前面两种方式都没有启动用户空间的init程序,那么就执行linux内核默认的程序了。

(注:以下代码片段出自kernel_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"))
		return 0;

可见,在linux内核中默认指定了4个用户空间的程序:/sbin/init/etc/init/bin/init/bin/sh。调用try_to_run_init_process()函数启动它们。如果其中的任意一个程序被启动了,则退出。

(4-5)linux内核“罢工”

​ 当通过以上三种方式都还没有启动用户空间的init进程。linux内核则调用panic给出信息

panic("No working init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/init.txt for guidance.");

​ 至此,linux内核也没有启动用户空间的init程序。系统停止/罢工啦。。。。。

​ 工作中,当看到这条消息,说明代码运行到这个位置啦。既激动,又懊恼!!!


小生由于知识和精力有限,如果文章存在有不妥的地方,还请看官们多多批评。也可与我交流(e-mail:iriczhao@163.com),O(∩_∩)O谢谢啦。


扫一扫关注微信公众号,获取更多精彩内容>>>>
请添加图片描述

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值