Ⅰ linux關於地址空間和MMAP映射有何特點
Linux採用
虛擬
內存技術,系統中的所有進程之間以虛擬方式共享內存。對每個進程來說,它們好像都可以訪問整個系統的所有物理內存。更重要的是,即使單獨一個進程,它擁有的地址空間也可以遠遠大於系統物理內存。
進程地址空間由每個進程中的線性地址區組成,每個進程都有一個32位或64位的平坦(flat)空間,空間的具體大小取決於體系結構。「平坦」指地址空間范圍是一個獨立的連續區間。通常情況下,每個進程都有唯一的這種平坦空間,而且每個進程的地址空間之間彼此互不相干。兩個不同的進程可以在它們各自地址空間的相同地址內存存放不同的數據。但是進程之間也可以選擇共享地址空間,我們稱這樣的進程為線程。
在地址空間中,我們更為關心的是進程有權訪問的虛擬內存地址區間,比如08048000~0804c000。這些可被訪問的合法地址區間被成為內存區域(memory area),通過內核,進程可以給自己的地址空間動態地添加或減少內存區域。
進程只能訪問有效范圍內的內存地址。每個內存區域也具有相應進程必須遵循的特定訪問屬性,如只讀、只寫、可執行等屬性。如果一個進程訪問了不在有效范圍中的地址,或以不正確的方式訪問有效地址,那麼內核就會終止該進程,並返回「段錯誤」信息。
?
內存區域可以包含各種內存對象,如下:
?
可執行文件代碼的內存映射,成為代碼段(text section)。
?
可執行文件的已初始化全局變數的內存映射,成為數據段(data section)。
?
包含未初始化全局變數的零頁(也就是bss段)的內存映射。零頁是指頁面中的數據全部為0。
?
用於進程用戶空間棧的零頁的內存映射。
?
每一個諸如C庫或動態鏈接程序等共享庫的代碼段、數據段和bss也會被載入進程的地址空間。
?
任何內存映射文件。
?
任何共享內存段。
?
任何匿名的內存映射,比如由malloc()分配的內存。
進程地址空間的任何有效地址都只能位於唯一的區域,這些內存區域不能相互覆蓋。可以看到,在執行的進程中,每個不同的內存片斷都對應一個獨立的內存區域:棧、對象代碼、全局變數、被映射的文件等等。
內核使用內存描述符表示進程的地址空間。內存描述符由mm_struct結構體表示,定義在文件中,該結構包含了和進程地址空間有關的全部信息。
VMA
內存區域由vm_area_struct結構體描述,定義在文件中,內存區域在內核中也經常被稱作虛擬內存區域或者VMA。
VMA標志是一種位標志,它定義在vm_area_struct結構中(該結構中的vm_flags子域)。和物理頁的訪問許可權不同,VMA標志反映了內核處理頁面索需要遵守的行為准則,而不是硬體要求。VM_IO標志內存區域中包含對設備I/O空間的映射。該標志通常在設備驅動程序執行 mmap()函數進行I/O空間映射時才被設置,同時該標志也表示該內存區域不能被包含在任何進程的存放轉存(core mp)中。VM_RESERVED標志內存區域不能被換出,它也是在設備驅動程序進行映射時被設置。
vm_area_struct結構體中的vm_ops域指向與指定內存區域相關的操作函數表,內核使用表中的方法操作VMA。
mmap()和do_mmap():創建地址區間
內核使用do_mmap()函數創建一個新的線性地址區間。但是說給函數創建一個新VMA並不非常准確,因為如果創建的地址區間和一個已經存在的地址區間相鄰,並且它們具有相同的訪問許可權的話,那麼兩個區間將合並為一個。如果不能合並,那麼就確實需要創建一個新的VMA了。但無論哪種情況,do_mmap()函數都會將一個地址區間加入到進程的地址空間中——無論是擴展已經存在的內存區域還是創建一個新的區域。
do_mmap()函數聲明在文件中,原型如下:
unsigned long do_mmap(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flag, unsigned long offset)
在用戶空間可以通過mmap()函數調用獲取內核函數do_mmap()的功能。mmap()系統調用原型如下:
void *mmap2(void *start, size_t length,
int prot, int flags,
int fd, off_t pgoff)
do_munmap()函數從特定的進程地址空間中刪除指定地址區間,該函數在文件中聲明:
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
系統調用munmap()給用戶空間程序提供了一種從自身地址空間中刪除指定地址區間的方法,它和系統調用mmap()的作用相反:
int munmap(void *start, size_t length)
mmap設備操作
對於驅動程序來說,內存映射可以提供給用戶程序直接訪問設備內存的能力。映射一個設備,意味著使用戶空間的一段地址關聯到設備內存上。無論何時,只要程序在分配的地址范圍內進行讀取或者寫入,實際上就是對設備的訪問。
並不是所有的設備都能進行mmap抽象。例如,串口設備和其他面向流的設備就無法實現這種抽象。mmap的另一個限制是映射都是以 PAGE_SIZE為單位的。內核只能在頁表一級處理虛擬地址;因此,被映射的區域必須是PAGE_SIZE的整數倍,而且必須位於起始於 PAGE_SIZE整數倍地址的物理內存內。如果區域的大小不是頁大小的整數倍,內核就通過生成一個稍微大一些的區域來容納它。
mmap方法是file_operations結構中的一員,並且在執行mmap系統調用時就會調用該方法。在調用實際方法之前,內核會完成很多工作,而且該方法的原型與系統調用的原型由很大區別。關於Linux命令的介紹,看看《linux就該這么學》,具體關於這一章地址3w(dot)linuxprobe/chapter-02(dot)html
文件操作聲明如下:
int (*mmap) (struct file * filp, struct vm_area_struct *vma);
其中vma參數包含了用於訪問設備的虛擬地址區間的信息。大部分工作已經由內核完成了,要實現mmap,驅動程序只要為這一地址范圍構造合適的頁表即可,如果需要的話,就用一個新的操作集替換vma->vm_ops。
有兩種建立頁表的方法:使用remap_page_range函數可一次建立所有的頁表,或者通過nopage VMA方法每次建立一個頁表。
構造用於映射一段物理地址的新頁表的工作是由remap_page_range完
Ⅱ Linux內核黑科技——mmap實現詳解
本文旨在詳細闡述 Linux 內核中的 mmap 實現機制。mmap 的全稱是 memory map,即內存映射,其功能是將文件內容映射到內存中,允許我們直接對映射的內存區域進行讀寫操作,效果等同於直接對文件進行讀寫。
mmap 實現分為兩個關鍵步驟:文件映射和缺頁異常處理。首先,使用 mmap() 系統調用時,內核會通過 do_mmap_pgoff() 函數進行處理,這一過程主要是為進程分配虛擬內存空間,並初始化相關數據結構。文件映射則通過 mmmap_region() 函數完成,該函數負責在 vm_area_struct 結構中登記文件信息,以便後續的內存訪問操作。
在文件映射階段,虛擬內存地址會映射到文件的頁緩存中。當進程試圖訪問映射後的虛擬內存地址時,若該地址對應的內容未被載入到物理內存中,則會導致缺頁異常。這就是我們接下來要介紹的第二步:缺頁異常處理。
當 CPU 觸發缺頁異常時,內核會調用 do_page_fault() 函數來處理這一異常情況。在這一過程中,文件的頁緩存內容會被載入到物理內存中,與虛擬內存地址建立起映射關系。這一機制確保了當進程訪問文件內容時,可以無縫地在物理內存和文件之間進行數據交換,從而實現高效的文件讀寫操作。
綜上所述,mmap 通過將文件內容映射到虛擬內存中,允許我們直接對映射區域進行讀寫操作,而背後的關鍵在於文件的頁緩存與虛擬內存地址之間的動態映射。這一機制是 Linux 內核實現高效文件訪問和管理的重要技術之一。
對於需要深入學習 Linux 內核源碼、內存調優、文件系統、進程管理、設備驅動、網路協議棧等領域的開發者,推薦加入 Linux 內核源碼交流群:【869634926】,群內提供豐富的學習資源,包括精選書籍、視頻資料等,以及價值600的內核資料包,包含視頻教程、電子書、實戰項目及代碼。前50名加入者還將獲得額外贈送的資料。
此外,我們整理了以下精選文章,供對 Linux 內核感興趣的讀者參考: