[Linux內存管理] linux內存佈局的內核實現--用戶空間的映射方式

引用牛人的一個表格(本人繪製能力實在有限,引自:http://blog.csdn.net/absurd/archive/2006/06/19/814268.aspx)

Linux的內存模型,一般爲:

地址

作用

說明

>=0xc000 0000

內核虛擬存儲器

用戶代碼不可見區域

Stack(用戶棧)

ESP指向棧頂

 

空閒內存

>=0x4000 0000

文件映射區

 

空閒內存

 

Heap(運行時堆)

通過brk/sbrk系統調用擴大堆,向上增長。

 

.data、.bss(讀寫段)

從可執行文件中加載

>=0x0804 8000

.init、.text、.rodata(只讀段)

從可執行文件中加載

保留區域

 

這 裏的數據是怎麼得到的呢?我們一般用到的malloc是從哪裏分配的內存呢?爲什麼從這裏分配呢?實際上malloc是c庫的內存分配管理函數,其種種弊 端使得人們不太喜歡它,從而使人們寫出很多自己的內存分配函數實現,不管哪種實現在linux下都是調用系統調用brk實現的,在內核當中就是 sys_brk。 
我們知道,一個進程的所有的地址空間是按照區域組織的,每個區域都是一個vm_area_struct的結構體,每個結構體內部的地址是連續的,而不同的 結構體之間可能留有空洞,既然這樣,不管brk出來的空間還是其它,比如說映射得到的空間都是vm_area_struct的表現,那麼爲何0x4000 0000是個分界點呢?之上的就是映射空間而之下的就是brk空間即堆空間呢?我們只有看一下這兩種實現的內核源代碼,分別是:sys_mmap2和 sys_brk。在可能具體代碼之前首先要明白一件事就是:爲了管理方便,所有的vm_area_struct的首地址和末地址都是頁對齊的。好了,現在 可以看相關的代碼了(我省略了很多不相關的代碼,那些很重要,但不是這篇文章要說的): 
asmlinkage long sys_mmap2(unsigned long addr, unsigned long len, 
unsigned long prot, unsigned long flags, 
unsigned long fd, unsigned long pgoff) 

... 
down_write(&mm->mmap_sem); 
error = do_mmap_pgoff(file, addr, len, prot, flags, pgoff); 
... return error; 

unsigned long do_mmap_pgoff(struct file * file, unsigned long addr, 
unsigned long len, unsigned long prot, 
unsigned long flags, unsigned long pgoff) 

//見SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, unsigned long, pgoff) in mmap.c


len = PAGE_ALIGN(len); //對齊了長度,使得首地址加上長度也是頁對齊 
... 
addr = get_unmapped_area(file, addr, len, pgoff, flags);//此函數內部對齊了addr 
if (addr & ~PAGE_MASK) //如果現在都不是頁對齊的,那麼返回的肯定是錯誤碼,返回之 
return addr; 
... 

unsigned long get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, 
unsigned long pgoff, unsigned long flags) 

unsigned long (*get_area)(struct file *, unsigned long, 
unsigned long, unsigned long, unsigned long); 
get_area = current->mm->get_unmapped_area; 
if (file && file->f_op && file->f_op->get_unmapped_area) 
get_area = file->f_op->get_unmapped_area
addr = get_area(file, addr, len, pgoff, flags); 
if (IS_ERR_VALUE(addr)) //這個宏很有意思,值得研究,如果返回真,那麼addr就是一個錯誤碼了,返回之 
return addr; 
if (addr > TASK_SIZE - len)//跨到了內核空間,返回錯誤碼 
return -ENOMEM; 
if (addr & ~PAGE_MASK) //到此還是非頁對齊,返回錯誤碼 
return -EINVAL; 
return arch_rebalance_pgtables(addr, len); 

在很多平臺get_unmapped_area就是mm/mmap.c中定義的arch_get_unmapped_area 
unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr, 
unsigned long len, unsigned long pgoff, unsigned long flags) 

