Linux中的內核地址空間【轉】

轉自:https://zhuanlan.zhihu.com/p/68501351

在32位系統中,內核地址空間是指虛擬地址3GB~4GB的部分。大家應該都知道,C語言中的指針近似於地址的概念,所以我們可以通過打印指針的值來查看它做代表的地址。在用戶應用程序中,如果你打印一個指針,則結果應該是在0~3GB範圍內,不管是CPU,還是編譯器,鏈接器,看到的都是虛擬地址。而在內核程序(比如一個驅動模塊)中打印一個指針,結果會是在3~4GB的範圍內。

內核鏡像

在3GB~(3GB+896MB)這段直接/線性映射區域,包含了內核初始化頁表swapper_pg_dir,內核鏡像等。內核也是由一個elf文件(比如vmlinux)加載啓動的,加載後也有text段,data段,bss段等。

可通過cat /proc/iomem命令查看kernel的text段,data段和bss段的內存分佈。這裏給出的地址範圍都是小於0xC0000000的,所以可以判斷這是物理地址。

內存分配

kmalloc和vmalloc

在虛擬內存空間的normal memory區域,內核使用kmalloc()來分配內存,kmalloc()返回的也是虛擬地址,但是分到的內存在物理地址上是連續的(因爲是直接映射,在虛擬地址上自然也是連續的)。

在VMALLOC_START和VMALLOC_END之間的區域爲vmolloc area,它和normal memory中有8MB的間隔。這部分間隔不作任何地址映射,相當於一個空洞,主要用做安全保護,防止不正確的越界內存訪問,因爲此處沒有進行任何形式的映射,如果進入到空洞地帶,將會觸發處理器產生一個異常。

在vmolloc area中使用vmalloc()分配內存,具體的分配過程是:

  1. 根據要分配的內存大小,調用get_vm_area( ),獲取vmlist_lock鎖以掃描vmlist鏈表,在vmolloc area中找到一塊大小滿足要求的空閒內存;
  2. 調用__vmalloc_area_pages() --> alloc_page(),通過內核的buddy系統獲得相應大小的物理頁面,關於物理頁面的分配請參考這篇文章
  3. vmalloc area中的地址映射不再是簡單的3GB偏移,因此需要調用map_vm_area(),建立虛擬地址和物理頁面的映射關係,並添加到內核頁表中。

同kmalloc()相比,vmalloc()分配的內存只能保證在虛擬地址上連續,不能保證在物理地址上連續。在物理地址上連續有什麼好處呢?

  • 可以更好的根據空間局部性原理利用cache,增加數據訪問的速度。
  • 由於kmalloc()基於的是直接映射,其虛擬地址和物理地址之間是一個固定的偏移,因此可以利用既有的內核頁表,而不需要爲新的地址增加新的page table entries,因此其分配速度也比vmalloc()更快。
  • 因爲物理地址不連續,通過vmalloc()獲得的每個page需要單獨映射,而TLB資源很有限,因此這將比直接映射造成更嚴重的TLB thrashing問題。

有連續的物理內存和簡單的直接映射關係誰不想要啊,可是如果系統運行久了,內存碎片就多起來,想要找到一塊物理上連續的大塊內存就越來越困難,這時就只能靠vmalloc()出馬了。因爲有一些應用場景是需要物理上連續的內存的(比如硬件設備),那是不是如果是沒有這個要求的,就用vmalloc()就好了,把寶貴的normal area的地址資源留給那些真正需要的同志呢?

這種做法也有問題,如果大家都謙讓着不用kmalloc(),那可能normal area就不能被充分利用起來。公平和效率始終是需要兼顧和平衡的,在32位系統中,如果不是分配大塊內存,還是推薦使用更高效的kmalloc(64位系統由於虛擬地址空間很充足,vmalloc映射的開銷已經變小,參考這篇文章)。

vmalloc區域

還記得上篇文章舉的那個房間和鑰匙的例子麼,用戶空間的進程通過malloc()分配內存時,獲得的只是虛擬地址的使用權,要等到真正往這塊內存寫數據了,纔會獲得對應的物理頁面,而且是用多少給多少,而不是要多少給多少。內核空間自己的vmalloc()就不一樣了,申請的物理內存立刻滿足,房間鑰匙一起給,在上級單位幹活就是不一樣啊。

分配到的每個內存區域(以下稱vmalloc區域)都用一個vm_struct結構體表示,對這個名字有沒有一點眼熟?跟進程地址空間裏的vm_area_struct很像是吧,事實上,它們不光是名字相似,組織方式也有類似的地方,vm_struct也是通過一個叫vmlist的單項鍊表串起來的。

同樣都是爲了描述一段內存區域,包括這段區域的地址,大小,屬性等,那這裏可以直接用vm_area_struct嗎?可以倒是可以,但是vmalloc區域相對要簡單一些,用vm_area_struct來表達就顯得複雜了,所以單獨有了一個vm_struct,來看下這個數據結構的定義:

 struct vm_struct {
         struct vm_struct * next;
         void * addr;
         unsigned long size;
         unsigned long flags;
	 struct page **pages;
         unsigned int nr_pages;
	 phys_addr_t phys_addr;
	 const void *caller;
 };

