30分鐘搞懂Linux內(nèi)核內(nèi)存映射,(值得大神們收藏)
一、內(nèi)存映射基礎(chǔ)知識(shí)
1、內(nèi)核地址映射模型
比如:X86CPU采用段頁式地址映射模型,進(jìn)程代碼地址為邏輯地址,經(jīng)過段頁式地址映射之后,才能夠真正訪問物理內(nèi)存。32位Linux內(nèi)核地址空間劃分:0GB--3GB為用戶空間,3GB--4GB為內(nèi)核空間。32位和64位內(nèi)核地址空間劃分是不同的。具體空間表示如下:

2、內(nèi)核高端內(nèi)存
比如:當(dāng)內(nèi)核模塊代碼或絡(luò)訪問內(nèi)存時(shí),代碼中的地址為邏輯地址,而且對(duì)應(yīng)到真正的物理內(nèi)存地址,需要地址和它一對(duì)一的映射,假設(shè):邏輯地址0xc0000003對(duì)應(yīng)的物理地址為0x3。內(nèi)核邏輯地址空間訪問為0xc0000000--0xFFFFFFFF,那么對(duì)應(yīng)的物理內(nèi)存范圍為0x0--0x40000000。即只能訪問1GB物理內(nèi)存。假設(shè)我們的電腦8GB物理內(nèi)存,那么內(nèi)核就只能訪問前面1GB物理內(nèi)存,剩下7GB物理內(nèi)存它無法訪問,因?yàn)閮?nèi)核的地址空間已經(jīng)全部映射到物理內(nèi)存地址范圍0x--0x40000000。
Linux將內(nèi)核地址空間劃分為三部分:
ZONE_DMA/ZONE_NORMAL/ZONE_HIGHMEM。高端內(nèi)存HIGH_MEM地址空間范圍為0xF8000000--0xFFFFFFFF(896MB-1024MB)。針對(duì)高端內(nèi)存的最基本思想:借一段地址空間,建立臨時(shí)地址映射,用完后釋放,達(dá)到此段地址空間可以循環(huán)使用,訪問所有物理內(nèi)存。
3、內(nèi)存映射
內(nèi)存映射是在進(jìn)程的虛擬地址空間中創(chuàng)建一個(gè)映射,可分為兩種:
A、文件映射(文件支持的內(nèi)存映射,把文件的一個(gè)區(qū)間映射到進(jìn)程的虛擬地址空間,數(shù)據(jù)源是存儲(chǔ)設(shè)備上文件);?
B、匿名映射(沒有文件支持的內(nèi)存映射,把物理內(nèi)存映射到進(jìn)程的虛擬地址空間,沒有數(shù)據(jù)源)。通常把文件映射的物理頁稱為文件頁,把匿名映射的物理頁稱為匿名頁。
4、根據(jù)修改是否對(duì)其他進(jìn)程可見和是否傳遞到底層文件,內(nèi)存映射分為共享映射和私有映射。
A、共享映射:修改數(shù)據(jù)時(shí)映射相同區(qū)域的其他進(jìn)程可以看見,如果是文件支持的映射,修改會(huì)傳遞到底層文件。?
B、私有映射:第一次修改數(shù)據(jù)時(shí)會(huì)從數(shù)據(jù)源復(fù)制一個(gè)副本,然后修改副本,其他進(jìn)程看不見,不影響我們的數(shù)據(jù)源。
兩個(gè)進(jìn)程可以使用共享的文件映射實(shí)現(xiàn)共享內(nèi)存。匿名映射通常為私有映射,共享的匿名映射只可能出現(xiàn)在父進(jìn)程和子進(jìn)程之間。在進(jìn)程的虛擬地址空間中,代碼段和數(shù)據(jù)段是私有的文件映射,未初始化數(shù)據(jù)段、堆棧是私有的匿名映射。
5、內(nèi)存映射原理機(jī)制:
創(chuàng)建內(nèi)存映射的時(shí),在進(jìn)程的用戶虛擬地址空間中分配一個(gè)虛擬內(nèi)存區(qū)域(vm_area_struct);
Linux內(nèi)核采用延遲分配物理內(nèi)存的策略,在進(jìn)程第一次訪問虛擬頁的時(shí)候,產(chǎn)生缺頁異常,如果是文件映射,那么分配物理頁,把文件指定區(qū)間的數(shù)據(jù)讀到物理頁中,然后在頁表中把虛擬頁映射到物理頁;如果你是匿名映射,那么分配物理頁,然后在頁表中把虛擬頁映射到物理頁。
二、應(yīng)用層API系統(tǒng)調(diào)用
Linux內(nèi)核當(dāng)中內(nèi)存管理子系統(tǒng)提供給我們用戶層的系統(tǒng)調(diào)用API函數(shù),具體如下:
1、用來創(chuàng)建內(nèi)存映射
2、用來刪除內(nèi)存映射
3、專用用來擴(kuò)大或縮小已經(jīng)存在的內(nèi)存映射,可能同時(shí)移動(dòng)
4、用來設(shè)置堆的上界
5、用來創(chuàng)建非線性的文件映射,即文件區(qū)域和虛擬地址空間之間的映射不是線性關(guān)系,現(xiàn)在已不用
6、用來設(shè)置虛擬內(nèi)存區(qū)域的訪問權(quán)限
在Linux空間中可以使用如下函數(shù):
1、把內(nèi)存的物理頁映射到進(jìn)程的虛擬地址空間,這個(gè)函數(shù)用處是:實(shí)現(xiàn)進(jìn)程和內(nèi)核共享內(nèi)存。
int remap_pfn_range (struct vm_area_struct *vma,unsigned long addr,unsigned long pfn,unsigned long size,pgprot_t prot);
2、把外設(shè)寄存器的物理地址映射到進(jìn)程的虛擬地址空間,進(jìn)程可能直接訪問外設(shè)寄存器。
int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr,unsigned long phys_addr, unsigned long size,pgprot_t prot);
應(yīng)用層當(dāng)中應(yīng)用程序通過使用C標(biāo)準(zhǔn)庫提供的函數(shù)malloc()申請(qǐng)內(nèi)存。glibc庫的內(nèi)存分配器ptmalloc使用brk或mmap向內(nèi)核以頁為單位申請(qǐng)?zhí)摂M內(nèi)存,然后把頁劃分成小內(nèi)存塊分配給應(yīng)用程序。默認(rèn)的閾值為128KB,如果應(yīng)用程序申請(qǐng)的內(nèi)存長(zhǎng)度小于閾值,ptmalloc分配器使用brk向內(nèi)核申請(qǐng)?zhí)摂M內(nèi)存,否則ptmalloc分配器使用mmap內(nèi)內(nèi)核申請(qǐng)?zhí)摂M內(nèi)存。應(yīng)用程序可以直接使用mmap向內(nèi)核申請(qǐng)?zhí)摂M內(nèi)存。
三、Linux內(nèi)核常用數(shù)據(jù)結(jié)構(gòu)
1、進(jìn)程描述符
2、內(nèi)存描述符
3、虛擬內(nèi)存區(qū)域

