Linu內核內存管理解析_第1頁
Linu內核內存管理解析_第2頁
Linu內核內存管理解析_第3頁
Linu內核內存管理解析_第4頁
Linu內核內存管理解析_第5頁
已閱讀5頁,還剩168頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

iLinuxx86_64與i386區(qū)別|之內

存尋址逾

21引子

毫無疑問,不管是32位,還是64位處理器,所有進程(執(zhí)行的程序)都必須占

用一定數(shù)量的內存,它或是用來存放從磁盤載入的程序代碼,或是

存放取自用戶輸入的數(shù)據(jù)等等。不過進程對這些內存的管理方式因內存用途不一

而不盡相同,有些內存是事先靜態(tài)分配和統(tǒng)一回收的,而有些卻是按需要動態(tài)分

配和回收的。

對任何一個普通進程來講,它都會涉及到5種不同的數(shù)據(jù)段。稍有編程知識的朋

友都該能想到這幾個數(shù)據(jù)段種包含有“程序代碼段”、“程序數(shù)據(jù)段”、“程序

堆棧段”等。不錯,這兒種數(shù)據(jù)段都在其中,但除了以上兒種數(shù)據(jù)段之外,進程

還另外包含兩種數(shù)據(jù)段。下面我們來簡單歸納一下進程對應的內存空間中所包含

的5種不同的數(shù)據(jù)區(qū)。

代碼段:代碼段是用來存放可執(zhí)行文件的操作指令,也就是說是它是可執(zhí)行程序

在內存種的鏡像。代碼段需要防止在運行時被非法修改,所以只準許讀取操作,

而不允許寫入(修改)操作——它是不可寫的。

數(shù)據(jù)段:數(shù)據(jù)段用來存放可執(zhí)行文件中已初始化全局變量,換句話說就是存放程

序靜態(tài)分配的變量和全局變量。

BSS段:BSS段包含了程序中未初始化全局變量,在內存中bss段全部置零。

堆(heap):堆是用于存放進程運行中被動態(tài)分配的內存段,它大小并不固定,

可動態(tài)擴張或縮減。當進程調用malloc等函數(shù)分配內存時,新分配的內存就被

動態(tài)添加到堆上(堆被擴張);當利用free等函數(shù)釋放內存時,被釋放的內存

從堆中被剔除(堆被縮減)。

棧:棧是用戶存放程序臨時創(chuàng)建的局部變量,也就是說我們函數(shù)括弧“{}”中定

義的變量(但不包括static聲明的變量,static意味這在數(shù)據(jù)段中存放變量)。

除此以外在函數(shù)被調用時,其參數(shù)也會被壓入發(fā)起調用的進程棧中,并且待到調

用結束后,函數(shù)的返回值也回被存放回棧中。由于棧的先進先出特點,所以棧

特別方便用來保存/恢復調用現(xiàn)場。從這個意義上將我們可以把堆??闯梢粋€臨

時數(shù)據(jù)寄存、交換的內存區(qū)。

靜態(tài)分配內存就是編譯器在編譯程序的時候根據(jù)源程序來分配內存.動態(tài)分配

內存就是在程序編譯之后,運行時調用運行時刻庫函數(shù)來分配內存的.靜態(tài)分

配由于是在程序運行之前,所以速度快,效率高,但是局限性大.動態(tài)分配在程

序運行時執(zhí)行,所以速度慢,但靈活性高。

術語〃BSS〃已經有些年頭了,它是blockstartedbysymbol的縮寫。因為未初

始化的變量沒有對應的值,所以并不需要存儲在可執(zhí)行對象中。但是因為C標準

強制規(guī)定未初始化的全局變量要被賦予特殊的默認值(基本上是0值),所以內

核要從可執(zhí)行代碼裝入變量(未賦值的)到內存中,然后將零頁映射到該片內存上,

于是這些未初始化變量就被賦予了0值。這樣做避免了在目標文件中進行顯式

地初始化,減少空間浪費(來自《Linux內核開發(fā)》)

我們在x86_64環(huán)境上運行以下經典程序:

#include<stdio.h>

#include<malloc.h>

#include<unistd.h>

intbss_var;

intdata_varO=l;

intmain(intargc,char**argv)

(

printf(z,belowareaddressesoftypesofprocess,smem\n,z);

printf(Z/Textlocation:\n,z);

printf(z,\tAddressofmain(CodeSegment):%p\nz,,main);

printf(z,\n〃);

intstack_var0=2;

printf(z?StackLocation:\n,z);

printf(z,\tlnitialendofstack:%p\nz/,&stack_varO);

intstack_varl=3;

printf(z,\tnewendofstack:%p\n/z,&stack_varl);

printf\n〃);

printf(z,DataLocation:\n,z);

printf(z,\tAddressofdata_var(DataSegment):%p\n,z,&data_varO);

staticintdata_varl=4;

printf(z,\tNewendofdata_var(DataSegment):%p\n,z,&data_varl);

printf(z,\n〃);

printf(Z,BSSLocation:\n,z);

