前言


Linux 下有 3 个特殊的进程,idle 进程(PID = 0), init 进程(PID = 1)和 kthreadd(PID = 2)

* idle 进程由系统自动创建, 运行在内核态

idle 进程其 pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过 fork 或者 kernel_thread 产生的进程。完成加载系统后,演变为进程调度、交换

* init 进程由 idle 通过 kernel_thread 创建,在内核空间完成初始化后, 加载 init 程序, 并最终用户空间

由 0 进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程 Linux 中的所有进程都是有 init 进程创建并运行的。首先 Linux 内核启动,然后在用户空间中启动 init 进程,再启动其他系统进程。在系统启动完成完成后,init 将变为守护进程监视系统其他进程。

* kthreadd 进程由 idle 通过 kernel_thread 创建,并始终运行在内核空间, 负责所有内核线程的调度和管理

它的任务就是管理和调度其他内核线程 kernel_thread, 会循环执行一个 kthread 的函数,该函数的作用就是运行 kthread_create_list 全局链表中维护的 kthread, 当我们调用 kernel_thread 创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以 kthreadd 为父进程

我们下面就详解分析 0 号进程的前世(init_task)今生(idle)

idle 的创建


在 smp 系统中,每个处理器单元有独立的一个运行队列,而每个运行队列上又有一个 idle 进程,即有多少处理器单元,就有多少 idle 进程。

idle 进程其 pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过 fork()产生的进程。在 smp 系统中,每个处理器单元有独立的一个运行队列,而每个运行队列上又有一个 idle 进程,即有多少处理器单元,就有多少 idle 进程。系统的空闲时间,其实就是指 idle 进程的”运行时间”。既然是 idle 是进程,那我们来看看 idle 是如何被创建,又具体做了哪些事情?

我们知道系统是从 BIOS 加电自检,载入 MBR 中的引导程序(LILO/GRUB),再加载 linux 内核开始运行的,一直到指定 shell 开始运行告一段落,这时用户开始操作 Linux。

0 号进程上下文信息–init_task 描述符


init_task是内核中所有进程、线程的 task_struct 雏形,在内核初始化过程中,通过静态定义构造出了一个 task_struct 接口,取名为 init_task,然后在内核初始化的后期,通过 rest_init()函数新建了内核 init 线程,kthreadd 内核线程

  • 内核 init 线程,最终执行/sbin/init 进程,变为所有用户态程序的根进程(pstree 命令显示),即用户空间的 init 进程

    开始的 init 是有 kthread_thread 创建的内核线程, 他在完成初始化工作后, 转向用户空间, 并且生成所有用户进程的祖先

  • 内核 kthreadd 内核线程,变为所有内核态其他守护线程的父线程。

    它的任务就是管理和调度其他内核线程 kernel_thread, 会循环执行一个 kthread 的函数,该函数的作用就是运行 kthread_create_list 全局链表中维护的 kthread, 当我们调用 kernel_thread 创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以 kthreadd 为父进程

ps-aux
ps-aux

所以init_task 决定了系统所有进程、线程的基因, 它完成初始化后, 最终演变为 0 号进程 idle, 并且运行在内核态

内核在初始化过程中,当创建完 init 和 kthreadd 内核线程后,内核会发生调度执行,此时内核将使用该 init_task 作为其 task_struct 结构体描述符,当系统无事可做时,会调度其执行, 此时该内核会变为 idle 进程,让出 CPU,自己进入睡眠,不停的循环,查看 init_task 结构体,其 comm 字段为 swapper,作为 idle 进程的描述符。

idle 的运行时机

idle 进程优先级为 MAX_PRIO-20。早先版本中,idle 是参与调度的,所以将其优先级设低点,当没有其他进程可以运行时,才会调度执行 idle。而目前的版本中 idle 并不在运行队列中参与调度,而是在运行队列结构中含 idle 指针,指向 idle 进程,在调度器发现运行队列为空的时候运行,调入运行

简言之, 内核中 init_task 变量就是是进程 0 使用的进程描述符,也是 Linux 系统中第一个进程描述符,init_task 并不是系统通过 kernel_thread 的方式(当然更不可能是 fork)创建的, 而是由内核黑客静态创建的.

该进程的描述符在init/init_task中定义,代码片段如下

