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

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

帶你玩轉(zhuǎn)Linux內(nèi)核進(jìn)程創(chuàng)建-fork背后隱藏的技術(shù)細(xì)節(jié)(一)

2022-05-26 17:53 作者:補(bǔ)給站Linux內(nèi)核  | 我要投稿
  • 環(huán)境:處理器架構(gòu):arm64內(nèi)核源碼:linux-5.11ubuntu版本:20.04.1代碼閱讀工具:vim+ctags+cscope

  • 今天正式開(kāi)通微信公眾號(hào)-Linux內(nèi)核遠(yuǎn)航者,終于以后可以發(fā)布Linux內(nèi)核相關(guān)的技術(shù)文章,既興奮又有一份責(zé)任感,做技術(shù)是個(gè)不歸路,面對(duì)現(xiàn)實(shí)的毒打,充滿了太多的無(wú)奈,也希望能不忘初心,為了紀(jì)念公眾號(hào)的誕生,想了很久決定寫一篇進(jìn)程創(chuàng)建的文章,fork對(duì)于Linux開(kāi)發(fā)者已經(jīng)耳熟能詳,但是我們是否真正理解fork的實(shí)現(xiàn)機(jī)理,當(dāng)然fork的實(shí)現(xiàn)中涉及到了太多的技術(shù)細(xì)節(jié),不可能將所有的細(xì)節(jié)都講清楚,因此本文主要從內(nèi)存管理和進(jìn)程管理兩個(gè)維度來(lái)窺探一下fork背后隱藏的技術(shù)細(xì)節(jié),希望能夠通過(guò)本文讓大家站在一個(gè)高度去看進(jìn)程創(chuàng)建。

  • 全文分為兩部分講解:fork的內(nèi)存管理部分和進(jìn)程管理部分,內(nèi)存管理主要講解子進(jìn)程如何構(gòu)建自己的內(nèi)存管理相關(guān)基礎(chǔ)設(shè)施,父子進(jìn)程如何共享地址空間的,寫時(shí)復(fù)制如何發(fā)生,頁(yè)表層面為我們做了哪些事情等等。而進(jìn)程管理主要講解,子進(jìn)程如何構(gòu)建自己的進(jìn)程管理相關(guān)基礎(chǔ)設(shè)施,如何加入到cpu的運(yùn)行隊(duì)列,第一次運(yùn)行時(shí)如何執(zhí)行等等。

  • 實(shí)際上,除了0號(hào)進(jìn)程,其他的所有進(jìn)程無(wú)論是內(nèi)核線程還是普通的用戶進(jìn)程和線程都是fork出來(lái)的,而創(chuàng)建進(jìn)程是內(nèi)核所做的事情,要么在內(nèi)核空間直接創(chuàng)建出所謂的內(nèi)核線程,要么是通過(guò)fork,clone這樣的系統(tǒng)調(diào)用陷入內(nèi)核空間來(lái)創(chuàng)建。對(duì)于內(nèi)核線程沒(méi)有異常級(jí)別的切換,構(gòu)建好調(diào)度相關(guān)基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)時(shí)就可以在第一次參與調(diào)度的時(shí)候執(zhí)行他的執(zhí)行函數(shù),任務(wù)切換的時(shí)候也不需要進(jìn)行地址空間切換。而對(duì)于用戶任務(wù)來(lái)說(shuō),需要異常級(jí)別的切換(也是一種上下文切換),任務(wù)切換的時(shí)候甚至還需要切換地址空間。

  • 說(shuō)明:我們將參與調(diào)度的實(shí)體稱為任務(wù),包括用戶進(jìn)程,用戶線程,內(nèi)核線程。

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


1.1、內(nèi)存相關(guān)基礎(chǔ)設(shè)施構(gòu)建

  • 我們移步到如下調(diào)用路徑(當(dāng)前處于copy_mm函數(shù)中):

