前邊我已經(jīng)說過了內(nèi)核是如何管理物理內(nèi)存。但事實(shí)是內(nèi)核是操作系統(tǒng)的核心,,不光管理本身的內(nèi)存,,還要管理進(jìn)程的地址空間。linux操作系統(tǒng)采用虛擬內(nèi)存
技術(shù),,所有進(jìn)程之間以虛擬方式共享內(nèi)存,。進(jìn)程地址空間由每個(gè)進(jìn)程中的線性地址區(qū)組成,而且更為重要的特點(diǎn)是內(nèi)核允許進(jìn)程使用該空間中的地址,。通常情況況
下,,每個(gè)進(jìn)程都有唯一的地址空間,而且進(jìn)程地址空間之間彼此互不相干。但是進(jìn)程之間也可以選擇共享地址空間,,這樣的進(jìn)程就叫做線程,。
內(nèi)核使用內(nèi)存描述符結(jié)構(gòu)表示進(jìn)程的地址空間,由結(jié)構(gòu)體mm_struct結(jié)構(gòu)體表示,,定義在linux/sched.h中,,如下:
我前邊說過的進(jìn)程描述符中有一個(gè)mm域,這里邊存放的就是該進(jìn)程使用的內(nèi)存描述符,,通過current->mm便可以指向當(dāng)前進(jìn)程的內(nèi)存描述符,。 fork函數(shù)利用copy_mm()函數(shù)就實(shí)現(xiàn)了復(fù)制父進(jìn)程的內(nèi)存描述符,而子進(jìn)程中的mm_struct結(jié)構(gòu)體實(shí)際是通過文件 kernel/fork.c中的allocate_mm()宏從mm_cachep slab緩存中分配得到的,。通常,,每個(gè)進(jìn)程都有唯一的mm_struct結(jié)構(gòu)體。 前邊也說過,,在linux中,,進(jìn)程和線程其實(shí)是一樣的,唯一的不同點(diǎn)就是是否共享這里的地址空間,。這個(gè)可以通過CLONE_VM標(biāo)志來實(shí)現(xiàn),。linux內(nèi) 核并不區(qū)別對(duì)待它們,,線程對(duì)內(nèi)核來說僅僅是一個(gè)共向特定資源的進(jìn)程而已。好了,,如果你設(shè)置這個(gè)標(biāo)志了,,似乎很多問題都解決了。不再要 allocate_mm函數(shù)了,,前邊剛說作用,。而且在copy_mm()函數(shù)中將mm域指向其父進(jìn)程的內(nèi)存描述符就可以了,如下:
最后,,當(dāng)進(jìn)程退出的時(shí)候,,內(nèi)核調(diào)用exit_mm()函數(shù),這個(gè)函數(shù)調(diào)用mmput()來減少內(nèi)存描述符中的mm_users用戶計(jì)數(shù),。如果計(jì)數(shù)降為0,, 繼續(xù)調(diào)用mmdrop函數(shù),減少mm_count使用計(jì)數(shù),。如果使用計(jì)數(shù)也為0,,則調(diào)用free_mm()宏通過kmem_cache_free()函數(shù) 將mm_struct結(jié)構(gòu)體歸還到mm_cachep slab緩存中,。
但對(duì)于內(nèi)核而言,,內(nèi)核線程沒有進(jìn)程地址空間,也沒有相關(guān)的內(nèi)存描述符,,內(nèi)核線程對(duì)應(yīng)的進(jìn)程描述符中mm域也為空,。但內(nèi)核線程還是需要使用一些數(shù)據(jù)的,比如
頁表,,為了避免內(nèi)核線程為內(nèi)存描述符和頁表浪費(fèi)內(nèi)存,,也為了當(dāng)新內(nèi)核線程運(yùn)行時(shí),避免浪費(fèi)處理器周期向新地址空間進(jìn)行切換,,內(nèi)核線程將直接使用前一個(gè)進(jìn)程
的內(nèi)存描述符,。回憶一下我剛說的進(jìn)程調(diào)度問題,,當(dāng)一個(gè)進(jìn)程被調(diào)度時(shí),,進(jìn)程結(jié)構(gòu)體中mm域指向的地址空間會(huì)被裝載到內(nèi)存,進(jìn)程描述符中的active_mm
域會(huì)被更新,,指向新的地址空間,。但我們這里的內(nèi)核是沒有mm域(為空),所以,,當(dāng)一個(gè)內(nèi)核線程被調(diào)度時(shí),,內(nèi)核發(fā)現(xiàn)它的mm域?yàn)镹ULL,就會(huì)保留前一個(gè)進(jìn)
程的地址空間,,隨后內(nèi)核更新內(nèi)核線程對(duì)應(yīng)的進(jìn)程描述符中的active域,,使其指向前一個(gè)進(jìn)程的內(nèi)存描述符,。所以在需要的時(shí)候,內(nèi)核線程便可以使用前一個(gè)
進(jìn)程的頁表,。因?yàn)閮?nèi)核線程不妨問用戶空間的內(nèi)存,,所以它們僅僅使用地址空間中和內(nèi)核內(nèi)存相關(guān)的信息,這些信息的含義和普通進(jìn)程完全相同,。
每個(gè)內(nèi)存描述符都對(duì)應(yīng)于地址進(jìn)程空間中的唯一區(qū)間,。vm_mm域指向和VMA相關(guān)的mm_struct結(jié)構(gòu)體,。兩個(gè)獨(dú)立的進(jìn)程將同一個(gè)文件映射到各自的地 址空間,它們分別都會(huì)有一個(gè)vm_area_struct結(jié)構(gòu)體來標(biāo)志自己的內(nèi)存區(qū)域,;但是如果兩個(gè)線程共享一個(gè)地址空間,,那么它們也同時(shí)共享其中的所有 vm_area_struct結(jié)構(gòu)體。 在上面的vm_flags域中存放的是VMA標(biāo)志,,標(biāo)志了內(nèi)存區(qū)域所包含的頁面的行為和信息,,反映了內(nèi)核處理頁面所需要遵循的行為準(zhǔn)則,如下表下述:
上表已經(jīng)相當(dāng)詳細(xì)了,,而且給出了說明,,我就不說了。在vm_area_struct結(jié)構(gòu)體中的vm_ops域指向域指定內(nèi)存區(qū)域相關(guān)的操作函數(shù)表,,內(nèi)核使 用表中的方法操作VMA,。vm_area_struct作為通用對(duì)象代表了任何類型的內(nèi)存區(qū)域,而操作表描述針對(duì)特定的對(duì)象實(shí)例的特定方法,。操作函數(shù)表由 vm_operations_struct結(jié)構(gòu)體表示,,定義在linux/mm.h中,如下:
記性好的你一定記得內(nèi)存描述符中的mmap和mm_rb域都獨(dú)立地指向與內(nèi)存描述符相關(guān)的全體內(nèi)存區(qū)域?qū)ο?。它們包含完全相同? vm_area_struct結(jié)構(gòu)體的指針,僅僅組織方式不同而已,。前者以鏈表的方式進(jìn)行組織,,所有的區(qū)域按地址增長(zhǎng)的方向排序,,mmap域指向鏈表中第 一個(gè)內(nèi)存區(qū)域,鏈中最后一個(gè)VMA結(jié)構(gòu)體指針指向空,。而mm_rb域采用紅--黑樹連接所有的內(nèi)存區(qū)域?qū)ο?。它指向紅--黑輸?shù)母?jié)點(diǎn)。地址空間中每一個(gè) vm_area_struct結(jié)構(gòu)體通過自身的vm_rb域連接到樹中,。關(guān)于紅黑二叉樹結(jié)構(gòu)我就不細(xì)講了,,以后可能會(huì)詳細(xì)說這個(gè)問題。內(nèi)核之所以采用這兩 種結(jié)構(gòu)來表示同一內(nèi)存區(qū)域,,主要是鏈表結(jié)構(gòu)便于遍歷所有節(jié)點(diǎn),,而紅黑樹結(jié)構(gòu)體便于在地址空間中定位特定內(nèi)存區(qū)域的節(jié)點(diǎn)。我么可以使用/proc文件系統(tǒng)和 pmap工具查看給定進(jìn)程的內(nèi)存空間和其中所包含的內(nèi)存區(qū)域,。這里就不細(xì)說了,。 內(nèi)核也為我們提供了對(duì)內(nèi)存區(qū)域操作的API,定義在linux/mm.h中:
接下來要說的兩個(gè)函數(shù)就非常重要了,,它們負(fù)責(zé)創(chuàng)建和刪除地址空間,。
這個(gè)函數(shù)中由file指定文件,具體映射的是文件中從偏移offset處開始,,長(zhǎng)度為len字節(jié)的范圍內(nèi)的數(shù)據(jù),,如果file參數(shù)是NULL并且 offset參數(shù)也是0,,那么就代表這次映射沒有和文件相關(guān),該情況被稱作匿名映射,。如果指定了文件和偏移量,,那么該映射被稱為文件映射(file- backed mapping),其中參數(shù)prot指定內(nèi)存區(qū)域中頁面的訪問權(quán)限,,這些訪問權(quán)限定義在asm/mman.h中,,如下:
flag參數(shù)指定了VMA標(biāo)志,這些標(biāo)志定義在asm/mman.h中,,如下:
如果系統(tǒng)調(diào)用do_mmap的參數(shù)中有無效參數(shù),,那么它返回一個(gè)負(fù)值;否則,,它會(huì)在虛擬內(nèi)存中分配一個(gè)合適的新內(nèi)存區(qū)域,,如果有可能的話,將新區(qū)域和臨近區(qū)域進(jìn)行合并,,否則內(nèi)核從vm_area_cach
第一個(gè)參數(shù)指定要?jiǎng)h除區(qū)域所在的地址空間,,刪除從地址start開始,長(zhǎng)度為len字節(jié)的地址空間,,如果成功,,返回0,否則返回負(fù)的錯(cuò)誤碼,。與之相對(duì)應(yīng)的用戶空間系統(tǒng)調(diào)用是munmap,。 下面開始最后一點(diǎn)內(nèi)容:頁表 我們知道應(yīng)用程序操作的對(duì)象是映射到物理內(nèi)存之上的虛擬內(nèi)存,但是處理器直接操作的確實(shí)物理內(nèi)存,。所以當(dāng)應(yīng)用程序訪問一個(gè)虛擬地址時(shí),,首先必須將虛擬地址 轉(zhuǎn)化為物理地址,,然后處理器才能解析地址訪問請(qǐng)求。這個(gè)轉(zhuǎn)換工作需要通過查詢頁面才能完成,,概括地講,,地址轉(zhuǎn)換需要將虛擬地址分段,使每段虛地址都作為一 個(gè)索引指向頁表,,而頁表項(xiàng)則指向下一級(jí)別的頁表或者指向最終的物理頁面,。linux中使用三級(jí)頁表完成地址轉(zhuǎn)換。多數(shù)體系結(jié)構(gòu)中,,搜索頁表的工作由硬件完 成,,下表描述了虛擬地址通過頁表找到物理地址的過程:
在上面這個(gè)圖中,頂級(jí)頁表是頁全局目錄(PGD),,二級(jí)頁表是中間頁目錄(PMD).最后一級(jí)是頁表(PTE),該頁表結(jié)構(gòu)指向物理頁,。上圖中的頁表對(duì)應(yīng) 的結(jié)構(gòu)體定義在文件asm/page.h中。為了加快查找速度,,在linux中實(shí)現(xiàn)了快表(TLB),其本質(zhì)是一個(gè)緩沖器,,作為一個(gè)將虛擬地址映射到物理 地址的硬件緩存,當(dāng)請(qǐng)求訪問一個(gè)虛擬地址時(shí),,處理器將首先檢查TLB中是否緩存了該虛擬地址到物理地址的映射,,如果找到了,物理地址就立刻返回,,否則,,就 需要再通過頁表搜索需要的物理地址。 |
|