Mach 虛擬內(nèi)存
在內(nèi)核管理最重要的資源中,出了CPU本身酌壕,就是內(nèi)存了卵牍。Mach 和所有內(nèi)核一樣沦泌,代碼中有很大一部分都在負責高效地管理內(nèi)存(virtual memory谢谦,VM)萝衩。
虛擬內(nèi)存架構(gòu)
虛擬內(nèi)存的抽象是Mach 中提供的最重要的機制猩谊,虛擬內(nèi)存的抽象是通過內(nèi)存對象(memory object)和分頁器(pager)的形式提供的牌捷。和調(diào)度以及Mach 原語原語暗甥,我們這里要面對的是一個抽象層遮怜,這里抽象層提供了供上層使用的底層原語锯梁。在XNU 中,這個“上層”就是BSD層剥懒。
Mach 虛擬內(nèi)存的實現(xiàn)非常全面而且通用初橘。這部分由兩個層次構(gòu)成:一層是和硬件相關(guān)的部分充岛,另一層構(gòu)建在這一層之上崔梗,是和硬件無關(guān)的公共層。OS X 和 iOS 使用的幾乎一樣的底層機制扔亥,硬件無關(guān)層(以及之上的BSD 層中的機制)都是一樣的旅挤,只有架構(gòu)相關(guān)部分的代碼改為適合ARM 虛擬內(nèi)存的語義粘茄。
虛擬內(nèi)存全貌
Mach 的 虛擬內(nèi)存子系統(tǒng)可以說是和其要管理的虛擬內(nèi)存一樣復(fù)雜和充滿了各種細節(jié)秕脓。然后從高層次看,可以看到兩個層次:一個是虛擬內(nèi)存的層次嘹朗,一個是物理內(nèi)存的層次屹培。
虛擬內(nèi)存層
虛擬內(nèi)存這一層完全以一種機器無關(guān)的方式來管理虛擬內(nèi)存褪秀。這一層通過幾種關(guān)鍵的抽象表示虛擬內(nèi)存:vm_map:表示任務(wù)地址空間內(nèi)的一個或多個虛擬內(nèi)存區(qū)域薛训。每一個區(qū)域都是有一個獨立的條目vm_map_entry 表示乙埃。這些條目又一個雙向鏈表vm_map_links維護。
vm_map_entry:這是關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)甫何,盡管只有在包含這個結(jié)構(gòu)的映射的上下文中才會訪問到這個結(jié)構(gòu)辙喂。每一個vm_map_entry 都表示了虛擬內(nèi)存中一塊連續(xù)的區(qū)域(region)巍耗。每一個這樣的區(qū)域都可以通過指定的訪問保護權(quán)限進行保護(和虛擬內(nèi)存頁面采用同樣的權(quán)限)渐排。任務(wù)之間可以共享區(qū)域飞盆。vm_map_entry 通常指向一個vm_object,但是也可以指向一個嵌套的vm_map孽水,即子映射(submap)女气。
vm_object:用于將vm_map_entry 和實際支撐的內(nèi)存關(guān)聯(lián)起來炼鞠。這個數(shù)據(jù)結(jié)構(gòu)包含一個vm_page 的鏈表,還包含一個用于訪問正確分頁器的Mach 端口(稱為memory_object)谒主,通過這個分頁器進行頁面的獲取或清理操作霎肯。
vm_page:vm_page 真正表示了vn_object 或部分vm_object(由vm_object中的偏移量表示)。vm_page 可以有多種狀態(tài):駐留內(nèi)存搂捧、交換出允跑、加密聋丝、干凈和臟等拌屏。
Mach 允許使用多個分頁器。事實上每篷,默認就存在3~4個分頁器焦读。Mach 的分頁器以外部實體的形式存在:是專業(yè)的任務(wù)矗晃,有點類似于其他系統(tǒng)上的內(nèi)核交換(kernel-swapping)線程宴倍。Mach 的設(shè)計允許分頁器和內(nèi)核任務(wù)隔離開鸵贬,設(shè)置允許用戶態(tài)任務(wù)作為分頁器。類似地兆衅,底層的后備存儲也可以駐留在磁盤交換文件中(通過OS X 中的 default_pager 處理)羡亩,可以映射到一個文件(由vnode_pager處理),可以是一個設(shè)備(由device_pager 處理)雷袋。注意:在Mach 中片排,每一個分頁器處理的都是屬于這個分頁器的頁面的請求速侈,但是這些請求必須通過pageout 守護程序發(fā)出倚搬。這些守護程序(實際上就是內(nèi)核線程)維護內(nèi)核的頁面表乾蛤,并且判定哪些頁面需要被清除出去家卖。因此,這些守護程序維護的分頁策略和分頁器實現(xiàn)的分頁操作是分開的趴樱。物理內(nèi)存層:物理內(nèi)存的頁面處理的是虛擬內(nèi)存到物理內(nèi)存的映射酪捡,因為虛擬內(nèi)存中的內(nèi)容最終總要存儲在某個地方逛薇。這一層面只有一個抽象,那就是pmap啤呼,不過這個抽象非常重要呢袱,因為提供了機器無關(guān)的接口产捞。這個接口隱藏了底層平臺的細節(jié),底層的細節(jié)需要在處理器層次進行分頁操作焊唬,其中要處理的對象包括硬件頁表項(page table entry赶促,PTE)、翻譯查找表(translation lookaside buffer嗦哆,TLB)等婿滓。
虛擬內(nèi)存概述
每一個Mach 任務(wù)都要自己的虛擬內(nèi)存空間凸主,任務(wù)的struct task 中的 map 字段保存的就是這個虛擬內(nèi)存空間卿吐。
vm_page_entry 中最關(guān)鍵的元素是vm_map_object,這是一個聯(lián)合體箭窜,既可以包含另一個vm_map(作為子映射)衍腥,也可以包含一個vm_object_t(由于這是一個聯(lián)合體,所以具體的內(nèi)容需要用布爾字段is_sub_map 來判斷)婆咸。vm_object 是一個巨大的數(shù)據(jù)結(jié)構(gòu)擅耽,其中包含了處理底層虛擬內(nèi)存所需要的所有數(shù)據(jù)。vm_object的數(shù)據(jù)結(jié)構(gòu)中的大部分字段都是用位表示的標志憾儒。這些字段表示了底層的內(nèi)存狀態(tài)(聯(lián)動起趾、物理連續(xù)和持久化等狀態(tài))和一些計數(shù)器(引用計數(shù)警儒、駐留計數(shù)和聯(lián)動計數(shù)等)。不過有3個字段需要特別注意:
- memq:vm_page 對象的鏈表边琉,每一項都表示一個駐留內(nèi)存的虛擬內(nèi)存頁面变姨。盡管一個對象可以表示一個單獨的頁面,但是多數(shù)情況下一個對象可以包含多個頁面定欧,所以每一個頁面關(guān)聯(lián)到一個對象時都會有一個偏移值
- page:memory_object 對象砍鸠,這是指向分頁器的Mach 端口。分頁器將未駐留內(nèi)存的頁面關(guān)聯(lián)到后備存儲类茂,后備存儲可以是內(nèi)存映射的文件、設(shè)備和交換文件厚骗,后備存儲保存了沒有駐留內(nèi)存的頁面领舰。換句話說,分頁器(可以有多個)負責將數(shù)據(jù)從后備存儲移入內(nèi)存以及將數(shù)據(jù)從內(nèi)存移出到后備存儲舍咖。分頁器對于虛擬內(nèi)存子系統(tǒng)來說極為重要
- internal:vm_page 中眾多標志位之一排霉,如果這個位為真民轴,那么表示這個對象是由內(nèi)核內(nèi)部使用的后裸。這個標志位的值決定了對象中的頁面會進入哪一個pageout隊列
物理內(nèi)存管理
盡管內(nèi)核和用戶空間一樣微驶,基本上只在虛擬地址空間內(nèi)操作,但是虛擬內(nèi)存最終還是要翻譯為物理地址的篇恒。機器的RAM 實際上是虛擬內(nèi)存中開的窗口梁呈,允許程序訪問虛擬內(nèi)存是有限的官卡,而且通常是不連續(xù)的區(qū)域寻咒,這些區(qū)域的上線就是機器上安裝的內(nèi)存。而虛擬內(nèi)存中其他部分則要么延遲分配饭寺,要么共享艰匙,要么被交換到外部存儲中抹恳,外部存儲通常是磁盤奋献。
然而虛擬內(nèi)存和具體的底層架構(gòu)相關(guān)。盡管虛擬內(nèi)存和物理內(nèi)存的概念在所有架構(gòu)上本周都是一樣的糖埋,但是具體的實現(xiàn)細節(jié)則各有千秋瞳别。XNU 構(gòu)建與Mach 的物理內(nèi)存抽象層之上洒试,這個的抽象層成為pmap垒棋。pmap 從設(shè)計上對物理內(nèi)存提供了一個統(tǒng)一的接口痪宰,屏蔽了架構(gòu)相關(guān)的區(qū)別。這對于XNU來說非常有用扮饶,因為XNU支持的物理內(nèi)存的架構(gòu)包括以前的PowerPC乍构,現(xiàn)在主要是Intel哥遮,然后在iOS 中還支持ARM眠饮。
pmap 的 API
Mach 的pmap 層邏輯上由一下兩個子層構(gòu)成:
- 機器無關(guān)層:提供了一組基本上和及其無關(guān)的API仪召。只要求及其支持基本的虛擬內(nèi)存分頁的概念。VM層只考慮pamp_t 并傳遞這個類型的數(shù)據(jù)即可扔茅,pmap_t 是一個指向struct pmap 是指針已旧,實際上是一個void 指針
- 機器相關(guān)層:將pmap綁定到一個具體的實現(xiàn),處理底層敬愛個的各種細節(jié)
MachZone
Mach(以及XNU)Zone的概念相當于Linux的內(nèi)存緩存(memory cache)和Windows 的Pool召娜。Zone 是一種內(nèi)存區(qū)域运褪,用于快速分配和是否頻繁使用的固定大小的對象。Zone的API是內(nèi)核內(nèi)部使用的萤晴,在用戶態(tài)不能訪問。Mach中Zone的使用非常廣泛胁后。
Mach Zone 的結(jié)構(gòu)
所有的zone 內(nèi)存實際上都是在調(diào)用zinit( )時預(yù)先分配好的(zinit( )通過底層內(nèi)存分配器kernel_memory_allocate( )分配內(nèi)存)zalloc( )實際上是對REMOVE_FROM_ZONE 宏的封裝店读,作用是返回zone的空閑列表中的下一個元素(如果zone已滿,則調(diào)用kernel_memory_allocate( )分配這個zone在定義的alloc_size字節(jié))屯断。zfree( ) 使用的是相反功能的宏 ADD_TO_ZONE。這兩個函數(shù)都會執(zhí)行合理數(shù)量的參數(shù)檢查趴久,不過這些檢查幫助不大:過去zone分配相關(guān)的bug已經(jīng)導(dǎo)致了數(shù)據(jù)可以被黑客利用的內(nèi)存損壞灭忠。zalloc( ) 最重要的客戶是內(nèi)核中的kalloc( )弛作,這個函數(shù)從kalloc.*系列zone中分配內(nèi)存。BSD的mcache機制也會從自己的zone中分配內(nèi)存。BSD內(nèi)核zone也是如此诸尽,BSD內(nèi)核zone直接構(gòu)建與Mach的zone之上穿肄。
引導(dǎo)期間的zone 設(shè)置
內(nèi)核引導(dǎo)時,vm_mem_bootstrap( )通過兩個調(diào)用設(shè)置zone:
- zone_bootstrap:設(shè)置主zone(“zones”)脑溢,所有其他的zone數(shù)據(jù)都保存在這里面
- zone_init:初始化zone子系統(tǒng)的鎖和頁面(使用zone_page_init( ))
zone 垃圾回收
如果系統(tǒng)內(nèi)存不足顶吮,zone可能會進行垃圾回收搏恤。垃圾回收是通過consider_zone_gc( ) 函數(shù)進行的熟空,這個函數(shù)被 cm_pageout_garbage_collect 線程調(diào)用。consider_zone_gc( ) 可能會在以下某種情況中調(diào)用zone垃圾回收(zone_gc):
- zfree( ) 已經(jīng)釋放了zone中一個超過一個頁面大小的元素俏扩,而且系統(tǒng)的vm_pool低
- 距離上一次zone_gc 運行已經(jīng)有一段時間了录淡,這個時間由zone_gc_tie_throttle 指定的
- 系統(tǒng)在休眠,而且調(diào)用了hibernate_flush_memory( )
垃圾回收是一個兩趟的過程彬檀,首先系統(tǒng)先掃描所有的zone(跳過標記為不可回收的zone)诽偷,檢查這些zone的空閑列表报慕,判斷哪些對象是可以回收的飞苇。在第二趟中,將這些對象轉(zhuǎn)換為頁面:和非空閑對象共享一個頁面的對象不能被釋放忿等,只有頁面全部空閑的對象才能被釋放娃闲。最后卷哩,當判定好了可以釋放的頁面之后冷溶,通過kmem_free( )釋放。
zone 調(diào)試
zone是可以通過以下幾種發(fā)那個是進行調(diào)試的:
- 編譯時配置CONFIG_ZLEAKS:配置完CONFIG_ZLEAKS后,繪制每個struct zone 中多分配一些數(shù)據(jù)用于檢查內(nèi)存泄露基协。
- 開關(guān)zone元素檢查:通過-zc引導(dǎo)參數(shù)
- 開關(guān)zone污染:通過-zp引導(dǎo)參數(shù)
- 在每一個任務(wù)中保存zone信息:通過-zinfop引導(dǎo)參數(shù)
- 指定zone日志引導(dǎo)參數(shù):通過zlog參數(shù)指定要記錄日志的zone的準確名字惋鸥,通過zrecs指定日志中要保存的記錄數(shù)目(最多不超過8000)
內(nèi)核內(nèi)存分配器
當內(nèi)核代碼真的需要分配內(nèi)存時亭畜,特別是在自己的vm_map(即kernel_map)中分配內(nèi)存時,就需要實際的分配函數(shù)了,內(nèi)核的分配函數(shù)負責分配虛擬內(nèi)存聘芜,并且做好后備物理內(nèi)存頁面的映射。下圖是XNU中豐富的分配層次架構(gòu):
kernel_memory_allocate( )
所有的內(nèi)核分配(除了連續(xù)物理內(nèi)存的分配)的路徑最終都會到達一個函數(shù),那就是kernel_memory_allocate( )嗅战。這個函數(shù)執(zhí)行實際的內(nèi)存分配疟呐,同時對vm_map和pmap進行操作珊泳。
實際的物理存在分配是通過查看兩個空閑列表中的一個進行的:一個列表是每一個處理器自有的空閑列表晓褪,另一個列表是低內(nèi)存空閑列表。后面這張情況比較罕見好港,只有要求非常特殊的物理內(nèi)存區(qū)域(小于16MB的內(nèi)存區(qū)域)時才需要录择。vm_page_grablo( ) 函數(shù)調(diào)用 cpm_allocate( ),cmp_allocate( )函數(shù)直接從空閑列表中竊取頁面尊剔,從而分配連續(xù)的物理內(nèi)存京痢。
kmem_alloc( ) 系列函數(shù)
Mach 中最常用的內(nèi)存分配器就是kmem_alloc( ) 系列函數(shù)提供的分配器甩十,都是對kernel_memory_allocate( )的封裝
kmem_akkic系列函數(shù)都采用了同樣的原型臣淤,接受三個參數(shù)按厘,分別是map、一個地址指針的輸入輸出參數(shù)以及一個表示大小的參數(shù)草描。這些參數(shù)傳入的map參數(shù)基本上都是kernel_map vm_map逛绵,除非要求的是可分頁的內(nèi)存。
還有一些是構(gòu)建于kernel_memory_allocate( )的kmem_alloc_*函數(shù)。這些函數(shù)包括:
- kmem_alloc_contig( ):用于分配連續(xù)的物理內(nèi)存(通過cmp_allocte( ))實現(xiàn)
- **kmem_alloc_pageable( )( 通過cm_map_enter( ) 實現(xiàn)):分配非聯(lián)動的內(nèi)存,非聯(lián)動的內(nèi)存可以在沒有任何警告的情況下都會被交換出去
- kmem_alloc_pages( ):可以用于在已有對象中分配新的頁鲤孵,這個函數(shù)是對vm_page_alloc( )的封裝(vm_page_alloc( )本本身是對kernel_memory_allocate( )中調(diào)用的vm_page_grab( )/vm_page_insert( )的封裝)
kmem_alloc( )開銷非常大,主要是因為需要后備物理頁面的支持:底層調(diào)用的kernel_memory_allocate( ) 可能會永久阻塞。更多情況下桑滩,使用的是更快的alloc( )(這個分配器是基于更搞笑的zone機制實現(xiàn)的)
kalloc
一旦Mach中的zone都初始化之后胁澳,就可以用于快速的內(nèi)核內(nèi)部內(nèi)寸分配了,這些內(nèi)存分配是由 kalloc_( )系列函數(shù)完成的。這些函數(shù)從功能上等同于用戶態(tài)的 maclloc( ) 。
kalloc函數(shù)是XNU中使用最為廣泛的內(nèi)存分配器,有很多函數(shù)封裝了kalloc幔嗦,其中包括:
- IOKit 的 IOMalloc:直接封裝了kalloc( )汇恤,還調(diào)用了IOStatisticsAlloc 宏,用于記錄內(nèi)存分配
- Libkern的kern_os_malloc:直接封裝了kalloc( )风皿,會在分配的內(nèi)存塊之前追加上這個內(nèi)存的大小。new 操作符就是對這個函數(shù)的封裝炊苫。
- BSD的_MALLOC:用于BSD層的各種分配,也會在分配的內(nèi)存塊之前追加上這個內(nèi)存塊的大小
OSMalloc
Mach 還提供了另一組內(nèi)存分配函數(shù):OSMalloc。OSMalloc 中的關(guān)鍵概念就是標簽,標簽是一個透明的類型磺芭,必須首先分配放棒。調(diào)用者持有了標簽之后损肛,就可以將這個標簽傳入任何一個OSMalloc的函數(shù),那么OSMalloc 通過kmem_alloc_pageable 分配內(nèi)存仙辟。否則戴尸,通過kalloc( )從聯(lián)動內(nèi)存中分配內(nèi)存。標簽本身保存在一個標簽的鏈表中,每一個標簽都有一個引用計數(shù)。分配內(nèi)存會增加這個標簽的引用計數(shù)峭咒。
Mach 分頁器
進程的內(nèi)存需求早晚會超過可用的RAM顽决,系統(tǒng)必須有一種方法能夠?qū)⒉换顒拥捻撁鎮(zhèn)浞萜饋聿⑶覐腞AM中刪除赋访,騰出更多的RAM給活動的頁面使用旋炒,至少暫時能夠滿足活動頁面的需求鼎兽。在其他操作系統(tǒng)中,這個工作專門是由專門的內(nèi)核線程完成的。在Mach 中,這些專門的任務(wù)稱為分頁器(pager)厚宰,分頁器可以是內(nèi)核線程,設(shè)置建議是外部的用戶態(tài)(甚至遠程)服務(wù)程序。
Mach分頁器是一個內(nèi)存管理器,負責將虛擬內(nèi)存?zhèn)浞莸侥硞€特定類型的后備存儲中。當內(nèi)存容量不足,內(nèi)存頁面需要被交換出內(nèi)存是,后備存儲保存內(nèi)存頁面的內(nèi)容:當換出的內(nèi)存頁面需要被使用時,將內(nèi)存的頁面恢復(fù)到RAM中巨双。只有“臟”頁面才需要進行上述的換出和換入慢宗,因為“臟”頁面是在內(nèi)存中修改過的頁面贱田,要從RAM中剔除時必須保存到磁盤中防止數(shù)據(jù)丟失蔬墩。
要注意的是樟插,這里提到的分頁器僅僅實現(xiàn)了各自負責的內(nèi)存對象的分頁操作鸵熟,這些分頁器不會控制系統(tǒng)的分頁策略煮盼。分頁策略是有vm_pageout 守護線程負責的报破。
分頁器的類型
iOS 和 OS X 中XNU 包含的分頁器種類都是一樣的盹靴。下表是XNU中的內(nèi)存分頁器的多種類型:
內(nèi)存分頁器 | 用途 |
---|---|
Default 分頁器(默認) | 匿名內(nèi)存 |
VNode 分頁器 | 內(nèi)存映射的文件 |
Device 分頁器 | 設(shè)置后援的I/O |
Swapfile 分頁器 | 處理特定的swapfile 映射的嘗試控漠,防止通過內(nèi)存映射讀取交換文件的數(shù)據(jù) |
Apple-protected 分頁器 | 蘋果特有的擴展:對內(nèi)存(二進制文件所在的內(nèi)存)加密提供支持 |
Freezer(僅用于iOS) | iOS 特有的擴展毙驯,支持“冷凍”進程 |
分頁策略管理
Pageout 守護程序
pageout 守護程序其實不是一個真的守護程序,而是一個線程。而且不是一般的線程:當kernel_bootstarp_thread( ) 完成內(nèi)核初始化工作并且沒有其他事情可做時,就調(diào)用vm_pageout( ) 成為了pageour 守護程序, vm_pageout( ) 永遠不返回。這個線程管理頁面交換的策略哄酝,判斷哪些頁面需要寫回到其后備存儲直晨。
vm_pageout線程
vm_pageout( ) 函數(shù)講kernel_bootstrap_thread 線程轉(zhuǎn)變?yōu)閜ageout 守護程序的止,這個函數(shù)實際上重新設(shè)置了這個線程氓润。設(shè)置完成后崩溪,調(diào)用vm_pageout_continute( )乳幸,這個函數(shù)周期性地喚醒并執(zhí)行vm_page_scan( )姿染,維護4個頁面表(稱為頁面隊列)盾戴。系統(tǒng)中的每一個vm_page 都通過pageq字段綁定這4個隊列中的一個:vm_page_queue_active:最近活躍且駐留在內(nèi)存中的頁面
vm_page_queue_inactive:最近不活躍的頁面,因此這些頁面是頁面換出的備選頁面。根據(jù)這些頁面的使用情況,可能會被換出极祸,也可能會被重新激活
vm_page_queue_free:空閑頁面表汰规。這些頁面曾經(jīng)是非活躍的頁面色解,但是被清理出去了(頁面換出)
vm_page_queue_speculative:這些頁面是通過預(yù)讀策略投機映射的頁面蝌矛,這些頁面是不活躍的璃赡,但是很可能很快會變?yōu)榛钴S頁面
vm_pageout iothread線程
內(nèi)部和外部iothread 線程各自檢查一個vm_pageout_queue_t,這兩個vm_page_queue_t 都是由vm_pageout( )初始化的。vm_pageout_queue_internal 專門用于內(nèi)部的VM對象(即那些內(nèi)核創(chuàng)建的VM對象窍箍,又默認分頁器維護祷蝌,internel 標準設(shè)置為true),vm_pageout_queue_external 用于其他所有的VM對象
這兩個線程都使用了同一個線程函數(shù)vm_pageout_iothread_continue( ) 只不過操作的是不同的隊列。這個函數(shù)(嚴格地說是一個續(xù)體)遍歷自己的隊列,出隊隊列中的每一個頁面邪蛔,獲得其對應(yīng)的分頁器(通過vm_object 引用)污抬,然后調(diào)用器分頁器的memory_object_data_return( ) 函數(shù)多柑。這種方式可以使得pageout 線程和實際的分頁操作實現(xiàn)解耦,分頁器操作是由分頁器單獨負責的兔毒。垃圾回收線程
垃圾回收線程(vm_pageout_garbage_collect( ))偶爾會被vm_pageout_scan( ) 通過其續(xù)體喚醒。垃圾回收機制線程處理4個方面的垃圾回收工作:srack_collect( ):內(nèi)核棧中的頁面
consider_machine_collect( ):回收機器相關(guān)的頁面
consider_buffer_cache_collect( ):如果確定定義了這個函數(shù)則調(diào)用這個函數(shù)钦睡。調(diào)用這通過vm_set_buffer_cleanup_callout( ) 定義這個函數(shù)。BSD 層在bufinit( ) 函數(shù)中注冊了buffer_cache_gc( ) 函數(shù)
consider_zone_gc( ):zone 相關(guān)的垃圾回收
處理頁錯誤
vm_pageout( ) 守護程序處理的只是交換的一個方向啦撮,從物理內(nèi)存換出到后備存儲骄崩。而另外一個方向是頁面換入,則是發(fā)生在頁面錯誤的時候處理的蔚润。這個邏輯非常復(fù)雜挫以,簡化為一下步驟:
- 如果陷阱的原因是頁錯誤,那么機器級別的線程處理程序調(diào)用vm_fault( )
- vm_fault( ) 函數(shù)調(diào)用 vm_pageout_fault( )處理實際發(fā)生錯誤的頁面殴蹄,并且從后備存儲中將這個頁面返回
- PMAP_ENTER( ) 將頁面插入任務(wù)的pmap
頁錯誤有很多種工腋,上述只是其中一種,其他類型的也錯誤還包括:
- 非法訪問:訪問應(yīng)該沒有映射到進程地址空間(即任務(wù)的vm_map)的地址沐绒。解引用應(yīng)該野指針時通常會發(fā)生這種錯誤括尸。發(fā)生這種錯誤時進程會收到SIGSEGV信號
- 頁面保護錯誤:訪問應(yīng)該映射的地址僧家,但是頁面的保護掩碼拒絕請求的訪問
- 寫時復(fù)制(copy-on-write):頁面可以被標記可讀爹谭,因此如果任務(wù)試圖寫入頁面時,會捕捉到這個錯誤运沦,在重新嘗試寫入操作之前可以將這個頁面復(fù)制出來