首先,任務(wù)在創(chuàng)建的時(shí)候根據(jù)傳遞的fork的參數(shù)clone_flags來(lái)決定是否需要?jiǎng)?chuàng)建一個(gè)mm_struct結(jié)構(gòu)來(lái)管理任務(wù)的地址空間,如果傳遞過(guò)來(lái)的clone_flags帶有CLONE_VM標(biāo)志,則不需要?jiǎng)?chuàng)建,直接和父進(jìn)程共享地址空間即可,如內(nèi)核線程和用戶線程。

而當(dāng)傳遞過(guò)來(lái)的clone_flags不帶CLONE_VM標(biāo)志,則需要為子進(jìn)程創(chuàng)建新的地址空間,如創(chuàng)建子進(jìn)程,就調(diào)用到dup_mm中。為了看的清晰貼出了如下代碼:

  • 這里需要注意的地方有三個(gè)地方:1.通過(guò)allocate_mm分配屬于進(jìn)程自己的mm_struct結(jié)構(gòu)來(lái)管理自己的地址空間;2.通過(guò)mm_init來(lái)初始化mm_struct中相關(guān)成員;3.通過(guò)dup_mmap來(lái)復(fù)制父進(jìn)程的地址空間(實(shí)際上后面我們會(huì)看到是復(fù)制父進(jìn)程的vma以及頁(yè)表)。

  • 分配mm_struct結(jié)構(gòu)就不需要贅述,我們先看下mm_init,調(diào)用鏈如下:

這里有兩個(gè)地方暗藏玄機(jī),首先是mm_alloc_pgd,對(duì)于像amr64這種處理器架構(gòu)來(lái)說(shuō),只不過(guò)是分配一個(gè)進(jìn)程私有pge頁(yè)而已,當(dāng)va->pa轉(zhuǎn)換的時(shí)候,查找屬于當(dāng)前進(jìn)程的pgd表項(xiàng)。

