1. 程式人生 > >linux的 0號程序 和 1 號程序

linux的 0號程序 和 1 號程序

前言

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

所以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)建立的, 而是由核心黑客靜態建立的.

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

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引數公用這一記憶體空間空間,

而init_thread_info則是一段體系結構相關的定義,被定義在[/arch/對應體系/include/asm/thread_info.h]中,但是他們大多數為如下定義

#define init_thread_info        (init_thread_union.thread_info)
#define init_stack              (init_thread_union.stack)
  • 1
  • 2
/*
 * 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) };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我們可以發現init_task是用INIT_THREAD_INFO巨集進行初始化的, 這個才是我們真正體系結構相關的部分, 他與init_thread_info定義在一起,被定義在/arch/對應體系/include/asm/thread_info.h中,以下為x86架構的定義

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

init_thread_info定義中的__init_task_data表明該核心棧所在的區域位於核心映像的init data區,我們可以通過編譯完核心後所產生的System.map來看到該變數及其對應的邏輯地址

cat System.map-3.1.6 | grep init_thread_union
  • 1

init_thread_union

程序記憶體空間

init_task的虛擬地址空間,也採用同樣的方法被定義

由於init_task是一個執行在核心空間的核心執行緒, 因此其虛地址段mm為NULL, 但是必要時他還是需要使用虛擬地址的,因此avtive_mm被設定為init_mm

.mm             = NULL,                                         \
  .active_mm      = &init_mm,                                     \
  • 1
  • 2
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)
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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程序

這部分程式碼如下:

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);
}
  • 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
  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):

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)

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

它的任務就是管理和排程其他核心執行緒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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

因此我們回過頭來看pid=0的程序,在建立了init程序後,pid=0的程序呼叫 cpu_idle()演變成了idle程序。

0號程序首先執行init_idle_bootup_task, 讓init_task程序隸屬到idle排程類中。即選擇idle的排程相關函式。

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

接著通過schedule_preempt_disabled來執行呼叫schedule()函式切換當前程序,在呼叫該函式之前,Linux系統中只有兩個程序,即0號程序init_task和1號程序kernel_init,其中kernel_init程序也是剛剛被建立的。呼叫該函式後,1號程序kernel_init將會執行

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

最後cpu_startup_entry**呼叫cpu_idle_loop(),0號執行緒進入idle函式的迴圈,在該迴圈中會週期性地檢查**

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

其中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. 低退出延遲。

其程式碼如下

/*
 * 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();
        }
}
  • 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

迴圈判斷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中, 我們可以看到

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

也就是說,如果系統中沒有普通程序,那麼會選擇下個排程類優先順序的程序,即使用idle_sched_class排程類進行排程的程序

當系統空閒的時候,最後就是呼叫idle的pick_next_task函式,被定義在/kernel/sched/idle_task.c中

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程序。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

這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程序進入自己的事件處理迴圈