struct mm_struct *mm = current->mm; 
struct vm_area_struct *vma; 
unsigned long start_addr; 
if (len > TASK_SIZE) //跨越內核空間邊界 
return -ENOMEM; 
if (flags & MAP_FIXED) //如果有MAP_FIXED標誌那麼就直接返回addr,其實在MAP_FIXED 
設置的前提下,用戶傳入的addr必須是頁對齊的,若不是則出錯,這裏將addr返回,如果addr不是頁對齊,那麼get_unmapped_area中的if (addr & ~PAGE_MASK)驗證將無法通過。 
return addr; 
if (addr) { //如果用戶提供了addr則優先考慮用戶提供的addr,但是不保證這個addr就是最後返回的addr。下面還是要說一下這裏的要點。 
addr = PAGE_ALIGN(addr); 
vma = find_vma(mm, addr); 
if (TASK_SIZE - len >= addr && 
(!vma || addr + len vm_start))//addr的地址沒有被映射,而且空洞足夠我們這次的映射,那麼返回addr以準備這次的映射 
return addr; 

if (len > mm->cached_hole_size) { //我們需要的長度大於當前的vm區域間的空洞 
start_addr = addr = mm->free_area_cache;//從上次的查詢位置開始 
} else { //需要的長度小於當前空洞,爲了不至於時間浪費,那麼從0開始搜尋,這裏的 
搜尋基地址TASK_UNMAPPED_BASE很重要,用戶mmap的地址的基地址必須在TASK_UNMAPPED_BASE之上,但是一定這樣嚴格 嗎?看上面的if (addr)判斷,如果用戶給了一個地址在TASK_UNMAPPED_BASE之下,映射實際上還是會發生的。 
start_addr = addr = TASK_UNMAPPED_BASE; 
mm->cached_hole_size = 0;//空洞大小從0開始,以後逐漸增加 

full_search: 
for (vma = find_vma(mm, addr); ; vma = vma->vm_next) { 
if (TASK_SIZE - len free_area_cache,因此下面的if判斷當然可以通過,所以從新開始,從TASK_UNMAPPED_BASE開始搜索,若開 始地址就是TASK_UNMAPPED_BASE,那麼肯定出錯了 
if (start_addr != TASK_UNMAPPED_BASE) { 
addr = TASK_UNMAPPED_BASE; 
start_addr = addr; 
mm->cached_hole_size = 0; 
goto full_search; 

return -ENOMEM; 

if (!vma || addr + len vm_start) { 
mm->free_area_cache = addr + len; 
return addr; 

if (addr + mm->cached_hole_size vm_start) 
mm->cached_hole_size = vma->vm_start - addr;//更新當前空洞長度 
addr = vma->vm_end; 


TASK_UNMAPPED_BASE就是mmap時的地址限制,我們看一眼TASK_UNMAPPED_BASE的定義: 
#define TASK_UNMAPPED_BASE (PAGE_ALIGN(TASK_SIZE / 3)) 
就是1g的位置,即0x40000000的位置。可是這種限制並不是強制的,如果我做以下程序: 
#include <sys><br>int main() <br>{ <br> char * buf; <br> buf = (char*)mmap(0x30000000,1024,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS,-1,0); <br>} <br>用gdb調試可以看出buf的地址確實是0x30000000,也就是0x40000000以下的地址,這麼說,linux的內存佈局並不是強制用戶執行 的,把策略完全留給用戶,內核真是慣壞了用戶,僅僅提供給用戶一個建議而已。說完了sys_mmap2的邏輯簡單談談sys_brk就可以了,每個 mm_struct都有一個brk字段,這個字段記錄着用戶堆的位置,每次用戶調用brk的時候sys_brk都回擴展或者縮減堆空間,本質上也是映射 vm_area_struct結構,而brk的限制是在elf文件裏面確定的,elf文件格式規定了堆,bss變量,棧在哪裏,但是這些都不是強制的。 <br>通過這裏的分析,我們看到,linux的內存佈局是非常靈活的,看到很多人問怎麼把內核空間擴展成2g,正如windows一樣,這個其實是很簡單的,將 sys_mmap和sys_brk的檢測條件一改就可以,實際不用改動任何這些代碼,僅僅改TASK_SIZE宏就可以了,用戶空間分配內存的唯一限制就 是不能超越內核邊界,別的限制就不是那麼重要了,那只是用戶程序自己的事情,而分配用戶內存就是映射vm_area_struct結構,進一步僅僅在 sys_mmap2和sys_brk裏會有如此動作,因此擴展內核空間的任務就會很簡單,那麼內核的內存怎麼映射呢?這就完全是內核的事情了,無非有兩種 方式,一種是一一映射,另一種是非線性映射(包括類似高端內存的臨時映射方式)。</sys>



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