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

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

一篇搞懂Linux內(nèi)核線程及普通進(jìn)程總結(jié)(超詳細(xì)~)

2022-06-22 20:09 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿

Linux 中的進(jìn)程與線程

  • 對(duì)于 Linux 來(lái)講,所有的線程都當(dāng)作進(jìn)程來(lái)實(shí)現(xiàn),因?yàn)闆]有單獨(dú)為線程定義特定的調(diào)度算法,也沒有單獨(dú)為線程定義特定的數(shù)據(jù)結(jié)構(gòu)(所有的線程或進(jìn)程的核心數(shù)據(jù)結(jié)構(gòu)都是 task_struct)。

對(duì)于一個(gè)進(jìn)程,相當(dāng)于是它含有一個(gè)線程,就是它自身。對(duì)于多線程來(lái)說(shuō),原本的進(jìn)程稱為主線程,它們?cè)谝黄鸾M成一個(gè)線程組。

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

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

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

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

三個(gè)數(shù)據(jù)結(jié)構(gòu)

  • 每個(gè)進(jìn)程或線程都有三個(gè)數(shù)據(jù)結(jié)構(gòu),分別是:

  1. struct thread_info

  2. struct task_struct

  3. 內(nèi)核棧

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

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

  • thread_info 結(jié)構(gòu)體中有一個(gè) struct task_struct *task,task 指向的就是這個(gè)進(jìn)程或線程相關(guān)的 task_struct 對(duì)象(也在內(nèi)核空間中),這個(gè)對(duì)象叫做進(jìn)程描述符(叫做任務(wù)描述符更為貼切,因?yàn)槊總€(gè)線程也都有自己的 task_struct)。內(nèi)核使用 slab 分配器為每個(gè)進(jìn)程/線程分配這塊空間。

  • 如下圖所示:


【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ?


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

task_struct 結(jié)構(gòu)體中的主要元素

task_struct 結(jié)構(gòu)體中的三個(gè) ID 與一個(gè)指針

  1. pid:每個(gè) task_struct 都會(huì)有一個(gè)不同的 ID,就是這個(gè) PID。

  2. tid:線程 ID,用來(lái)標(biāo)識(shí)每個(gè)線程的。

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

  4. pgid:進(jìn)程組領(lǐng)頭進(jìn)程的 PID。

  5. sid:會(huì)話領(lǐng)頭進(jìn)程的 PID。

  6. group_leader:是一個(gè) task_struct 類型的指針,指向的是進(jìn)程組的組長(zhǎng)對(duì)應(yīng)的 task_struct 對(duì)象。

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

內(nèi)存管理

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

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

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

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

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

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

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

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

為什么要有虛擬地址空間

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

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

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

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

虛擬地址空間并不單純地指線性地址空間。準(zhǔn)確地說(shuō),指的是頁(yè)面不能因?yàn)榱⒓囱b入物理內(nèi)存而采取折衷處理后擁有的線性地址空間。因此,雖然普通進(jìn)程的虛擬地址空間為 4GB,但是從內(nèi)核的角度來(lái)說(shuō),內(nèi)核地址空間(也是線性空間)不能稱為虛擬地址空間,內(nèi)核線程不擁有也不需要虛擬地址空間。因此,虛擬地址空間只針對(duì)普通進(jìn)程。

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

虛擬地址空間的劃分

  • 每一個(gè)普通進(jìn)程都擁有 4GB 的虛擬地址空間(對(duì)于 32 位的 CPU 來(lái)說(shuō),即 232 B)。

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

  • 如下圖所示:



虛擬地址空間并不單純地指線性地址空間。準(zhǔn)確地說(shuō),指的是頁(yè)面不能因?yàn)榱⒓囱b入物理內(nèi)存而采取折衷處理后擁有的線性地址空間。因此,雖然普通進(jìn)程的虛擬地址空間為 4GB,但是從內(nèi)核的角度來(lái)說(shuō),內(nèi)核地址空間(也是線性空間)不能稱為虛擬地址空間,內(nèi)核線程不擁有也不需要虛擬地址空間。因此,虛擬地址空間只針對(duì)普通進(jìn)程。

另外,

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

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

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

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

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

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

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

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

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

普通進(jìn)程的頁(yè)表

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

  • 普通進(jìn)程使用的則是進(jìn)程頁(yè)表,而且每個(gè)普通進(jìn)程都有自己的進(jìn)程頁(yè)表。如果是多線程,則這些線程共享的是主線程的進(jìn)程頁(yè)表。

四級(jí)頁(yè)表

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

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

  2. 頁(yè)上級(jí)目錄 (Page Upper Directory, pud);

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

  4. 頁(yè)表 (Page Table, pt)。

  • task_struct 中的 mm_struct 對(duì)象用于管理該進(jìn)程(或者線程共享的)頁(yè)表。準(zhǔn)確地說(shuō),mm_struct 中的 pgd 指針指向著該進(jìn)程的頁(yè)全局目錄。

普通進(jìn)程的頁(yè)全局目錄

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

內(nèi)核線程

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

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

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

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

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

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

init 內(nèi)核線程

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

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

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

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

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

kthreadd 內(nèi)核線程

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

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

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

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

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

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

active_mm

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

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

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

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

mm_users 和 mm_count

  • 但是這樣還是不行,因?yàn)槿绻驗(yàn)榍耙粋€(gè)普通進(jìn)程退出了而導(dǎo)致它的 mm_struct 對(duì)象也被釋放了,則內(nèi)核線程就訪問(wèn)不到了。

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

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

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



一篇搞懂Linux內(nèi)核線程及普通進(jìn)程總結(jié)(超詳細(xì)~)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
盐津县| 孟津县| 凤庆县| 满洲里市| 津南区| 宁强县| 昭通市| 和顺县| 商洛市| 资源县| 遵义市| 会昌县| 阿拉善盟| 万宁市| 宁强县| 赤峰市| 略阳县| 阳原县| 陆丰市| 谷城县| 鲁山县| 尤溪县| 永年县| 灵宝市| 普格县| 克东县| 彭山县| 平顺县| 林州市| 铜陵市| 永仁县| 壶关县| 新建县| 锦屏县| 内丘县| 夏邑县| 门头沟区| 太和县| 江陵县| 巴林左旗| 濉溪县|