Linux是這樣管理內(nèi)存的
Linux存儲管理模型非常簡單,因為這種Linux機(jī)制使其具有可移植性,并且可以在具有類似存儲管理單元的計算機(jī)上實現(xiàn)Linux。 讓我們看看如何實現(xiàn)Linux內(nèi)存管理。
基本概念
每個 Linux 進(jìn)程都會有地址空間,這些地址空間由三個段區(qū)域組成:text 段、data 段、stack 段。下面是進(jìn)程地址空間的示例。
數(shù)據(jù)段(data segment) 包含了程序的變量、字符串、數(shù)組和其他數(shù)據(jù)的存儲。數(shù)據(jù)段分為兩部分,已經(jīng)初始化的數(shù)據(jù)和尚未初始化的數(shù)據(jù)。其中尚未初始化的數(shù)據(jù)就是我們說的 BSS。數(shù)據(jù)段部分的初始化需要編譯就期確定的常量以及程序啟動就需要一個初始值的變量。所有 BSS 部分中的變量在加載后被初始化為 0 。
和 代碼段(Text segment) 不一樣,data segment 數(shù)據(jù)段可以改變。程序總是修改它的變量。而且,許多程序需要在執(zhí)行時動態(tài)分配空間。Linux 允許數(shù)據(jù)段隨著內(nèi)存的分配和回收從而增大或者減小。為了分配內(nèi)存,程序可以增加數(shù)據(jù)段的大小。在 C 語言中有一套標(biāo)準(zhǔn)庫 malloc 經(jīng)常用于分配內(nèi)存。進(jìn)程地址空間描述符包含動態(tài)分配的內(nèi)存區(qū)域稱為 堆(heap)。
第三部分段是 棧段(stack segment)。在大部分機(jī)器上,棧段會在虛擬內(nèi)存地址頂部地址位置處,并向低位置處(向地址空間為 0 處)拓展。舉個例子來說,在 32 位 x86 架構(gòu)的機(jī)器上,棧開始于 0xC0000000,這是用戶模式下進(jìn)程允許可見的 3GB 虛擬地址限制。如果棧一直增大到超過棧段后,就會發(fā)生硬件故障并把頁面下降一個頁面。
當(dāng)程序啟動時,棧區(qū)域并不是空的,相反,它會包含所有的 shell 環(huán)境變量以及為了調(diào)用它而向 shell 輸入的命令行。舉個例子,當(dāng)你輸入
cp cxuan lx復(fù)制代碼
時,cp 程序會運行并在棧中帶著字符串 cp cxuan lx ,這樣就能夠找出源文件和目標(biāo)文件的名稱。
當(dāng)兩個用戶運行在相同程序中,例如編輯器(editor),那么就會在內(nèi)存中保持編輯器程序代碼的兩個副本,但是這種方式并不高效。Linux 系統(tǒng)支持共享文本段作為替代。下面圖中我們會看到 A 和 B 兩個進(jìn)程,它們有著相同的文本區(qū)域。
數(shù)據(jù)段和棧段只有在 fork 之后才會共享,共享也是共享未修改過的頁面。如果任何一個都需要變大但是沒有相鄰空間容納的話,也不會有問題,因為相鄰的虛擬頁面不必映射到相鄰的物理頁面上。
除了動態(tài)分配更多的內(nèi)存,Linux 中的進(jìn)程可以通過內(nèi)存映射文件來訪問文件數(shù)據(jù)。這個特性可以使我們把一個文件映射到進(jìn)程空間的一部分而該文件就可以像位于內(nèi)存中的字節(jié)數(shù)組一樣被讀寫。把一個文件映射進(jìn)來使得隨機(jī)讀寫比使用 read 和 write 之類的 I/O 系統(tǒng)調(diào)用要容易得多。共享庫的訪問就是使用了這種機(jī)制。如下所示
我們可以看到兩個相同文件會被映射到相同的物理地址上,但是它們屬于不同的地址空間。
映射文件的優(yōu)點是,兩個或多個進(jìn)程可以同時映射到同一文件中,任意一個進(jìn)程對文件的寫操作對其他文件可見。通過使用映射臨時文件的方式,可以為多線程共享內(nèi)存提供高帶寬,臨時文件在進(jìn)程退出后消失。但是實際上,并沒有兩個相同的地址空間,因為每個進(jìn)程維護(hù)的打開文件和信號不同。
Linux 內(nèi)存管理系統(tǒng)調(diào)用
下面我們探討一下關(guān)于內(nèi)存管理的系統(tǒng)調(diào)用方式。事實上,POSIX 并沒有給內(nèi)存管理指定任何的系統(tǒng)調(diào)用。然而,Linux 卻有自己的內(nèi)存系統(tǒng)調(diào)用,主要系統(tǒng)調(diào)用如下
系統(tǒng)調(diào)用描述s = brk(addr)改變數(shù)據(jù)段大小a = mmap(addr,len,prot,flags,fd,offset)進(jìn)行映射s = unmap(addr,len)取消映射
如果遇到錯誤,那么 s 的返回值是 -1,a 和 addr 是內(nèi)存地址,len 表示的是長度,prot 表示的是控制保護(hù)位,flags 是其他標(biāo)志位,fd 是文件描述符,offset 是文件偏移量。
brk 通過給出超過數(shù)據(jù)段之外的第一個字節(jié)地址來指定數(shù)據(jù)段的大小。如果新的值要比原來的大,那么數(shù)據(jù)區(qū)會變得越來越大,反之會越來越小。
mmap 和 unmap 系統(tǒng)調(diào)用會控制映射文件。mmp 的第一個參數(shù) addr 決定了文件映射的地址。它必須是頁面大小的倍數(shù)。如果參數(shù)是 0,系統(tǒng)會分配地址并返回 a。第二個參數(shù)是長度,它告訴了需要映射多少字節(jié)。它也是頁面大小的倍數(shù)。prot 決定了映射文件的保護(hù)位,保護(hù)位可以標(biāo)記為 可讀、可寫、可執(zhí)行或者這些的結(jié)合。第四個參數(shù) flags 能夠控制文件是私有的還是可讀的以及 addr 是必須的還是只是進(jìn)行提示。第五個參數(shù) fd 是要映射的文件描述符。只有打開的文件是可以被映射的,因此如果想要進(jìn)行文件映射,必須打開文件;最后一個參數(shù) offset 會指示文件從什么時候開始,并不一定每次都要從零開始。
Linux 內(nèi)存管理實現(xiàn)
內(nèi)存管理系統(tǒng)是操作系統(tǒng)最重要的部分之一。從計算機(jī)早期開始,我們實際使用的內(nèi)存都要比系統(tǒng)中實際存在的內(nèi)存多。內(nèi)存分配策略克服了這一限制,并且其中最有名的就是 虛擬內(nèi)存(virtual memory)。通過在多個競爭的進(jìn)程之間共享虛擬內(nèi)存,虛擬內(nèi)存得以讓系統(tǒng)有更多的內(nèi)存。虛擬內(nèi)存子系統(tǒng)主要包括下面這些概念。
大地址空間
操作系統(tǒng)使系統(tǒng)使用起來好像比實際的物理內(nèi)存要大很多,那是因為虛擬內(nèi)存要比物理內(nèi)存大很多倍。
保護(hù)
系統(tǒng)中的每個進(jìn)程都會有自己的虛擬地址空間。這些虛擬地址空間彼此完全分開,因此運行一個應(yīng)用程序的進(jìn)程不會影響另一個。并且,硬件虛擬內(nèi)存機(jī)制允許內(nèi)存保護(hù)關(guān)鍵內(nèi)存區(qū)域。
內(nèi)存映射
內(nèi)存映射用來向進(jìn)程地址空間映射圖像和數(shù)據(jù)文件。在內(nèi)存映射中,文件的內(nèi)容直接映射到進(jìn)程的虛擬空間中。
公平的物理內(nèi)存分配
內(nèi)存管理子系統(tǒng)允許系統(tǒng)中的每個正在運行的進(jìn)程公平分配系統(tǒng)的物理內(nèi)存。
共享虛擬內(nèi)存
盡管虛擬內(nèi)存讓進(jìn)程有自己的內(nèi)存空間,但是有的時候你是需要共享內(nèi)存的。例如幾個進(jìn)程同時在 shell 中運行,這會涉及到 IPC 的進(jìn)程間通信問題,這個時候你需要的是共享內(nèi)存來進(jìn)行信息傳遞而不是通過拷貝每個進(jìn)程的副本獨立運行。
下面我們就正式探討一下什么是 虛擬內(nèi)存
虛擬內(nèi)存的抽象模型
在考慮 Linux 用于支持虛擬內(nèi)存的方法之前,考慮一個不會被太多細(xì)節(jié)困擾的抽象模型是很有用的。
處理器在執(zhí)行指令時,會從內(nèi)存中讀取指令并將其解碼(decode),在指令解碼時會獲取某個位置的內(nèi)容并將他存到內(nèi)存中。然后處理器繼續(xù)執(zhí)行下一條指令。這樣,處理器總是在訪問存儲器以獲取指令和存儲數(shù)據(jù)。
在虛擬內(nèi)存系統(tǒng)中,所有的地址空間都是虛擬的而不是物理的。但是實際存儲和提取指令的是物理地址,所以需要讓處理器根據(jù)操作系統(tǒng)維護(hù)的一張表將虛擬地址轉(zhuǎn)換為物理地址。
為了簡單的完成轉(zhuǎn)換,虛擬地址和物理地址會被分為固定大小的塊,稱為 頁(page)。這些頁有相同大小,如果頁面大小不一樣的話,那么操作系統(tǒng)將很難管理。Alpha AXP系統(tǒng)上的 Linux 使用 8 KB 頁面,而 Intel x86 系統(tǒng)上的 Linux 使用 4 KB 頁面。每個頁面都有一個唯一的編號,即頁面框架號(PFN)。
上面就是 Linux 內(nèi)存映射模型了,在這個頁模型中,虛擬地址由兩部分組成:偏移量和虛擬頁框號。每次處理器遇到虛擬地址時都會提取偏移量和虛擬頁框號。處理器必須將虛擬頁框號轉(zhuǎn)換為物理頁號,然后以正確的偏移量的位置訪問物理頁。
上圖中展示了兩個進(jìn)程 A 和 B 的虛擬地址空間,每個進(jìn)程都有自己的頁表。這些頁表將進(jìn)程中的虛擬頁映射到內(nèi)存中的物理頁中。頁表中每一項均包含
有效標(biāo)志(valid flag): 表明此頁表條目是否有效
該條目描述的物理頁框號
訪問控制信息,頁面使用方式,是否可寫以及是否可以執(zhí)行代碼
要將處理器的虛擬地址映射為內(nèi)存的物理地址,首先需要計算虛擬地址的頁框號和偏移量。頁面大小為 2 的次冪,可以通過移位完成操作。
如果當(dāng)前進(jìn)程嘗試訪問虛擬地址,但是訪問不到的話,這種情況稱為 缺頁異常,此時虛擬操作系統(tǒng)的錯誤地址和頁面錯誤的原因?qū)⑼ㄖ僮飨到y(tǒng)。
通過以這種方式將虛擬地址映射到物理地址,虛擬內(nèi)存可以以任何順序映射到系統(tǒng)的物理頁面。
按需分頁
由于物理內(nèi)存要比虛擬內(nèi)存少很多,因此操作系統(tǒng)需要注意盡量避免直接使用低效的物理內(nèi)存。節(jié)省物理內(nèi)存的一種方式是僅加載執(zhí)行程序當(dāng)前使用的頁面(這何嘗不是一種懶加載的思想呢?)。例如,可以運行數(shù)據(jù)庫來查詢數(shù)據(jù)庫,在這種情況下,不是所有的數(shù)據(jù)都裝入內(nèi)存,只裝載需要檢查的數(shù)據(jù)。這種僅僅在需要時才將虛擬頁面加載進(jìn)內(nèi)中的技術(shù)稱為按需分頁。
交換
如果某個進(jìn)程需要將虛擬頁面?zhèn)魅雰?nèi)存,但是此時沒有可用的物理頁面,那么操作系統(tǒng)必須丟棄物理內(nèi)存中的另一個頁面來為該頁面騰出空間。
如果頁面已經(jīng)修改過,那么操作系統(tǒng)必須保留該頁面的內(nèi)容,以便以后可以訪問它。這種類型的頁面被稱為臟頁,當(dāng)將其從內(nèi)存中移除時,它會保存在稱為交換文件的特殊文件中。相對于處理器和物理內(nèi)存的速度,對交換文件的訪問非常慢,并且操作系統(tǒng)需要兼顧將頁面寫到磁盤的以及將它們保留在內(nèi)存中以便再次使用。
Linux 使用最近最少使用(LRU)頁面老化技術(shù)來公平的選擇可能會從系統(tǒng)中刪除的頁面,這個方案涉及系統(tǒng)中的每個頁面,頁面的年齡隨著訪問次數(shù)的變化而變化,如果某個頁面訪問次數(shù)多,那么該頁就表示越 年輕,如果某個呃頁面訪問次數(shù)太少,那么該頁越容易被換出。
物理和虛擬尋址模式
大多數(shù)多功能處理器都支持 物理地址模式和虛擬地址模式的概念。物理尋址模式不需要頁表,并且處理器不會在此模式下嘗試執(zhí)行任何地址轉(zhuǎn)換。 Linux 內(nèi)核被鏈接在物理地址空間中運行。
Alpha AXP 處理器沒有物理尋址模式。相反,它將內(nèi)存空間劃分為幾個區(qū)域,并將其中兩個指定為物理映射的地址。此內(nèi)核地址空間稱為 KSEG 地址空間,它包含從 0xfffffc0000000000 向上的所有地址。為了從 KSEG 中鏈接的代碼(按照定義,內(nèi)核代碼)執(zhí)行或訪問其中的數(shù)據(jù),該代碼必須在內(nèi)核模式下執(zhí)行。鏈接到 Alpha 上的 Linux內(nèi)核以從地址 0xfffffc0000310000 執(zhí)行。
訪問控制
頁面表的每一項還包含訪問控制信息,訪問控制信息主要檢查進(jìn)程是否應(yīng)該訪問內(nèi)存。
必要時需要對內(nèi)存進(jìn)行訪問限制。 例如包含可執(zhí)行代碼的內(nèi)存,自然是只讀內(nèi)存; 操作系統(tǒng)不應(yīng)允許進(jìn)程通過其可執(zhí)行代碼寫入數(shù)據(jù)。 相比之下,包含數(shù)據(jù)的頁面可以被寫入,但是嘗試執(zhí)行該內(nèi)存的指令將失敗。 大多數(shù)處理器至少具有兩種執(zhí)行模式:內(nèi)核態(tài)和用戶態(tài)。 你不希望訪問用戶執(zhí)行內(nèi)核代碼或內(nèi)核數(shù)據(jù)結(jié)構(gòu),除非處理器以內(nèi)核模式運行。
訪問控制信息被保存在上面的 Page Table Entry ,頁表項中,上面這幅圖是 Alpha AXP的 PTE。位字段具有以下含義
V
表示 valid ,是否有效位
FOR
讀取時故障,在嘗試讀取此頁面時出現(xiàn)故障
FOW
寫入時錯誤,在嘗試寫入時發(fā)生錯誤
FOE
執(zhí)行時發(fā)生錯誤,在嘗試執(zhí)行此頁面中的指令時,處理器都會報告頁面錯誤并將控制權(quán)傳遞給操作系統(tǒng),
ASM
地址空間匹配,當(dāng)操作系統(tǒng)希望清除轉(zhuǎn)換緩沖區(qū)中的某些條目時,將使用此選項。
GH
當(dāng)在使用單個轉(zhuǎn)換緩沖區(qū)條目而不是多個轉(zhuǎn)換緩沖區(qū)條目映射整個塊時使用的提示。
KRE
內(nèi)核模式運行下的代碼可以讀取頁面
URE
用戶模式下的代碼可以讀取頁面
KWE
以內(nèi)核模式運行的代碼可以寫入頁面
UWE
以用戶模式運行的代碼可以寫入頁面
頁框號
對于設(shè)置了 V 位的 PTE,此字段包含此 PTE 的物理頁面幀號(頁面幀號)。對于無效的 PTE,如果此字段不為零,則包含有關(guān)頁面在交換文件中的位置的信息。
除此之外,Linux 還使用了兩個位
_PAGE_DIRTY
如果已設(shè)置,則需要將頁面寫出到交換文件中
_PAGE_ACCESSED
Linux 用來將頁面標(biāo)記為已訪問。
緩存
上面的虛擬內(nèi)存抽象模型可以用來實施,但是效率不會太高。操作系統(tǒng)和處理器設(shè)計人員都嘗試提高性能。 但是除了提高處理器,內(nèi)存等的速度之外,最好的方法就是維護(hù)有用信息和數(shù)據(jù)的高速緩存,從而使某些操作更快。在 Linux 中,使用很多和內(nèi)存管理有關(guān)的緩沖區(qū),使用緩沖區(qū)來提高效率。
緩沖區(qū)緩存
緩沖區(qū)高速緩存包含塊設(shè)備驅(qū)動程序使用的數(shù)據(jù)緩沖區(qū)。
還記得什么是塊設(shè)備么?這里回顧下
塊設(shè)備是一個能存儲固定大小塊信息的設(shè)備,它支持以固定大小的塊,扇區(qū)或群集讀取和(可選)寫入數(shù)據(jù)。每個塊都有自己的物理地址。通常塊的大小在 512 - 65536 之間。所有傳輸?shù)男畔⒍紩赃B續(xù)的塊為單位。塊設(shè)備的基本特征是每個塊都較為對立,能夠獨立的進(jìn)行讀寫。常見的塊設(shè)備有 硬盤、藍(lán)光光盤、USB 盤
與字符設(shè)備相比,塊設(shè)備通常需要較少的引腳。
緩沖區(qū)高速緩存通過設(shè)備標(biāo)識符和塊編號用于快速查找數(shù)據(jù)塊。 如果可以在緩沖區(qū)高速緩存中找到數(shù)據(jù),則無需從物理塊設(shè)備中讀取數(shù)據(jù),這種訪問方式要快得多。
頁緩存
頁緩存用于加快對磁盤上圖像和數(shù)據(jù)的訪問
它用于一次一頁地緩存文件中的內(nèi)容,并且可以通過文件和文件中的偏移量進(jìn)行訪問。當(dāng)頁面從磁盤讀入內(nèi)存時,它們被緩存在頁面緩存中。
交換區(qū)緩存
僅僅已修改(臟頁)被保存在交換文件中
只要這些頁面在寫入交換文件后沒有修改,則下次交換該頁面時,無需將其寫入交換文件,因為該頁面已在交換文件中。 可以直接丟棄。 在大量交換的系統(tǒng)中,這節(jié)省了許多不必要的和昂貴的磁盤操作。
硬件緩存
處理器中通常使用一種硬件緩存。頁表條目的緩存。在這種情況下,處理器并不總是直接讀取頁表,而是根據(jù)需要緩存頁的翻譯。 這些是轉(zhuǎn)換后備緩沖區(qū) 也被稱為 TLB,包含來自系統(tǒng)中一個或多個進(jìn)程的頁表項的緩存副本。
引用虛擬地址后,處理器將嘗試查找匹配的 TLB 條目。 如果找到,則可以將虛擬地址直接轉(zhuǎn)換為物理地址,并對數(shù)據(jù)執(zhí)行正確的操作。 如果處理器找不到匹配的 TLB 條目, 它通過向操作系統(tǒng)發(fā)信號通知已發(fā)生 TLB 丟失獲得操作系統(tǒng)的支持和幫助。系統(tǒng)特定的機(jī)制用于將該異常傳遞給可以修復(fù)問題的操作系統(tǒng)代碼。 操作系統(tǒng)為地址映射生成一個新的 TLB 條目。 清除異常后,處理器將再次嘗試轉(zhuǎn)換虛擬地址。這次能夠執(zhí)行成功。
使用緩存也存在缺點,為了節(jié)省精力,Linux 必須使用更多的時間和空間來維護(hù)這些緩存,并且如果緩存損壞,系統(tǒng)將會崩潰。
Linux 頁表
Linux 假定頁表分為三個級別。訪問的每個頁表都包含下一級頁表
圖中的 PDG 表示全局頁表,當(dāng)創(chuàng)建一個新的進(jìn)程時,都要為新進(jìn)程創(chuàng)建一個新的頁面目錄,即 PGD。
要將虛擬地址轉(zhuǎn)換為物理地址,處理器必須獲取每個級別字段的內(nèi)容,將其轉(zhuǎn)換為包含頁表的物理頁的偏移量,并讀取下一級頁表的頁框號。這樣重復(fù)三次,直到找到包含虛擬地址的物理頁面的頁框號為止。
Linux 運行的每個平臺都必須提供翻譯宏,這些宏允許內(nèi)核遍歷特定進(jìn)程的頁表。這樣,內(nèi)核無需知道頁表條目的格式或它們的排列方式。
頁分配和取消分配
對系統(tǒng)中物理頁面有很多需求。例如,當(dāng)圖像加載到內(nèi)存中時,操作系統(tǒng)需要分配頁面。
系統(tǒng)中所有物理頁面均由 mem_map 數(shù)據(jù)結(jié)構(gòu)描述,這個數(shù)據(jù)結(jié)構(gòu)是 mem_map_t 的列表。它包括一些重要的屬性
count :這是頁面的用戶數(shù)計數(shù),當(dāng)頁面在多個進(jìn)程之間共享時,計數(shù)大于 1
age:這是描述頁面的年齡,用于確定頁面是否適合丟棄或交換
map_nr :這是此mem_map_t描述的物理頁框號。
頁面分配代碼使用 free_area向量查找和釋放頁面,free_area 的每個元素都包含有關(guān)頁面塊的信息。
頁面分配
Linux 的頁面分配使用一種著名的伙伴算法來進(jìn)行頁面的分配和取消分配。頁面以 2 的冪為單位進(jìn)行塊分配。這就意味著它可以分配 1頁、2 頁、4頁等等,只要系統(tǒng)中有足夠可用的頁面來滿足需求就可以。判斷的標(biāo)準(zhǔn)是nr_free_pages> min_free_pages,如果滿足,就會在 free_area 中搜索所需大小的頁面塊完成分配。free_area 的每個元素都有該大小的塊的已分配頁面和空閑頁面塊的映射。
分配算法會搜索請求大小的頁面塊。如果沒有任何請求大小的頁面塊可用的話,會搜尋一個是請求大小二倍的頁面塊,然后重復(fù),直到一直搜尋完 free_area 找到一個頁面塊為止。如果找到的頁面塊要比請求的頁面塊大,就會對找到的頁面塊進(jìn)行細(xì)分,直到找到合適的大小塊為止。
因為每個塊都是 2 的次冪,所以拆分過程很容易,因為你只需將塊分成兩半即可。空閑塊在適當(dāng)?shù)年犃兄信抨?,分配的頁面塊返回給調(diào)用者。
如果請求一個 2 個頁的塊,則 4 頁的第一個塊(從第 4 頁的框架開始)將被分成兩個 2 頁的塊。第一個頁面(從第 4 頁的幀開始)將作為分配的頁面返回給調(diào)用方,第二個塊(從第 6 頁的頁面開始)將作為 2 頁的空閑塊排隊到 free_area 數(shù)組的元素 1 上。
頁面取消分配
上面的這種內(nèi)存方式最造成一種后果,那就是內(nèi)存的碎片化,會將較大的空閑頁面分成較小的頁面。頁面解除分配代碼會盡可能將頁面重新組合成為更大的空閑塊。每釋放一個頁面,都會檢查相同大小的相鄰的塊,以查看是否空閑。如果是,則將其與新釋放的頁面塊組合以形成下一個頁面大小塊的新的自由頁面塊。 每次將兩個頁面塊重新組合為更大的空閑頁面塊時,頁面釋放代碼就會嘗試將該頁面塊重新組合為更大的空閑頁面。 通過這種方式,可用頁面的塊將盡可能多地使用內(nèi)存。
例如上圖,如果要釋放第 1 頁的頁面,則將其與已經(jīng)空閑的第 0 頁頁面框架組合在一起,并作為大小為 2頁的空閑塊排隊到 free_area 的元素 1 中
內(nèi)存映射
內(nèi)核有兩種類型的內(nèi)存映射:共享型(shared) 和私有型(private)。私有型是當(dāng)進(jìn)程為了只讀文件,而不寫文件時使用,這時,私有映射更加高效。 但是,任何對私有映射頁的寫操作都會導(dǎo)致內(nèi)核停止映射該文件中的頁。所以,寫操作既不會改變磁盤上的文件,對訪問該文件的其它進(jìn)程也是不可見的。
按需分頁
一旦可執(zhí)行映像被內(nèi)存映射到虛擬內(nèi)存后,它就可以被執(zhí)行了。因為只將映像的開頭部分物理的拉入到內(nèi)存中,因此它將很快訪問物理內(nèi)存尚未存在的虛擬內(nèi)存區(qū)域。當(dāng)進(jìn)程訪問沒有有效頁表的虛擬地址時,操作系統(tǒng)會報告這項錯誤。
頁面錯誤描述頁面出錯的虛擬地址和引起的內(nèi)存訪問(RAM)類型。
Linux 必須找到代表發(fā)生頁面錯誤的內(nèi)存區(qū)域的 vm_area_struct 結(jié)構(gòu)。由于搜索 vm_area_struct 數(shù)據(jù)結(jié)構(gòu)對于有效處理頁面錯誤至關(guān)重要,因此它們以 AVL(Adelson-Velskii和Landis)樹結(jié)構(gòu)鏈接在一起。如果引起故障的虛擬地址沒有 vm_area_struct 結(jié)構(gòu),則此進(jìn)程已經(jīng)訪問了非法地址,Linux 會向進(jìn)程發(fā)出 SIGSEGV 信號,如果進(jìn)程沒有用于該信號的處理程序,那么進(jìn)程將會終止。
然后,Linux 會針對此虛擬內(nèi)存區(qū)域所允許的訪問類型,檢查發(fā)生的頁面錯誤類型。 如果該進(jìn)程以非法方式訪問內(nèi)存,例如寫入僅允許讀的區(qū)域,則還會發(fā)出內(nèi)存訪問錯誤信號。
現(xiàn)在,Linux 已確定頁面錯誤是合法的,因此必須對其進(jìn)行處理。