printf(zz\tAddressofbss_var:%p\n,z,&bss_var);

printf(〃\n〃);

char*b=sbrk((ptrdiff_t)O);

printfC'HeapLocation:\n,z);

printf(z,\tlnitialendofheap:%p\n,/,b);

brk(b+4);

b=sbrk((ptrdiff_t)0);

printf(zz\tNewendofheap:%p\n〃,b);

return0;

運行結果:

[root@kolleraupdilogs]#./memory

belowareaddressesoftypesofprocess,smem

Textlocation:

Addressofmain(CodeSegment):0x400568

StackLocation:

Initialendofstack:0x7fff0e0dc544

newendofstack:0x7fff0e0dc540

DataLocation:

Addressofdata_var(DataSegment):0x600bfc

Newendofdata_var(DataSegment):0x600c00

BSSLocation:

Addressofbss_var:0x600cl4

HeapLocation:

Initialendofheap:0xb059000

Newendofheap:0xb059004

32x86_64體系新變化

AMDx86_64的出現(xiàn),給全新的64位的x86帶來了很多結構上的變化:

1)64位整型數(shù)

在X86-64中,所有通用寄存器(GPRs)都從32位擴充到了64位,名字也發(fā)生

了變化。8個通用寄存器(eax,ebx,ecx,edx,

ebp,esp,esi,edi)在新的結構中被命名為rax,rbx,rex,rdx,rbp,rsp,

rsi,rdi,它們都是64位的。呵呵,想當年,從16位擴充到32位時,同樣也

有一次名字的變化。所有算術邏輯操作、寄存器到內存的數(shù)據(jù)傳輸現(xiàn)在都能以

64位的整形類型進行操作。堆棧的壓棧和彈出操作都以8字節(jié)的單位進行,而

且指針類型也擁有了64位。

2)新增寄存器

在新的架構中,另外新增了8個通用寄存器:64位的r8,r9,rlO,rll,rl2,rl3,

rl4,rl5o這樣就有利與編譯器將函數(shù)參數(shù)、返回值等放在這些新增的GPR里面

進行傳遞,從而提高了程序的運行速度。同時,128位的MMX寄存器也從原來的

8個增加到了16個。

3)增大的邏輯地址空間

目前在新的架構中,應用程序可以擁有的邏輯地址空間從4GB增加到了256TB

(2~48),而且這一邏輯地址空間在未來可能增加到16EB

(2*64,1EB=1O24PB,1PB=1024TB,1TB=1O24GB)。

4)增大的物理地址空間

目前的X86-64架構,可以支持的物理內存擴展到了1TB(2*40),當然,在未

來該數(shù)字可以擴展到4PB(2"52)o相比于經過PAE技術擴展的i386的64GB物

理內存,新的架構帶來了不小的飛躍。

5)無縫使用SSE指令

新的架構借鑒和吸收了Intel的SSE、SSE2的核心指令,并在2005年加入了SSE3。

在這一新的架構下,可以不再需要x87浮點協(xié)處理器來完成浮點運算了。

6)NX位

跟PAE技術一樣,新的X86-64架構也在頁表項中增加了NX位,來幫助CPU判斷

該頁包含的內容是否是可以執(zhí)行的,從而避免借助"bufferoverrun”導致的病

毒攻擊。

7)去除舊的機制

在新架構的“長模式(longmode)”下,很多在IA32中被提出,但確不經常被

操作系統(tǒng)用到的一些機制不再被支持。這些機制包括段式地址變化機制(FS和

GS仍然被保留),任務轉移門(TSS)機制,以及虛擬86模式。當然,出于向

下兼容的考慮,X86-64在“傳統(tǒng)模式"(Legacymode)下,仍然對這些機制進

行了保留。

43x86_64段式管理

x86的兩種工作模式:實地址模式和虛地址模式(保護模式)。Linux主要工作

在保護模式下。

在保護模式下,64位x86體系架構的虛地址空間可達2c48Byte,即256TB,這

可比只能到達區(qū)區(qū)4GB的32位x86體系大多了。邏輯地址到線性地址的轉換由

x86分段機制管理。段寄存器CS、DS、ES、SS、FS或GS各標識一個段。這些段

寄存器作為段選擇器,用來選擇該段的描述符。

Linux中關于段描述符的宏定義集中在文件/arch/x86/include/asm/Segment.h

中,我們先貼出部分代碼:

32位的:

#defineGDT_ENTRY_KERNEL_BASE12/*

0x0000000cc=>1100*/

#defineGDT_ENTRY_KERNEL_CS(GDT_ENTRY_KERNEL_BASE+0)/*

0x0000000cc=>1100*/

#defineGDT_ENTRY_KERNEL_DS(GDT_ENTRY_KERNEL_BASE+1)/*

OxOOOOOOOdc=>1101*/

64位的:

#defineGDT_ENTRY_KERNEL32_CS1/*0x00000001*/

#defineGDT_ENTRY_KERNEL_CS2/*0x00000002*/

#defineGDT_ENTRY_KERNEL_DS3/*0x00000003*/

