在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

詳解分析0號(hào)進(jìn)程的前世(init_task)今生(idle)

Linux閱碼場(chǎng) ? 來源:Linuxer ? 2020-06-03 16:08 ? 次閱讀

前言

Linux下有3個(gè)特殊的進(jìn)程,idle進(jìn)程(PID = 0), init進(jìn)程(PID = 1)和kthreadd(PID = 2)

* idle進(jìn)程由系統(tǒng)自動(dòng)創(chuàng)建, 運(yùn)行在內(nèi)核態(tài)

idle進(jìn)程其pid=0,其前身是系統(tǒng)創(chuàng)建的第一個(gè)進(jìn)程,也是唯一一個(gè)沒有通過fork或者kernel_thread產(chǎn)生的進(jìn)程。完成加載系統(tǒng)后,演變?yōu)檫M(jìn)程調(diào)度、交換


* init進(jìn)程由idle通過kernel_thread創(chuàng)建,在內(nèi)核空間完成初始化后, 加載init程序, 并最終用戶空間
由0進(jìn)程創(chuàng)建,完成系統(tǒng)的初始化. 是系統(tǒng)中所有其它用戶進(jìn)程的祖先進(jìn)程
Linux中的所有進(jìn)程都是有init進(jìn)程創(chuàng)建并運(yùn)行的。首先Linux內(nèi)核啟動(dòng),然后在用戶空間中啟動(dòng)init進(jìn)程,再啟動(dòng)其他系統(tǒng)進(jìn)程。在系統(tǒng)啟動(dòng)完成完成后,init將變?yōu)槭刈o(hù)進(jìn)程監(jiān)視系統(tǒng)其他進(jìn)程。


* kthreadd進(jìn)程由idle通過kernel_thread創(chuàng)建,并始終運(yùn)行在內(nèi)核空間, 負(fù)責(zé)所有內(nèi)核線程的調(diào)度和管理
它的任務(wù)就是管理和調(diào)度其他內(nèi)核線程kernel_thread, 會(huì)循環(huán)執(zhí)行一個(gè)kthread的函數(shù),該函數(shù)的作用就是運(yùn)行kthread_create_list全局鏈表中維護(hù)的kthread, 當(dāng)我們調(diào)用kernel_thread創(chuàng)建的內(nèi)核線程會(huì)被加入到此鏈表中,因此所有的內(nèi)核線程都是直接或者間接的以kthreadd為父進(jìn)程

我們下面就詳解分析0號(hào)進(jìn)程的前世(init_task)今生(idle)

idle的創(chuàng)建

在smp系統(tǒng)中,每個(gè)處理器單元有獨(dú)立的一個(gè)運(yùn)行隊(duì)列,而每個(gè)運(yùn)行隊(duì)列上又有一個(gè)idle進(jìn)程,即有多少處理器單元,就有多少idle進(jìn)程。

idle進(jìn)程其pid=0,其前身是系統(tǒng)創(chuàng)建的第一個(gè)進(jìn)程,也是唯一一個(gè)沒有通過fork()產(chǎn)生的進(jìn)程。在smp系統(tǒng)中,每個(gè)處理器單元有獨(dú)立的一個(gè)運(yùn)行隊(duì)列,而每個(gè)運(yùn)行隊(duì)列上又有一個(gè)idle進(jìn)程,即有多少處理器單元,就有多少idle進(jìn)程。系統(tǒng)的空閑時(shí)間,其實(shí)就是指idle進(jìn)程的”運(yùn)行時(shí)間”。既然是idle是進(jìn)程,那我們來看看idle是如何被創(chuàng)建,又具體做了哪些事情?

我們知道系統(tǒng)是從BIOS加電自檢,載入MBR中的引導(dǎo)程序(LILO/GRUB),再加載linux內(nèi)核開始運(yùn)行的,一直到指定shell開始運(yùn)行告一段落,這時(shí)用戶開始操作Linux。

0號(hào)進(jìn)程上下文信息–init_task描述符

init_task是內(nèi)核中所有進(jìn)程、線程的task_struct雛形,在內(nèi)核初始化過程中,通過靜態(tài)定義構(gòu)造出了一個(gè)task_struct接口,取名為init_task,然后在內(nèi)核初始化的后期,通過rest_init()函數(shù)新建了內(nèi)核init線程,kthreadd內(nèi)核線程

