init进程应该是一个用户空间的进程, 但是这里却是通过kernel_thread的方式创建的, 哪岂不是式一个永远运行在内核态的内核线程么, 它是怎么演变为真正意义上用户空间的init进程的?
1号kernel_init进程完成linux的各项配置(包括启动AP)后,就会在/sbin,/etc,/bin寻找init程序来运行。该init程序会替换kernel_init进程(注意:并不是创建一个新的进程来运行init程序,而是一次变身,使用sys_execve函数改变核心进程的正文段,将核心进程kernel_init转换成用户进程init),此时处于内核态的1号kernel_init进程将会转换为用户空间内的1号进程init。户进程init将根据/etc/inittab中提供的信息完成应用程序的初始化调用。然后init进程会执行/bin/sh产生shell界面提供给用户来与Linux系统进行交互。
调用init_post()创建用户模式1号进程。
关于init其他的信息我们这次先不研究,因为我们这篇旨在探究0号进程的详细过程,
创建kthreadd
在rest_init函数中,内核将通过下面的代码产生第一个kthreadd(pid=2)
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthread的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程
0号进程演变为idle
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
init_idle_bootup_task(current);
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
因此我们回过头来看pid=0的进程,在创建了init进程后,pid=0的进程调用 cpu_idle()演变成了idle进程。
0号进程首先执行init_idle_bootup_task, 让init_task进程隶属到idle调度类中。即选择idle的调度相关函数。
这个函数被定义在kernel/sched/core.c中,如下
void init_idle_bootup_task(struct task_struct *idle)
{
idle->sched_class = &idle_sched_class;
}
接着通过schedule_preempt_disabled来执行调用schedule()函数切换当前进程,在调用该函数之前,Linux系统中只有两个进程,即0号进程init_task和1号进程kernel_init,其中kernel_init进程也是刚刚被创建的。调用该函数后,1号进程kernel_init将会运行
这个函数被定义在kernel/sched/core.c中,如下
/**
* schedule_preempt_disabled - called with preemption disabled
*
* Returns with preemption disabled. Note: preempt_count must be 1
*/
void __sched schedule_preempt_disabled(void)
{
sched_preempt_enable_no_resched();
schedule();
preempt_disable();
}
最后cpu_startup_entry调用cpu_idle_loop(),0号线程进入idle函数的循环,在该循环中会周期性地检查
cpu_startup_entry定义在kernel/sched/idle.c
void cpu_startup_entry(enum cpuhp_state state)
{
/*
* This #ifdef needs to die, but it's too late in the cycle to
* make this generic (arm and sh have never invoked the canary
* init for the non boot cpus!). Will be fixed in 3.11
*/
#ifdef CONFIG_X86
/*
* If we're the non-boot CPU, nothing set the stack canary up
* for us. The boot CPU already has it initialized but no harm
* in doing it again. This is a good place for updating it, as
* we wont ever return from this function (so the invalid
* canaries already on the stack wont ever trigger).
*/
boot_init_stack_canary();
#endif
arch_cpu_idle_prepare();
cpu_idle_loop();
}
其中cpu_idle_loop就是idle进程的事件循环,定义在kernel/sched/idle.c
整个过程简单的说就是,原始进程(pid=0)创建init进程(pid=1),然后演化成idle进程(pid=0)。init进程为每个从处理器(运行队列)创建出一个idle进程(pid=0),然后演化成/sbin/init。
idle的运行与调度
idle的workload–cpu_idle_loop
从上面的分析我们知道,idle在系统没有其他就绪的进程可执行的时候才会被调度。不管是主处理器,还是从处理器,最后都是执行的cpu_idle_loop()函数
其中cpu_idle_loop就是idle进程的事件循环,定义在kernel/sched/idle.c,早期的版本中提供的是cpu_idle,但是这个函数是完全依赖于体系结构的,不利用架构的分层,因此在新的内核中更新为更加通用的cpu_idle_loop,由他来调用体系结构相关的代码
所以我们来看看cpu_idle_loop做了什么事情。
因为idle进程中并不执行什么有意义的任务,所以通常考虑的是两点
- 节能
- 低退出延迟。
其代码如下
/*
* Generic idle loop implementation
*
* Called with polling cleared.
*/
static void cpu_idle_loop(void)
{
while (1) {
/*
* If the arch has a polling bit, we maintain an invariant:
*
* Our polling bit is clear if we're not scheduled (i