Linux內存管理圖解(2)線性地址轉物理地址

二、線性地址轉物理地址

前面說了Linux中邏輯地址等於線性地址,那麼線性地址怎麼對應到物理地址呢?這個大家都知道,那就是通過分頁機制,具體的說,就是通過頁表查找來對應物理地址。

分頁是CPU提供的一種機制,Linux只是根據這種機制的規則,利用它實現了內存管理。

分頁的基本原理是把線性地址分成固定長度的單元,稱爲頁(page)。頁內部連續的線性地址映射到連續的物理地址中。X86每頁爲4KB(爲簡化分析,我們不考慮擴展分頁的情況)。爲了能轉換成物理地址,我們需要給CPU提供當前任務的線性地址轉物理地址的查找表,即頁表(page table),頁表存放在內存中。

在保護模式下,控制寄存器CR0的最高位PG位控制着分頁管理機制是否生效,如果PG=1,分頁機制生效,需通過頁表查找才能把線性地址轉換物理地址。如果PG=0,則分頁機制無效,線性地址就直接作爲物理地址。

爲了實現每個任務的平坦的虛擬內存和相互隔離,每個任務都有自己的頁目錄表和頁表。

爲了節約頁表佔用的內存空間,x86將線性地址通過頁目錄表和頁表兩級查找轉換成物理地址。

32位的線性地址被分成3個部分:

最高10位 Directory 頁目錄表偏移量,中間10位 Table是頁表偏移量,最低12位Offset是物理頁內的字節偏移量。

頁目錄表的大小爲4KB(剛好是一個頁的大小),包含1024項,每個項4字節(32位),表項裏存儲的內容就是頁表的物理地址(因爲物理頁地址4k字節對齊,物理地址低12位總是0,所以表項裏的最低12字節記錄了一些其他信息,這裏做簡化分析)。如果頁目錄表中的頁表尚未分配,則物理地址填0。

頁表的大小也是4k,同樣包含1024項,每個項4字節,內容爲最終物理頁的物理內存起始地址。

<ignore_js_op>

每個活動的任務,必須要先分配給它一個頁目錄表,並把頁目錄表的物理地址存入cr3寄存器。頁表可以提前分配好,也可以在用到的時候再分配。

還是以 mov    0x80495b0, %eax  中的地址爲例分析一下線性地址轉物理地址的過程。

前面說到Linux中邏輯地址等於線性地址,那麼我們要轉換的線性地址就是0x80495b0。轉換的過程是由CPU自動完成的,Linux所要做的就是準備好轉換所需的頁目錄表和頁表(假設已經準備好,給頁目錄表和頁表分配物理內存的過程很複雜,後文再分析)。

內核先將當前任務的頁目錄表的物理地址填入cr3寄存器。

線性地址 0x80495b0 轉換成二進制後是 0000 1000 0000 0100 1001 0101 1011 0000,最高10位0000 1000 00的十進制是32,CPU查看頁目錄表第32項,裏面存放的是頁表的物理地址。線性地址中間10位00 0100 1001 的十進制是73,頁表的第73項存儲的是最終物理頁的物理起始地址。物理頁基地址加上線性地址中最低12位的偏移量,CPU就找到了線性地址最終對應的物理內存單元。

我們知道Linux中用戶進程線性地址能尋址的範圍是0 - 3G,那麼是不是需要提前先把這3G虛擬內存的頁表都建立好呢?一般情況下,物理內存是遠遠小於3G的,加上同時有很多進程都在運行,根本無法給每個進程提前建立3G的線性地址頁表。Linux利用CPU的一個機制解決了這個問題。進程創建後我們可以給頁目錄表的表項值都填0,CPU在查找頁表時,如果表項的內容爲0,則會引發一個缺頁異常,進程暫停執行,Linux內核這時候可以通過一系列複雜的算法給分配一個物理頁,並把物理頁的地址填入表項中,進程再恢復執行。當然進程在這個過程中是被矇蔽的,它自己的感覺還是正常訪問到了物理內存。

怎樣防止進程訪問不屬於自己的線性地址(如內核空間)或無效的地址呢?內核裏記錄着每個進程能訪問的線性地址範圍(進程的vm_area_struct 線性區鏈表和紅黑樹裏存放着),在引發缺頁異常的時候,如果內核檢查到引發缺頁的線性地址不在進程的線性地址範圍內,就發出SIGSEGV信號,進程結束,我們將看到程序員最討厭看到的Segmentation fault。

 

轉自:http://bbs.chinaunix.net/thread-2015439-1-1.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章