vmalloc與mmap

mmap()系統調用是在用戶進程與內核之間共享內存區域的常用方法。我們最近有個程序,需要應用進程能夠讀取內核驅動獲取的數據,經過簡單的調研,決定採用mmap方式。實現起來不難,在驅動中註冊一個字符設備,實現該設備的mmap()方法即可。但這其中有一點小曲折。

在實現設備的mmap()方法時,需要將物理內存映射到應用程序通過mmap()系統調用傳下來的vma中。vma代表的是進程的一段虛擬地址空間。在第一版裏,考慮的不全面,利用alloc_pages()將整個內存段申請爲一段連續的物理地址空間。然後通過remap_pfn_range()函數將這段連續的物理內存映射到vma中。經過長時間的測試,沒有發現問題。直到今天,在部署一個老集羣時,遇到了問題。這個集羣中有很多老機器,內存只有十多個G,而且長時間運行後產生了大量的內存碎片。從而導致,我們無法獲得足夠的連續物理內存。沒辦法,只好重新調整驅動中分配內存的方式,改用vmalloc獲取地址空間。

在kernel裏,通常有3種申請內存的方式:vmalloc, kmalloc, alloc_pages。kmalloc與alloc_pages類似,均是申請連續的地址空間。而vmalloc則可以申請一段不連續的物理地址空間,並將其映射到連續的線性地址上。每次vmalloc之後,內核會創建一個vm_struct,用以映射分配到的不連續的內存區域。vm_struct類似vma,但是又不是一回事。vma是將物理內存映射到進程的虛擬地址空間。而vm_struct是將物理內存映射到內核的線性地址空間。  
既然vmalloc拿到的不是連續的物理內存,那麼將這些內存映射到vma時,就不能直接利用remap_pfn_range()了。
此時可以採用兩種方法,一種是實現vm_operations_struct的fault()方法,用以在缺頁時再映射需要的頁。此方法操作起來較爲麻煩。
另一種方法是直接使用remap_vmalloc_range()函數。該函數的原型爲:

int remap_vmalloc_range(struct vm_area_struct *vma, void *addr,
unsigned long pgoff)

其中參數vma是mmap使用調用傳下來的,addr即爲vmalloc()所分配內存的起始地址。而pgoff則爲mmap()系統調用裏的偏移參數,可以通過vma->vm_pgoff獲得。該函數成功執行後,返回值爲0。如果返回值爲負數,則說明出錯了。通常是由於所傳的參數不正確。

需要注意的是,需要映射到用戶空間的內存段,不能直接利用vmalloc()分配,而應該使用vmalloc_user()函數。該函數除了分配內存之外,還會將相應的vm_struct結構標記爲VM_USERMAP。否則,remap_vmalloc_range將返回錯誤。

在這個項目中碰到的教訓是,永遠不要假設系統中一定會有超過一個頁的連續物理內存。

不過較新的內核具有compact機制,可以整理內存碎片。但是,目前至少有一大部分機器不支持,或未開啓此機制。

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