最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Linux內(nèi)核線程及普通進程總結(趕快學起來吧~)

2022-05-28 16:06 作者:補給站Linux內(nèi)核  | 我要投稿

Linux 中的進程與線程

  • 對于 Linux 來講,所有的線程都當作進程來實現(xiàn),因為沒有單獨為線程定義特定的調(diào)度算法,也沒有單獨為線程定義特定的數(shù)據(jù)結構(所有的線程或進程的核心數(shù)據(jù)結構都是 task_struct)。

對于一個進程,相當于是它含有一個線程,就是它自身。對于多線程來說,原本的進程稱為主線程,它們在一起組成一個線程組。

  • 進程擁有自己的地址空間,所以每個進程都有自己的頁表。而線程卻沒有,只能和其它線程共享某一個地址空間和同一份頁表。

  • 這個區(qū)別的根本原因是,在 進程/線程 創(chuàng)建時,因是否拷貝當前進程的地址空間還是共享當前進程的地址空間,而使得指定的參數(shù)不同而導致的。

  • 具體地說,進程和線程的創(chuàng)建都是執(zhí)行 clone 系統(tǒng)調(diào)用進行的。而 clone 系統(tǒng)調(diào)用會執(zhí)行 do_fork 內(nèi)核函數(shù),而它則又會調(diào)用 copy_process 內(nèi)核函數(shù)來完成。主要包括如下操作:

  • 在調(diào)用 copy_process 的過程中,會創(chuàng)建并拷貝當前進程的 task_stuct,同時還會創(chuàng)建屬于子進程的 thread_info 結構以及內(nèi)核棧。此后,會為創(chuàng)建好的 task_stuct 指定一個新的 pid(在 task_struct 結構體中)。然后根據(jù)傳遞給 clone 的參數(shù)標志,來選擇拷貝還是共享打開的文件,文件系統(tǒng)信息,信號處理函數(shù),進程地址空間等。這就是進程和線程不一樣地方的本質(zhì)所在。

三個數(shù)據(jù)結構

  • 每個進程或線程都有三個數(shù)據(jù)結構,分別是:

  1. struct thread_info

  2. struct task_struct

  3. 內(nèi)核棧

注意,雖然線程與主線程共享地址空間,但是線程也是有自己獨立的內(nèi)核棧的。

  • thread_info 對象中存放的進程/線程的基本信息,它和這個 進程/線程 的內(nèi)核棧存放在內(nèi)核空間里的一段 2 倍頁長的空間中。其中 thread_info 結構存放在低地址段的末尾,其余空間用作內(nèi)核棧。內(nèi)核使用 伙伴系統(tǒng) 為每個進程/線程分配這塊空間。

  • thread_info 結構體中有一個 struct task_struct *task,task 指向的就是這個進程或線程相關的 task_struct 對象(也在內(nèi)核空間中),這個對象叫做進程描述符(叫做任務描述符更為貼切,因為每個線程也都有自己的 task_struct)。內(nèi)核使用 slab 分配器為每個進程/線程分配這塊空間。

  • 如下圖所示:


【文章福利】小編推薦自己的Linux內(nèi)核技術交流群:【891587639】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。?!前100名進群領取,額外贈送一份價值699的內(nèi)核資料包(含視頻教程、電子書、實戰(zhàn)項目及代碼)? ? ?


  • 每個進程或線程都有只屬于自己的 task_struct 對象,是它們各自最為核心的數(shù)據(jù)結構。

task_struct 結構體中的主要元素

task_struct 結構體中的三個 ID 與一個指針

  1. pid:每個 task_struct 都會有一個不同的 ID,就是這個 PID。

  2. tid:線程 ID,用來標識每個線程的。

  3. tgid:線程組領頭線程的 PID,事實上就是主線程的 PID。當創(chuàng)建一個子進程時,它的 tgid 與 pid 相等;當創(chuàng)建一個線程時,它的 tgid 等于主線程的 pid。getpid() 函數(shù)事實上返回的是當前進程或線程的 tgid。

  4. pgid:進程組領頭進程的 PID。

  5. sid:會話領頭進程的 PID。

  6. group_leader:是一個 task_struct 類型的指針,指向的是進程組的組長對應的 task_struct 對象。

虛擬內(nèi)存地址空間

內(nèi)存管理

  • 內(nèi)存是由內(nèi)核來管理的。

  • 內(nèi)存被分為 n 個頁框,然后進一步組織為多個區(qū)。而裝入頁框中的內(nèi)容稱為頁。

  • 當內(nèi)核函數(shù)申請內(nèi)存時,內(nèi)核總是立即滿足(因為內(nèi)核完全信任它們,所以優(yōu)先級最高)。在分配適當內(nèi)存空間后,將其映射到內(nèi)核地址空間中(3-4GB 中的某部分空間),然后將地址映射寫入頁表。

  • 申請內(nèi)存空間的內(nèi)核函數(shù)有 vmalloc, kmalloc, alloc_pages, __get_free_pages 等。