4、mmap系統(tǒng)調(diào)用
a.進(jìn)程創(chuàng)建匿名的內(nèi)存映射,把內(nèi)存的物理頁映射到進(jìn)程的虛擬地址空間;?
b.進(jìn)程把文件映射到進(jìn)程的虛擬地址空間,可以像訪問內(nèi)存一樣訪問文件,不需要調(diào)用系統(tǒng)調(diào)用read()/write()訪問文件,從面避免用戶模式和內(nèi)核模式之間的切換,提高讀寫文件的速度。?
c.兩個(gè)進(jìn)程針對(duì)同一個(gè)文件創(chuàng)建共享的內(nèi)存映射,實(shí)現(xiàn)共享會(huì)敗在。
函數(shù)原型:
參數(shù)如下:
addr:起始虛擬地址,如果addr為0,內(nèi)核選擇虛擬地址。如果addr為非0,內(nèi)核把這個(gè)參數(shù)作為提示,在附近選擇虛擬地址。
length:映射的長(zhǎng)度,單位是字節(jié)。
prot:保護(hù)位,具體參數(shù)如下:
PROT_EXEC:頁可執(zhí)行 PROT_READ:頁可讀 PROT_WRITE:頁可寫 PROT_NONE:面不可訪問
lags:標(biāo)志,具體參數(shù)如下:
MAP_SHARED:共享映射
MAP_PRIVATE:私有映射
MAP_ANONYMOUS:匿名映射
MAP_FIXED:固定映射
MAP_HUGETLB:使用巨型頁
MAP_LOCKED:把頁鎖在內(nèi)存中
fd:文件描述符
offset:偏移,單位是字節(jié),必須是頁長(zhǎng)度的整數(shù)倍。
返回值:
返回起始虛擬地址,否則返回負(fù)的錯(cuò)誤號(hào)。
四、創(chuàng)建/刪除內(nèi)存映射
1、創(chuàng)建內(nèi)存映射
C標(biāo)準(zhǔn)庫封裝函數(shù)mmap()用來創(chuàng)建內(nèi)存映射,內(nèi)核提花POSIX標(biāo)準(zhǔn)定義的系統(tǒng)調(diào)用mmap,內(nèi)核系統(tǒng)調(diào)用sys_mmap(),具體操作流程如下:

函數(shù)sys_mmap_pgoff執(zhí)行流程視圖如下:
a.如果是創(chuàng)建文件映射,根據(jù)文件描述符在進(jìn)程的打開文件表中找到file實(shí)例;
b.如果是創(chuàng)建匿名巨型頁映射,在hugetlbfs文件系統(tǒng)中創(chuàng)建文件“anon_hugepage",并且創(chuàng)建此文件 的一個(gè)打開實(shí)例file;
c.調(diào)用vm_mmap_pgoff進(jìn)行處理。
