linux的 0號進程 和 1 號進程
Linux下有3個特殊的進程,idle進程(PID = 0), init進程(PID = 1)和kthreadd(PID = 2)linux
* idle進程由系統自動建立, 運行在內核態
idle進程其pid=0,其前身是系統建立的第一個進程,也是惟一一個沒有經過fork或者kernel_thread產生的進程。完成加載系統後,演變爲進程調度、交換shell
* init進程由idle經過kernel_thread建立,在內核空間完成初始化後, 加載init程序, 並最終用戶空間
由0進程建立,完成系統的初始化. 是系統中全部其它用戶進程的祖先進程
Linux中的全部進程都是有init進程建立並運行的。首先Linux內核啓動,而後在用戶空間中啓動init進程,再啓動其餘系統進程。在系統啓動完成完成後,init將變爲守護進程監視系統其餘進程。bootstrap
* kthreadd進程由idle經過kernel_thread建立,並始終運行在內核空間, 負責全部內核線程的調度和管理
它的任務就是管理和調度其餘內核線程kernel_thread, 會循環執行一個kthread的函數,該函數的做用就是運行kthread_create_list全局鏈表中維護的kthread, 當咱們調用kernel_thread建立的內核線程會被加入到此鏈表中,所以全部的內核線程都是直接或者間接的以kthreadd爲父進程
windows
咱們下面就詳解分析0號進程的前世(init_task)此生(idle)promise
idle的建立
在smp系統中,每一個處理器單元有獨立的一個運行隊列,而每一個運行隊列上又有一個idle進程,即有多少處理器單元,就有多少idle進程。數據結構
idle進程其pid=0,其前身是系統建立的第一個進程,也是惟一一個沒有經過fork()產生的進程。在smp系統中,每一個處理器單元有獨立的一個運行隊列,而每一個運行隊列上又有一個idle進程,即有多少處理器單元,就有多少idle進程。系統的空閒時間,其實就是指idle進程的」運行時間」。既然是idle是進程,那咱們來看看idle是如何被建立,又具體作了哪些事情?架構
咱們知道系統是從BIOS加電自檢,載入MBR中的引導程序(LILO/GRUB),再加載linux內核開始運行的,一直到指定shell開始運行告一段落,這時用戶開始操做Linux。app
0號進程上下文信息–init_task描述符
init_task是內核中全部進程、線程的task_struct雛形,在內核初始化過程當中,經過靜態定義構造出了一個task_struct接口,取名爲init_task,而後在內核初始化的後期,經過rest_init()函數新建了內核init線程,kthreadd內核線程electron
-
內核init線程,最終執行/sbin/init進程,變爲全部用戶態程序的根進程(pstree命令顯示),即用戶空間的init進程ide
開始的init是有kthread_thread建立的內核線程, 他在完成初始化工做後, 轉向用戶空間, 而且生成全部用戶進程的祖先
-
內核kthreadd內核線程,變爲全部內核態其餘守護線程的父線程。
它的任務就是管理和調度其餘內核線程kernel_thread, 會循環執行一個kthread的函數,該函數的做用就是運行kthread_create_list全局鏈表中維護的kthread, 當咱們調用kernel_thread建立的內核線程會被加入到此鏈表中,所以全部的內核線程都是直接或者間接的以kthreadd爲父進程
因此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](http://lxr.free-electrons.com/source/init/init_task.c?v=4.5#L17
)中定義,代碼片斷以下
/* Initial task structure */ struct task_struct init_task = INIT_TASK(init_task); EXPORT_SYMBOL(init_task);
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,
而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)
其中init_thread_union被定義在init/init_task.c, 緊跟着前面init_task的定義
/* * 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) };
咱們能夠發現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
#define INIT_THREAD_INFO(tsk) \ { \ .task = &tsk, \ .flags = 0, \ .cpu = 0, \ .addr_limit = KERNEL_DS, \ }
其餘體系結構的定義請參見
架構 | 定義 |
---|---|
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_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
.mm = NULL, \ .active_mm = &init_mm, \
其中init_mm被定義爲init-mm.c中,參見 http://lxr.free-electrons.com/source/mm/init-mm.c?v=4.5#L16
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) };
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); }
-
調用kernel_thread()建立1號內核線程, 該線程隨後轉向用戶空間, 演變爲init進程
-
調用kernel_thread()建立kthreadd內核線程。
-
init_idle_bootup_task():當前0號進程init_task最終會退化成idle進程,因此這裏調用init_idle_bootup_task()函數,讓init_task進程隸屬到idle調度類中。即選擇idle的調度相關函數。
-
調用schedule()函數切換當前進程,在調用該函數以前,Linux系統中只有兩個進程,即0號進程init_task和1號進程kernel_init,其中kernel_init進程也是剛剛被建立的。調用該函數後,1號進程kernel_init將會運行!
-
調用cpu_idle(),0號線程進入idle函數的循環,在該循環中會週期性地檢查。
建立kernel_init
在rest_init函數中,內核將經過下面的代碼產生第一個真正的進程(pid=1):
kernel_thread(kernel_init, NULL, CLONE_FS);
這個進程就是着名的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);
它的任務就是管理和調度其餘內核線程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.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(); } }
循環判斷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,
也就是說,若是系統中沒有普通進程,那麼會選擇下個調度類優先級的進程,即便用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
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進程。 }
這idle進程在啓動start_kernel函數的時候調用init_idle函數的時候,把當前進程(0號進程)置爲每一個rq運行隊列的的idle上。
rq->curr = rq->idle = idle;
這裏idle就是調用start_kernel函數的進程,就是0號進程。
idle進程總結
系統容許一個進程建立新進程,新進程即爲子進程,子進程還能夠建立新的子進程,造成進程樹結構模型。整個linux系統的全部進程也是一個樹形結構。樹根是系統自動構造的(或者說是由內核黑客手動建立的),即在內核態下執行的0號進程,它是全部進程的遠古先祖。
在smp系統中,每一個處理器單元有獨立的一個運行隊列,而每一個運行隊列上又有一個idle進程,即有多少處理器單元,就有多少idle進程。
-
idle進程其pid=0,其前身是系統建立的第一個進程(咱們稱之爲init_task),也是惟一一個沒有經過fork或者kernel_thread產生的進程。
-
init_task是內核中全部進程、線程的task_struct雛形,它是在內核初始化過程當中,經過靜態定義構造出了一個task_struct接口,取名爲init_task,而後在內核初始化的後期,在rest_init()函數中經過kernel_thread建立了兩個內核線程內核init線程,kthreadd內核線程, 前者後來經過演變,進入用戶空間,成爲全部用戶進程的先祖, 然後者則成爲全部內核態其餘守護線程的父線程, 負責接手內核線程的建立工做
-
而後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進程進入本身的事件處理循環 |
前言
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爲父進程
因此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](http://lxr.free-electrons.com/source/init/init_task.c?v=4.5#L17
)中定義,代碼片斷以下
/* Initial task structure */ struct task_struct init_task = INIT_TASK(init_task); EXPORT_SYMBOL(init_task);
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,
- 2
而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)
其中init_thread_union被定義在init/init_task.c, 緊跟着前面init_task的定義
/* * 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) };
咱們能夠發現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
#define INIT_THREAD_INFO(tsk) \ { \ .task = &tsk, \ .flags = 0, \ .cpu = 0, \ .addr_limit = KERNEL_DS, \ }
這裏idle就是調用start_kernel函數的進程,就是0號進程。
idle進程總結
系統容許一個進程建立新進程,新進程即爲子進程,子進程還能夠建立新的子進程,造成進程樹結構模型。整個linux系統的全部進程也是一個樹形結構。樹根是系統自動構造的(或者說是由內核黑客手動建立的),即在內核態下執行的0號進程,它是全部進程的遠古先祖。
在smp系統中,每一個處理器單元有獨立的一個運行隊列,而每一個運行隊列上又有一個idle進程,即有多少處理器單元,就有多少idle進程。
-
idle進程其pid=0,其前身是系統建立的第一個進程(咱們稱之爲init_task),也是惟一一個沒有經過fork或者kernel_thread產生的進程。
-
init_task是內核中全部進程、線程的task_struct雛形,它是在內核初始化過程當中,經過靜態定義構造出了一個task_struct接口,取名爲init_task,而後在內核初始化的後期,在rest_init()函數中經過kernel_thread建立了兩個內核線程內核init線程,kthreadd內核線程, 前者後來經過演變,進入用戶空間,成爲全部用戶進程的先祖, 然後者則成爲全部內核態其餘守護線程的父線程, 負責接手內核線程的建立工做
-
而後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進程進入本身的事件 |