其中,"next"指向vmlist鏈表中的下一個節點。"addr"和"size"分別定義了這個vmalloc區域的虛擬起始地址和大小,"nr_pages"是指含有多少個page,它其實跟size表達的是同樣的東西,只不過一個以頁的長度爲單位,一個以字節的長度爲單位。

有意思的是,每個vmalloc區域都會附加上一個額外的page,目的麼,也是爲了越界內存訪問檢測,這種page被稱作guard page。所以,每個vmalloc區域的size,除了本身需要的內存,還要多加上一個page的大小。

你看,又是8MB的空洞間隔,又是那麼多的guard page,多浪費空間啊……其實沒關係的啦,因爲這裏都是虛擬地址空間,並沒有多佔用實際的物理內存。guard page也並不是強制的,可通過VM_NO_GUARD宏選擇。

因爲通過vmalloc()分配獲得的各個物理頁面是不連續的,每個物理頁面用struct page描述,一個vm_struct對應的所有物理頁面的struct page就構成了一個數組,"pages"就是指向這個數組的指針。

vm_struct不光用於vmalloc(),它還可以用於vmap()和ioremap(),"flags"可以表示的屬性很多,其中有3個就是分別對應vmalloc, vmap和ioremap的VM_ALLOC, VM_MAP和VM_IOREMAP。

vmap()用於已經分配了物理頁面,只需要建立映射的情況。ioremap()與vmap()類似,但它是用於I/O設備的內存映射的,依賴於特定的體系結構,"phys_addr"就是對應設備的起始物理地址。

"caller"指向調用者,通常爲__builtin_return_address(0),__builtin_return_address(level)是利用GCC的編譯特性來獲得當前函數或者調用函數的返回地址。

那vm_struct這個控制結構本身又是存在什麼地方的呢?它不是和它要管理的vmalloc區域一起放在vmalloc area的(沒有和羣衆打成一片啊),而是放在normal area的(有自己單獨的辦公室),也就是說,vm_struct結構體佔用的內存是通過kmalloc()分配的(享受幹部待遇)。

內存釋放

和malloc()與free()的配對(只有malloc沒有free就會造成內存泄露)一樣,釋放vmalloc()分配的內存就用vfree(),對應着vmalloc()的創建過程,vfree()就是一步一步反過來的,具體過程包括調用remove_vm_area()釋放vmalloc area中的虛擬地址空間,然後調用unmap_vm_area()解除和物理地址的映射關係,最後調用deallocate_pages()釋放對應的物理頁面。

別忘了vm_struct這個管理人員,它要管的區域都不存在了,它自身也就沒什麼存在的必要的,用kfree()把它佔的內存也釋放掉吧。

如果想暫時保留分配到的物理頁面,只釋放一個vmalloc區域的虛擬地址空間也是可以的,就用vummap()來解除映射,重新要用的時候再vmap()回來,當然,這時新映射的vmalloc區域有可能就不是原來映射的那個了。

新版內核的vmalloc

前面爲了演示的需要,採用的是內核2.4版本的數據結構和API(因爲相對簡單很多)。事實上,當vmalloc區域的數量變多之後,遍歷vmlist鏈表查找會面臨進程地址空間中vm_area_struct曾經也遇到的問題,就是效率太低。

vm_area_struct採用了加入紅黑樹來共同管理的方法,從內核2.6的某個版本(我暫時還不知道具體是哪個版本)開始,vmalloc區域也開始使用紅黑樹(每個節點用vmap_area結構體表示),而且查找的時候也首先從一個緩存(free_vmap_cache,對應進程地址空間中的mmap_cache)中找。反正,兩者在管理機制上是越來越像了。現在較新的Linux內核中的vmalloc實現是這樣的:

一個重要的變化是伴隨NUMA系統引入的node的概念。__get_vm_area_node()主要用於分配vmap_area和vm_struct__vmalloc_area_node()主要用於分配物理頁面和創建頁表映射。

特殊映射

在靠近虛擬地址空間的頂部,有一個PKMap和一個固定映射(fix-mapped)的特殊區域,它們和vmalloc area中有一個8KB的間隔,作用麼,還是越界防護。

這裏固定映射是指虛擬地址是固定的,而被映射的物理地址並不固定。採用固定虛擬地址的好處是它相當於一個指針常量(常量的值在編譯時確定),指向物理地址,如果虛擬地址不固定,則相當於一個指針變量。指針常量相比指針變量的好處是可以減少一次內存訪問,因爲指針變量需要通過內存訪問纔可以獲得指針本身的值。關於fixmap的詳細介紹,請參考這篇文章

關於PKMap Region的介紹,請參考這篇文章

 

參考:

 

原創文章,轉載請註明出處。

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