內(nèi)核常駐內(nèi)存

  • 就是說,內(nèi)核地址空間(3-4GB)中的頁面所映射的頁框始終在物理內(nèi)存中存在,不會被換出。即使是 vmalloc 動態(tài)申請的頁面也會一直在物理內(nèi)存中,直至通過相關內(nèi)核函數(shù)釋放掉。

  • 其原因在于,一方面內(nèi)核文件不是太大,完全可以一次性裝入物理內(nèi)存;另一方面在于即使是動態(tài)申請內(nèi)存空間,也能立即得到滿足。

  • 因此,處于內(nèi)核態(tài)的普通進程或內(nèi)核線程(后面會提到)不會因為頁面沒有在內(nèi)存中而產(chǎn)生缺頁異常(不過處于內(nèi)核態(tài)的普通進程會因為頁表項沒有同步的原因而產(chǎn)生缺頁異常)。

為什么要有虛擬地址空間

  • 普通進程在申請內(nèi)存空間時會被內(nèi)核認為是不緊要的,優(yōu)先級較低。因而總是延遲處理,在之后的某個時候才會真正為其分配物理內(nèi)存空間。

  • 比如,普通進程中的 malloc 函數(shù)在申請物理內(nèi)存空間時,內(nèi)核不會直接為其分配頁框。

  • 另一方面,普通進程對應的可執(zhí)行程序文件較大,不能夠立即裝入內(nèi)存,而是采取運行時按需裝入。

  • 要實現(xiàn)這種延遲分配策略,就需要引入一種新的地址空間,即 虛擬地址空間??蓤?zhí)行文件在裝入時或者進程在執(zhí)行 malloc 時,內(nèi)核只會為其分配適當大小的虛擬地址空間。

虛擬地址空間并不單純地指線性地址空間。準確地說,指的是頁面不能因為立即裝入物理內(nèi)存而采取折衷處理后擁有的線性地址空間。因此,雖然普通進程的虛擬地址空間為 4GB,但是從內(nèi)核的角度來說,內(nèi)核地址空間(也是線性空間)不能稱為虛擬地址空間,內(nèi)核線程不擁有也不需要虛擬地址空間。因此,虛擬地址空間只針對普通進程。

  • 當然,這樣的話就會產(chǎn)生所要訪問的頁面不在物理內(nèi)存中而發(fā)生缺頁異常。

虛擬地址空間的劃分

  • 每一個普通進程都擁有 4GB 的虛擬地址空間(對于 32 位的 CPU 來說,即 232 B)。

  • 主要分為兩部分,一部分是用戶空間(0-3GB),一部分是內(nèi)核空間(3-4GB)。每個普通進程都有自己的用戶空間,但是內(nèi)核空間被所有普通進程所共享。

  • 如下圖所示:


虛擬地址空間并不單純地指線性地址空間。準確地說,指的是頁面不能因為立即裝入物理內(nèi)存而采取折衷處理后擁有的線性地址空間。因此,雖然普通進程的虛擬地址空間為 4GB,但是從內(nèi)核的角度來說,內(nèi)核地址空間(也是線性空間)不能稱為虛擬地址空間,內(nèi)核線程不擁有也不需要虛擬地址空間。因此,虛擬地址空間只針對普通進程。

另外,

用戶態(tài)下的普通進程只能訪問 0-3GB 的用戶空間;內(nèi)核態(tài)下的普通進程既能訪問 0-3GB 的用戶空間,也能訪問 3-4GB 的內(nèi)核空間(內(nèi)核態(tài)下的普通進程有時也會需要訪問用戶空間)。