內(nèi)核init線程,最終執(zhí)行/sbin/init進(jìn)程,變?yōu)樗杏脩魬B(tài)程序的根進(jìn)程(pstree命令顯示),即用戶空間的init進(jìn)程

開始的init是有kthread_thread創(chuàng)建的內(nèi)核線程, 他在完成初始化工作后, 轉(zhuǎn)向用戶空間, 并且生成所有用戶進(jìn)程的祖先

內(nèi)核kthreadd內(nèi)核線程,變?yōu)樗袃?nèi)核態(tài)其他守護(hù)線程的父線程。

它的任務(wù)就是管理和調(diào)度其他內(nèi)核線程kernel_thread, 會(huì)循環(huán)執(zhí)行一個(gè)kthread的函數(shù),該函數(shù)的作用就是運(yùn)行kthread_create_list全局鏈表中維護(hù)的kthread, 當(dāng)我們調(diào)用kernel_thread創(chuàng)建的內(nèi)核線程會(huì)被加入到此鏈表中,因此所有的內(nèi)核線程都是直接或者間接的以kthreadd為父進(jìn)程

所以init_task決定了系統(tǒng)所有進(jìn)程、線程的基因, 它完成初始化后, 最終演變?yōu)?號(hào)進(jìn)程idle, 并且運(yùn)行在內(nèi)核態(tài)

內(nèi)核在初始化過程中,當(dāng)創(chuàng)建完init和kthreadd內(nèi)核線程后,內(nèi)核會(huì)發(fā)生調(diào)度執(zhí)行,此時(shí)內(nèi)核將使用該init_task作為其task_struct結(jié)構(gòu)體描述符,當(dāng)系統(tǒng)無事可做時(shí),會(huì)調(diào)度其執(zhí)行, 此時(shí)該內(nèi)核會(huì)變?yōu)閕dle進(jìn)程,讓出CPU,自己進(jìn)入睡眠,不停的循環(huán),查看init_task結(jié)構(gòu)體,其comm字段為swapper,作為idle進(jìn)程的描述符。

idle的運(yùn)行時(shí)機(jī)

idle 進(jìn)程優(yōu)先級(jí)為MAX_PRIO-20。早先版本中,idle是參與調(diào)度的,所以將其優(yōu)先級(jí)設(shè)低點(diǎn),當(dāng)沒有其他進(jìn)程可以運(yùn)行時(shí),才會(huì)調(diào)度執(zhí)行 idle。而目前的版本中idle并不在運(yùn)行隊(duì)列中參與調(diào)度,而是在運(yùn)行隊(duì)列結(jié)構(gòu)中含idle指針,指向idle進(jìn)程,在調(diào)度器發(fā)現(xiàn)運(yùn)行隊(duì)列為空的時(shí)候運(yùn)行,調(diào)入運(yùn)行

簡(jiǎn)言之,內(nèi)核中init_task變量就是是進(jìn)程0使用的進(jìn)程描述符,也是Linux系統(tǒng)中第一個(gè)進(jìn)程描述符,init_task并不是系統(tǒng)通過kernel_thread的方式(當(dāng)然更不可能是fork)創(chuàng)建的, 而是由內(nèi)核黑客靜態(tài)創(chuàng)建的.