#define_KERNEL32_CS(GDT_ENTRY_KERNEL32_CS*8)/*

0x00000100*/

^defineGDT_ENTRY_DEFAULT_USER32_CS4/*0x00000004*/

#defineGDT_ENTRY_DEFAULT_USER?DS5/*0x00000005*/

#defineGDT_ENTRY_DEFAULT_USER_CS6/*0x00000006*/

#define_USER32_CS(GDT_ENTRY_DEFAULT_USER32_CS*8+3)/*

0x00000403*/

#define_USER32_DS_USER_DS

不管32位還是64位的:(我們只關心64位)

^define_KERNEL_CS(GDT_ENTRY_KERNEL_CS*8)/*0x00000200

*/

#define_KERNEL_DS(GDT_ENTRY_KERNEL_DS*8)/*0x00000300

*/

#define_USER_DS(GDT_ENTRY_DEFAULT_USER_DS*8+3)/*

0x00000503*/

#define_USER?CS(GDT_ENTRY_DEFAULT_USER_CS*8+3)/*

0x00000603*/

看見沒有,我們熟悉的_USER_CS,_USER_DS,_KERNEL_CS,和_KERNEL_DS,

就是傳說中的段選擇子。

我們看到,內核代碼段的描述子存放在以0x200為基地址的內存單元中,占8

個字節(jié)。同樣,內核數(shù)據(jù)段、用戶代碼段、用戶數(shù)據(jù)段分別存放在

以0x300、0x500、0x600為基地址的內存單元中。我們注意到,_USER_DS和

_USER_CS的最低三位為3,也就是011,這正說明

其CPL位為11,代表用戶模式,TI為0,代表GDT。

對于x86_64來說,虛擬地址由16位選擇子和64位偏移量組成,段寄存器僅僅

存放選擇子。CPU的分段單元(SU)執(zhí)行以下操作:

[1]先檢查選擇子的TI字段,以決定描述子對應的描述子保存在哪一個描述符

表中。TI字段指明描述子是在GDT中(在這種情況下,分段單元從gdtr寄存器

中得到GDT的線性基地址)還是在激活的LDT中(在這種情況下,分段單元從

Idtr寄存器中得到LDT的線性基地址)。

[2]從選擇子的13位index字段計算描述子的地址,index字段的值乘以8(一

個描述子的大小,其實就是屏蔽掉末尾那三位指示特權級的CPL和指示TI的字

段),這個結果與gdtr或Idtr寄存器中的內容相加。

[3]將對應的段描述子從內存拷貝到CPU的影子Cache中,這樣,只有在選擇子

改變的情況下才會修改影子Cache中的內容。

[4]把虛擬地址的偏移量與隱Cache中描述子Base字段的值相加就得到了線性

地址。

例如,為了對內核代碼段尋址,內核只需要把_KERNEL_CS宏產生的選擇子的值

裝進cs段寄存器即可。注意,與段相關的線性地址還是從

0開始,達到264T的尋址限長。這就意味著在用戶態(tài)或內核態(tài)下的所有進程

任然使用相同的虛擬地址,這就是傳說中的“基本平坦模式”。

按照這個模式,虛擬地址跟線性地址數(shù)字一?樣,唯一-的不同就是CS和DS裝的內

容不同,可能是KERNEL級別的選擇子,也可能是USER級別

的選擇子。

54x86_64分頁管理

雖然邏輯地址擴展到了64位,但是,現(xiàn)有的設計并沒有完全用到這64位的空間

(2*64=16EB),因為使用到如此大的空間,勢必造成很大的系統(tǒng)開銷。AMD64

在設計的時候就決定在x86_64的第一階段,只用這64位中的低48位來做頁式

地址轉換,高16位(48-64位)將填充第47位相同的內容(這種方式類似于符

號擴展)。如果邏輯地址不符合此規(guī)定,系統(tǒng)將產生異常。符合此規(guī)定的地址稱

為canonicalform,地址的范圍分為兩段:0到00007FFF-FFFFFFFF,以及FFFF8

構,也為操作系統(tǒng)的設計帶來了一定便利:可以取地址的上半段保留做為操作

系統(tǒng)的邏輯地址空間,而低地址部分做為裝載應用程序的空間,而canonical

form不允許的地址空間則做為操作系統(tǒng)的標志、以及特權級的標識等。當然,

這樣的設計在未來地址進一步擴展的時候將成為一個新的問題。

采用64位地址空間的X86-86被稱為是運行在“長模式"(longmode)下,該

模式可以看成是對PAE模式的一個擴充。長模式允許使用三個不同的物理頁面大

小:4KB、2MB和1GB。在使用64位中的48位用來存放地址時,與PAE模式下

的三級頁面映射機制不同的是,長模式下線性地址到物理地址的映射需要經過四

級地址映射。在這四級地址映射機制中,原來PAE模式下僅擁有4個表項的頁

目錄指針表被擴展到512個表項。同時,在最末一級加入一?級新的頁面映射結構,

該結構被稱為第四級頁表(MapLevel4Table,PML4),它跟PAE模式下