1
2
3
/* Initial task structure */
struct task_struct init_task = INIT_TASK(init_task);
EXPORT_SYMBOL(init_task);123

init_task 描述符使用宏 INIT_TASK 对 init_task 的进程描述符进行初始化,宏 INIT_TASK 在include/linux/init_task.h文件中

init_task 是 Linux 内核中的第一个线程,它贯穿于整个 Linux 系统的初始化过程中,该进程也是 Linux 系统中唯一一个没有用 kernel_thread()函数创建的内核态进程(内核线程)

在 init_task 进程执行后期,它会调用 kernel_thread()函数创建第一个核心进程 kernel_init,同时 init_task 进程继续对 Linux 系统初始化。在完成初始化后,init_task 会退化为 cpu_idle 进程,当 Core 0 的就绪队列中没有其它进程时,该进程将会获得 CPU 运行。新创建的 1 号进程 kernel_init 将会逐个启动次 CPU,并最终创建用户进程!

备注:core0 上的 idle 进程由 init_task 进程退化而来,而 AP 的 idle 进程则是 BSP 在后面调用 fork()函数逐个创建的

进程堆栈 init_thread_union


init_task 进程使用 init_thread_union 数据结构描述的内存区域作为该进程的堆栈空间,并且和自身的 thread_info 参数公用这一内存空间空间,

请参见 http://lxr.free-electrons.com/source/include/linux/init_task.h?v=4.5#L193

    .stack          = &init_thread_info,
1

而 init_thread_info 则是一段体系结构相关的定义,被定义在[/arch/对应体系/include/asm/thread_info.h]中,但是他们大多数为如下定义

1
2
#define init_thread_info        (init_thread_union.thread_info)
#define init_stack              (init_thread_union.stack)12

其中 init_thread_union 被定义在init/init_task.c, 紧跟着前面init_task的定义

1
2
3
4
5
6
/*
 * Initial thread structure. Alignment of this is handled by a special
 * linker map entry.
 */
union thread_union init_thread_union __init_task_data =
        { INIT_THREAD_INFO(init_task) };123456

我们可以发现 init_task 是用 INIT_THREAD_INFO 宏进行初始化的, 这个才是我们真正体系结构相关的部分, 他与 init_thread_info 定义在一起,被定义在/arch/对应体系/include/asm/thread_info.h中,以下为x86 架构的定义

参见

http://lxr.free-electrons.com/source/arch/x86/include/asm/thread_info.h?v=4.5#L65

1
2
3
4
5
6
7
#define INIT_THREAD_INFO(tsk)                   \
{                                               \
    .task           = &tsk,                 \
    .flags          = 0,                    \
    .cpu            = 0,                    \
    .addr_limit     = KERNEL_DS,            \
}1234567

其他体系结构的定义请参见

/arch/对应体系/include/asm/thread_info.h

架构 定义
x86 arch/x86/include/asm/thread_info.h
arm64 arch/arm64/include/asm/thread_info.h

init_thread_info 定义中的__init_task_data 表明该内核栈所在的区域位于内核映像的 init data 区,我们可以通过编译完内核后所产生的 System.map 来看到该变量及其对应的逻辑地址

cat System.map-3.1.6 | grep init_thread_union1

init_thread_union
init_thread_union

进程内存空间


init_task 的虚拟地址空间,也采用同样的方法被定义

由于 init_task 是一个运行在内核空间的内核线程, 因此其虚地址段 mm 为 NULL, 但是必要时他还是需要使用虚拟地址的,因此 avtive_mm 被设置为 init_mm

参见

http://lxr.free-electrons.com/source/include/linux/init_task.h?v=4.5#L202

1
2
.mm             = NULL,                                         \
  .active_mm      = &init_mm,                                     \12

其中 init_mm 被定义为 init-mm.c 中,参见 http://lxr.free-electrons.com/source/mm/init-mm.c?v=4.5#L16

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct mm_struct init_mm = {
    .mm_rb          = RB_ROOT,
    .pgd            = swapper_pg_dir,
    .mm_users       = ATOMIC_INIT(2),
    .mm_count       = ATOMIC_INIT(1),
    .mmap_sem       = __RWSEM_INITIALIZER(init_mm.mmap_sem),
    .page_table_lock =  __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),
    .mmlist         = LIST_HEAD_INIT(init_mm.mmlist),
    INIT_MM_CONTEXT(init_mm)
};12345678910