該進(jìn)程的描述符在[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對(duì)init_task的進(jìn)程描述符進(jìn)行初始化,宏INIT_TASK在include/linux/init_task.h文件中

init_task是Linux內(nèi)核中的第一個(gè)線程,它貫穿于整個(gè)Linux系統(tǒng)的初始化過程中,該進(jìn)程也是Linux系統(tǒng)中唯一一個(gè)沒有用kernel_thread()函數(shù)創(chuàng)建的內(nèi)核態(tài)進(jìn)程(內(nèi)核線程)

在init_task進(jìn)程執(zhí)行后期,它會(huì)調(diào)用kernel_thread()函數(shù)創(chuàng)建第一個(gè)核心進(jìn)程kernel_init,同時(shí)init_task進(jìn)程繼續(xù)對(duì)Linux系統(tǒng)初始化。在完成初始化后,init_task會(huì)退化為cpu_idle進(jìn)程,當(dāng)Core 0的就緒隊(duì)列中沒有其它進(jìn)程時(shí),該進(jìn)程將會(huì)獲得CPU運(yùn)行。新創(chuàng)建的1號(hào)進(jìn)程kernel_init將會(huì)逐個(gè)啟動(dòng)次CPU,并最終創(chuàng)建用戶進(jìn)程!

備注:core0上的idle進(jìn)程由init_task進(jìn)程退化而來,而AP的idle進(jìn)程則是BSP在后面調(diào)用fork()函數(shù)逐個(gè)創(chuàng)建的

進(jìn)程堆棧init_thread_union

init_task進(jìn)程使用init_thread_union數(shù)據(jù)結(jié)構(gòu)描述的內(nèi)存區(qū)域作為該進(jìn)程的堆棧空間,并且和自身的thread_info參數(shù)公用這一內(nèi)存空間空間,

請(qǐng)參見

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

.stack = &init_thread_info,

而init_thread_info則是一段體系結(jié)構(gòu)相關(guān)的定義,被定義在[/arch/對(duì)應(yīng)體系/include/asm/thread_info.h]中,但是他們大多數(shù)為如下定義

#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) };

我們可以發(fā)現(xiàn)init_task是用INIT_THREAD_INFO宏進(jìn)行初始化的, 這個(gè)才是我們真正體系結(jié)構(gòu)相關(guān)的部分, 他與init_thread_info定義在一起,被定義在/arch/對(duì)應(yīng)體系/include/asm/thread_info.h中,以下為x86架構(gòu)的定義

參見

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, }

其他體系結(jié)構(gòu)的定義請(qǐng)參見

/arch/對(duì)應(yīng)體系/include/asm/thread_info.h中

架構(gòu) 定義
x86 arch/x86/include/asm/thread_info.h
arm64 arch/arm64/include/asm/thread_info.h

init_thread_info定義中的__init_task_data表明該內(nèi)核棧所在的區(qū)域位于內(nèi)核映像的init data區(qū),我們可以通過編譯完內(nèi)核后所產(chǎn)生的System.map來看到該變量及其對(duì)應(yīng)的邏輯地址

cat System.map-3.1.6| grep init_thread_union

進(jìn)程內(nèi)存空間

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

由于init_task是一個(gè)運(yùn)行在內(nèi)核空間的內(nèi)核線程, 因此其虛地址段mm為NULL, 但是必要時(shí)他還是需要使用虛擬地址的,因此avtive_mm被設(shè)置為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號(hào)進(jìn)程的演化

rest_init創(chuàng)建init進(jìn)程(PID =1)和kthread進(jìn)程(PID=2)

Linux在無進(jìn)程概念的情況下將一直從初始化部分的代碼執(zhí)行到start_kernel,然后再到其最后一個(gè)函數(shù)調(diào)用rest_init

大致是在vmlinux的入口startup_32(head.S)中為pid號(hào)為0的原始進(jìn)程設(shè)置了執(zhí)行環(huán)境,然后原是進(jìn)程開始執(zhí)行start_kernel()完成Linux內(nèi)核的初始化工作。包括初始化頁(yè)表,初始化中斷向量表,初始化系統(tǒng)時(shí)間等。

從rest_init開始,Linux開始產(chǎn)生進(jìn)程,因?yàn)閕nit_task是靜態(tài)制造出來的,pid=0,它試圖將從最早的匯編代碼一直到start_kernel的執(zhí)行都納入到init_task進(jìn)程上下文中。

這個(gè)函數(shù)其實(shí)是由0號(hào)進(jìn)程執(zhí)行的, 他就是在這個(gè)函數(shù)中, 創(chuàng)建了init進(jìn)程和kthreadd進(jìn)程

這部分代碼如下:

參見

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

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

調(diào)用kernel_thread()創(chuàng)建1號(hào)內(nèi)核線程, 該線程隨后轉(zhuǎn)向用戶空間, 演變?yōu)閕nit進(jìn)程

調(diào)用kernel_thread()創(chuàng)建kthreadd內(nèi)核線程。