的頁目錄及頁表(在長模式中,成為了頁目錄)一樣,擁有512個表項。如果地

址進一步擴充,如把64位尋址全部用上,該頁表就能夠擴充到33,554,432個

表項,或者干脆再加一層地址映射(PML5),當然,按照目前只用了48位的情

況下,用到512個表項的PML4就已經夠用了。

可以想象,用到48位的X86-64虛擬地址的分配機制為:

-0—11(12)位:頁內偏移;

-12-20(9)位:由PML4來映射;

-21-29(9)位:高一級頁目錄來映射(如果PS=1,則該頁表項指向一個2MB

的頁);

-30-38(9)位:再高一級的頁目錄來映射(如果PS=2,則該頁表項指向一個

1GB的頁);

-39—47(9)位:頁目錄指針表來映射。

X86-64的長模式下,對16位以及32位代碼進行了兼容,即使CPU上跑的是64

位的操作系統(tǒng),歷史遺留的16位以及32位代碼將都能夠在該操作系統(tǒng)上運行。

由于X86-64兼容IA32的指令,所以,這些代碼在這種情況下運行,基本上沒有

性能損耗。

在傳統(tǒng)模式(Legacymode)下,x86-64的CPU的工作模式跟傳統(tǒng)的IA32沒有

什么兩樣。

6堆的管理譴

一般人喜歡把堆和棧來做對比,網(wǎng)上資料也很多,這里我只分享一下我本人的理

解。堆這個東西跟棧沒有直接的關聯(lián),它只給程序員提供一個手工分配和釋放的

內存空間,僅此而已。

對于每個Unix進程來說,都擁有一個特殊的線性區(qū),這個線性區(qū)就是所謂的堆

(heap),堆用于滿足進程的動態(tài)內存請求。內存描述符的start_brk與brk

字段分別限定了這個區(qū)的開始地址和結束地址。

進程可以使用下面的C語言API來請求和釋放動態(tài)內存:

malloc(size)

請求size個字節(jié)的動態(tài)內存。如果分配成功,就返回所分配內存單元第一

個字節(jié)的線性地址。

calloc(n,size)

請求含有n個大小為size的元素的一個數(shù)組。如果分配成功,就把數(shù)組元

素初始化為0,并返回第一個元素的線性地址。

realloc(ptr,size)

改變由前面的malloc?;騝alloc()分配的內存區(qū)字段的大小。

free(addr)

釋放由mallocO或calloc。分配的起始地址為addr的線性區(qū)。

brk(addr)

直接修改堆的大小。addr參數(shù)指定current->mm->brk的新值,返回值是線

性區(qū)新的結束地址(進程必須檢查這個地址和所請求的地址值addr是否致)。

sbrk(incr)

類似于brk(),不過其中的incr參數(shù)指定是增加還是減少以字節(jié)為單位的

堆大小。

brk()函數(shù)和以上列出的函數(shù)有所不同,因為它是唯一以系統(tǒng)調用的方式實現(xiàn)的

函數(shù),而其他所有的函數(shù)都是使用brk()和mmap()系統(tǒng)調用實現(xiàn)的C語言庫函數(shù)。

當用戶態(tài)的進程調用brk()系統(tǒng)調用時,內核執(zhí)行sys_brk(addr)函數(shù)。該函數(shù)

首先驗證addr參數(shù)是否位干進程代碼所在的線性區(qū)。如果是,則立即返回,

因為堆不能與進程代碼所在的線性區(qū)重疊:

mm=current->mm;

down_write(&.mm->mmap_sem);

