當前位置:
首頁 > 知識 > linux虛擬地址轉物理地址

linux虛擬地址轉物理地址

80386虛擬地址和物理地址轉換

CPU的發展

之前在看malloc內存分配函數的原理時,有涉及到分配虛擬內存,然後再映射到物理內存,當初也是看得一頭霧水,因為對虛擬內存和物理內存不是很了解。所以這篇文章總結下我在學習虛擬內存和物理內存的一些收穫。

首先給出CPU的進化表,圖片來自博客wjlkoorey的博客

linux虛擬地址轉物理地址

CPU發展從定址物理地址;定址段地址到物理地址轉換;定址邏輯地址轉換為線性地址,再轉換為物理地址。在8086之前的CPU,定址都為物理地址,是並沒有段的概念。當程序要訪問內存時都是要給出內存的實際物理地址,這樣在程序源代碼中就會出現很多硬編碼的物理地址。這樣的程序可想而知,難重定位,可控性弱,結構醜陋,那個年代寫這樣的程序在我們現在看來是多麼讓人惱火的一件事兒。

後來8086引入一個非常重要的概念--段,這樣就實現了分段機制;8086CPU地址匯流排為16,這樣定址範圍為2^16=64k,而8086的定址空間為1M,那麼是怎麼實現的了?原來這時候cpu給出的地址為段地址,需要加上段地址(由cs,ds,ss,es)之後才構成物理地址。物理地址為=段地址:段內偏移量;段地址左移4位+段內偏移量,即可構成20位的物理地址。例如ES=0x1000,DI=0xFFFF,那麼物理地址為:


AD(Absolute Address)=(ES)*(0x10)+(DI)=0x1FFFF

0x10為16,段地址*16(2^4),即向左移動4位。這樣就可以對20位的1M內存空間進行定址。

而這這種方式的定址最大地址為0xFFFF:0xFFFF=0x10FFEF,大於1M空間,這樣如果訪問大於1M的內存空間時,將會產生結果了?8086的做法是自動從物理內存0地址開始定址,有人就是說[0x0000,0x10FFEF]地址是按0xFFFF取模定址。下圖說明這種情況:

linux虛擬地址轉物理地址

CPU發展的下一個里程碑是1985年80386的問世,從16位到32位CPU的飛躍,這中間80286就成了這次飛躍的跳板,80286地址上升到24位並且引入了保護模式。保護模式即規定進程能訪問的內存,有些內存是不能訪問的,例如進程不能訪問內核代碼。80386繼承了80286的內存保護模式和分段機制,並且引入了分頁虛擬機制。首先80386繼承了80286的基礎上添加兩個段寄存器FS和GS。很顯然,為了實現保護模式,段寄存器只存儲段基地址是不夠的,至少還需要段地址的長度還有一些諸如訪問許可權之類的其他信息。所以段寄存器存儲的並不是真正的段基地址,而是存儲每個段描述符的選擇符,通過這個選擇符在GDT表格中找到這個段的基地址。

現在主流的x86CPU上的主流操作系統,Linux,FreeBSD,Windows等待都是工作在保護模式下,處理器只有在上電啟動,引導階段初始化時在會進入實時模式,實時模式任務處理之後,即進入保護模式。


80386CPU邏輯地址轉換為物理地址

邏輯地址轉化為線性地址

80386cpu當需要訪問內存時,首先給出的邏輯地址,然後通過MMU內存管理單元分段機制轉換為線性地址,然後線性地址再通過MMU內存管理單元分頁機制轉換為物理地址。

首先來看下cs,ds等段寄存器的內容,如下圖所示:

linux虛擬地址轉物理地址

  1. INDEX為在描述符表的索引,因為總共13位,所以描述符表總共可以存儲8192個描述符,處理器將INDEX*8(一個描述符佔8個位元組)+GDTR(全局描述符表)/LDTR(局部描述符表)即為這個段的描述符。
  2. TI(TABLE INDICATOR)指明在哪個描述符查找段描述符;如果為0,則在GDT查找,如果為1,則在LDT查找。
  3. RPL(REQUESTOR"S PRIVILEGE LEVEL)請求許可權,用於保護機制,0為最高優先順序,3為最低優先順序,linux只使用0和3優先順序,分別表示內核態和用戶態。

當處理器需要訪問內存時,給出的邏輯地址是:選擇符+偏移量,然後通過MMU的分段機制,取出的邏輯地址的INDEX,乘以8,再加上GDTR存儲的全局描述符表的基地址,即可獲取這個段的描述符,然後存入描述符寄存器(一種對用戶隱藏的寄存器,也稱為不可編程寄存器),然後取出這個64位的段描述符的段基地址+偏移量,即可獲得這個邏輯地址對應的線性地址,下圖展示了轉換過程:

linux虛擬地址轉物理地址

線性地址由目錄項(DIR)+頁表項(PAGE)+頁內偏移(OFFSET)組成。在介紹頁地址轉物理地址時,先介紹下段描述符的格式,如下圖所示:

linux虛擬地址轉物理地址

  1. BASE:定義了4G的線程地址空間的基地址,處理器將描述符中三個BASE欄位拼接成一個32位的值,這樣就可以定址4G的線性地址空間。
  2. LIMIT:定義了段的空間大小,由描述符中的兩個欄位拼接而成,形成一個20位的值,20位總共可以表示為1M個數值,而這個1M個數值的單位則由Granularity bit位決定:
  • 當granularity bit為0時,單位為1位元組,這時limit表示的是1M的內存;
  • 當granularity bit為1時,此時單位為4kb,這時limit表示的是4g的內存;
  1. TYPE: 區別不同的描述符,即描述符的類型。
  2. DPL(Descriptor Privilege Level),描述符訪問許可權,用於保護機制。
  3. Segment-Present bit:如果這個bit設置為0,則這個描述符不能被使用來進行地址轉換,如果這個描述符被載入段描述符,處理器將會發出一個異常,下圖展示了bit=0時的描述符格式,處理器可以使用標有AVALABLE的位置存儲描述符。

linux虛擬地址轉物理地址

  1. Accessed bit:當這個描述符被訪問時,設置為1.

線性地址轉化為物理地址

之前,已經將邏輯地址轉換為線性地址,接下來看下是如何從線性地址轉化為物理地址,先給出下面示意圖:

linux虛擬地址轉物理地址

一個線性地址由10位目錄表+10位頁表+12位偏移量組成,當給定一個線性地址時,

  1. 首先從控制寄存器CR3獲取頁目錄基地址,然後加上線性地址的前10位頁目錄偏移量即可得到頁表的基地址;
  2. 接著頁表基地址+線性地址第二個10位的頁表偏移,即可得到內存頁的首地址;
  3. 最後內存頁的首地址+線性地址的最後12位偏移量,即可得到最後的物理地址;

這樣就完成了從線性地址轉為物理地址

接下來,看下頁表項的格式。先來看下空閑的頁表項和存儲數據的頁表項的格式:

linux虛擬地址轉物理地址

linux虛擬地址轉物理地址

三層頁表都有著相同的格式,頁幀地址為一塊內存頁的首地址,而內存頁是4kb對齊的,所以頁表項的低12位為0;而頁目錄項指向的是頁表的首地址。

  1. Present bit表示這個頁表項是否可以用於地址轉換,p=1表示可以使用。當p=0時,表示這個頁表項可以被進程使用。
  2. Accessed and Dirty Bits這些位表示一個頁中數據的使用情況。當要對一個頁寫或讀之前,處理器會在兩個層次的頁表格設置訪問位;當要對一個地址進行寫操作,而這個操作在第二表格某個表項指向的內存頁中,這時處理器會設置第二個表格相對應的頁表項的臟位,設置頁目錄的臟位是未定義的。操作系統可以根據這些位來決定當內存不夠時,換出哪些物理內存。
  3. Read/Write和User/Supervisor Bits位在地址轉換過程中,沒有使用。

頁轉換緩存

為了提高系統,防止每次地址轉換都需要訪問頁表,處理器還設置了TLB(Translation lookaside buffer)轉換後備高速緩衝區,存儲最近使用的線性地址到物理地址的映射;下面給出TLB的原理圖:

linux虛擬地址轉物理地址

當CPU需要訪問一個一個內存地址時,給出一個虛擬地址,先是到TLB中查找是否有對應的物理地址,如果有,即命中,直接用TLB中的對應物理地址訪問緩存;如果在TLB中沒有對應的物理地址,即未命中,則需要到內存頁表求出物理地址,並將這個物理地址存入TLB中。訪問cache時,如果Cache中有需要的數據,則直接返回需要的數據,如果Cache中沒有需要訪問的數據,則需要到內存獲取數據返回給用戶,並將獲取的數據存入Cache中。

所以,如果對計算機有足夠的了解,那麼就會發現計算機架構裡面存在著好多的緩存設計,首先應用程序從磁碟獲取數據時,在內核有一塊內存存儲最近訪問的數據緩存;當CPU從主存獲取數據時,也是先從緩衝區獲取數據,然後在讀進CPU。還有應用程序也有緩存,例如MySQL的存儲引擎innodb也有一塊緩衝區,存儲磁碟數據;在web網站應用程序中,當從資料庫獲取數據時,先用memcache或者redis獲取最近訪問的數據等等。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

DeepLearning-Ng編程中遇到的一些問題
在Linux上增加swap空間的技巧

TAG:程序員小新人學習 |