0 号进程的演化


rest_init 创建 init 进程(PID =1)和 kthread 进程(PID=2)


Linux 在无进程概念的情况下将一直从初始化部分的代码执行到 start_kernel,然后再到其最后一个函数调用 rest_init

大致是在 vmlinux 的入口 startup_32(head.S)中为 pid 号为 0 的原始进程设置了执行环境,然后原是进程开始执行 start_kernel()完成 Linux 内核的初始化工作。包括初始化页表,初始化中断向量表,初始化系统时间等。

从 rest_init 开始,Linux 开始产生进程,因为 init_task 是静态制造出来的,pid=0,它试图将从最早的汇编代码一直到 start_kernel 的执行都纳入到 init_task 进程上下文中。

这个函数其实是由 0 号进程执行的, 他就是在这个函数中, 创建了 init 进程和 kthreadd 进程

这部分代码如下:

参见

http://lxr.free-electrons.com/source/init/main.c?v=4.5#L386

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static noinline void __init_refok rest_init(void)
{
    int pid;

    rcu_scheduler_starting();
    smpboot_thread_init();

    /*
    * We need to spawn init first so that it obtains pid 1, however
    * the init task will end up wanting to create kthreads, which, if
    * we schedule it before we create kthreadd, will OOPS.
    */
    kernel_thread(kernel_init, NULL, CLONE_FS);
    numa_default_policy();
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    rcu_read_unlock();
    complete(&kthreadd_done);

    /*
    * 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);
}1234567891011121314151617181920212223242526272829
  1. 调用 kernel_thread()创建 1 号内核线程, 该线程随后转向用户空间, 演变为 init 进程
  2. 调用 kernel_thread()创建 kthreadd 内核线程。
  3. init_idle_bootup_task():当前 0 号进程 init_task 最终会退化成 idle 进程,所以这里调用 init_idle_bootup_task()函数,让 init_task 进程隶属到 idle 调度类中。即选择 idle 的调度相关函数。
  4. 调用 schedule()函数切换当前进程,在调用该函数之前,Linux 系统中只有两个进程,即 0 号进程 init_task 和 1 号进程 kernel_init,其中 kernel_init 进程也是刚刚被创建的。调用该函数后,1 号进程 kernel_init 将会运行!
  5. 调用 cpu_idle(),0 号线程进入 idle 函数的循环,在该循环中会周期性地检查。

创建 kernel_init


在 rest_init 函数中,内核将通过下面的代码产生第一个真正的进程(pid=1):

1
kernel_thread(kernel_init, NULL, CLONE_FS);1

这个进程就是着名的 pid 为 1 的 init 进程,它会继续完成剩下的初始化工作,然后 execve(/sbin/init), 成为系统中的其他所有进程的祖先。

但是这里我们发现一个问题, 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)

1
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);1

它的任务就是管理和调度其他内核线程 kernel_thread, 会循环执行一个 kthread 的函数,该函数的作用就是运行 kthread_create_list 全局链表中维护的 kthread, 当我们调用 kernel_thread 创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以 kthreadd 为父进程

0 号进程演变为 idle


1
2
3
4
5
6
7
8
    /*
    * 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);12345678

因此我们回过头来看 pid=0 的进程,在创建了 init 进程后,pid=0 的进程调用 cpu_idle()演变成了 idle 进程。

0 号进程首先执行 init_idle_bootup_task, 让 init_task 进程隶属到 idle 调度类中。即选择 idle 的调度相关函数。

这个函数被定义在kernel/sched/core.c中,如下

1
2
3
4
void init_idle_bootup_task(struct task_struct *idle)
{
    idle->sched_class = &idle_sched_class;
}1234

接着通过 schedule_preempt_disabled 来执行调用 schedule()函数切换当前进程,在调用该函数之前,Linux 系统中只有两个进程,即 0 号进程 init_task 和 1 号进程 kernel_init,其中 kernel_init 进程也是刚刚被创建的。调用该函数后,1 号进程 kernel_init 将会运行

这个函数被定义在kernel/sched/core.c中,如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/**
* 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();
}1234567891011

最后 cpu_startup_entry调用 cpu_idle_loop(),0 号线程进入 idle 函数的循环,在该循环中会周期性地检查

cpu_startup_entry 定义在kernel/sched/idle.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
 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();
}1234567891011121314151617181920

其中 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 进程中并不执行什么有意义的任务,所以通常考虑的是两点

  1. 节能
  2. 低退出延迟。

其代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*
 * 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.e. if
                 * rq->curr != rq->idle).  This means that, if rq->idle has
                 * the polling bit set, then setting need_resched is
                 * guaranteed to cause the cpu to reschedule.
                 */

                __current_set_polling();
                quiet_vmstat();
                tick_nohz_idle_enter();

                while (!need_resched()) {
                        check_pgt_cache();
                        rmb();

                        if (cpu_is_offline(smp_processor_id())) {
                                rcu_cpu_notify(NULL, CPU_DYING_IDLE,
                                               (void *)(long)smp_processor_id());
                                smp_mb(); /* all activity before dead. */
                                this_cpu_write(cpu_dead_idle, true);
                                arch_cpu_idle_dead();
                        }

                        local_irq_disable();
                        arch_cpu_idle_enter();

                        /*
                         * In poll mode we reenable interrupts and spin.
                         *
                         * Also if we detected in the wakeup from idle
                         * path that the tick broadcast device expired
                         * for us, we don't want to go deep idle as we
                         * know that the IPI is going to arrive right
                         * away
                         */
                        if (cpu_idle_force_poll || tick_check_broadcast_expired())
                                cpu_idle_poll();
                        else
                                cpuidle_idle_call();

                        arch_cpu_idle_exit();
                }

                /*
                 * Since we fell out of the loop above, we know
                 * TIF_NEED_RESCHED must be set, propagate it into
                 * PREEMPT_NEED_RESCHED.
                 *
                 * This is required because for polling idle loops we will
                 * not have had an IPI to fold the state for us.
                 */
                preempt_set_need_resched();
                tick_nohz_idle_exit();
                __current_clr_polling();

                /*
                 * We promise to call sched_ttwu_pending and reschedule
                 * if need_resched is set while polling is set.  That
                 * means that clearing polling needs to be visible
                 * before doing these things.
                 */
                smp_mb__after_atomic();

                sched_ttwu_pending();
                schedule_preempt_disabled();
        }
}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677

