我們在用偵錯程式看linux使用者程序**時,發現了一件很有意思的事情,在一段記憶體空間中,有一整頁(4k)都是data abort,如下:
第一頁4011c000資料正常
... ...
4011cfec [0xe28dd014] add r13,r13,#0x14
4011cff0 [0xe8bd40f0] ldmfd r13!,
4011cff4 [0xe12fff1e] bx r14
4011cff8 [0xe92d41f0] stmfd r13!,
4011cffc [0xe59f4064] ldr r4,0x4011d068
第二頁4011d000 都是data abort
4011d000 *** data abort ***
4011d004 *** data abort ***
4011d008 *** data abort ***
4011d00c *** data abort ***
... ...
第三頁 4011e000 資料正常
4011e000 資料正常
由於當時並不知道linux是如何處理使用者程序的記憶體分配,所以認為這是乙個「錯誤」。既然有錯誤,我們決定找到這個問題發生的根源。
http://blog.chinaunix.net/u3/99423/showart_2096904.html)。大概的方法就是將物理記憶體全部dump出來,通過第一頁的**,比如0xe59f4064,查詢其在記憶體中的實體地址,再通過提取實體地址的前20位,就可以查詢linux系統的二級頁表(對應arm的tlb,linux中叫pte)。這個實驗雖然看上去很不可思議,但實現起來並不複雜。最終得到了linux存放在記憶體中的tlb資料。
每個表項是32bit
虛擬頁位址 對應表項的內容
4011c000 3156caae
4011d000 00000000
4011e000 3156faae
很有意思,data abort的資料段,對應的pte也是空的,這難道是乙個系統錯誤?
no!經過進一步的學習後發現,在linux系統中,這是乙個很正常的現象。linux在使用者程序執行時並沒有建立所有記憶體頁面的對映,而是需要用到的時候再建立對映關係。當linux使用者程序訪問到沒有建立對映的頁表(此時pte指標為0),會呼叫相應的函式進行處理,或建立、或換出,具體執行這個操作的函式叫handle_pte_fault(),位於核心的mm/memory.c中。
但是,linux是如何進入缺頁處理的呢?
有兩種情況,都是利用了arm處理器的異常中斷進行相應的處理。
第一種是程式順序執行,正常頁面的最後一條指令執行完後進入空頁面,當空頁面的第一條指令進入arm處理器流水線的執行週期時,arm處理器會報告乙個指令預取異常中斷,並跳轉到位址0x0c,在linux系統中由於使用了高位址向量表,所以會跳轉到0xffff000c。此時arm處理器進入abort狀態,執行一系列**儲存現場(**位於/arch/arm/kernel/entry-armv.s),然後進入svc狀態執行arch/arm/mm/fault.c中的do_prefetchabort(),最後會呼叫handle_pte_fault()處理缺頁異常。
第二種情況,頁面中的程式執行時需要使用未分配頁面的資料,比如「ldr r0,未分配頁位址」。遇到這種情況,就不是指令預取異常了,而是資料訪問異常(data abort)。此時處理器依然會進入abort狀態,跳轉到0xffff0010執行相應的vector_dabt**(entry_armv.s)儲存狀態,進入svc態,執行do_dataabort()函式,最後同樣呼叫handle_pte_fault()處理缺頁異常。
因此,最開始遇到的情況:三個pte,中間是空的,這是乙個很正常的情況。因為第三頁很可能由於前面的呼叫而已經建立,第二頁卻還沒有建立。
至於handle_pte_fault()如何處理缺頁異常,我還沒有看完,就不在本文討論了。已知至少有do_no_page()、do_swap_page()、do_wp_page等多種方式,此為後話。
通過跟蹤使用者程式,發現linux使用者程序基本所有的頁面都是這樣處理,因此處理器會很頻繁的進出abort狀態,執行頁面處理函式,這是會不會效率有點低了呢?待研究。
handle_pte_fault()
上文最後提到了handle_pte_fault()這個函式,用來處理頁錯,分配pte。為了更清楚的了解pte是如何申請到的,還是有必要深究一下。
handle_pte_fault()有幾個函式用來檢查當前pte的狀態:
pte_present() 檢測頁面是否在記憶體中
pte_none() 檢測頁表項是否為空
pte_file() 同一位址多對映(此函式不重要)
vm->ops->fault標記位
核心用likely對其做了標記,說明這個標記一般滿足,適用於已經建立好虛擬記憶體和檔案的對映關係的情況。
1)針對滿足pte_present()函式,即pte不在記憶體中,會在以4個下函式中選擇乙個進行處理:
(1)do_linear_fault();
最常見的情況,pte表項為空,但滿足vm->ops->fault,說明已經在記憶體中建立虛擬記憶體和檔案的對映關係。
(2)do_anonymous_page();
pte表項為空,但是沒有建立和檔案的對映關係,說明是第一次demanding page。
(3)do_nonlinear_fault();
pte表項非空,滿足pte_file()檢查,對同乙個實體地址做多個虛擬對映。
(4)do_swap_page();
pte表項非空,不滿足pte_file()檢查,此頁將會被換出。
2)如果不滿足pte_present(),即pte在記憶體中,則會執行下面的cow操作:
cow的全稱叫做「copy on write」,即寫時複製。這一塊是涉及到兩個程序共享操作的,簡單的說兩個程序可以共享頁面(特別是fork出的程序),只有當乙個程序需要寫入檔案時,才從同一頁面複製乙份副本。下面的函式呼叫do_wp_page(),將生成的複製頁賦值給寫程序。由於我僅僅跟了系統的「/sbin/init」,所以這裡根本沒有呼叫到。 附:
do_anonymous_page()函式的跟蹤
——>mk_pte() 構建對映表
——>ptn_pte()
——>__pte((ptn << 12 | pgprot_val)
最終生成的pte為32bit,其中20bit實體地址,12bit控制資訊
Linux使用者及許可權分配
一 使用者分類 所有者 u 所屬組 g 其它使用者 o 所有使用者 a 二 使用者管理 1 2 3 4 5 6 7 8 9 檢視使用者 id user 新增使用者 useradd user 設定密碼 passwd user 刪除使用者 userdel r user 刪除使用者的時候使用者組被刪除 三...
二 記憶體分配
c語言中描述變數的時候常用的兩個用語 1.作用域 也叫可見域,指的是變數的作用範圍。在哪個範圍內,該變數是可見的 可以使用的。2.生存期 也叫儲存期,指的是變數從建立到銷毀的生存時間段。作用域和存在域是兩個不同的概念,比如在程式的某個位置,某變數存在 記憶體中分配了位址 但不可見 不可使用 從作用域...
malloc 底層實現及Linux記憶體分配原理
1 malloc 函式實在虛擬位址空間中劃分一片區域,而沒有與物理頁對應。1 當開闢的空間小於 128k 時,malloc 的底層實現是呼叫brk 系統呼叫函式來在虛擬位址空間分配記憶體,其主要移動指標 enddata 此時的 enddata指的是 linux 位址空間中堆段的末尾位址,不是資料段的...