vmalloc原理與實現(xiàn)
在 Linux 系統(tǒng)中的每個進程都有獨立 4GB 內存空間,而 Linux 把這 4GB 內存空間劃分為用戶內存空間(0 ~ 3GB)和內核內存空間(3GB ~ 4GB),而內核內存空間由劃分為直接內存映射區(qū)和動態(tài)內存映射區(qū)(vmalloc區(qū))。
直接內存映射區(qū)從 3GB 開始到 3GB+896MB 處結束,直接內存映射區(qū)的特點就是物理地址與虛擬地址的關系為:虛擬地址 = 物理地址 + 3GB。而動態(tài)內存映射區(qū)不能通過這種簡單的關系關聯(lián),而是需要訪問動態(tài)內存映射區(qū)時,由內核動態(tài)申請物理內存并且映射到動態(tài)內存映射區(qū)中。下圖是動態(tài)內存映射區(qū)在內存空間的位置:

為什么需要vmalloc區(qū)
由于直接內存映射區(qū)(3GB ~ 3GB+896MB)是直接映射到物理地址(0 ~ 896MB)的,所以內核不能通過直接內存映射區(qū)使用到超過 896MB 之外的物理內存。這時候就需要提供一個機制能夠讓內核使用 896MB 之外的物理內存,所以 Linux 就實現(xiàn)了一個 vmalloc 機制。vmalloc 機制的目的是在內核內存空間提供一個內存區(qū),能夠讓這個內存區(qū)映射到 896MB 之外的物理內存。如下圖:

那么什么時候使用 vmalloc 呢?一般來說,如果要申請大塊的內存就可以用vmalloc。
【文章福利】小編推薦自己的Linux內核技術交流群:【891587639】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ? ??


vmalloc實現(xiàn)
可以通過 vmalloc() 函數(shù)向內核申請一塊內存,其原型如下:
__vmalloc() 函數(shù)主要工作有兩點:
調用 get_vm_area() 函數(shù)申請一個合法的虛擬內存地址。
調用 vmalloc_area_pages() 函數(shù)把虛擬內存地址映射到物理內存地址。
接下來,我們看看 get_vm_area() 函數(shù)的實現(xiàn),代碼如下:
get_vm_area() 函數(shù)比較簡單,首先申請一個類型為 vm_struct 的結構 area 用于保存申請到的虛擬內存地址。然后查找可用的虛擬內存地址,如果找到,就把虛擬內存到虛擬內存地址保存到 area 變量中。最后把 area 連接到 vmalloc 虛擬內存地址管理鏈表 vmlist 中。vmlist 鏈表最終結果如下圖:

申請到虛擬內存地址后,__vmalloc() 函數(shù)會調用 vmalloc_area_pages() 函數(shù)來對虛擬內存地址與物理內存地址進行映射。
我們知道,映射過程就是對進程的 頁表 進行映射。但每個進程都有一個獨立 頁表(內核線程除外),并且我們知道內核空間是所有進程共享的,那么就有個問題:如果只映射當前進程 頁表 的內核空間,那么怎么同步到其他進程的內核空間呢?
為了解決內核空間同步問題,Linux 并不是直接對當前進程的內核空間映射的,而是對 init 進程的內核空間(init_mm)進行映射,我們來看看 vmalloc_area_pages() 函數(shù)的實現(xiàn):
從上面代碼可以看出,vmalloc_area_pages() 函數(shù)映射的主體是 init 進程的內存空間。因為映射的 init 進程的內存空間,所以當前進程訪問 vmalloc() 函數(shù)申請的內存時,由于沒有對虛擬內存進行映射,所以會發(fā)生 缺頁異常 而觸發(fā)內核調用 do_page_fault() 函數(shù)來修復。我們看看 do_page_fault() 函數(shù)對 vmalloc() 申請的內存異常處理:
上面的代碼就是當進程訪問 vmalloc() 函數(shù)申請到的內存時,發(fā)生 缺頁異常 而進行的異常修復,主要的修復過程就是把 init 進程的 頁表項 復制到當前進程的 頁表項 中,這樣就可以實現(xiàn)所有進程的內核內存地址空間同步。