init_idle_bootup_task():當(dāng)前0號(hào)進(jìn)程init_task最終會(huì)退化成idle進(jìn)程,所以這里調(diào)用init_idle_bootup_task()函數(shù),讓init_task進(jìn)程隸屬到idle調(diào)度類中。即選擇idle的調(diào)度相關(guān)函數(shù)。

調(diào)用schedule()函數(shù)切換當(dāng)前進(jìn)程,在調(diào)用該函數(shù)之前,Linux系統(tǒng)中只有兩個(gè)進(jìn)程,即0號(hào)進(jìn)程init_task和1號(hào)進(jìn)程kernel_init,其中kernel_init進(jìn)程也是剛剛被創(chuàng)建的。調(diào)用該函數(shù)后,1號(hào)進(jìn)程kernel_init將會(huì)運(yùn)行!

調(diào)用cpu_idle(),0號(hào)線程進(jìn)入idle函數(shù)的循環(huán),在該循環(huán)中會(huì)周期性地檢查。

創(chuàng)建kernel_init

在rest_init函數(shù)中,內(nèi)核將通過下面的代碼產(chǎn)生第一個(gè)真正的進(jìn)程(pid=1):

kernel_thread(kernel_init, NULL, CLONE_FS);

這個(gè)進(jìn)程就是著名的pid為1的init進(jìn)程,它會(huì)繼續(xù)完成剩下的初始化工作,然后execve(/sbin/init), 成為系統(tǒng)中的其他所有進(jìn)程的祖先。

但是這里我們發(fā)現(xiàn)一個(gè)問題, init進(jìn)程應(yīng)該是一個(gè)用戶空間的進(jìn)程, 但是這里卻是通過kernel_thread的方式創(chuàng)建的, 哪豈不是式一個(gè)永遠(yuǎn)運(yùn)行在內(nèi)核態(tài)的內(nèi)核線程么, 它是怎么演變?yōu)檎嬲饬x上用戶空間的init進(jìn)程的?

1號(hào)kernel_init進(jìn)程完成linux的各項(xiàng)配置(包括啟動(dòng)AP)后,就會(huì)在/sbin,/etc,/bin尋找init程序來運(yùn)行。該init程序會(huì)替換kernel_init進(jìn)程(注意:并不是創(chuàng)建一個(gè)新的進(jìn)程來運(yùn)行init程序,而是一次變身,使用sys_execve函數(shù)改變核心進(jìn)程的正文段,將核心進(jìn)程kernel_init轉(zhuǎn)換成用戶進(jìn)程init),此時(shí)處于內(nèi)核態(tài)的1號(hào)kernel_init進(jìn)程將會(huì)轉(zhuǎn)換為用戶空間內(nèi)的1號(hào)進(jìn)程init。戶進(jìn)程init將根據(jù)/etc/inittab中提供的信息完成應(yīng)用程序的初始化調(diào)用。然后init進(jìn)程會(huì)執(zhí)行/bin/sh產(chǎn)生shell界面提供給用戶來與Linux系統(tǒng)進(jìn)行交互。

調(diào)用init_post()創(chuàng)建用戶模式1號(hào)進(jìn)程。

關(guān)于init其他的信息我們這次先不研究,因?yàn)槲覀冞@篇旨在探究0號(hào)進(jìn)程的詳細(xì)過程,

創(chuàng)建kthreadd

在rest_init函數(shù)中,內(nèi)核將通過下面的代碼產(chǎn)生第一個(gè)kthreadd(pid=2)

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

它的任務(wù)就是管理和調(diào)度其他內(nèi)核線程kernel_thread, 會(huì)循環(huán)執(zhí)行一個(gè)kthread的函數(shù),該函數(shù)的作用就是運(yùn)行kthread_create_list全局鏈表中維護(hù)的kthread, 當(dāng)我們調(diào)用kernel_thread創(chuàng)建的內(nèi)核線程會(huì)被加入到此鏈表中,因此所有的內(nèi)核線程都是直接或者間接的以kthreadd為父進(jìn)程

0號(hào)進(jìn)程演變?yōu)閕dle

