1. 程式人生 > >brk系統呼叫實現分析

brk系統呼叫實現分析

brk(addr)直接修改堆的大小。addr指定current->mm->brk的新值,返回值是線性區新的結束地址,這是一個系統呼叫。當用戶態的程序呼叫brk()系統呼叫時,核心執行sys_brk(addr)函式。下面分析這個函式的執行流程:

1:檢測addr引數是否位於程序程式碼段所在的線性區,如果是直接返回,因為堆不能與程序程式碼段所在的線性區重合。

  1. mm=current->mm;  
  2. down_write(&mm->mmap_sem);  
  3. if(addr<mm->end_code){  
  4. out:  
  5.     up_write(&mm->mmap_sem);  
  6.     return mm->brk;  
  7. }  

2:由於brk系統呼叫作用於一個線性區,它分配和釋放完整的頁。因此,該函式把addr的值調整為PAGE_SIZE的倍數,然後把調整的結果和記憶體描述的brk程序比較。
  1. newbrk=(addr+0xfff)&0xfffff000;  
  2. oldbrk=(mm->brk+0xfff)&0xfffff000;  
  3. if(oldbrk==newbrk)  
  4. {  
  5.     mm->brk=addr;  
  6.     goto out;  
  7. }  

3:如果程序請求縮小堆,則sys_brk()呼叫do_munmap()完成這項任務,然後返回
  1. if(addr<=mm->brk)  
  2. {  
  3.     if(!do_munmap(mm,newbrk,oldbrk-newbrk))  
  4.         mm->brk=addr;  
  5.     goto out;  
  6. }  

4:如果程序請求擴大堆,則sys_brk首先檢查是否允許程序這麼做。如果程序企圖分配在其限制範圍之外的記憶體,函式並不多分配記憶體,只簡單返回mm->brk的原有值
  1. rlim=current->signal->rlim[RLIMIT_DATA].rlim_cur;  
  2. if(rlim<RLIM_INFINITY & addr - mm->start_data>rlim)  
  3.     goto out;  

5:然後,函式檢查擴大之後的堆是否和程序的其他線性區重疊,如果是,不做任何事情就返回:
  1. if(find_vma_itersection(mm,oldbrk,newbrk+PAGE_SIZE))  
  2.     goto out;  

6:如果一切都順利,則呼叫do_brk()函式。如果返回oldbrk,則分配成功且sys_brk返回addr的值,否則返回舊的mm->brk
  1. if(do_brk(oldbrk,newbrk-oldbrk)==oldbrk)  
  2.     mm->brk=addr;  
  3. goto out;  
do_brk()函式實際上是僅處理匿名線性區的do_mmap()簡化版。可以認為它的呼叫等價於:

do_mmap(NULL,oldbrk,newbrk-oldbrk,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_FIXED|MAP_PRIVATE,0)

當然do_brk()比do_mmap()稍快,因為前者假定線性區不對映磁碟上的檔案,從而避免了檢查線性區物件的幾個欄位。在這裡就不介紹do_brk()函數了,因後面會寫一篇專門介紹重要的do_munmap。