所謂進程地址空間(process address space),就是從進程的視角看到的地址空間,是進程運行時所用到的虛擬地址的集合。

32位系統的進程地址空間

以IA-32處理器為例,其虛擬地址為32位,因此其虛擬地址空間的範圍為 2^{32}=4GB ,linux系統將地址空間按3:1比例劃分,其中用戶空間(user space)佔3GB,內核空間(kernel space)佔1GB。

假設物理內存也是4GB(事實上,虛擬地址空間的範圍不一定需要和物理地址空間的大小相同),則虛擬地址空間和物理地址空間的轉換如下圖所示:

因為內核的虛擬地址空間只有1GB,但它需要訪問整個4GB的物理空間,因此從物理地址0~896MB的部分(ZONE_DMA+ZONE_NORMAL),直接加上3GB的偏移(在linux中用PAGE_OFFSET表示),就得到了對應的虛擬地址,這種映射方式被稱為線性映射。而896M~4GB的物理地址部分(ZONE_HIGHMEM)需要映射到(3G+896M)~4GB這128MB的虛擬地址空間,顯然也按線性映射是不行的。採用的是做法是,ZONE_HIGHMEM中的某段物理內存和這128M中的某段虛擬空間建立映射,完成所需操作後,需要斷開與這部分虛擬空間的映射關係,以便ZONE_HIGHMEM中其他的物理內存可以繼續往這個區域映射,即動態映射的方式。

用戶空間的進程只能訪問整個虛擬地址空間的0~3GB部分,不能直接訪問3G~4GB的內核空間部分,但出於對性能方面的考慮,linux中內核使用的地址也是映射到進程地址空間的(被所有進程共享),因此進程的虛擬地址空間可視為整個4GB(雖然實際只有3GB)。

64位系統的進程地址空間

在64位系統中,進程地址空間的大小就不固定了,以ARMv8-A為例,它的page大小可以是4KB, 16KB或者64KB(默認為4KB,選一種來用,不要混用),可採用3級頁表或4級頁表,因此可以有多種組合的形式。以採用4KB的頁,4級頁表,虛擬地址為48位的系統為例(從ARMv8.2架構開始,支持虛擬地址和物理地址的大小最多為52位),其虛擬地址空間的範圍為 2^{48}=256TB ,按照1:1的比例劃分,內核空間和用戶空間各佔128TB。

256TB已經很大很大了,但是面對64位系統所具備的16EB的地址範圍,根本就用不完。為了以後擴展的需要(比如虛擬地址擴大到56位),用戶虛擬空間和內核虛擬空間不再是挨著的,但同32位系統一樣,還是一個佔據底部,一個佔據頂部,所以這時user space和kernel space之間偌大的區域就空出來了。但這段空閑區域也不是一點用都沒有,它可以輔助進行地址有效性的檢測。如果某個虛擬地址落在這段空閑區域,那就是既不在user space,也不在kernel space,肯定是非法訪問了。使用48位虛擬地址,則kernel space的高16位都為1,如果一個試圖訪問kernel space的虛擬地址的高16位不全為1,則可以判斷這個訪問也是非法的。同理,user space的高16位都為0。這種高位空閑地址被稱為canonical。

在64位系統中,內核空間的映射變的簡單了,因為這時內核的虛擬地址空間已經足夠大了,即便它要訪問所有的物理內存,直接映射就是,不再需要ZONE_HIGHMEM那種動態映射機制了。

64位系統中用戶空間的映射和32位系統沒有太大的差別。

ARM公司宣稱64位的ARMv8是兼容32位的ARM應用的,所有的32位應用都可以不經修改就在ARMv8上運行。那32位應用的虛擬地址在64位內核上是怎麼分布的呢?事實上,64位內核上的所有進程都是一個64位進程。要運行32位的應用程序, linux 內核仍然從64位init進程創建一個進程, 但將用戶地址空間限制為4GB。通過這種方式, 我們可以讓64位linux內核同時支持32位和64位應用程序。

要注意的是, 32位應用程序仍然對應128TB的內核虛擬地址空間, 並且不與內核共享自己的4GB虛擬地址空間, 此時用戶應用程序具有完整的4GB虛擬地址。而32位內核上的32位應用程序只有3GB真正意義上的虛擬地址空間。

那進程地址空間到底是由哪些元素構成的呢?請看下文分解。

參考:

jake.dothome.co.kr/pt64,裡面對ARM64的各種page size和頁表級數的組合做了詳盡介紹。


推薦閱讀:
相关文章