但是,當(dāng)我們看其他處理器架構(gòu)mm_alloc_pgd的實(shí)現(xiàn)時(shí)會(huì)發(fā)現(xiàn),除了會(huì)分配pge頁(yè)還會(huì)做主內(nèi)核頁(yè)表的內(nèi)核空間的pge表項(xiàng)的同步工作,如riscv,x86。下面是riscv的實(shí)現(xiàn):

  • 這些處理器為什么要這樣多此一舉呢?原因是這樣的:當(dāng)內(nèi)核初始化完成轉(zhuǎn)換,進(jìn)程切換的時(shí)候都是使用tsk->mm->pgd指向的頁(yè)表作為base來(lái)進(jìn)程頁(yè)表遍歷(walk),對(duì)于arm64架構(gòu)來(lái)說(shuō),他有兩個(gè)頁(yè)表基址寄存器ttbr0_el1和ttbr1_el1(只考慮階段1的el0和el1的地址轉(zhuǎn)換),內(nèi)核在初始化的時(shí)候會(huì)將主內(nèi)核頁(yè)表swapper_pg_dir的地址存放在ttbr1_el1,進(jìn)程切換的時(shí)候?qū)⑦M(jìn)程tsk->mm->pgd存放在ttbr0_el1,當(dāng)進(jìn)行va->pa的轉(zhuǎn)換的時(shí)候,mmu會(huì)判斷地址是屬于用戶空間地址還是內(nèi)核空間,如果是用戶空間就使用ttbr0_el1作為base來(lái)進(jìn)行頁(yè)表walk,當(dāng)?shù)刂穼儆趦?nèi)核空間地址就使用ttbr1_el1作為base來(lái)進(jìn)行頁(yè)表walk。所有不需要同步內(nèi)核空間的pgd表項(xiàng),在訪問(wèn)內(nèi)核地址空間的內(nèi)容的時(shí)候沒(méi)有任何問(wèn)題。

  • 但是,像x86這樣的處理器架構(gòu)就不一樣了,只有一個(gè)頁(yè)表基址寄存器如cr3,所有fork子進(jìn)程的時(shí)候就需要同步主內(nèi)核頁(yè)表的內(nèi)核相關(guān)部分的pgd表項(xiàng),這樣通過(guò)一個(gè)頁(yè)表基址寄存器就可以找到內(nèi)核空間的各級(jí)表項(xiàng)。

  • 接下來(lái)我們看一下,mm_init中的另一個(gè)比較重要的初始化:

  • 可以看的最后設(shè)置了mm->context.id為0,這點(diǎn)很重要,當(dāng)進(jìn)程調(diào)度的時(shí)候進(jìn)行地址空間切換,如果mm->context.id為0就為進(jìn)程分配新的ASID(ASID技術(shù)為了在進(jìn)程地址空間切換的時(shí)候防止頻繁刷tlb的一種優(yōu)化)。

  • 好了,講完了mm_init相關(guān)的一些隱藏的技術(shù)細(xì)節(jié),我們?cè)诜祷豥up_mm中來(lái)看看dup_mmap的實(shí)現(xiàn):

  • 這里注意看兩個(gè)地方,分別是:vm_area_dup和copy_page_range(這是fork的主要內(nèi)存開(kāi)銷)。

  • vm_area_dup主要是拷貝父進(jìn)程的vma,代碼實(shí)現(xiàn)很簡(jiǎn)單,我們重點(diǎn)來(lái)看copy_page_range。

  • 對(duì)于每一個(gè)vma都調(diào)用copy_page_range,此函數(shù)會(huì)遍歷vma中每一個(gè)虛擬頁(yè),然后拷貝父進(jìn)程的頁(yè)表到子進(jìn)程(虛擬頁(yè)對(duì)應(yīng)的頁(yè)表存在的話),這里主要是頁(yè)表遍歷的代碼,從pgd->pud->pmd->pte。我們不關(guān)注頁(yè)表拷貝過(guò)程,我們把目光聚集到對(duì)私有頁(yè)面的處理上來(lái):

  • 最終,我們看的在copy_present_pte函數(shù)中,對(duì)父子進(jìn)程的寫保護(hù)處理,也就是當(dāng)發(fā)現(xiàn)父進(jìn)程的vma的屬性為私有可寫的時(shí)候,就設(shè)置父進(jìn)程和子進(jìn)程的相關(guān)的頁(yè)表項(xiàng)為只讀。這點(diǎn)很重要,因?yàn)檫@樣既保證了父子進(jìn)程的地址空間的共享(讀的時(shí)候),又保證了他們有獨(dú)立的地址空間(寫的時(shí)候)。

  • 總結(jié)來(lái)說(shuō):fork中構(gòu)建了內(nèi)存管理相關(guān)的基礎(chǔ)設(shè)施如mm_struct ,vma,pgd頁(yè)等,以及拷貝父進(jìn)程的vma和拷貝父進(jìn)程的頁(yè)表來(lái)達(dá)到和父進(jìn)程共享地址空間的目的,可以看的處理這種共享并不是像共享內(nèi)存那種純粹意義上的共享,而是讓子進(jìn)程能夠使用父進(jìn)程的內(nèi)存資源,而且在寫的時(shí)候能夠讓父子進(jìn)程開(kāi)來(lái)創(chuàng)造了條件(寫保護(hù))。當(dāng)然這種方式并沒(méi)有拷貝父進(jìn)程的任何物理頁(yè),只是通過(guò)頁(yè)表來(lái)共享而已,當(dāng)然這種內(nèi)存開(kāi)銷也是很大的,如果子進(jìn)程fork之后立馬進(jìn)程exec加載自己的程序,這這種寫時(shí)復(fù)制意義并不大,但是試想,如果不通過(guò)頁(yè)表共享,則子進(jìn)程寸步難行,甚至連exec都無(wú)法調(diào)用。