/*

* 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的進(jìn)程,在創(chuàng)建了init進(jìn)程后,pid=0的進(jìn)程調(diào)用 cpu_idle()演變成了idle進(jìn)程。

0號(hào)進(jìn)程首先執(zhí)行init_idle_bootup_task,讓init_task進(jìn)程隸屬到idle調(diào)度類中。即選擇idle的調(diào)度相關(guān)函數(shù)。

這個(gè)函數(shù)被定義在kernel/sched/core.c中,如下

void init_idle_bootup_task(struct task_struct *idle)

{

idle->sched_class = &idle_sched_class;

}

接著通過schedule_preempt_disabled來執(zhí)行調(diào)用schedule()函數(shù)切換當(dāng)前進(jìn)程,在調(diào)用該函數(shù)之前,Linux系統(tǒng)中只有兩個(gè)進(jìn)程,即0號(hào)進(jìn)程init_task和1號(hào)進(jìn)程kernel_init,其中kernel_init進(jìn)程也是剛剛被創(chuàng)建的。調(diào)用該函數(shù)后,1號(hào)進(jìn)程kernel_init將會(huì)運(yùn)行

這個(gè)函數(shù)被定義在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**調(diào)用cpu_idle_loop(),0號(hào)線程進(jìn)入idle函數(shù)的循環(huán),在該循環(huán)中會(huì)周期性地檢查**

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進(jìn)程的事件循環(huán),定義在kernel/sched/idle.c

整個(gè)過程簡(jiǎn)單的說就是,原始進(jìn)程(pid=0)創(chuàng)建init進(jìn)程(pid=1),然后演化成idle進(jìn)程(pid=0)。init進(jìn)程為每個(gè)從處理器(運(yùn)行隊(duì)列)創(chuàng)建出一個(gè)idle進(jìn)程(pid=0),然后演化成/sbin/init。

idle的運(yùn)行與調(diào)度

idle的workload–cpu_idle_loop

從上面的分析我們知道,idle在系統(tǒng)沒有其他就緒的進(jìn)程可執(zhí)行的時(shí)候才會(huì)被調(diào)度。不管是主處理器,還是從處理器,最后都是執(zhí)行的cpu_idle_loop()函數(shù)

其中cpu_idle_loop就是idle進(jìn)程的事件循環(huán),定義在kernel/sched/idle.c,早期的版本中提供的是cpu_idle,但是這個(gè)函數(shù)是完全依賴于體系結(jié)構(gòu)的,不利用架構(gòu)的分層,因此在新的內(nèi)核中更新為更加通用的cpu_idle_loop,由他來調(diào)用體系結(jié)構(gòu)相關(guān)的代碼

所以我們來看看cpu_idle_loop做了什么事情。

因?yàn)閕dle進(jìn)程中并不執(zhí)行什么有意義的任務(wù),所以通常考慮的是兩點(diǎn)

節(jié)能

低退出延遲。

其代碼如下

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

循環(huán)判斷need_resched以降低退出延遲,用idle()來節(jié)能。

默認(rèn)的idle實(shí)現(xiàn)是hlt指令,hlt指令使CPU處于暫停狀態(tài),等待硬件中斷發(fā)生的時(shí)候恢復(fù),從而達(dá)到節(jié)能的目的。即從處理器C0態(tài)變到 C1態(tài)(見 ACPI標(biāo)準(zhǔn))。這也是早些年windows平臺(tái)上各種”處理器降溫”工具的主要手段。當(dāng)然idle也可以是在別的ACPI或者APM模塊中定義的,甚至是自定義的一個(gè)idle(比如說nop)。

1.idle是一個(gè)進(jìn)程,其pid為0。

2.主處理器上的idle由原始進(jìn)程(pid=0)演變而來。從處理器上的idle由init進(jìn)程fork得到,但是它們的pid都為0。

3.Idle進(jìn)程為最低優(yōu)先級(jí),且不參與調(diào)度,只是在運(yùn)行隊(duì)列為空的時(shí)候才被調(diào)度。

4.Idle循環(huán)等待need_resched置位。默認(rèn)使用hlt節(jié)能。

希望通過本文你能全面了解linux內(nèi)核中idle知識(shí)。

idle的調(diào)度和運(yùn)行時(shí)機(jī)

我們知道, linux進(jìn)程的調(diào)度順序是按照 rt實(shí)時(shí)進(jìn)程(rt調(diào)度器), normal普通進(jìn)程(cfs調(diào)度器),和idel的順序來調(diào)度的

那么可以試想如果rt和cfs都沒有可以運(yùn)行的任務(wù),那么idle才可以被調(diào)度,那么他是通過怎樣的方式實(shí)現(xiàn)的呢?

由于我們還沒有講解調(diào)度器的知識(shí), 所有我們只是簡(jiǎn)單講解一下

在normal的調(diào)度類,cfs公平調(diào)度器sched_fair.c中, 我們可以看到

staticconststruct sched_class fair_sched_class = {

.next= &idle_sched_class,

也就是說,如果系統(tǒng)中沒有普通進(jìn)程,那么會(huì)選擇下個(gè)調(diào)度類優(yōu)先級(jí)的進(jìn)程,即使用idle_sched_class調(diào)度類進(jìn)行調(diào)度的進(jìn)程

當(dāng)系統(tǒng)空閑的時(shí)候,最后就是調(diào)用idle的pick_next_task函數(shù),被定義在/kernel/sched/idle_task.c中

參見

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

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進(jìn)程。}

這idle進(jìn)程在啟動(dòng)start_kernel函數(shù)的時(shí)候調(diào)用init_idle函數(shù)的時(shí)候,把當(dāng)前進(jìn)程(0號(hào)進(jìn)程)置為每個(gè)rq運(yùn)行隊(duì)列的的idle上。

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

這里idle就是調(diào)用start_kernel函數(shù)的進(jìn)程,就是0號(hào)進(jìn)程。

idle進(jìn)程總結(jié)

系統(tǒng)允許一個(gè)進(jìn)程創(chuàng)建新進(jìn)程,新進(jìn)程即為子進(jìn)程,子進(jìn)程還可以創(chuàng)建新的子進(jìn)程,形成進(jìn)程樹結(jié)構(gòu)模型。整個(gè)linux系統(tǒng)的所有進(jìn)程也是一個(gè)樹形結(jié)構(gòu)。樹根是系統(tǒng)自動(dòng)構(gòu)造的(或者說是由內(nèi)核黑客手動(dòng)創(chuàng)建的),即在內(nèi)核態(tài)下執(zhí)行的0號(hào)進(jìn)程,它是所有進(jìn)程的遠(yuǎn)古先祖。

在smp系統(tǒng)中,每個(gè)處理器單元有獨(dú)立的一個(gè)運(yùn)行隊(duì)列,而每個(gè)運(yùn)行隊(duì)列上又有一個(gè)idle進(jìn)程,即有多少處理器單元,就有多少idle進(jìn)程。

idle進(jìn)程其pid=0,其前身是系統(tǒng)創(chuàng)建的第一個(gè)進(jìn)程(我們稱之為init_task),也是唯一一個(gè)沒有通過fork或者kernel_thread產(chǎn)生的進(jìn)程。

init_task是內(nèi)核中所有進(jìn)程、線程的task_struct雛形,它是在內(nèi)核初始化過程中,通過靜態(tài)定義構(gòu)造出了一個(gè)task_struct接口,取名為init_task,然后在內(nèi)核初始化的后期,在rest_init()函數(shù)中通過kernel_thread創(chuàng)建了兩個(gè)內(nèi)核線程內(nèi)核init線程,kthreadd內(nèi)核線程, 前者后來通過演變,進(jìn)入用戶空間,成為所有用戶進(jìn)程的先祖, 而后者則成為所有內(nèi)核態(tài)其他守護(hù)線程的父線程, 負(fù)責(zé)接手內(nèi)核線程的創(chuàng)建工作

然后init_task通過變更調(diào)度類為sched_idle等操作演變成為idle進(jìn)程, 此時(shí)系統(tǒng)中只有0(idle), 1(init), 2(kthreadd)3個(gè)進(jìn)程, 然后執(zhí)行一次進(jìn)程調(diào)度, 必然切換當(dāng)前進(jìn)程到到init

附錄–rest_init的執(zhí)行解析

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 處理器
    +關(guān)注

    關(guān)注

    68

    文章

    19723

    瀏覽量

    232743
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11415

    瀏覽量

    212264
  • 進(jìn)程
    +關(guān)注

    關(guān)注

    0

    文章

    206

    瀏覽量

    14177

原文標(biāo)題:Linux下0號(hào)進(jìn)程的前世(init_task進(jìn)程)今生(idle進(jìn)程)

文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Linux進(jìn)程狀態(tài)詳解

    進(jìn)程狀態(tài)是task_struct內(nèi)的一個(gè)整數(shù);進(jìn)行:進(jìn)程在調(diào)度隊(duì)列中,進(jìn)程的狀態(tài)都是running,阻塞:等待某種設(shè)備或者資源就緒。進(jìn)程是一
    的頭像 發(fā)表于 04-01 09:46 ?168次閱讀
    Linux<b class='flag-5'>進(jìn)程</b>狀態(tài)<b class='flag-5'>詳解</b>

    一文詳解無線電波的前世今生

    在忙碌一天后,您回到裝有智能家居設(shè)備的家里。手機(jī)會(huì)自動(dòng)連接到本地網(wǎng)絡(luò),室內(nèi)溫度也很舒適,不冷也不熱。當(dāng)您坐在最喜歡的沙發(fā)上,插上耳機(jī)準(zhǔn)備聆聽一首動(dòng)聽的樂曲的時(shí)候,家里人走過來讓您連接設(shè)備共享給他一些文件。在這個(gè)等待傳輸過程中,你被一臺(tái)曾經(jīng)屬于你祖母的舊收音機(jī)吸引了目光。剎那間,一切都悵然若失了,你瞥見了過去,回想著過去十年的林林總總,那時(shí)還沒有這些短程無線技術(shù)。
    的頭像 發(fā)表于 03-14 17:56 ?484次閱讀
    一文<b class='flag-5'>詳解</b>無線電波的<b class='flag-5'>前世</b><b class='flag-5'>今生</b>

    請(qǐng)問如何在Python中實(shí)現(xiàn)多線程與多進(jìn)程的協(xié)作?

    \") if __name__ == \'__main__\': # 使用多線程處理I/O任務(wù) thread = threading.Thread(target=io_task) # 使用多進(jìn)程
    發(fā)表于 03-11 06:57

    安泰功率放大器應(yīng)用:納米材料的前世今生

    ,因此在各個(gè)領(lǐng)域具有廣泛的應(yīng)用前景。那么你知道納米材料是如何被發(fā)現(xiàn),又是如何走入我們的生活,獲得長(zhǎng)足發(fā)展的嗎?今天Aigtek安泰電子帶大家詳細(xì)了解一下。 納米材料的前世今生 1861年,隨著膠體化學(xué)的建立,科學(xué)家們開始了對(duì)直徑為
    的頭像 發(fā)表于 01-02 14:05 ?336次閱讀
    安泰功率放大器應(yīng)用:納米材料的<b class='flag-5'>前世</b><b class='flag-5'>今生</b>

    深入了解Java泛型——從前世今生到PECS原則

    本文主要介紹泛型誕生的前世今生,特性,以及著名PECS原則的由來。 在日常開發(fā)中,必不可少的會(huì)使用到泛型,這個(gè)過程中經(jīng)常會(huì)出現(xiàn)類似“為什么這樣會(huì)編譯報(bào)錯(cuò)?”,“為什么這個(gè)列表無法添加
    的頭像 發(fā)表于 11-21 11:45 ?413次閱讀
    深入了解Java泛型——從<b class='flag-5'>前世</b><b class='flag-5'>今生</b>到PECS原則

    移植NXP GUI Guider的界面到小安派SCP4.3

    ); //wifi_start_firmware_task(); lwip_sntp_init(); bflb_mtd_init(); easyflash_init(); /* lvg
    的頭像 發(fā)表于 11-06 11:29 ?558次閱讀
    移植NXP GUI Guider的界面到小安派SCP4.3

    一文搞懂Linux進(jìn)程的睡眠和喚醒

    一、常見的進(jìn)程狀態(tài)與理解 在操作系統(tǒng)內(nèi)部,有專門用來管理進(jìn)程的結(jié)構(gòu)體,叫做struct task_struct,也稱作進(jìn)程控制塊(PCB),主要包含描述
    發(fā)表于 11-04 15:15

    Systemd是什么?Systemd Service配置文件詳解

    Systemd是什么,以前l(fā)inux系統(tǒng)啟動(dòng)init機(jī)制,由于init一方面對(duì)于進(jìn)程的管理是串行化的,容易出現(xiàn)阻塞情況,另一方面init也僅僅是執(zhí)行啟動(dòng)腳本,并不能對(duì)服務(wù)本身進(jìn)行更多的
    的頭像 發(fā)表于 07-22 18:03 ?1987次閱讀

    esp32s3遇到Task watchdog got triggered. The following tasks did not reset the watchdog in time故障怎么解決?

    ) task_wdt: Tasks currently running: E (1486850) task_wdt: CPU 0: IDLE0 E (1486850)
    發(fā)表于 07-19 07:21

    esp8266運(yùn)行REMOTE_OTA_TASK出錯(cuò)的原因?

    的LOG開關(guān)打開(LOG在附件里)。發(fā)現(xiàn)出現(xiàn)異常 [inf] _ssl_parse_crt(142): crt content:451 [inf] _ssl_client_init(185):ok (0
    發(fā)表于 07-10 06:32

    esp8266EX報(bào)\"event_task\"(stack_size = 0,task handle = 40108368) overflow the heap_size.是不是內(nèi)存不夠用?

    @ Mar 30 2018 18:54:06 phy ver: 1055_1, pp ver: 10.7 rf cal sector: 251 idle_task_hdl : 40107ab0,prio:0
    發(fā)表于 07-09 07:43

    在menuconfig中,如何配置wifi thread的stacksize的選項(xiàng)?

    進(jìn)程運(yùn)行狀態(tài) Task Name StatusPrio HWMTask# Task_cliR 4 30813 IDLE0R 0 1008
    發(fā)表于 06-26 07:33

    ble_mesh_fast_prov_client在初始化的時(shí)候看門狗一直重啟的原因?

    ) task_wdt:- IDLE0 (CPU 0)E (5570) task_wdt: Tasks currently running:E (5570)
    發(fā)表于 06-26 07:12

    esp32同時(shí)讓wifi和藍(lán)牙工作,會(huì)出現(xiàn)wifi task看門狗復(fù)位的情況怎么解決?

    in time: E (6870) task_wdt:- IDLE (CPU 0) E (6870) task_wdt: Tasks currently running: E (68
    發(fā)表于 06-21 06:21

    將simple_ota_example移植到smart_config下,串口log提示http下載ota文件失敗的原因?

    ;32mI (717) timer: Init timer with auto-reload[0m I (737) wifi:wifi driver task: 3ffbfaa0, p
    發(fā)表于 06-11 08:01
    主站蜘蛛池模板: 中文字幕乱码人成乱码在线视频 | 婷婷丁香综合网 | aa级毛片| 亚洲一区二区中文字幕 | 新版天堂中文资源8在线 | 久操视频在线免费观看 | 噜噜噜噜影院 | 拍拍拍交性免费视频 | 久久永久免费视频 | 国产高清视频在线免费观看 | 亚洲第一免费播放区 | 悠悠影院欧美日韩国产 | 午夜伦理片在线观看 | 四虎永久地址4hu紧急入口 | 天天摸天天澡天天碰天天弄 | 国内在线观看精品免费视频 | 高h细节肉爽文男男 | 爱爱免费小视频 | 色妞视频资源在线观看 | 午夜寂寞影院视频观看 | 黄频网站免费大全在线观看 | 欧美日韩一级视频 | 亚洲美女激情视频 | 色妞在线 | 色宅男看片午夜大片免费看 | 激情综合色五月丁香六月亚洲 | 欧美日韩生活片 | 国产精品青草久久 | 日韩久久精品视频 | 国产成人精品本亚洲 | 特级毛片免费视频观看 | 亚洲2020天天堂在线观看 | 国产一卡二卡3卡4卡四卡在线 | 5566精品资源在线播放 | 亚洲黄视频 | 2019天天干夜夜操 | 久久久久激情免费观看 | 亚洲444444在线观看 | 免费黄色 | 九九99视频在线观看视频观看 | 天天操精品 |