普通線程的用戶堆棧與寄存器

  • 對于多線程環(huán)境,雖然所有線程都共享同一片虛擬地址空間,但是每個線程都有自己的用戶??臻g和寄存器,而用戶堆仍然是所有線程共享的。

  • ??臻g的使用是有明確限制的,棧中相鄰的任意兩條數(shù)據(jù)在地址上都是連續(xù)的。試想,假設多個普通線程函數(shù)都在執(zhí)行遞歸操作。如果多個線程共有用戶棧空間,由于線程是異步執(zhí)行的,那么某個線程從棧中取出數(shù)據(jù)時,這條數(shù)據(jù)就很有可能是其它線程之前壓入的,這就導致了沖突。所以,每個線程都應該有自己的用戶棧空間。

  • 寄存器也是如此,如果共用寄存器,很可能出現(xiàn)使用混亂的現(xiàn)象。

  • 而堆空間的使用則并沒有這樣明確的限制,某個線程在申請堆空間時,內(nèi)核只要從堆空間中分配一塊大小合適的空間給線程就行了。所以,多個線程同時執(zhí)行時不會出現(xiàn)像棧那樣產(chǎn)生沖突的情況,因而線程組中的所有線程共享用戶堆。

  • 那么在創(chuàng)建線程時,內(nèi)核是怎樣為每個線程分配??臻g的呢?

  • 由之前所講解可知,進程/線程的創(chuàng)建主要是由 clone 系統(tǒng)調(diào)用完成的。而 clone 系統(tǒng)調(diào)用的參數(shù)中有一個 void *child_stack,它就是用來指向所創(chuàng)建的進程/線程的堆棧指針。

  • 而在該進程/線程在用戶態(tài)下是通過調(diào)用 pthread_create 庫函數(shù)而陷入內(nèi)核的。對于 pthread_create 函數(shù),它則會調(diào)用一個名為 pthread_allocate_stack 的函數(shù),專門用來為所創(chuàng)建的線程分配的棧空間(通過 mmap 系統(tǒng)調(diào)用)。然后再將這個棧空間的地址傳遞給 clone 系統(tǒng)調(diào)用。這也是為什么線程組中的每個線程都有自己的??臻g。

普通進程的頁表

  • 有兩種頁表,一種是內(nèi)核頁表(會在后面說明),另一種是進程頁表。

  • 普通進程使用的則是進程頁表,而且每個普通進程都有自己的進程頁表。如果是多線程,則這些線程共享的是主線程的進程頁表。

四級頁表

  • 現(xiàn)在的 Linux 內(nèi)核中采用四級頁表,分別為:

  1. 頁全局目錄 (Page Global Directory, pgd);

  2. 頁上級目錄 (Page Upper Directory, pud);

  3. 頁中間目錄 (Page Middle Directory, pmd);

  4. 頁表 (Page Table, pt)。

  • task_struct 中的 mm_struct 對象用于管理該進程(或者線程共享的)頁表。準確地說,mm_struct 中的 pgd 指針指向著該進程的頁全局目錄。

普通進程的頁全局目錄

  • 普通進程的頁全局目錄中,第一部分表項映射的線性地址為 0-3GB 部分,剩余部分存放的是主內(nèi)核頁全局目錄(后面會提到)中的所有表項。

內(nèi)核線程

  • 內(nèi)核線程是一種只運行在內(nèi)核地址空間的線程。所有的內(nèi)核線程共享內(nèi)核地址空間(對于 32 位系統(tǒng)來說,就是 3-4GB 的虛擬地址空間),所以也共享同一份內(nèi)核頁表。這也是為什么叫內(nèi)核線程,而不叫內(nèi)核進程的原因。

  • 由于內(nèi)核線程只運行在內(nèi)核地址空間中,只會訪問 3-4GB 的內(nèi)核地址空間,不存在虛擬地址空間,因此每個內(nèi)核線程的 task_struct 對象中的 mm 為 NULL。

普通線程雖然也是同主線程共享地址空間,但是它的 task_struct 對象中的 mm 不為空,指向的是主線程的 mm_struct 對象。

普通進程與內(nèi)核線程有如下區(qū)別:

  • 內(nèi)核線程只運行在內(nèi)核態(tài),而普通進程既可以運行在內(nèi)核態(tài),也可以運行在用戶態(tài);內(nèi)核線程只使用 3-4GB (假設為 32 位系統(tǒng)) 的內(nèi)核地址空間(共享的),但普通進程由于既可以運行在用戶態(tài),又可以運行在內(nèi)核態(tài),因此可以使用 4GB 的虛擬地址空間。

  • 系統(tǒng)在正式啟動內(nèi)核時,會執(zhí)行 start_kernel 函數(shù)。在這個函數(shù)中,會自動創(chuàng)建一個進程,名為 init_task。其 PID 為 0,運行在內(nèi)核態(tài)中。然后開始執(zhí)行一系列初始化。

init 內(nèi)核線程

  • init_task 在執(zhí)行 rest_init 函數(shù)時,會執(zhí)行 kernel_thread 創(chuàng)建 init 內(nèi)核線程。它的 PID 為 1,用來完成內(nèi)核空間初始化。

  • 在內(nèi)核空間完成初始化后,會調(diào)用 exceve 執(zhí)行 init 可執(zhí)行程序 (/sbin/init)。之后,init 內(nèi)核線程變成了一個普通的進程,運行在用戶空間中。