循环判断 need_resched 以降低退出延迟,用 idle()来节能。

默认的 idle 实现是 hlt 指令,hlt 指令使 CPU 处于暂停状态,等待硬件中断发生的时候恢复,从而达到节能的目的。即从处理器 C0 态变到 C1 态(见 ACPI 标准)。这也是早些年 windows 平台上各种”处理器降温”工具的主要手段。当然 idle 也可以是在别的 ACPI 或者 APM 模块中定义的,甚至是自定义的一个 idle(比如说 nop)。

1.idle 是一个进程,其 pid 为 0。

2.主处理器上的 idle 由原始进程(pid=0)演变而来。从处理器上的 idle 由 init 进程 fork 得到,但是它们的 pid 都为 0。

3.Idle 进程为最低优先级,且不参与调度,只是在运行队列为空的时候才被调度。

4.Idle 循环等待 need_resched 置位。默认使用 hlt 节能。

希望通过本文你能全面了解 linux 内核中 idle 知识。

idle 的调度和运行时机


我们知道, linux 进程的调度顺序是按照 rt 实时进程(rt 调度器), normal 普通进程(cfs 调度器),和 idel 的顺序来调度的

那么可以试想如果 rt 和 cfs 都没有可以运行的任务,那么 idle 才可以被调度,那么他是通过怎样的方式实现的呢?

由于我们还没有讲解调度器的知识, 所有我们只是简单讲解一下

在 normal 的调度类,cfs 公平调度器sched_fair.c中, 我们可以看到