if(addr<mm->end_code){

out:

up_write(&mm->mmap_sem);

returnmm->brk;

由于brk()系統(tǒng)調用作用于某一個非代碼的線性區(qū),它分配和釋放完整的頁。

因此,該函數(shù)把addr的值調整為PAGE_SIZE的倍數(shù),然后把調整的結果與內存

描述符的brk字段的值進行比較:

newbrk=(addr+Oxfff)&OxfffffOOO;

oldbrk=(mm->brk+Oxfff)&OxfffffOOO;

if(oldbrk==newbrk){

mm->brk=addr;

gotoout;

如果進程請求縮小堆,則sys_brk()調用do_munmap()函數(shù)完成這項任務,然后

返回:

if(addr<=mm->brk){

if(!do_munmap(mm,newbrk,o1dbrk-newbrk))

mm->brk=addr;

gotoout;

如果進程請求擴大堆,則sys_brk()首先檢查是否允許進程這樣做。如果進程企

圖分配在其跟制范圍之外的內存,函數(shù)并不多分配內存,只簡單地返回mm->brk

的原有值:

rlim=current->signal->rlim[RLIMIT_DATA].rlim_cur;

if(rlim<RLIM_INFINITY&&addr-mm->start_data>rlim)

gotoout;

然后,函數(shù)檢查擴大后的堆是否和進程的其他線性區(qū)相重疊,如果是,不做任何

事情就返回:

if(find_vma_intersection(mm,oldbrk,newbrk+PAGE_SIZE))

gotoout;

如果一切都順利,則調用do_brk()函數(shù)。如果它返回oldbrk,則分配成功且

sys_brt()函數(shù)返回addr的值;否則,返回舊的mm->brk值:

if(do_brk(oldbrk,newbrk-oldbrk)==oldbrk)

mm->brk=addr;

gotoout;

do_brk()函數(shù)實際上是僅處理匿名線性區(qū)的do_mmap()的簡化版。可以認為它的

調論等價于:

dommap(NULL,oldbrk,newbrk-oldbrk,

PR0T_READ|PROT__WRITE|PR0T_EXEC,

MAP_FIXED|MAP_PRIVATE,0)

當然,do_brk()比do_mmap()稍快,因為前者假定線性區(qū)不映射磁盤上的文件,

從而避免亍檢查線性反對象的兒個字段。

7創(chuàng)建和刪除進程的地址空間收藏

本博,我們重點關注fork。系統(tǒng)調用為子進程創(chuàng)建一個完整的新地址空間。相

反,當進程結束時,內核撤消它的地址空間。我們重點來討論Linux如何執(zhí)行這

兩種操作。

81創(chuàng)建進程的地址空間

回憶一下“進程的創(chuàng)建——dofork。函數(shù)詳解”博文:當創(chuàng)建一個新的進程

時內核調用copy_mm()函數(shù)。這個函數(shù)通過建立新進程的所有頁表和內存描述符

來創(chuàng)建進程一的地址空間:

staticintcopy_mm(unsignedlongclone_flags,structtaskstruct*tsk)

{

structmmstruct*mm,*oldmm;

intretval;

tsk->min_fIt=tsk->maj_fIt=0;

tsk->nvcsw=tsk->nivcsw=0;

tsk->mm=NULL;

tsk->active_mm=NULL;

/*

*Arewecloningakernelthread?

*

*WeneedtostealaactiveVMforthat..

*/

oldmm=current->mm;

if(!oldmm)

return0;

if(clone_flags&CLONE_VM){

atomicinc(&o1users);

mm=oldmm;

gotogood_mm;

)

retval=-ENOMEM;

mm=dup_mm(tsk);

if(!mm)

gotofailnomem;

goodmm:

tsk->mm=mm;

tsk->active_mm=mm;

return0;

fail_nomem:

returnretval;

}

通常,每個進程都有自己的地址空間,但是輕進程可以通過調用clone。函數(shù)(設

置了CL0NE_VM標志)來創(chuàng)建。這些輕量級進程共享同一?地址空間,也就是說,

允許它們對同一組頁進行尋址。

按照前面講述的“寫時復制”方法,傳統(tǒng)的進程繼承父進程的地址空間,只要

頁是只讀的,就依然共享它們。當其中的一個進程試圖對某個頁進行寫時,此時,

這個頁才被復制一份。一段時間之后,所創(chuàng)建的子進程通常會因為缺頁異常而獲

得與父進程不一樣的完全屬于自己的地址空間。

另一方面,輕量級的進程使用父進程的地址空間。Linux實現(xiàn)輕量級進程很簡單,

即不復制父進程地址空間。創(chuàng)建輕量級的進程(clone)比創(chuàng)建普通進程相應要

快得多,而且只要父進程和子進程謹慎地協(xié)調它們的訪問,就可以認為頁的共享

是有益的。

如果通過clone()系統(tǒng)調用已經創(chuàng)建了新進程,并且flag參數(shù)的CLONE_VM標志

被設置,則copy_mm()函數(shù)把父進程(current)地址空間給子進程(tsk):

if(clone_flags&CL0NE_VM){

atomic_inc(&o1dmm->mm_users);

mm=oldmm;

gotogood_mm;

good_mm:

tsk->mm=mm;

tsk->active_mm=mm;

return0;

如果沒有設置CLONE_VM標志,copy_mm()函數(shù)就必須創(chuàng)建一個新的地址空間(在

進程請求一個地址之前,即使在地癥空間內沒有分配內存):

mm=dup_mm(tsk);

dup_mm()函數(shù)分配一個新的內存描述符,把它的地址存放在新進程描述符tsk

的mm字段中,并把current->min的內容復制到tsk->mm中。然后改變新進程描

述符的一些字段:

staticstructmm_struct*dupmm(structtask_struct*tsk)

{

structmm_struct*mm,*oldmm=current->mm;

interr;

if(!oldmm)

returnNULL;

mm二allocate_mm();

if(!mm)

gotofailnomem;

memcpy(mm,oldmm,sizeof;

if(!mminit(mm))

gotofail_nomem;

if(init_new_context(tsk,mm))

gotofail_nocontext;

err二dup_mmap(mm,oldmm);

if(err)

gotofree_pt;

mm->hiwater_rss=get_mm_rss(mm);

mm->hiwater_vm=mm->total_vm;

returnmm;

free_pt:

mmput(mm);

fail_nomem:

returnNULL;

fail_nocontext:

free_mm_flags(mm);

mm_free_pgd(mm);

free_mm(mm);

returnNULL;

}

ttdefineallocate_mmO(kmem_cache_alloc(mmcachep,SLABKERNEL))

函數(shù)首先使用allocatemm()函數(shù)調用kmem_cache_alloc(mmcachep,

SLAB_KERNEL)從slab中分配一個mm_struct結構,然后調用mm_init對其進行

初始必

staticstructmm_struct*mminit(structmm_struct*mm)

(

unsignedlongmm_flags;

atomic_set(&mm->mm_users,1);

atomic_set(&mm->mm_count,1);

init_rwsem(&mm->mmap_sem);

INIT_LIST_HEAD(&mm->innilist);

mm->core_waiters=0;

mm->nr_ptes=0;

set_mm_counter(mm,file_rss,0);

set_mm_counter(mm,anon_rss,0);

spin_lock_init(&mm->page_table_lock);

rwlockinit(&mm->ioctx_list_lock);

mm->ioctx_list=NULL;

mm->free_area_cache=TASK_UNMAPPED_BASE;

mm->cached_hole_size=~OUL;

mm_flags=get_mm_flags(current->mm);

if(mm_flags!=MMF_DUMP_FILTER_DEFAULT){

if(uniikely(set_mm_flags(mm,mm_flags,0)<0))

gotofail_nomem;

)

if(likely(!mm_a11oc_pgd(mm))){

mm->def_flags=0;

mmu_notifier_mm_init(mm);

returnmm;

if(mm_flags!=MMF_DUMP_FILTER_DEFAULT)

free_mm_flags(mm);

fail_nomem:

free_mm(mm);

returnNULL;

)

回想一下,mmallocpgd()調用pgd_alloc()宏為新進程分配一個全新的頁全局

目錄:(/arch/i386/mm/Pgtable.c)

staticinlineintmm_alloc_pgd(structmmstruct*mm)

(

mm->pgd=pgd_alloc(mm);

if(unlikely(!mm->pgd))

return-ENOMEM;

return0;

)

pgd_t*pgd_alloc(structmm_struct*mm)

(

inti;

pgd_t*pgd=kmem_cache_alloc(pgd_cache,GFPKERNEL);

if(PTRS_PER_PMD==1||!pgd)

returnpgd;

for(i=0;i<USER_PTRS_PER_PGD;++i){

pmd_t*pmd=kmem_cache_alloc(pmd_cache,GFP_KERNEL);

if(!pmd)

gotoout_oom;

set_pgd(&pgd[i],_pgd(l+_pa(pmd)));

)

returnpgd;

out_oom:

for(i--;i>=0;i--)

kmem_cache_free(pmd_cache,(void*)_va(pgd_val(pgd[i])~l));

kmem_cache_free(pgd_cache,pgd);

returnNULL;

}

SdefineUSER_PTRS_PER_PGD(TASK_SIZE/PGDIR_SIZE)

ttdefinePGDIR_SIZE(1UL?PGDIR_SHIFT)

SdefinePGDIR_SHIFT22

#defineTASKSIZE(PAGE_OFFSET)/*Userspaceprocesssize:3GB

(default).*/

注意,執(zhí)行完mm_a芯oc_pgd()函數(shù)之后,子進程的pgd和pmd有了(32位i386

體系結構),但是pte是沒有的,后面的工作需要dupjnmapO函數(shù)來完成,馬

上會談到。

接著來,隨后調用依賴于體系結構的init_new_context()函數(shù):對于80x86處

理器,該函數(shù)檢查當前進程是否擁有定制而局而描述符表,如果是,

init_new_context()復制一份current的局部描述符表并把它插入tsk的地址空

間:

intinit_new_context(structtask_struct*tsk,structmm_struct*mm)

(

structmmstruct*old_mm;

intretval=0;

init_MUTEX(&mm->context.sem);

mm->context.size=0;

old_mm=current->mm;

if(old_mm&&old_mm->context.size>0){

down(&old_mm->context.sem);

retval=copy_ldt(&mm->context,&old_mm->context);

up(&old_mm->context.sem);

)

returnretval;

)

下一個重點步驟:err=dup_mmap(mm,oldmm):

staticinlineintdupmmap(structmm_struct*mm,structmm_struct*oldmm)

{

structvm_area_struct*mpnt,*tmp,**pprev;

structrb_node**rb_link,*rb_parent;

intretval;

unsignedlongcharge;

structmempolicy*pol;

down_write(&oldmm->mmap_sem);

flush_cache_mm(oldmm);

/*

*Notlinkedinyet-nodeadlockpotential:

*/

down_write_nested(&mm->mmap_sem,SINGLE_DEPTH_NESTING);

mm->locked_vm=0;

mm->mmap=NULL;

mm->mmap_cache=NULL;

mm->free_area_cache=oldmm->mmap_base;

mm->cached_ho1e_size=~OUL;

mm->map_count=0;

cpus_clear(mm->cpu_vm_mask);

mm->mm_rb=RBROOT;

rb_link=rb_node;

rb_parent=NULL;

pprev=&mm->mmap;

for(mpnt=oldmm->mmap;mpnt;mpnt=mpnt->vm_next){

structfile

if(mpnt->vm_flags&VM_DONTCOPY){

longpages=vma_pages(mpnt);

mm->total_vm-二pages;

vm_stat_account(mm,mpnt->vm_flags,mpnt->vm_file,

s);

continue;

)

charge=0;

if(mpnt->vm_flags&VM_ACCOUNT){

unsignedintlen=(mpnt->vm_end-mpnt->vm_start)?

PAGE_SHIFT;

if(security_vm_enough_memory(1en))

gotofail_nomem;

charge=len;

}

tmp=kmem_cache_alloc(vm_area_cachep,SLAB_KERNEL);

if(!tmp)

gotofail_nomem;

*tmp=*mpnt;

pol=mpol_copy(vma_policy(mpnt));

retval=PTR_ERR(pol);

if(IS_ERR(pol))

gotofail_nomempolicy;

vma_set_po1icy(tmp,pol);

tmp->vm_flags&="VM_L0CKED;

tmp->vm_mm=mm;

tmp->vm_next=NULL;

anon_vma_link(tmp);

file=tmp->vm_file;

if(file){

structinodebinode=file->f_dentry->d_inode;

get_file(file);

if(tmp->vm_flags&VM_DENYWRITE)

atomic_dec(&inode->i_writecount);

/*inserttmpintothesharelist,justaftermpnt*/

spinlock(&file->f_mapping->i_mmap_lock);

tmp->vm_truncate_count=mpnt->vm_truncate_count;

flushdcachemmap_lock(file->fmapping);

vma_prio_tree_add(tmp,mpnt);

flushdcachemmap_unlock(file->f_mapping);

spin_unlock(&fi1e->f_mapping->i_mmap_lock);

}

/*

*Linkinthenewvmaandcopythepagetableentries.

*/

*pprev=tmp;

pprev=&tmp->vm_next;

_vma_link_rb(mm,tmp,rb_link,rb_parent);

rb_link=&tmp->vm_rb.rb_right;

rb_parent=&tmp->vmrb;

mm->map_count++;

retval=copyjpage_range(mm,oldmm,mpnt);

if(tmp->vm_ops&&tmp->vm_ops->open)

tmp->vmops->open(tmp);

if(retval)

gotoout;

}

#ifdefarch_dup_mmap

arch_dup_mmap(mm,oldmm);

Sendif

retval=0;

out:

upwrite(&mm->mmapsem);

f1ush_11b_mm(o1dmm);

upwrite(&oldmm->mmap_sem);

returnretval;

fail_nomem_policy:

kmem_cache_free(vm_area_cachep,tmp);

fail_nomem:

retval=-ENOMEM;

vm_unacct_memory(charge);

gotoout;

}

dup_mmap()函數(shù)既復制父進程的線性區(qū),也復制父進程的頁表。

然后,從current->mm->mmap所指向的線性區(qū)開始掃描父進程的線性區(qū)鏈表:

for(mpnt=oldmm->mmap;mpnt;mpnt=mpnt->vm_next)

它復制遇到的每個vm_area_struct線性區(qū)描述符,并把復制品插入到子進程的

線性區(qū)鏈表和紅-黑樹中:

tmp=kmem_cache_alloc(vm_area_cachep,SLABKERNEL);

*tmp=*mpnt;/*完全復制,這個技巧在這里又用到了*/

rb_link=rb_node;

rbparent=NULL;

_vma_link_rb(mm,tmp,rb_link,rb_parent);

rb_link=&tmp->vm_rb.rb_right;

rb_parent=&tmp->vm_rb;

在插入一個新的線性區(qū)描述符之后,如果需要的話,dup_mmap()立即調用

copy_page_range()創(chuàng)建必要的頁表來映射這個線性區(qū)所包含的一組頁,并且初

始化新頁袤的表項。尤其是,與私有的、可寫的頁(VM_SHARED標志關閉,

VM—MAYWRITE標志打開)所對應的任一頁框都標記為對父子進程是只讀的,以

便這種頁框能用寫時復制機制進行處理:

intcopypage_range(structmmstruct*dst_mm,structmm_struct*src_mm,

structvm_area_struct*vma)

(

pgd_t*src_pgd,*dst_pgd;

unsignedlongnext;

unsignedlongaddr=vma->vm_start;

unsignedlongend=vma->vm_end;

intret;

/*

*Don,tcopypteswhereapagefaultwillfillthemcorrectly.

*Forkbecomesmuchlighterwhentherearebigsharedorprivate

*readonlymappings.Thetradeoffisthatcopy_page_rangeismore

*efficientthanfaulting.

*/

if(!(vma->vm_flags&

(VM_HUGETLB|VM_NONLINEAR|VM_PFNMAP|VM_INSERTPAGE))){

if(!vma->anon_vma)

return0;

if(is_vm_huge11b_page(vma))

returncopyhugetlb_page_range(dstmm,srcmm,vma);

/*

*WeneedtoinvalidatethesecondaryMMUmappingsonlywhen

*therecouldbeapermissiondowngradeontheptesofthe

*parentmm.Andapermissiondowngradewillonlyhappenif

*is_cow_mapping()returnstrue.

*/

if(is_cow_mapping(vma->vm_flags))

mmu_notifier_invalidate_range_start(src_mm,addr,end);

ret=0;

dst_pgd=pgdoffset(dstmm,addr);

src_pgd=pgd_offset(src_mm,addr);

do{

next=pgd_addr_end(addr,end);

if(pgd_none_or_c1ear_bad(src_pgd))

continue;

if(unlikely(copy_pud_range(dst_mm,srcmm,dst_pgd,srcpgd,

vma,addr,next))){

ret=-ENOMEM;

break;

}

}while(dst_pgd++,src_pgd++,addr=next,addr!=end);

if(is_cow_mapping(vma->vm_flags))

mmu_notifier_invalidate_range_end(src_mm,

vma->vm_start,end);

returnret;

)

staticinlineintcopy_pud_range(structmm_structstruct

mm_struct*src_mm,

pgd_t*dst_pgd,pgd_t*src_pgd,structvm_area_struct*vma,

unsignedlongaddr,unsignedlongend)

(

pud_t*src_pud,*dst_pud;

unsignedlongnext;

dst_pud=pud_alloc(dst_mm,dst_pgd,addr);

if(!dst_pud)

return-ENOMEM;

src_pud=pud_offset(src_pgd,addr);

do{

next=pud_addr_end(addr,end);

if(pud_none_or_clear_bad(src_pud))

continue;

if(copy_pmd_range(dst_mm,src_mm,dst_pud,src_pud,

vma,addr,next))

return-ENOMEM;

}while(dst_pud++,src_pud++,addr=next,addr!=end);

return0;

)

staticinlineintcopy_pmd_range(structmm_struct*dst_mm,struct

mm_struct*src_mm,

pud_t*dst_pud,pud_t*src_pud,structvm_area_struct*vma,

unsignedlongaddr,unsignedlongend)

{

pmd_t*src_pmd,*dst_pmd;

unsignedlongnext;

dst_pmd=pmd_alloc(dst_mm,dst_pud,addr);

if(!dst_pmd)

return-ENOMEM;

src_pmd=pmd_offset(src_pud,addr);

do{

next=pmd_addr_end(addr,end);

if(pmd_none_or_c1ear_bad(src_pmd))

continue;

if(copy_pte_range(dst_mm,src_mm,dst_pmd,src_pmd,

vma,addr,next))

return-ENOMEM;

}while(dst_pmd++,src_pmd++,addr=next,addr!=end);

return0;

staticintcopy_pte_range(structmm__structstructmm_struct

*src_mm,

pmd_t*dst_pmd,pmd_t*src_pmd,structvm_area_struct*vma,

unsignedlongaddr,unsignedlongend)

pte_t*src_pte,*dst_pte;

spinlock_t*src_ptl,*dst_ptl;

intprogress=0;

intrss[2];

again:

rssEl]=rss[0]=0;

dst_pte=pte_alloc_map_lock(dst_mm,dst_pmd,addr,&dst_ptl);

if(!dst_pte)

return-ENOMEM;

src_pte=pte_offset_map_nested(src_pmd,addr);

src_ptl=pte_lockptr(src_mm,src_pmd);

spin_lock_nested(src_ptl,SINGLE_DEPTH_NESTING);

do{

/*

*Weareholdingtwolocksatthispoint-eitherofthem

*couldgeneratelatenciesinanothertaskonanotherCPU.

*/

if(progress>=32){

progress=0;

if(need_resched()

need_lockbreak(src_ptl)

needlockbreak(dst_ptl))

break;

)

if(pte_none(*src_pte)){

progress++;

continue;

)

copy_one_pte(dstmm,src_mm,dst_pte,src_pte,vma,addr,rss);

progress+=8;

}while(dst_pte++,src_pte++,addr+二PAGE_SIZE,addr!=end);

spin_unlock(src_ptl);

pte_unmap_nested(src_pte-1);

add_mm_rss(dst_mm,rss[0],rss[l]);

pte_unmap_unlock(dst_pte-1,dst_ptl);

cond_resched();

if(addr!=end)

gotoagain;

return0;

staticinlinevoid

copy_one_pte(structmmstructstructmm_struct*src_mm,

pte_t*dst_pte,pte_t*src_pte,structvm_area_struct*vma,

unsignedlongaddr,int*rss)

(

unsignedlongvm_flags=vma->vm_flags;

pte_tpte=*src_pte;

structpage*page;

/*ptecontainspositioninswaporfile,socopy.*/

if(unlikely(!pte_present(pte))){

if(!pte_file(pte)){

swp_entry_tentry=pte_to_swp_entry(pte);

swap_duplicate(entry);

/*makesuredst_mmisonswapoff,smmlist.*/

if(unlikely(list_empty(&dst_mm->mmlist))){

spin_lock(&mmlist_lock);

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論