Mach 虛擬內(nèi)存
在內(nèi)核管理最重要的資源中,出了CPU本身,就是內(nèi)存了。Mach 和所有內(nèi)核一樣,代碼中有很大一部分都在負(fù)責(zé)高效地管理內(nèi)存(virtual memory,VM)。
虛擬內(nèi)存架構(gòu)
虛擬內(nèi)存的抽象是Mach 中提供的最重要的機(jī)制,虛擬內(nèi)存的抽象是通過(guò)內(nèi)存對(duì)象(memory object)和分頁(yè)器(pager)的形式提供的。和調(diào)度以及Mach 原語(yǔ)原語(yǔ),我們這里要面對(duì)的是一個(gè)抽象層,這里抽象層提供了供上層使用的底層原語(yǔ)。在XNU 中,這個(gè)“上層”就是BSD層。
Mach 虛擬內(nèi)存的實(shí)現(xiàn)非常全面而且通用。這部分由兩個(gè)層次構(gòu)成:一層是和硬件相關(guān)的部分,另一層構(gòu)建在這一層之上,是和硬件無(wú)關(guān)的公共層。OS X 和 iOS 使用的幾乎一樣的底層機(jī)制,硬件無(wú)關(guān)層(以及之上的BSD 層中的機(jī)制)都是一樣的,只有架構(gòu)相關(guān)部分的代碼改為適合ARM 虛擬內(nèi)存的語(yǔ)義。
虛擬內(nèi)存全貌
Mach 的 虛擬內(nèi)存子系統(tǒng)可以說(shuō)是和其要管理的虛擬內(nèi)存一樣復(fù)雜和充滿(mǎn)了各種細(xì)節(jié)。然后從高層次看,可以看到兩個(gè)層次:一個(gè)是虛擬內(nèi)存的層次,一個(gè)是物理內(nèi)存的層次。
虛擬內(nèi)存層
虛擬內(nèi)存這一層完全以一種機(jī)器無(wú)關(guān)的方式來(lái)管理虛擬內(nèi)存。這一層通過(guò)幾種關(guān)鍵的抽象表示虛擬內(nèi)存:vm_map:表示任務(wù)地址空間內(nèi)的一個(gè)或多個(gè)虛擬內(nèi)存區(qū)域。每一個(gè)區(qū)域都是有一個(gè)獨(dú)立的條目vm_map_entry 表示。這些條目又一個(gè)雙向鏈表vm_map_links維護(hù)。
vm_map_entry:這是關(guān)鍵的數(shù)據(jù)結(jié)構(gòu),盡管只有在包含這個(gè)結(jié)構(gòu)的映射的上下文中才會(huì)訪問(wèn)到這個(gè)結(jié)構(gòu)。每一個(gè)vm_map_entry 都表示了虛擬內(nèi)存中一塊連續(xù)的區(qū)域(region)。每一個(gè)這樣的區(qū)域都可以通過(guò)指定的訪問(wèn)保護(hù)權(quán)限進(jìn)行保護(hù)(和虛擬內(nèi)存頁(yè)面采用同樣的權(quán)限)。任務(wù)之間可以共享區(qū)域。vm_map_entry 通常指向一個(gè)vm_object,但是也可以指向一個(gè)嵌套的vm_map,即子映射(submap)。
vm_object:用于將vm_map_entry 和實(shí)際支撐的內(nèi)存關(guān)聯(lián)起來(lái)。這個(gè)數(shù)據(jù)結(jié)構(gòu)包含一個(gè)vm_page 的鏈表,還包含一個(gè)用于訪問(wèn)正確分頁(yè)器的Mach 端口(稱(chēng)為memory_object),通過(guò)這個(gè)分頁(yè)器進(jìn)行頁(yè)面的獲取或清理操作。
vm_page:vm_page 真正表示了vn_object 或部分vm_object(由vm_object中的偏移量表示)。vm_page 可以有多種狀態(tài):駐留內(nèi)存、交換出、加密、干凈和臟等。
Mach 允許使用多個(gè)分頁(yè)器。事實(shí)上,默認(rèn)就存在3~4個(gè)分頁(yè)器。Mach 的分頁(yè)器以外部實(shí)體的形式存在:是專(zhuān)業(yè)的任務(wù),有點(diǎn)類(lèi)似于其他系統(tǒng)上的內(nèi)核交換(kernel-swapping)線(xiàn)程。Mach 的設(shè)計(jì)允許分頁(yè)器和內(nèi)核任務(wù)隔離開(kāi),設(shè)置允許用戶(hù)態(tài)任務(wù)作為分頁(yè)器。類(lèi)似地,底層的后備存儲(chǔ)也可以駐留在磁盤(pán)交換文件中(通過(guò)OS X 中的 default_pager 處理),可以映射到一個(gè)文件(由vnode_pager處理),可以是一個(gè)設(shè)備(由device_pager 處理)。注意:在Mach 中,每一個(gè)分頁(yè)器處理的都是屬于這個(gè)分頁(yè)器的頁(yè)面的請(qǐng)求,但是這些請(qǐng)求必須通過(guò)pageout 守護(hù)程序發(fā)出。這些守護(hù)程序(實(shí)際上就是內(nèi)核線(xiàn)程)維護(hù)內(nèi)核的頁(yè)面表,并且判定哪些頁(yè)面需要被清除出去。因此,這些守護(hù)程序維護(hù)的分頁(yè)策略和分頁(yè)器實(shí)現(xiàn)的分頁(yè)操作是分開(kāi)的。物理內(nèi)存層:物理內(nèi)存的頁(yè)面處理的是虛擬內(nèi)存到物理內(nèi)存的映射,因?yàn)樘摂M內(nèi)存中的內(nèi)容最終總要存儲(chǔ)在某個(gè)地方。這一層面只有一個(gè)抽象,那就是pmap,不過(guò)這個(gè)抽象非常重要,因?yàn)樘峁┝藱C(jī)器無(wú)關(guān)的接口。這個(gè)接口隱藏了底層平臺(tái)的細(xì)節(jié),底層的細(xì)節(jié)需要在處理器層次進(jìn)行分頁(yè)操作,其中要處理的對(duì)象包括硬件頁(yè)表項(xiàng)(page table entry,PTE)、翻譯查找表(translation lookaside buffer,TLB)等。
虛擬內(nèi)存概述
每一個(gè)Mach 任務(wù)都要自己的虛擬內(nèi)存空間,任務(wù)的struct task 中的 map 字段保存的就是這個(gè)虛擬內(nèi)存空間。
vm_page_entry 中最關(guān)鍵的元素是vm_map_object,這是一個(gè)聯(lián)合體,既可以包含另一個(gè)vm_map(作為子映射),也可以包含一個(gè)vm_object_t(由于這是一個(gè)聯(lián)合體,所以具體的內(nèi)容需要用布爾字段is_sub_map 來(lái)判斷)。vm_object 是一個(gè)巨大的數(shù)據(jù)結(jié)構(gòu),其中包含了處理底層虛擬內(nèi)存所需要的所有數(shù)據(jù)。vm_object的數(shù)據(jù)結(jié)構(gòu)中的大部分字段都是用位表示的標(biāo)志。這些字段表示了底層的內(nèi)存狀態(tài)(聯(lián)動(dòng)、物理連續(xù)和持久化等狀態(tài))和一些計(jì)數(shù)器(引用計(jì)數(shù)、駐留計(jì)數(shù)和聯(lián)動(dòng)計(jì)數(shù)等)。不過(guò)有3個(gè)字段需要特別注意:
- memq:vm_page 對(duì)象的鏈表,每一項(xiàng)都表示一個(gè)駐留內(nèi)存的虛擬內(nèi)存頁(yè)面。盡管一個(gè)對(duì)象可以表示一個(gè)單獨(dú)的頁(yè)面,但是多數(shù)情況下一個(gè)對(duì)象可以包含多個(gè)頁(yè)面,所以每一個(gè)頁(yè)面關(guān)聯(lián)到一個(gè)對(duì)象時(shí)都會(huì)有一個(gè)偏移值
- page:memory_object 對(duì)象,這是指向分頁(yè)器的Mach 端口。分頁(yè)器將未駐留內(nèi)存的頁(yè)面關(guān)聯(lián)到后備存儲(chǔ),后備存儲(chǔ)可以是內(nèi)存映射的文件、設(shè)備和交換文件,后備存儲(chǔ)保存了沒(méi)有駐留內(nèi)存的頁(yè)面。換句話(huà)說(shuō),分頁(yè)器(可以有多個(gè))負(fù)責(zé)將數(shù)據(jù)從后備存儲(chǔ)移入內(nèi)存以及將數(shù)據(jù)從內(nèi)存移出到后備存儲(chǔ)。分頁(yè)器對(duì)于虛擬內(nèi)存子系統(tǒng)來(lái)說(shuō)極為重要
- internal:vm_page 中眾多標(biāo)志位之一,如果這個(gè)位為真,那么表示這個(gè)對(duì)象是由內(nèi)核內(nèi)部使用的。這個(gè)標(biāo)志位的值決定了對(duì)象中的頁(yè)面會(huì)進(jìn)入哪一個(gè)pageout隊(duì)列
物理內(nèi)存管理
盡管內(nèi)核和用戶(hù)空間一樣,基本上只在虛擬地址空間內(nèi)操作,但是虛擬內(nèi)存最終還是要翻譯為物理地址的。機(jī)器的RAM 實(shí)際上是虛擬內(nèi)存中開(kāi)的窗口,允許程序訪問(wèn)虛擬內(nèi)存是有限的,而且通常是不連續(xù)的區(qū)域,這些區(qū)域的上線(xiàn)就是機(jī)器上安裝的內(nèi)存。而虛擬內(nèi)存中其他部分則要么延遲分配,要么共享,要么被交換到外部存儲(chǔ)中,外部存儲(chǔ)通常是磁盤(pán)。
然而虛擬內(nèi)存和具體的底層架構(gòu)相關(guān)。盡管虛擬內(nèi)存和物理內(nèi)存的概念在所有架構(gòu)上本周都是一樣的,但是具體的實(shí)現(xiàn)細(xì)節(jié)則各有千秋。XNU 構(gòu)建與Mach 的物理內(nèi)存抽象層之上,這個(gè)的抽象層成為pmap。pmap 從設(shè)計(jì)上對(duì)物理內(nèi)存提供了一個(gè)統(tǒng)一的接口,屏蔽了架構(gòu)相關(guān)的區(qū)別。這對(duì)于XNU來(lái)說(shuō)非常有用,因?yàn)閄NU支持的物理內(nèi)存的架構(gòu)包括以前的PowerPC,現(xiàn)在主要是Intel,然后在iOS 中還支持ARM。
pmap 的 API
Mach 的pmap 層邏輯上由一下兩個(gè)子層構(gòu)成:
- 機(jī)器無(wú)關(guān)層:提供了一組基本上和及其無(wú)關(guān)的API。只要求及其支持基本的虛擬內(nèi)存分頁(yè)的概念。VM層只考慮pamp_t 并傳遞這個(gè)類(lèi)型的數(shù)據(jù)即可,pmap_t 是一個(gè)指向struct pmap 是指針,實(shí)際上是一個(gè)void 指針
- 機(jī)器相關(guān)層:將pmap綁定到一個(gè)具體的實(shí)現(xiàn),處理底層敬愛(ài)個(gè)的各種細(xì)節(jié)
MachZone
Mach(以及XNU)Zone的概念相當(dāng)于Linux的內(nèi)存緩存(memory cache)和Windows 的Pool。Zone 是一種內(nèi)存區(qū)域,用于快速分配和是否頻繁使用的固定大小的對(duì)象。Zone的API是內(nèi)核內(nèi)部使用的,在用戶(hù)態(tài)不能訪問(wèn)。Mach中Zone的使用非常廣泛。
Mach Zone 的結(jié)構(gòu)
所有的zone 內(nèi)存實(shí)際上都是在調(diào)用zinit( )時(shí)預(yù)先分配好的(zinit( )通過(guò)底層內(nèi)存分配器kernel_memory_allocate( )分配內(nèi)存)zalloc( )實(shí)際上是對(duì)REMOVE_FROM_ZONE 宏的封裝,作用是返回zone的空閑列表中的下一個(gè)元素(如果zone已滿(mǎn),則調(diào)用kernel_memory_allocate( )分配這個(gè)zone在定義的alloc_size字節(jié))。zfree( ) 使用的是相反功能的宏 ADD_TO_ZONE。這兩個(gè)函數(shù)都會(huì)執(zhí)行合理數(shù)量的參數(shù)檢查,不過(guò)這些檢查幫助不大:過(guò)去zone分配相關(guān)的bug已經(jīng)導(dǎo)致了數(shù)據(jù)可以被黑客利用的內(nèi)存損壞。zalloc( ) 最重要的客戶(hù)是內(nèi)核中的kalloc( ),這個(gè)函數(shù)從kalloc.*系列zone中分配內(nèi)存。BSD的mcache機(jī)制也會(huì)從自己的zone中分配內(nèi)存。BSD內(nèi)核zone也是如此,BSD內(nèi)核zone直接構(gòu)建與Mach的zone之上。
引導(dǎo)期間的zone 設(shè)置
內(nèi)核引導(dǎo)時(shí),vm_mem_bootstrap( )通過(guò)兩個(gè)調(diào)用設(shè)置zone:
- zone_bootstrap:設(shè)置主zone(“zones”),所有其他的zone數(shù)據(jù)都保存在這里面
- zone_init:初始化zone子系統(tǒng)的鎖和頁(yè)面(使用zone_page_init( ))
zone 垃圾回收
如果系統(tǒng)內(nèi)存不足,zone可能會(huì)進(jìn)行垃圾回收。垃圾回收是通過(guò)consider_zone_gc( ) 函數(shù)進(jìn)行的,這個(gè)函數(shù)被 cm_pageout_garbage_collect 線(xiàn)程調(diào)用。consider_zone_gc( ) 可能會(huì)在以下某種情況中調(diào)用zone垃圾回收(zone_gc):
- zfree( ) 已經(jīng)釋放了zone中一個(gè)超過(guò)一個(gè)頁(yè)面大小的元素,而且系統(tǒng)的vm_pool低
- 距離上一次zone_gc 運(yùn)行已經(jīng)有一段時(shí)間了,這個(gè)時(shí)間由zone_gc_tie_throttle 指定的
- 系統(tǒng)在休眠,而且調(diào)用了hibernate_flush_memory( )
垃圾回收是一個(gè)兩趟的過(guò)程,首先系統(tǒng)先掃描所有的zone(跳過(guò)標(biāo)記為不可回收的zone),檢查這些zone的空閑列表,判斷哪些對(duì)象是可以回收的。在第二趟中,將這些對(duì)象轉(zhuǎn)換為頁(yè)面:和非空閑對(duì)象共享一個(gè)頁(yè)面的對(duì)象不能被釋放,只有頁(yè)面全部空閑的對(duì)象才能被釋放。最后,當(dāng)判定好了可以釋放的頁(yè)面之后,通過(guò)kmem_free( )釋放。
zone 調(diào)試
zone是可以通過(guò)以下幾種發(fā)那個(gè)是進(jìn)行調(diào)試的:
- 編譯時(shí)配置CONFIG_ZLEAKS:配置完CONFIG_ZLEAKS后,繪制每個(gè)struct zone 中多分配一些數(shù)據(jù)用于檢查內(nèi)存泄露。
- 開(kāi)關(guān)zone元素檢查:通過(guò)-zc引導(dǎo)參數(shù)
- 開(kāi)關(guān)zone污染:通過(guò)-zp引導(dǎo)參數(shù)
- 在每一個(gè)任務(wù)中保存zone信息:通過(guò)-zinfop引導(dǎo)參數(shù)
- 指定zone日志引導(dǎo)參數(shù):通過(guò)zlog參數(shù)指定要記錄日志的zone的準(zhǔn)確名字,通過(guò)zrecs指定日志中要保存的記錄數(shù)目(最多不超過(guò)8000)
內(nèi)核內(nèi)存分配器
當(dāng)內(nèi)核代碼真的需要分配內(nèi)存時(shí),特別是在自己的vm_map(即kernel_map)中分配內(nèi)存時(shí),就需要實(shí)際的分配函數(shù)了,內(nèi)核的分配函數(shù)負(fù)責(zé)分配虛擬內(nèi)存,并且做好后備物理內(nèi)存頁(yè)面的映射。下圖是XNU中豐富的分配層次架構(gòu):
kernel_memory_allocate( )
所有的內(nèi)核分配(除了連續(xù)物理內(nèi)存的分配)的路徑最終都會(huì)到達(dá)一個(gè)函數(shù),那就是kernel_memory_allocate( )。這個(gè)函數(shù)執(zhí)行實(shí)際的內(nèi)存分配,同時(shí)對(duì)vm_map和pmap進(jìn)行操作。
實(shí)際的物理存在分配是通過(guò)查看兩個(gè)空閑列表中的一個(gè)進(jìn)行的:一個(gè)列表是每一個(gè)處理器自有的空閑列表,另一個(gè)列表是低內(nèi)存空閑列表。后面這張情況比較罕見(jiàn),只有要求非常特殊的物理內(nèi)存區(qū)域(小于16MB的內(nèi)存區(qū)域)時(shí)才需要。vm_page_grablo( ) 函數(shù)調(diào)用 cpm_allocate( ),cmp_allocate( )函數(shù)直接從空閑列表中竊取頁(yè)面,從而分配連續(xù)的物理內(nèi)存。
kmem_alloc( ) 系列函數(shù)
Mach 中最常用的內(nèi)存分配器就是kmem_alloc( ) 系列函數(shù)提供的分配器,都是對(duì)kernel_memory_allocate( )的封裝
kmem_akkic系列函數(shù)都采用了同樣的原型,接受三個(gè)參數(shù),分別是map、一個(gè)地址指針的輸入輸出參數(shù)以及一個(gè)表示大小的參數(shù)。這些參數(shù)傳入的map參數(shù)基本上都是kernel_map vm_map,除非要求的是可分頁(yè)的內(nèi)存。
還有一些是構(gòu)建于kernel_memory_allocate( )的kmem_alloc_*函數(shù)。這些函數(shù)包括:
- kmem_alloc_contig( ):用于分配連續(xù)的物理內(nèi)存(通過(guò)cmp_allocte( ))實(shí)現(xiàn)
- **kmem_alloc_pageable( )( 通過(guò)cm_map_enter( ) 實(shí)現(xiàn)):分配非聯(lián)動(dòng)的內(nèi)存,非聯(lián)動(dòng)的內(nèi)存可以在沒(méi)有任何警告的情況下都會(huì)被交換出去
- kmem_alloc_pages( ):可以用于在已有對(duì)象中分配新的頁(yè),這個(gè)函數(shù)是對(duì)vm_page_alloc( )的封裝(vm_page_alloc( )本本身是對(duì)kernel_memory_allocate( )中調(diào)用的vm_page_grab( )/vm_page_insert( )的封裝)
kmem_alloc( )開(kāi)銷(xiāo)非常大,主要是因?yàn)樾枰髠湮锢眄?yè)面的支持:底層調(diào)用的kernel_memory_allocate( ) 可能會(huì)永久阻塞。更多情況下,使用的是更快的alloc( )(這個(gè)分配器是基于更搞笑的zone機(jī)制實(shí)現(xiàn)的)
kalloc
一旦Mach中的zone都初始化之后,就可以用于快速的內(nèi)核內(nèi)部?jī)?nèi)寸分配了,這些內(nèi)存分配是由 kalloc_( )系列函數(shù)完成的。這些函數(shù)從功能上等同于用戶(hù)態(tài)的 maclloc( ) 。
kalloc函數(shù)是XNU中使用最為廣泛的內(nèi)存分配器,有很多函數(shù)封裝了kalloc,其中包括:
- IOKit 的 IOMalloc:直接封裝了kalloc( ),還調(diào)用了IOStatisticsAlloc 宏,用于記錄內(nèi)存分配
- Libkern的kern_os_malloc:直接封裝了kalloc( ),會(huì)在分配的內(nèi)存塊之前追加上這個(gè)內(nèi)存的大小。new 操作符就是對(duì)這個(gè)函數(shù)的封裝。
- BSD的_MALLOC:用于BSD層的各種分配,也會(huì)在分配的內(nèi)存塊之前追加上這個(gè)內(nèi)存塊的大小
OSMalloc
Mach 還提供了另一組內(nèi)存分配函數(shù):OSMalloc。OSMalloc 中的關(guān)鍵概念就是標(biāo)簽,標(biāo)簽是一個(gè)透明的類(lèi)型,必須首先分配。調(diào)用者持有了標(biāo)簽之后,就可以將這個(gè)標(biāo)簽傳入任何一個(gè)OSMalloc的函數(shù),那么OSMalloc 通過(guò)kmem_alloc_pageable 分配內(nèi)存。否則,通過(guò)kalloc( )從聯(lián)動(dòng)內(nèi)存中分配內(nèi)存。標(biāo)簽本身保存在一個(gè)標(biāo)簽的鏈表中,每一個(gè)標(biāo)簽都有一個(gè)引用計(jì)數(shù)。分配內(nèi)存會(huì)增加這個(gè)標(biāo)簽的引用計(jì)數(shù)。
Mach 分頁(yè)器
進(jìn)程的內(nèi)存需求早晚會(huì)超過(guò)可用的RAM,系統(tǒng)必須有一種方法能夠?qū)⒉换顒?dòng)的頁(yè)面?zhèn)浞萜饋?lái)并且從RAM中刪除,騰出更多的RAM給活動(dòng)的頁(yè)面使用,至少暫時(shí)能夠滿(mǎn)足活動(dòng)頁(yè)面的需求。在其他操作系統(tǒng)中,這個(gè)工作專(zhuān)門(mén)是由專(zhuān)門(mén)的內(nèi)核線(xiàn)程完成的。在Mach 中,這些專(zhuān)門(mén)的任務(wù)稱(chēng)為分頁(yè)器(pager),分頁(yè)器可以是內(nèi)核線(xiàn)程,設(shè)置建議是外部的用戶(hù)態(tài)(甚至遠(yuǎn)程)服務(wù)程序。
Mach分頁(yè)器是一個(gè)內(nèi)存管理器,負(fù)責(zé)將虛擬內(nèi)存?zhèn)浞莸侥硞€(gè)特定類(lèi)型的后備存儲(chǔ)中。當(dāng)內(nèi)存容量不足,內(nèi)存頁(yè)面需要被交換出內(nèi)存是,后備存儲(chǔ)保存內(nèi)存頁(yè)面的內(nèi)容:當(dāng)換出的內(nèi)存頁(yè)面需要被使用時(shí),將內(nèi)存的頁(yè)面恢復(fù)到RAM中。只有“臟”頁(yè)面才需要進(jìn)行上述的換出和換入,因?yàn)椤芭K”頁(yè)面是在內(nèi)存中修改過(guò)的頁(yè)面,要從RAM中剔除時(shí)必須保存到磁盤(pán)中防止數(shù)據(jù)丟失。
要注意的是,這里提到的分頁(yè)器僅僅實(shí)現(xiàn)了各自負(fù)責(zé)的內(nèi)存對(duì)象的分頁(yè)操作,這些分頁(yè)器不會(huì)控制系統(tǒng)的分頁(yè)策略。分頁(yè)策略是有vm_pageout 守護(hù)線(xiàn)程負(fù)責(zé)的。
分頁(yè)器的類(lèi)型
iOS 和 OS X 中XNU 包含的分頁(yè)器種類(lèi)都是一樣的。下表是XNU中的內(nèi)存分頁(yè)器的多種類(lèi)型:
| 內(nèi)存分頁(yè)器 | 用途 |
|---|---|
| Default 分頁(yè)器(默認(rèn)) | 匿名內(nèi)存 |
| VNode 分頁(yè)器 | 內(nèi)存映射的文件 |
| Device 分頁(yè)器 | 設(shè)置后援的I/O |
| Swapfile 分頁(yè)器 | 處理特定的swapfile 映射的嘗試,防止通過(guò)內(nèi)存映射讀取交換文件的數(shù)據(jù) |
| Apple-protected 分頁(yè)器 | 蘋(píng)果特有的擴(kuò)展:對(duì)內(nèi)存(二進(jìn)制文件所在的內(nèi)存)加密提供支持 |
| Freezer(僅用于iOS) | iOS 特有的擴(kuò)展,支持“冷凍”進(jìn)程 |
分頁(yè)策略管理
Pageout 守護(hù)程序
pageout 守護(hù)程序其實(shí)不是一個(gè)真的守護(hù)程序,而是一個(gè)線(xiàn)程。而且不是一般的線(xiàn)程:當(dāng)kernel_bootstarp_thread( ) 完成內(nèi)核初始化工作并且沒(méi)有其他事情可做時(shí),就調(diào)用vm_pageout( ) 成為了pageour 守護(hù)程序, vm_pageout( ) 永遠(yuǎn)不返回。這個(gè)線(xiàn)程管理頁(yè)面交換的策略,判斷哪些頁(yè)面需要寫(xiě)回到其后備存儲(chǔ)。
vm_pageout線(xiàn)程
vm_pageout( ) 函數(shù)講kernel_bootstrap_thread 線(xiàn)程轉(zhuǎn)變?yōu)閜ageout 守護(hù)程序,這個(gè)函數(shù)實(shí)際上重新設(shè)置了這個(gè)線(xiàn)程。設(shè)置完成后,調(diào)用vm_pageout_continute( ),這個(gè)函數(shù)周期性地喚醒并執(zhí)行vm_page_scan( ),維護(hù)4個(gè)頁(yè)面表(稱(chēng)為頁(yè)面隊(duì)列)。系統(tǒng)中的每一個(gè)vm_page 都通過(guò)pageq字段綁定這4個(gè)隊(duì)列中的一個(gè):vm_page_queue_active:最近活躍且駐留在內(nèi)存中的頁(yè)面
vm_page_queue_inactive:最近不活躍的頁(yè)面,因此這些頁(yè)面是頁(yè)面換出的備選頁(yè)面。根據(jù)這些頁(yè)面的使用情況,可能會(huì)被換出,也可能會(huì)被重新激活
vm_page_queue_free:空閑頁(yè)面表。這些頁(yè)面曾經(jīng)是非活躍的頁(yè)面,但是被清理出去了(頁(yè)面換出)
vm_page_queue_speculative:這些頁(yè)面是通過(guò)預(yù)讀策略投機(jī)映射的頁(yè)面,這些頁(yè)面是不活躍的,但是很可能很快會(huì)變?yōu)榛钴S頁(yè)面
vm_pageout iothread線(xiàn)程
內(nèi)部和外部iothread 線(xiàn)程各自檢查一個(gè)vm_pageout_queue_t,這兩個(gè)vm_page_queue_t 都是由vm_pageout( )初始化的。vm_pageout_queue_internal 專(zhuān)門(mén)用于內(nèi)部的VM對(duì)象(即那些內(nèi)核創(chuàng)建的VM對(duì)象,又默認(rèn)分頁(yè)器維護(hù),internel 標(biāo)準(zhǔn)設(shè)置為true),vm_pageout_queue_external 用于其他所有的VM對(duì)象
這兩個(gè)線(xiàn)程都使用了同一個(gè)線(xiàn)程函數(shù)vm_pageout_iothread_continue( ) 只不過(guò)操作的是不同的隊(duì)列。這個(gè)函數(shù)(嚴(yán)格地說(shuō)是一個(gè)續(xù)體)遍歷自己的隊(duì)列,出隊(duì)隊(duì)列中的每一個(gè)頁(yè)面,獲得其對(duì)應(yīng)的分頁(yè)器(通過(guò)vm_object 引用),然后調(diào)用器分頁(yè)器的memory_object_data_return( ) 函數(shù)。這種方式可以使得pageout 線(xiàn)程和實(shí)際的分頁(yè)操作實(shí)現(xiàn)解耦,分頁(yè)器操作是由分頁(yè)器單獨(dú)負(fù)責(zé)的。垃圾回收線(xiàn)程
垃圾回收線(xiàn)程(vm_pageout_garbage_collect( ))偶爾會(huì)被vm_pageout_scan( ) 通過(guò)其續(xù)體喚醒。垃圾回收機(jī)制線(xiàn)程處理4個(gè)方面的垃圾回收工作:srack_collect( ):內(nèi)核棧中的頁(yè)面
consider_machine_collect( ):回收機(jī)器相關(guān)的頁(yè)面
consider_buffer_cache_collect( ):如果確定定義了這個(gè)函數(shù)則調(diào)用這個(gè)函數(shù)。調(diào)用這通過(guò)vm_set_buffer_cleanup_callout( ) 定義這個(gè)函數(shù)。BSD 層在bufinit( ) 函數(shù)中注冊(cè)了buffer_cache_gc( ) 函數(shù)
consider_zone_gc( ):zone 相關(guān)的垃圾回收
處理頁(yè)錯(cuò)誤
vm_pageout( ) 守護(hù)程序處理的只是交換的一個(gè)方向,從物理內(nèi)存換出到后備存儲(chǔ)。而另外一個(gè)方向是頁(yè)面換入,則是發(fā)生在頁(yè)面錯(cuò)誤的時(shí)候處理的。這個(gè)邏輯非常復(fù)雜,簡(jiǎn)化為一下步驟:
- 如果陷阱的原因是頁(yè)錯(cuò)誤,那么機(jī)器級(jí)別的線(xiàn)程處理程序調(diào)用vm_fault( )
- vm_fault( ) 函數(shù)調(diào)用 vm_pageout_fault( )處理實(shí)際發(fā)生錯(cuò)誤的頁(yè)面,并且從后備存儲(chǔ)中將這個(gè)頁(yè)面返回
- PMAP_ENTER( ) 將頁(yè)面插入任務(wù)的pmap
頁(yè)錯(cuò)誤有很多種,上述只是其中一種,其他類(lèi)型的也錯(cuò)誤還包括:
- 非法訪問(wèn):訪問(wèn)應(yīng)該沒(méi)有映射到進(jìn)程地址空間(即任務(wù)的vm_map)的地址。解引用應(yīng)該野指針時(shí)通常會(huì)發(fā)生這種錯(cuò)誤。發(fā)生這種錯(cuò)誤時(shí)進(jìn)程會(huì)收到SIGSEGV信號(hào)
- 頁(yè)面保護(hù)錯(cuò)誤:訪問(wèn)應(yīng)該映射的地址,但是頁(yè)面的保護(hù)掩碼拒絕請(qǐng)求的訪問(wèn)
- 寫(xiě)時(shí)復(fù)制(copy-on-write):頁(yè)面可以被標(biāo)記可讀,因此如果任務(wù)試圖寫(xiě)入頁(yè)面時(shí),會(huì)捕捉到這個(gè)錯(cuò)誤,在重新嘗試寫(xiě)入操作之前可以將這個(gè)頁(yè)面復(fù)制出來(lái)