1
2
static const struct sched_class fair_sched_class = {
.next = &idle_sched_class,12

也就是说,如果系统中没有普通进程,那么会选择下个调度类优先级的进程,即使用 idle_sched_class 调度类进行调度的进程

当系统空闲的时候,最后就是调用 idle 的 pick_next_task 函数,被定义在/kernel/sched/idle_task.c 中

参见

http://lxr.free-electrons.com/source/kernel/sched/idle_task.c?v=4.5#L27

1
2
3
4
5
6
static struct task_struct *pick_next_task_idle(struct rq *rq)
{
        schedstat_inc(rq, sched_goidle);
        calc_load_account_idle(rq);
        return rq->idle;    //可以看到就是返回rq中idle进程。
}123456

这 idle 进程在启动 start_kernel 函数的时候调用 init_idle 函数的时候,把当前进程(0 号进程)置为每个 rq 运行队列的的 idle 上。

rq->curr = rq->idle = idle;1

这里 idle 就是调用 start_kernel 函数的进程,就是 0 号进程。

idle 进程总结


系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。整个 linux 系统的所有进程也是一个树形结构。树根是系统自动构造的(或者说是由内核黑客手动创建的),即在内核态下执行的 0 号进程,它是所有进程的远古先祖。

在 smp 系统中,每个处理器单元有独立的一个运行队列,而每个运行队列上又有一个 idle 进程,即有多少处理器单元,就有多少 idle 进程。

  1. idle 进程其 pid=0,其前身是系统创建的第一个进程(我们称之为 init_task),也是唯一一个没有通过 fork 或者 kernel_thread 产生的进程。
  2. init_task是内核中所有进程、线程的 task_struct 雏形,它是在内核初始化过程中,通过静态定义构造出了一个 task_struct 接口,取名为 init_task,然后在内核初始化的后期,在 rest_init()函数中通过 kernel_thread 创建了两个内核线程内核 init 线程,kthreadd 内核线程, 前者后来通过演变,进入用户空间,成为所有用户进程的先祖, 而后者则成为所有内核态其他守护线程的父线程, 负责接手内核线程的创建工作
  3. 然后 init_task 通过变更调度类为 sched_idle 等操作演变成为idle 进程, 此时系统中只有 0(idle), 1(init), 2(kthreadd)3 个进程, 然后执行一次进程调度, 必然切换当前进程到到 init

附录–rest_init 的执行解析


rest_init 流程 说明
rcu_scheduler_starting 启动 Read-Copy Update,会调用 num_online_cpus 确认目前只有 bootstrap 处理器在运作,以及调用 nr_context_switches 确认在启动 RCU 前,没有进行过 Contex-Switch,最后就是设定 rcu_scheduler_active=1 启动 RCU 机制. RCU 在多核心架构下,不同的行程要读取同一笔资料内容/结构,可以提供高效率的同步与正确性. 在这之后就可以使用 rcu_read_lock/rcu_read_unlock 了
产生 Kernel Thread kernel_init Kernel Thread 函式 kernel_init 实例在 init/main.c 中, init Task PID=1,是内核第一个产生的 Task. 产生后,会阻塞在 wait_for_completion 处,等待 kthreadd_done Signal,以便往后继续执行下去.
产生 Kernel Thread kthreadd Kernel Thread 函式 kthreadd 实例在 kernel/kthread.c 中, kthreadd Task PID=2,是内核第二个产生的 Task.
find_task_by_pid_ns 实例在 kernel/pid.c 中, 调用函数 find_task_by_pid_ns,并传入参数 kthreadd 的 PID 2 与 PID NameSpace (struct pid_namespace init_pid_ns)取回 PID 2 的 Task Struct.
complete 实例在 kernel/sched.c 中, 会发送 kthreadd_done Signal,让 kernel_init(也就是 init task)可以往后继续执行.
init_idle_bootup_task 实例在 kernel/sched.c 中, 设定目前启动的 Task 为 IDLE Task. (idle->sched_class = &idle_sched_class), 而 struct sched_class idle_sched_class 的定义在 kernel/sched_idletask.c 中. 在 Linux 下 IDLE Task 并不占用 PID(也可以把它当作是 PID 0),每个处理器都会有这洋的 IDLE Task,用来在没有行程排成时,让处理器掉入执行的.而最基础的省电机制,也可透过 IDLE Task 来进行. (包括让系统可以关闭必要的周边电源与 Clock Gating).
schedule_preempt_disabled() 启动一次 Linux Kernel Process 的排成 Context-Switch 调度机制, 从而使得 kernel_init 即 1 号进程获得处理机
cpu_startup_entry 完成工作后, 调用 cpu_idle_loop()使得 idle 进程进入自己的事件处理循环