有什么網(wǎng)站可以兼職做翻譯色盲測試卡
內(nèi)存映射(mmap)是 Linux 內(nèi)核的一個重要機(jī)制,它為程序提供了一種將文件內(nèi)容直接映射到進(jìn)程虛擬地址空間的方式。同時內(nèi)存映射也是虛擬內(nèi)存管理和文件 IO 的重要組成部分。
在 Linux 中,虛擬內(nèi)存管理是基于內(nèi)存映射來實現(xiàn)的。在調(diào)用 mmap 函數(shù)時,會創(chuàng)建一個 vm_area_struct
結(jié)構(gòu)體,該結(jié)構(gòu)體代表了一段連續(xù)的虛擬地址空間,它們會相應(yīng)地映射到一個后備文件或者一個匿名文件的虛擬頁。
一個 vm_area_struct
結(jié)構(gòu)體映射到一組連續(xù)的頁表項,這些頁表項指向物理內(nèi)存中的一頁。這樣就把一個文件和物理內(nèi)存頁相映射起來。當(dāng)進(jìn)程試圖訪問映射到 vm_area_struct
的虛擬地址空間時,如果該空間沒有在內(nèi)存中,則會發(fā)生缺頁異常,內(nèi)核會通過文件系統(tǒng)將文件中對應(yīng)的數(shù)據(jù)讀入內(nèi)存。
內(nèi)存映射的優(yōu)點是可以有效地減少文件 IO 的次數(shù),提高文件讀寫性能。同時內(nèi)存映射還支持多進(jìn)程共享同一個映射,可以節(jié)省內(nèi)存空間,并且方便不同的進(jìn)程之間進(jìn)行通信。
虛擬地址映射的過程涉及到對 vm_area_struct
的匹配以及對頁表項的查找和操作。具體來說,當(dāng)程序訪問一個虛擬地址時,系統(tǒng)會根據(jù)已有的 vm_area_struct
結(jié)構(gòu)來確定這個虛擬地址是否屬于某個區(qū)域。
如果沒有匹配到相應(yīng)的 vm_area_struct
,就會觸發(fā)段錯誤,因為訪問了一個未分配的虛擬地址,這表示程序在訪問一個未經(jīng)內(nèi)核分配的內(nèi)存區(qū)域,是非法的操作。
如果匹配到了相應(yīng)的 vm_area_struct
,系統(tǒng)會根據(jù)虛擬地址和頁表的映射關(guān)系找到對應(yīng)的頁表項PTE。如果 PTE 沒有分配,就會觸發(fā)缺頁異常,此時系統(tǒng)會將相應(yīng)的文件數(shù)據(jù)加載到物理內(nèi)存中;如果 PTE 已經(jīng)分配,就可以直接從對應(yīng)的物理頁的偏移位置讀取數(shù)據(jù)。
在這個過程中,虛擬頁有三種狀態(tài):
- 未分配虛擬頁:指的是沒有使用 mmap 建立相應(yīng)的
vm_area_struct
,因此也就沒有對應(yīng)到具體的頁表項。 - 已分配虛擬頁,未映射到物理頁:表示已經(jīng)使用了 mmap 建立的
vm_area_struct
,虛擬頁可以映射到對應(yīng)的頁表項,但頁表項尚未指向具體的物理頁。 - 已分配虛擬頁,已映射到物理頁:表示已經(jīng)使用了 mmap 建立的
vm_area_struct
,虛擬頁可以映射到對應(yīng)的頁表項,并且頁表項已經(jīng)指向具體的物理頁。
mmap 函數(shù)可以將虛擬地址映射到一個后備文件或者一個匿名文件。當(dāng)操作系統(tǒng)分配物理內(nèi)存時,實際上會利用匿名文件的 mmap 來完成內(nèi)存分配。
mmap和虛擬內(nèi)存管理
用戶進(jìn)程的虛擬內(nèi)存管理是通過Linux內(nèi)核中的mm_struct
結(jié)構(gòu)來表示一個用戶進(jìn)程的虛擬內(nèi)存地址空間。該結(jié)構(gòu)包含了幾個重要的字段來描述不同區(qū)域的地址范圍和屬性。
-
start_code
和end_code
:指定了進(jìn)程的代碼段的起始地址和結(jié)束地址,用于表示可執(zhí)行代碼的邊界。一旦ELF二進(jìn)制文件映射到虛擬內(nèi)存后,這些地址就不會再改變。 -
start_data
和end_data
:指定了進(jìn)程數(shù)據(jù)段的起始地址和結(jié)束地址,用于表示已初始化數(shù)據(jù)的邊界。類似于代碼段,這些地址在映射后也不再改變。 -
start_brk
和brk
:指定了堆的起始地址和結(jié)束地址,用于表示動態(tài)分配內(nèi)存的邊界。start_brk
表示堆的初始位置,在進(jìn)程的整個生命周期中保持不變,而brk表示堆的結(jié)束位置,會隨著堆的長度動態(tài)改變。 -
stack_top
:指定了棧的起始位置,一般位于用戶進(jìn)程地址空間的頂部,用于存放函數(shù)調(diào)用的棧幀。 -
task_size
:指定了用戶進(jìn)程地址空間的長度,即用戶空間的頂部邊界。 -
mmap_base
:指定了用戶進(jìn)程虛擬地址空間中用作內(nèi)存映射部分的基地址。通常情況下,它位于用戶地址空間的1/3處,即TASK_SIZE / 3
位置。
這些字段中的地址都是用戶進(jìn)程的虛擬地址,通過虛擬地址和頁表結(jié)構(gòu),用戶進(jìn)程可以訪問內(nèi)存。當(dāng)用戶進(jìn)程訪問一個虛擬地址時,會將該地址轉(zhuǎn)換成對應(yīng)的頁表項索引,然后查找頁表項中保存的物理內(nèi)存頁的頁號,并加上虛擬地址低12位的偏移量,從而確定一個唯一的物理內(nèi)存地址。
如果物理內(nèi)存地址所在的頁已經(jīng)存在,就可以返回該物理地址存放的內(nèi)容。如果不存在,則會觸發(fā)缺頁異常。虛擬內(nèi)存管理采用按需分配和缺頁異常機(jī)制來管理頁表項并分配對應(yīng)的物理內(nèi)存頁。當(dāng)一個虛擬地址對應(yīng)的頁表項不存在時,會先創(chuàng)建頁表結(jié)構(gòu),然后分配物理內(nèi)存頁,并最后修改頁表。
除了mm_struct
結(jié)構(gòu),進(jìn)程的虛擬內(nèi)存管理還涉及到虛擬內(nèi)存區(qū)域的管理,即通過vm_area_struct
結(jié)構(gòu)來管理用戶進(jìn)程的不同虛擬內(nèi)存區(qū)域,如數(shù)據(jù)段、文本段和共享庫等。這些區(qū)域通過vm_area_struct
結(jié)構(gòu)進(jìn)行管理和映射。
vm_area_struct
struct vm_area_struct {struct mm_struct *vm_mm; /* 所屬進(jìn)程的內(nèi)存描述符 */unsigned long vm_start; /* 區(qū)域起始地址 */unsigned long vm_end; /* 區(qū)域結(jié)束地址 */struct vm_area_struct *vm_next, *vm_prev; /* 雙向鏈表指針 */pgprot_t vm_page_prot; /* 頁保護(hù)標(biāo)志 */unsigned long vm_flags; /* VMA標(biāo)志位,如映射類型等 */struct rb_node rb;union {struct {unsigned long shared_vm; /* 共享區(qū)域大小 */} anon_vma;struct vm_userfaultfd_ctx *userfaultfd_ctx;spinlock_t lock; /* 文件鎖 */struct list_head list; /* 指向共享VMA列表項 */ };#ifdef CONFIG_MMU_NOTIFIERstruct mmu_notifier_mm *mmu_notifier_mm;
#endif#ifdef CONFIG_NUMA_BALANCING/** Virtual memory areas in a shared-memory area. Protected by* mmap_sem and guarded by mm->mmap_lock.** WARNING: Once you add a new member to this group you MUST update* dup_mmap() function!!!** You also have to modify arch_dup_mmap() if your architecture is one* of the architectures which implement that function.** Also, __split_vma() must be taught about how to copy the information.*/unsigned long shared_dirty_pages;unsigned long private_dirty_pages;unsigned long shared_clean_pages;unsigned long private_clean_pages;
#endif /* CONFIG_NUMA_BALANCING */
};
vm_area_struct
是一種用于管理用戶進(jìn)程虛擬內(nèi)存區(qū)域的數(shù)據(jù)結(jié)構(gòu),它可以以兩種不同的組織形式存在。
首先是單鏈表形式,包含了所有已創(chuàng)建的vm_area_struct
實例。這種形式使得可以按照順序遍歷和訪問所有的虛擬內(nèi)存區(qū)域。
其次是紅黑樹形式,用于加速對虛擬內(nèi)存區(qū)域的查找。這種組織形式可以通過快速的二分查找來定位特定的虛擬內(nèi)存區(qū)域,提高了訪問效率。
需要注意的是,這兩種組織形式都是針對同一份vm_area_struct實例而言的,只是在不同的數(shù)據(jù)結(jié)構(gòu)中進(jìn)行組織和管理。
在考慮vm_area_struct
和頁表之間的關(guān)系時,我們可以看到,vm_area_struct
本質(zhì)上表示了用戶進(jìn)程的一段虛擬地址空間。而虛擬地址和頁表數(shù)組的索引是一一對應(yīng)的關(guān)系。頁表數(shù)組的最后一級PTE數(shù)組的數(shù)組項存放著物理內(nèi)存頁的頁號,從而建立了虛擬內(nèi)存地址到物理內(nèi)存地址的對應(yīng)關(guān)系。
有一種情況是當(dāng)先有虛擬地址時,通過訪問虛擬地址觸發(fā)缺頁異常,然后加載相應(yīng)的物理內(nèi)存頁,并更新頁表,以建立起虛擬地址、頁表和物理內(nèi)存之間的聯(lián)系。
另一種情況是在進(jìn)行內(nèi)存映射(mmap)時,首先從設(shè)備加載文件數(shù)據(jù),建立address_space
和頁緩存(物理內(nèi)存),然后創(chuàng)建vm_area_struct
結(jié)構(gòu),更新頁表,并返回相應(yīng)的虛擬地址。這樣就實現(xiàn)了從設(shè)備加載文件到建立虛擬地址、頁表和物理內(nèi)存之間聯(lián)系的過程。
vm_area_struct
是用于描述用戶進(jìn)程虛擬內(nèi)存區(qū)域的結(jié)構(gòu)體:
-
vm_start
和vm_end
:表示區(qū)域的起始位置和結(jié)束位置,用于確定區(qū)域的邊界。這兩個字段確保了不同的vm_area_struct
之間不會出現(xiàn)交叉的情況,從而清晰地劃分了各個虛擬內(nèi)存區(qū)域。 -
vm_page_prot
:表示了該區(qū)域的頁的訪問權(quán)限,包括讀、寫、執(zhí)行等。這些權(quán)限信息將影響到用戶進(jìn)程對該區(qū)域的訪問行為。 -
shared
:處理具有后備文件的內(nèi)存映射。它將該區(qū)域與后備文件的address_space
地址空間進(jìn)行關(guān)聯(lián),以便在需要時能夠正確地讀取和寫入數(shù)據(jù)。 -
anon_vma_node
和anon_vma
:處理匿名文件共享內(nèi)存映射的情況。當(dāng)多個虛擬內(nèi)存區(qū)域映射到同一物理內(nèi)存頁時,這些映射將保存在一個鏈表中,并由anon_vma_node
進(jìn)行管理,確保它們之間的正確關(guān)聯(lián)。 -
vm_pgoff
和vm_file
:處理具有后備文件的內(nèi)存映射的情況。vm_pgoff
表示了該映射在文件中的頁偏移量,而vm_file
則包含了打開文件file實例的相關(guān)信息,以便在需要時能夠正確地定位和操作對應(yīng)的文件數(shù)據(jù)。
這些字段的信息使得vm_area_struct
能夠全面描述用戶進(jìn)程的虛擬內(nèi)存區(qū)域,包括區(qū)域的邊界、訪問權(quán)限、關(guān)聯(lián)文件信息以及共享情況,為內(nèi)核提供了管理和操作虛擬內(nèi)存的重要依據(jù)。
對于有后備文件的映射,內(nèi)核利用優(yōu)先查找樹結(jié)構(gòu)來加速確定一個文件和所有映射到這個文件的虛擬內(nèi)存區(qū)域vm_area_struct
實例的關(guān)系,從而可以方便地獲取所有映射到這個文件的進(jìn)程信息。這種優(yōu)先查找樹結(jié)構(gòu)能夠高效地管理文件和映射關(guān)系,提高查找效率和操作性能。
同時,內(nèi)核提供了一系列函數(shù)用于對虛擬內(nèi)存區(qū)域vm_area_struct
進(jìn)行操作,包括創(chuàng)建、刪除、合并、查找等功能。這些函數(shù)可以幫助內(nèi)核有效地管理用戶進(jìn)程的虛擬內(nèi)存區(qū)域,確保內(nèi)存映射的正確性和一致性。
另外,mmap是C標(biāo)準(zhǔn)庫提供給用戶程序的函數(shù),用于通過內(nèi)存映射建立文件地址空間和虛擬內(nèi)存區(qū)域的映射關(guān)系。通過mmap函數(shù),用戶程序可以將文件映射到自身的虛擬內(nèi)存空間中,實現(xiàn)了方便的文件訪問和操作。這種映射關(guān)系的建立是通過內(nèi)核提供的相關(guān)功能實現(xiàn)的,確保了對文件數(shù)據(jù)的高效管理和訪問。
mmap的4種類型
mmap函數(shù)在Linux系統(tǒng)中用于創(chuàng)建內(nèi)存映射,可以分為有后備文件的映射和匿名文件的映射,每種映射又有私有映射和共享映射之分,因此mmap可以創(chuàng)建4種類型的映射。
-
有后備文件的共享映射:多個進(jìn)程的
vm_area_struct
指向同一個物理內(nèi)存區(qū)域,一個進(jìn)程對文件內(nèi)容的修改會被其他進(jìn)程看到,并且這些修改會被寫回到后備文件中。 -
有后備文件的私有映射:多個進(jìn)程的
vm_area_struct
指向同一個物理內(nèi)存區(qū)域,但采用寫時拷貝的方式。當(dāng)一個進(jìn)程對文件內(nèi)容做修改,不會被其他進(jìn)程看到,并且對文件的修改也不會被寫回到后備文件。當(dāng)內(nèi)存不足時,私有映射的頁被交換到交換區(qū)。這種映射常用于加載共享代碼庫。 -
匿名文件的共享映射:內(nèi)核創(chuàng)建一個初始為0的物理內(nèi)存區(qū)域,然后多個進(jìn)程的
vm_area_struct
指向這個共享的物理內(nèi)存區(qū)域。對該區(qū)域內(nèi)容的修改對所有進(jìn)程可見,而且在頁回收時被交換到交換區(qū)。 -
匿名文件的私有映射:內(nèi)核創(chuàng)建一個初始為0的物理內(nèi)存區(qū)域,對該區(qū)域內(nèi)容的修改只對創(chuàng)建者進(jìn)程可見。在頁回收時,這種映射也會被交換到交換區(qū)。
malloc()
函數(shù)底層使用了匿名文件的私有映射來分配大塊內(nèi)存。
這些不同類型的映射提供了靈活的內(nèi)存管理方式,使得進(jìn)程可以根據(jù)需要選擇適合的映射方式來處理內(nèi)存數(shù)據(jù),并且能夠滿足不同場景下的內(nèi)存管理需求。
內(nèi)核對堆空間的管理
從內(nèi)核管理用戶進(jìn)程虛擬地址空間的角度來看,內(nèi)存映射是主要的手段,通過建立vm_area_struct
結(jié)構(gòu)來分配虛擬內(nèi)存區(qū)域。對于堆空間的分配,主要通過brk
系統(tǒng)調(diào)用來實現(xiàn)。brk
系統(tǒng)調(diào)用本質(zhì)上也是利用了匿名文件的私有映射機(jī)制,它分配并初始化為0的物理內(nèi)存頁,然后建立相應(yīng)的vm_area_struct
,最后更新頁表結(jié)構(gòu)。
brk
系統(tǒng)調(diào)用分配的內(nèi)存最小單位是頁,需要按頁對齊。在內(nèi)核的視角下,每次對堆空間的分配至少是一頁大小,即以頁面為單位進(jìn)行擴(kuò)展。換句話說,更細(xì)粒度的字節(jié)級內(nèi)存分配是由C語言標(biāo)準(zhǔn)庫實現(xiàn)的,而在內(nèi)核層面,堆空間的分配是以頁面為單位進(jìn)行管理的。這種設(shè)計確保了內(nèi)核對虛擬內(nèi)存的有效管理,并提供了一種簡單且高效的方式來處理用戶進(jìn)程的內(nèi)存分配需求。
參考:Linux內(nèi)核源碼分析(內(nèi)存調(diào)優(yōu)/文件系統(tǒng)/進(jìn)程管理/設(shè)備驅(qū)動/網(wǎng)絡(luò)協(xié)議棧)教程
Linux內(nèi)核源碼學(xué)習(xí)