1.2、內(nèi)存基礎(chǔ)設(shè)施的使用之--寫實(shí)復(fù)制的發(fā)生

  • fork創(chuàng)建完子進(jìn)程后,通過(guò)復(fù)制父進(jìn)程的頁(yè)表來(lái)共享父進(jìn)程的地址空間,我們知道對(duì)于私有的可寫的頁(yè),設(shè)置了父子進(jìn)程的相應(yīng)頁(yè)表為為只讀,這樣就為寫實(shí)復(fù)制創(chuàng)造了頁(yè)表層面上的條件。當(dāng)父進(jìn)程或者子進(jìn)程,寫寫保護(hù)的頁(yè)時(shí)觸發(fā)訪問(wèn)權(quán)限異常:

  • 處理器架構(gòu)捕獲異常后,進(jìn)入通用的缺頁(yè)異常處理路徑:

在匿名頁(yè)缺頁(yè)異常處理路徑中,判斷這個(gè)頁(yè)錯(cuò)誤是寫保護(hù)錯(cuò)誤(也就是判斷虛擬頁(yè)可寫可是對(duì)應(yīng)的頁(yè)表為只讀)時(shí),就會(huì)調(diào)用do_wp_page做寫實(shí)復(fù)制處理:

  • 可以看的出來(lái),fork時(shí)對(duì)私有可寫的頁(yè)面做寫保護(hù)的準(zhǔn)備,在父子進(jìn)程有一方發(fā)生寫操作時(shí)觸發(fā)了處理器的訪問(wèn)權(quán)限缺頁(yè)異常,異常處理程序重新分配了新的頁(yè)面給了發(fā)起寫操作的進(jìn)程,父子進(jìn)程對(duì)應(yīng)這個(gè)頁(yè)面的引用就此分道揚(yáng)鑣。

1.3、內(nèi)存基礎(chǔ)設(shè)施的使用之--各級(jí)頁(yè)表創(chuàng)建

  • 我們知道,對(duì)于用戶進(jìn)程來(lái)說(shuō),內(nèi)核并不是馬上滿足進(jìn)程對(duì)于物理頁(yè)的請(qǐng)求,而僅僅是為他分配虛擬頁(yè),內(nèi)核采用一種惰性的內(nèi)存分配的方式,知道訪問(wèn)的最后一刻才為進(jìn)程分配物理頁(yè),這既是所謂的內(nèi)核的按需分配/掉頁(yè)機(jī)制。進(jìn)程fork的時(shí)候,僅僅分配了一級(jí)頁(yè)表頁(yè)也就是私有的pgd頁(yè),其他的各級(jí)頁(yè)表并沒(méi)有分配,當(dāng)進(jìn)程第一次訪問(wèn)虛擬頁(yè)時(shí),發(fā)生缺頁(yè)異常來(lái)分配:缺頁(yè)異常中分配各級(jí)頁(yè)表路徑如下:

  • 可以看的缺頁(yè)異常處理中按需創(chuàng)建了所需要的各級(jí)頁(yè)表。

