引用牛人的一個表格(本人繪製能力實在有限,引自: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>