init 內(nèi)核線程沒有地址空間,且它的 task_struct 對象中的 mm 為 NULL。因此,執(zhí)行 exceve 會使這個 mm 指向一個 mm_struct,而不會影響到 init_task 進程的地址空間。也正因為此,init 在轉變?yōu)檫M程后,其 PID 沒變,仍為 1。

  • 創(chuàng)建完 init 內(nèi)核線程后,init_task 進程演變?yōu)?idle 進程(PID 仍為 0)。

  • 之后,init 進程再根據(jù)再啟動其它系統(tǒng)進程 (/etc/init.d 目錄下的各個可執(zhí)行文件)。

kthreadd 內(nèi)核線程

  • init_task 進程演變?yōu)?idle 進程后,idle 進程會執(zhí)行 kernel_thread 來創(chuàng)建 kthreadd 內(nèi)核線程(仍然在 rest_init 函數(shù)中)。它的 PID 為 2,用來創(chuàng)建并管理其它內(nèi)核線程(用 kthread_create, kthread_run, kthread_stop 等內(nèi)核函數(shù))。

  • 系統(tǒng)中有很多內(nèi)核守護進程 (線程),可以通過:

  • 進行查看,其中帶有 [] 號的就屬于內(nèi)核守護進程。它們的祖先都是這個 kthreadd 內(nèi)核線程。

主內(nèi)核頁全局目錄

  • 內(nèi)核維持著一組自己使用的頁表,也即主內(nèi)核頁全局目錄。當內(nèi)核在初始化完成后,其存放在 swapper_pg_dir 中,而且所有的普通進程和內(nèi)核線程就不再使用它了。

內(nèi)核線程如何訪問頁表

active_mm

  • 對于內(nèi)核線程,雖然它的 task_struct 中的 mm 為 NULL,但是它仍然需要訪問內(nèi)核空間,因此需要知道關于內(nèi)核空間映射到物理內(nèi)存的頁表。然而不再使用 swapper_pg_dir,因此只能另外想法解決。

  • 由于所有的普通進程的頁全局目錄中的后面部分為主內(nèi)核頁全局目錄,因此內(nèi)核線程只需要使用某個普通進程的頁全局目錄就可以了。

  • 在 Linux 中,task_struct 中還有一個很重要的元素為 active_mm,它主要就是用于內(nèi)核線程訪問主內(nèi)核頁全局目錄。

  • 對于普通進程來說,task_struct 中的 mm 和 active_mm 指向的是同一片區(qū)域;然而對內(nèi)核線程來說,task_struct 中的 mm 為 NULL,active_mm 指向的是前一個普通進程的 mm_struct 對象。

mm_users 和 mm_count

  • 但是這樣還是不行,因為如果因為前一個普通進程退出了而導致它的 mm_struct 對象也被釋放了,則內(nèi)核線程就訪問不到了。

  • 為此,mm_struct 對象維護了一個計數(shù)器 mm_count,專門用來對引用這個 mm_struct 對象的自身及內(nèi)核線程進行計數(shù)。初始時為 1,表示普通進程本身引用了它自己的 mm_struct 對象。只有當這個引用計數(shù)為 0 時,才會真正釋放這個 mm_struct 對象。

  • 另外,mm_struct 中還定義了一個 mm_users 計數(shù)器,它主要是用來對共享地址空間的線程計數(shù)。事實上,就是這個主線程所在線程組中線程的總個數(shù)。初始時為 1。

注意,兩者在實質(zhì)上都是針對引用 mm_struct 對象而設置的計數(shù)器。 不同的是,mm_count 是專門針對自身及內(nèi)核線程或引用 mm_struct 而進行計數(shù);而 mm_users 是專門針對該普通線程所在線程組的所有普通線程而進行計數(shù)。 另外,只有當 mm_count 為 0 時,才會釋放 mm_struct 對象,并不會因為 mm_users 為 0 就進行釋放。


Linux內(nèi)核線程及普通進程總結(趕快學起來吧~)的評論 (共 條)

分享到微博請遵守國家法律
木兰县| 泰顺县| 鄢陵县| 东山县| 老河口市| 金门县| 龙井市| 宁城县| 新平| 新兴县| 常山县| 曲阳县| 德昌县| 申扎县| 元阳县| 宝兴县| 剑河县| 遵义市| 遂宁市| 桂平市| 包头市| 敦化市| 祁门县| 临潭县| 明光市| 宁乡县| 女性| 浙江省| 九龙坡区| 平原县| 贵港市| 南雄市| 鹤庆县| 定陶县| 那坡县| 彰化市| 大同县| 逊克县| 余江县| 西平县| 汨罗市|