1.4、內(nèi)存基礎(chǔ)設(shè)施的使用之--進(jìn)程調(diào)度地址空間的切換

  • 進(jìn)程fork之后最終會(huì)參與系統(tǒng)調(diào)度,系統(tǒng)為其分配一定的cpu時(shí)間,在進(jìn)程切換的時(shí)候,對(duì)于用戶進(jìn)程來(lái)說(shuō),處理要切換處理器狀態(tài)(如pc,sp等),最重要的就是切換地址空間,這樣進(jìn)程運(yùn)行的時(shí)候訪問(wèn)的才是自己地址空間的東西,也達(dá)到了虛擬地址空間隔離的效果。

  • 現(xiàn)在我們移步到進(jìn)程調(diào)度相關(guān)代碼,主要來(lái)看下進(jìn)程地址空間切換部分:

  • 可以看的對(duì)于內(nèi)核線程,它不需要切換地址空間,其實(shí)這里的地址空間指得是用戶虛擬地址空間,因?yàn)樗皇褂脙?nèi)核空間(所有進(jìn)程共享),但是他做了一步比較重要的操作,即是next->active_mm = prev->active_mm,這樣做的目的是:內(nèi)核線程運(yùn)行過(guò)程中也會(huì)不斷的發(fā)生va->pa的轉(zhuǎn)換,而轉(zhuǎn)化需要頁(yè)表,就借用上一個(gè)用戶進(jìn)程的頁(yè)表作為base(頁(yè)表walk的時(shí)候從prev->active_mm->pgd開(kāi)始)。

  • 說(shuō)完了內(nèi)核線程我們來(lái)看看用戶任務(wù)是如何切換地址空間的。

  • 這里依然有我們需要注意的地方,那就是當(dāng)發(fā)現(xiàn)prev != next,即是前一個(gè)任務(wù)和即將要切換的任務(wù)的地址空間不相等的時(shí)候才會(huì)執(zhí)行__switch_mm做地址空間切換,如果相等就不需要切換,大家可能已經(jīng)知道了,如果是兩個(gè)屬于同一進(jìn)程的不同線程之間(也有可能是同一進(jìn)程)不需要切換地址空間(他們共享地址空間,但是調(diào)度是獨(dú)立調(diào)度)。

  • 接下來(lái),當(dāng)發(fā)現(xiàn)是兩個(gè)不同的進(jìn)程直接切換,那么需要切換地址空間了。

  • 實(shí)際上,完成了上面的操作也就完成了進(jìn)程地址空間的切換。這里需要設(shè)置兩個(gè)頁(yè)表基址寄存器:ttbr0_el1 和 ttbr1_el1 。內(nèi)核將mm->pgd的虛擬地址轉(zhuǎn)化為物理地址之后設(shè)置到了ttbr0_el1,將為進(jìn)程分配的ASID設(shè)置到了ttbr1_el1(其實(shí)ttbr0_el1和ttbr1_el1都有ASID域,究竟設(shè)置到那個(gè)寄存器由TCR_EL1的A1, bit [22]來(lái)決定,為1設(shè)置到ttbr1_el1,內(nèi)核初始化的時(shí)候就是設(shè)置為1)。

  • 那么問(wèn)題來(lái)了,為什么說(shuō)我設(shè)置了ttbr0_el1 和 ttbr1_el1就完成了進(jìn)程地址空間切換呢?待我一一到來(lái)(假如地址合法)。1)訪問(wèn)用戶空間虛擬地址 當(dāng)?shù)谝淮卧L問(wèn)一個(gè)虛擬地址的時(shí)候,則mmu會(huì)在tlb中查找對(duì)應(yīng)的表項(xiàng),顯然查找不到,則這個(gè)時(shí)候就需要遍歷多級(jí)頁(yè)表,那么這個(gè)時(shí)候就需要有一個(gè)base地址開(kāi)始遍歷,判斷地址屬于用戶空間地址,那么就從ttbr0_el1中獲取這個(gè)地址,然后就會(huì)根據(jù)ttbr0_el1找到屬于當(dāng)前進(jìn)程在fork時(shí)創(chuàng)建的pgd頁(yè),然后結(jié)合虛擬地址就可以遍歷各級(jí)頁(yè)表表項(xiàng)(當(dāng)然會(huì)由缺頁(yè)異常來(lái)分配各級(jí)頁(yè)表并填充相應(yīng)表項(xiàng)),最終將葉子表項(xiàng)(即是最后一級(jí)頁(yè)表表項(xiàng))填充到tlb中,并返回物理地址。第二次再訪問(wèn)的時(shí)候,就直接可以在tlb中找到,不需要遍歷多級(jí)頁(yè)表。2)訪問(wèn)內(nèi)核空間虛擬地址 訪問(wèn)內(nèi)核空間虛擬地址,也會(huì)首先從tlb中查找對(duì)應(yīng)的表項(xiàng),找不到就會(huì)從ttbr1_el1開(kāi)始遍歷各級(jí)頁(yè)表,然后最終將葉子表項(xiàng)(即是最后一級(jí)頁(yè)表表項(xiàng))填充到tlb中,并返回物理地址。

  • 可以看到每一次做va->pa的地址翻譯的時(shí)候首先在tlb中查找,上面忘記說(shuō)了一點(diǎn),那就是對(duì)于用戶空間虛擬地址tlb的查找需要根據(jù)va和ASID共同查找(內(nèi)核空間虛擬地址所有進(jìn)程共享不需要ASID), tlb沒(méi)有找到就要接受系統(tǒng)懲罰,需要遍歷多級(jí)頁(yè)表項(xiàng)然后獲得所需要的表項(xiàng)從表項(xiàng)中獲得物理地址,這個(gè)過(guò)程呢需要根據(jù)是用戶空間虛擬地址還是內(nèi)核空間虛擬地址,從ttbr0_el1或 ttbr1_el1開(kāi)始遍歷多級(jí)頁(yè)表,然后將表項(xiàng)填入到tlb。這里就使用了fork時(shí)創(chuàng)建的基礎(chǔ)設(shè)施,mm->pgd已經(jīng)相應(yīng)的ASID結(jié)構(gòu),在缺頁(yè)異常時(shí)填充各級(jí)表項(xiàng),進(jìn)程切換時(shí)來(lái)使用他們。

  • 下面給出了一個(gè)用戶進(jìn)程的內(nèi)存組織圖(有fork時(shí)創(chuàng)建以及缺頁(yè)異常時(shí)創(chuàng)建和填充)

  • 講到這里,我們的fork時(shí)的第一個(gè)維度內(nèi)存管理部分講解完了,下面給出大致總結(jié):fork的時(shí)候會(huì)創(chuàng)建內(nèi)核管理的一些基礎(chǔ)設(shè)施:如mm_struct, vma等用于描述進(jìn)程自己的地址空間,然后會(huì)創(chuàng)建出進(jìn)程私有的pgd頁(yè),用于頁(yè)表遍歷時(shí)填充頁(yè)表,然后還會(huì)拷貝父進(jìn)程所有的vma,然后就是對(duì)于每個(gè)vma做頁(yè)表的拷貝和寫保護(hù)操作。后面的pud pmd的其他各級(jí)頁(yè)表的創(chuàng)建和填充工作由缺頁(yè)異常處理來(lái)完成,可以看的fork的主要開(kāi)銷為vma和頁(yè)表的拷貝,而這種拷貝看似多余但又不可或缺。

  • 接下來(lái)介紹fork的第二個(gè)維度-進(jìn)程管理相關(guān)的隱藏技術(shù)細(xì)節(jié),由于本文章篇幅較長(zhǎng),將放在下一篇文章中,敬請(qǐng)期待,感謝閱讀!


帶你玩轉(zhuǎn)Linux內(nèi)核進(jìn)程創(chuàng)建-fork背后隱藏的技術(shù)細(xì)節(jié)(一)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
常山县| 香港| 印江| 大港区| 榆中县| 体育| 苗栗市| 登封市| 南漳县| 遂宁市| 化州市| 汉中市| 凌云县| 贺州市| 呼伦贝尔市| 云和县| 正宁县| 建湖县| 大丰市| 开江县| 长葛市| 乐清市| 扎囊县| 桦南县| 雷波县| 锦州市| 辽阳县| 毕节市| 白山市| 资源县| 玛纳斯县| 马尔康县| 四子王旗| 田阳县| 伊金霍洛旗| 岗巴县| 阿尔山市| 湟源县| 平顺县| 县级市| 嫩江县|