在程序的執(zhí)行過程中,,因為遇到某種障礙而使 CPU 無法最終訪問到相應(yīng)的物理內(nèi)存單元,,即無法完成從虛擬地址到物理地址映射的時候,CPU 會產(chǎn)生一次缺頁異常,,從而進行相應(yīng)的缺頁異常處理,。基于 CPU 的這一特性,,Linux 采用了請求調(diào)頁(Demand Paging)和寫時復(fù)制(Copy On Write)的技術(shù),。 1. 請求調(diào)頁是一種動態(tài)內(nèi)存分配技術(shù),它把頁框的分配推遲到不能再推遲為止,。這種技術(shù)的動機是:進程開始運行的時候并不訪問地址空間中的全部內(nèi)容,。事實上,有 一部分地址也許永遠也不會被進程所使用,。程序的局部性原理也保證了在程序執(zhí)行的每個階段,,真正使用的進程頁只有一小部分,對于臨時用不到的頁,,其所在的頁 框可以由其它進程使用,。因此,請求分頁技術(shù)增加了系統(tǒng)中的空閑頁框的平均數(shù),,使內(nèi)存得到了很好的利用,。從另外一個角度來看,,在不改變內(nèi)存大小的情況下,請 求分頁能夠提高系統(tǒng)的吞吐量,。當進程要訪問的頁不在內(nèi)存中的時候,,就通過缺頁異常處理將所需頁調(diào)入內(nèi)存中。 2. 寫時復(fù)制主要應(yīng)用于系統(tǒng)調(diào)用fork,,父子進程以只讀方式共享頁框,,當其中之一要修改頁框時,內(nèi)核才通過缺頁異常處理程序分配一個新的頁框,,并將頁框標記 為可寫,。這種處理方式能夠較大的提高系統(tǒng)的性能,這和Linux創(chuàng)建進程的操作過程有一定的關(guān)系,。在一般情況下,,子進程被創(chuàng)建以后會馬上通過系統(tǒng)調(diào)用 execve將一個可執(zhí)行程序的映象裝載進內(nèi)存中,此時會重新分配子進程的頁框,。那么,,如果fork的時候就對頁框進行復(fù)制的話,顯然是很不合適的,。 在上述的兩種情況下出現(xiàn)缺頁異常,,進程運行于用戶態(tài),異常處理程序可以讓進程從出現(xiàn)異常的指令處恢復(fù)執(zhí)行,,使用戶感覺不到異常的發(fā)生,。當然,也 會有異常無法正?;謴?fù)的情況,,這時,異常處理程序會進行一些善后的工作,,并結(jié)束該進程,。也就是說,運行在用戶態(tài)的進程如果出現(xiàn)缺頁異常,,不會對操作系統(tǒng)核 心的穩(wěn)定性造成影響,。那么對于運行在核心態(tài)的進程如果發(fā)生了無法正常恢復(fù)的缺頁異常,,應(yīng)該如何處理呢,?是否會導(dǎo)致系統(tǒng)的崩潰呢?是否能夠解決好內(nèi)核態(tài)缺頁 異常對于操作系統(tǒng)核心的穩(wěn)定性來說會產(chǎn)生很大的影響,,如果一個誤操作就會造成系統(tǒng)的Oops,,這對于用戶來說顯然是不能容忍的。本文正是針對這個問題,介 紹了一種Linux內(nèi)核中所采取的解決方法,。 在讀者繼續(xù)往下閱讀之前,,有一點需要先說明一下,本文示例中所選的代碼取自于Linux-2.4.0,,編譯環(huán)境是gcc-2.96,,objdump的版本是2.11.93.0.2,具體的版本信息可以通過以下的命令進行查詢: $ gcc -v GCC的擴展功能 由于本文中會用到GCC的擴展功能,,即匯編器as中提供的.section偽操作,,在文章開始之前我再作一個簡要的介紹。此偽操作對于不同的可 執(zhí)行文件格式有不同的解釋,,我也不一一列舉,,僅對我們所感興趣的Linux中常用的ELF格式的用法加以描述,其指令格式如下: .section NAME[, "FLAGS"] 在Linux內(nèi)核中,通過使用.section的偽操作,,可以把隨后的代碼匯編到一個由NAME指定的段中。而FLAGS字段則說明了該段的屬性,,它可以用下面介紹的單個字符來表示,,也可以是多個字符的組合。 'a' 可重定位的段,;'w' 可寫段,; 'x' 可執(zhí)行段; 'W' 可合并的段,; 's' 共享段,。 舉個例子來說明,讀者在后面會看到的:.section .fixup, "ax",。這樣的一條指令定義了一個名為.fixup的段,,隨后的指令會被加入到這個段中,該段的屬性是可重定位并可執(zhí)行,。 內(nèi)核缺頁異常處理 在老版本的Linux中,這個工作是通過函數(shù)verify_area來完成的: extern inline int verify_area(int type, const void * addr, unsigned long size) 該函數(shù)驗證了是否可以以type中說明的訪問類型(read or write)訪問從地址addr開始,、大小為size的一塊虛擬存儲區(qū)域,。為了做到這一點,,verify_read首先需要找到包含地址addr的虛擬存 儲區(qū)域(vma)。一般的情況下(正確運行的程序)這個測試都會成功返回,,在少數(shù)情況下才會出現(xiàn)失敗的情況,。也就是說,大部分的情況下內(nèi)核在一些無用的驗 證操作上花費了不算短的時間,,這從操作系統(tǒng)運行效率的角度來說是不可接受的,。 為了解決這個問題,現(xiàn)在的Linux設(shè)計中將驗證的工作交給虛存中的硬件設(shè)備來完成,。當系統(tǒng)啟動分頁機制以后,,如果一條指令的虛擬地址所對應(yīng)的 頁框(page frame)不在內(nèi)存中或者訪問的類型有錯誤,就會發(fā)生缺頁異常,。處理器把引起缺頁異常的虛擬地址裝到寄存器CR2中,,并提供一個出錯碼,指示引起缺頁異 常的存儲器訪問的類型,,隨后調(diào)用Linux的缺頁異常處理函數(shù)進行處理,。 Linux中進行缺頁異常處理的函數(shù)如下: asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)/* Are we prepared to handle this kernel fault? */ if ((fixup = search_exception_table(regs->eip)) != 0) { regs->eip = fixup; return; } ……………………… } 首先讓我們來看看傳給這個函數(shù)調(diào)用的兩個參數(shù):它們都是通過entry.S在堆棧中建立的(arch/i386/kernel /entry.S),參數(shù)regs指向保存在堆棧中的寄存器,,error_code中存放著異常的出錯碼,,具體的堆棧布局參見圖一(堆棧的生成過程請參考 《Linux內(nèi)核源代碼情景分析》一書) 該函數(shù)首先從CPU的控制寄存器CR2中獲取出現(xiàn)缺頁異常的虛擬地址。由于缺頁異常處理程序需要處理的缺頁異常類型很多,,分支也很復(fù)雜,。基于本文的主旨,,我們只關(guān)心以下的幾種內(nèi)核缺頁異常處理的情況: 1." 程序要訪問的內(nèi)核地址空間的內(nèi)容不在內(nèi)存中,,先跳轉(zhuǎn)到標號vmalloc_fault,如果當前訪問的內(nèi)容所對應(yīng)的頁目錄項不在內(nèi)存中,,再跳轉(zhuǎn)到標號no_context,; 2. 缺頁異常發(fā)生在中斷或者內(nèi)核線程中,跳轉(zhuǎn)到標號no_context,; 3. 程序在核心態(tài)運行時訪問用戶空間的數(shù)據(jù),,被訪問的數(shù)據(jù)不在內(nèi)存中 a) 出現(xiàn)異常的虛擬地址在進程的某個vma中,但是系統(tǒng)內(nèi)存無法分配空閑頁框(page frame),,則先跳轉(zhuǎn)到標號out_of_memory,,再跳轉(zhuǎn)到標號no_context; b) 出現(xiàn)異常的虛擬地址不屬于進程任一個vma,,而且不屬于堆棧擴展的范疇,,則先跳轉(zhuǎn)到標號bad_area,最終也是到達標號no_context。 從上面的這幾種情況來看,,我們關(guān)注的焦點最后集中到標號no_context處,,即對函數(shù)search_exception_table的調(diào) 用。這個函數(shù)的作用就是通過發(fā)生缺頁異常的指令(regs->eip)在異常表(exception table)中尋找下一條可以繼續(xù)運行的指令(fixup),。這里提到的異常表包含一些地址對,,地址對中的前一個地址表示出現(xiàn)異常的指令的地址,后一個表 示當前一個指令出現(xiàn)錯誤時,,程序可以繼續(xù)得以執(zhí)行的修復(fù)地址,。 如果這個查找操作成功的話,缺頁異常處理程序?qū)⒍褩V械姆祷氐刂罚╮egs->eip)修改成修復(fù)地址并返回,,隨后,,發(fā)生異常的進程將按照fixup中安排好的指令繼續(xù)執(zhí)行下去。當然,,如果無法找到與之匹配的修復(fù)地址,,系統(tǒng)只有打印出出錯信息并停止運作。 那么,,這個所謂的修復(fù)地址又是如何生成的呢,?是系統(tǒng)自動生成的嗎?答案當然是否定的,,這些修復(fù)指令都是編程人員通過as提供的擴展功能寫進內(nèi)核源碼中的,。下面我們就來分析一下其實現(xiàn)機制。 |
|