W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎勵(lì)
邏輯和內(nèi)核虛擬地址之間的不同在配備大量內(nèi)存的 32-位系統(tǒng)中被突出. 用 32 位, 可能尋址 4 G 內(nèi)存. 但是, 直到最近, 在 32-位 系統(tǒng)的 Linux 被限制比那個(gè)少很多的內(nèi)存, 因?yàn)樗⑻摂M地址的方式.
內(nèi)核( 在 x86 體系上, 在缺省配置里) 在用戶空間和內(nèi)核之間劃分 4-G 虛擬地址; 在 2 個(gè)上下文中使用同一套映射. 一個(gè)典型的劃分分出 3 GB 給用戶空間, 和 1 GB 給內(nèi)核空間. [47]內(nèi)核的代碼和數(shù)據(jù)結(jié)構(gòu)必須要適合這個(gè)空間, 但是內(nèi)核地址空間最大的消費(fèi)者是物理內(nèi)存的虛擬映射. 內(nèi)核不能直接操作沒有映射到內(nèi)核的地址空間. 內(nèi)核, 換句話說, 需要它自己的虛擬地址給任何它必須直接接觸的內(nèi)存. 因此, 多年來, 能夠被內(nèi)核處理的的最大量的物理內(nèi)存是能夠映射到虛擬地址的內(nèi)核部分的數(shù)量, 減去內(nèi)核代碼自身需要的空間. 結(jié)果, 基于 x86 的 Linux 系統(tǒng)可以工作在最多稍小于 1 GB 物理內(nèi)存.
為應(yīng)對更多內(nèi)存的商業(yè)壓力而不破壞 32-位 應(yīng)用和系統(tǒng)的兼容性, 處理器制造商已經(jīng)增加了"地址擴(kuò)展"特性到他們的產(chǎn)品中. 結(jié)果, 在許多情況下, 即便 32-位 處理器也能夠?qū)ぶ范嘤?4GB 物理內(nèi)存. 但是, 多少內(nèi)存可被直接用邏輯地址映射的限制還存在. 這樣內(nèi)存的最低部分(上到 1 或 2 GB, 根據(jù)硬件和內(nèi)核配置)有邏輯地址; 剩下的(高內(nèi)存)沒有. 在存取一個(gè)特定高地址頁之前, 內(nèi)核必須建立一個(gè)明確的虛擬映射來使這個(gè)也在內(nèi)核地址空間可用. 因此, 許多內(nèi)核數(shù)據(jù)結(jié)構(gòu)必須放在低內(nèi)存; 高內(nèi)存用作被保留為用戶進(jìn)程頁.
術(shù)語"高內(nèi)存"對有些人可能是疑惑的, 特別因?yàn)樗?PC 世界里有其他的含義. 因此, 為清晰起見, 我們將定義這些術(shù)語:
Low memory
邏輯地址在內(nèi)核空間中存在的內(nèi)存. 在大部分每個(gè)系統(tǒng)你可能會遇到, 所有的內(nèi)存都是低內(nèi)存.
High memory
邏輯地址不存在的內(nèi)存, 因?yàn)樗跒閮?nèi)核虛擬地址設(shè)置的地址范圍之外.
在 i386 系統(tǒng)上, 低和高內(nèi)存之間的分界常常設(shè)置在剛剛在 1 GB 之下, 盡管那個(gè)邊界在內(nèi)核配置時(shí)可被改變. 這個(gè)邊界和在原始 PC 中有的老的 640 KB 限制沒有任何關(guān)聯(lián), 并且它的位置不是硬件規(guī)定的. 相反, 它是, 內(nèi)核自身設(shè)置的一個(gè)限制當(dāng)它在內(nèi)核和用戶空間之間劃分 32-位地址空間時(shí).
我們將指出使用高內(nèi)存的限制, 隨著我們在本章遇到它們時(shí).
歷史上, 內(nèi)核已使用邏輯地址來引用物理內(nèi)存頁. 高內(nèi)存支持的增加, 但是, 已暴露這個(gè)方法的一個(gè)明顯的問題 -- 邏輯地址對高內(nèi)存不可用. 因此, 處理內(nèi)存的內(nèi)核函數(shù)更多在使用指向 struct page 的指針來代替(在 <linux/mm.h> 中定義). 這個(gè)數(shù)據(jù)結(jié)構(gòu)只是用來跟蹤內(nèi)核需要知道的關(guān)于物理內(nèi)存的所有事情.
2.6 內(nèi)核(帶一個(gè)增加的補(bǔ)丁)可以支持一個(gè) "4G/4G" 模式在 x86 硬件上, 它以微弱的性能代價(jià)換來更大的內(nèi)核和用戶虛擬地址空間.
系統(tǒng)中每一個(gè)物理頁有一個(gè) struct page. 這個(gè)結(jié)構(gòu)的一些成員包括下列:
atomic_t count;
這個(gè)頁的引用數(shù). 當(dāng)這個(gè) count 掉到 0, 這頁被返回給空閑列表.
void *virtual;
這頁的內(nèi)核虛擬地址, 如果它被映射; 否則, NULL. 低內(nèi)存頁一直被映射; 高內(nèi)存頁常常不是. 這個(gè)成員不是在所有體系上出現(xiàn); 它通常只在頁的內(nèi)核虛擬地址無法輕易計(jì)算時(shí)被編譯. 如果你想查看這個(gè)成員, 正確的方法是使用 page_address 宏, 下面描述.
unsigned long flags;
一套描述頁狀態(tài)的一套位標(biāo)志. 這些包括 PG_locked, 它指示該頁在內(nèi)存中已被加鎖, 以及 PG_reserved, 它防止內(nèi)存管理系統(tǒng)使用該頁.
有很多的信息在 struct page 中, 但是它是內(nèi)存管理的更深的黑魔法的一部分并且和驅(qū)動編寫者無關(guān).
內(nèi)核維護(hù)一個(gè)或多個(gè) struct page 項(xiàng)的數(shù)組來跟蹤系統(tǒng)中所有物理內(nèi)存. 在某些系統(tǒng), 有一個(gè)單個(gè)數(shù)組稱為 mem_map. 但是, 在某些系統(tǒng), 情況更加復(fù)雜. 非一致內(nèi)存存取( NUMA )系統(tǒng)和那些有很大不連續(xù)的物理內(nèi)存的可能有多于一個(gè)內(nèi)存映射數(shù)組, 因此打算是可移植的代碼在任何可能時(shí)候應(yīng)當(dāng)避免直接對數(shù)組存取. 幸運(yùn)的是, 只是使用 struct page 指針常常是非常容易, 而不用擔(dān)心它們來自哪里.
有些函數(shù)和宏被定義來在 struct page 指針和虛擬地址之間轉(zhuǎn)換:
struct page virt_to_page(void kaddr);
這個(gè)宏, 定義在 <asm/page.h>, 采用一個(gè)內(nèi)核邏輯地址并返回它的被關(guān)聯(lián)的 struct page 指針. 因?yàn)樗枰粋€(gè)邏輯地址, 它不使用來自 vmalloc 的內(nèi)存或者高內(nèi)存.
struct page *pfn_to_page(int pfn);
為給定的頁幀號返回 struct page 指針. 如果需要, 它在傳遞給 pfn_to_page 之前使用 pfn_valid 來檢查一個(gè)頁幀號的有效性.
void page_address(struct page page);
返回這個(gè)頁的內(nèi)核虛擬地址, 如果這樣一個(gè)地址存在. 對于高內(nèi)存, 那個(gè)地址僅當(dāng)這個(gè)頁已被映射才存在. 這個(gè)函數(shù)在 <linux/mm.h> 中定義. 大部分情況下, 你想使用 kmap 的一個(gè)版本而不是 page_address.
kmap 為系統(tǒng)中的任何頁返回一個(gè)內(nèi)核虛擬地址. 對于低內(nèi)存頁, 它只返回頁的邏輯地址; 對于高內(nèi)存, kmap 在內(nèi)核地址空間的一個(gè)專用部分中創(chuàng)建一個(gè)特殊的映射. 使用 kmap 創(chuàng)建的映射應(yīng)當(dāng)一直使用 kunmap 來釋放;一個(gè)有限數(shù)目的這樣的映射可用, 因此最好不要在它們上停留太長時(shí)間. kmap 調(diào)用維護(hù)一個(gè)計(jì)數(shù)器, 因此如果 2 個(gè)或 多個(gè)函數(shù)都在同一個(gè)頁上調(diào)用 kmap, 正確的事情發(fā)生了. 還要注意 kmap 可能睡眠當(dāng)沒有映射可用時(shí).
kmap_atomic 是 kmap 的一種高性能形式. 每個(gè)體系都給原子的 kmaps 維護(hù)一小列插口( 專用的頁表項(xiàng)); 一個(gè) kmap_atomic 的調(diào)用者必須在 type 參數(shù)中告知系統(tǒng)使用這些插口中的哪個(gè). 對驅(qū)動有意義的唯一插口是 KM_USER0 和 KM_USER1 (對于直接從來自用戶空間的調(diào)用運(yùn)行的代碼), 以及 KM_IRQ0 和 KM_IRQ1(對于中斷處理). 注意原子的 kmaps 必須被原子地處理; 你的代碼不能在持有一個(gè)時(shí)睡眠. 還要注意內(nèi)核中沒有什么可以阻止 2 個(gè)函數(shù)試圖使用同一個(gè)插口并且相互干擾( 盡管每個(gè) CPU 有獨(dú)特的一套插口). 實(shí)際上, 對原子的 kmap 插口的競爭看來不是個(gè)問題.
在本章后面和后續(xù)章節(jié)中當(dāng)我們進(jìn)入例子代碼時(shí), 我們看到這些函數(shù)的一些使用,
在任何現(xiàn)代系統(tǒng)上, 處理器必須有一個(gè)機(jī)制來轉(zhuǎn)換虛擬地址到它的對應(yīng)物理地址. 這個(gè)機(jī)制被稱為一個(gè)頁表; 它本質(zhì)上是一個(gè)多級樹型結(jié)構(gòu)數(shù)組, 包含了虛擬-到-物理的映射和幾個(gè)關(guān)聯(lián)的標(biāo)志. Linux 內(nèi)核維護(hù)一套頁表即便在沒有直接使用這樣頁表的體系上.
設(shè)備驅(qū)動通??梢宰龅脑S多操作能涉及操作頁表. 幸運(yùn)的是對于驅(qū)動作者, 2.6 內(nèi)核已經(jīng)去掉了任何直接使用頁表的需要. 結(jié)果是, 我們不描述它們的任何細(xì)節(jié); 好奇的讀者可能想讀一下 Understanding The Linux Kernel 來了解完整的內(nèi)容, 作者是 Daniel P. Bovet 和 Marco Cesati (O' Reilly).
虛擬內(nèi)存區(qū)( VMA )用來管理一個(gè)進(jìn)程的地址空間的獨(dú)特區(qū)域的內(nèi)核數(shù)據(jù)結(jié)構(gòu). 一個(gè) VMA 代表一個(gè)進(jìn)程的虛擬內(nèi)存的一個(gè)同質(zhì)區(qū)域: 一個(gè)有相同許可標(biāo)志和被相同對象(如, 一個(gè)文件或者交換空間)支持的連續(xù)虛擬地址范圍. 它松散地對應(yīng)于一個(gè)"段"的概念, 盡管可以更好地描述為"一個(gè)有它自己特性的內(nèi)存對象". 一個(gè)進(jìn)程的內(nèi)存映射有下列區(qū)組成:
給程序的可執(zhí)行代碼(常常稱為 text)的一個(gè)區(qū).
給數(shù)據(jù)的多個(gè)區(qū), 包括初始化的數(shù)據(jù)(它有一個(gè)明確的被分配的值, 在執(zhí)行開始), 未初始化數(shù)據(jù)(BBS), [48]以及程序堆棧.
給每個(gè)激活的內(nèi)存映射的一個(gè)區(qū)域.
一個(gè)進(jìn)程的內(nèi)存區(qū)可看到通過 /proc/<pid/maps>(這里 pid, 當(dāng)然, 用一個(gè)進(jìn)程的 ID 來替換). /proc/self 是一個(gè) /proc/id 的特殊情況, 因?yàn)樗3V府?dāng)前進(jìn)程. 作為一個(gè)例子, 這里是幾個(gè)內(nèi)存映射(我們添加了簡短注釋)
# cat /proc/1/maps look at init
08048000-0804e000 r-xp 00000000 03:01 64652
0804e000-0804f000 rw-p 00006000 03:01 64652
0804f000-08053000 rwxp 00000000 00:00 0
40000000-40015000 r-xp 00000000 03:01 96278
40015000-40016000 rw-p 00014000 03:01 96278
40016000-40017000 rw-p 00000000 00:00 0
42000000-4212e000 r-xp 00000000 03:01 80290
4212e000-42131000 rw-p 0012e000 03:01 80290
42131000-42133000 rw-p 00000000 00:00 0
bffff000-c0000000 rwxp 00000000 00:00 0
ffffe000-fffff000 ---p 00000000 00:00 0
/sbin/init text /sbin/init data zero-mapped BSS /lib/ld-2.3.2.so text /lib/ld-2.3.2.so data BSS for ld.so /lib/tls/libc-2.3.2.so text /lib/tls/libc-2.3.2.so data BSS for libc Stack segment vsyscall page
# rsh wolf cat /proc/self/maps #### x86-64 (trimmed)
00400000-00405000 r-xp 00000000 03:01 1596291 /bin/cat text
00504000-00505000 rw-p 00004000 03:01 1596291 /bin/cat data
00505000-00526000 rwxp 00505000 00:00 0 bss
3252200000-3252214000 r-xp 00000000 03:01 1237890 /lib64/ld-2.3.3.so
3252300000-3252301000 r--p 00100000 03:01 1237890 /lib64/ld-2.3.3.so
3252301000-3252302000 rw-p 00101000 03:01 1237890 /lib64/ld-2.3.3.so
7fbfffe000-7fc0000000 rw-p 7fbfffe000 00:00 0 stack
ffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0 vsyscall
每行的字段是:
start-end perm offset major:minor inode image
每個(gè)在 /proc/*/maps (出來映象的名子) 對應(yīng) struct vm_area_struct 中的一個(gè)成員:
start end
這個(gè)內(nèi)存區(qū)的開始和結(jié)束虛擬地址.
perm
帶有內(nèi)存區(qū)的讀,寫和執(zhí)行許可的位掩碼. 這個(gè)成員描述進(jìn)程可以對屬于這個(gè)區(qū)的頁做什么. 成員的最后一個(gè)字符要么是給"私有"的 p 要么是給"共享"的 s.
offset
內(nèi)存區(qū)在它被映射到的文件中的起始位置. 0 偏移意味著內(nèi)存區(qū)開始對應(yīng)文件的開始.
major minor
持有已被映射文件的設(shè)備的主次編號. 易混淆地, 對于設(shè)備映射, 主次編號指的是持有被用戶打開的設(shè)備特殊文件的磁盤分區(qū), 不是設(shè)備自身.
inode
被映射文件的 inode 號.
image
已被映射的文件名((常常在一個(gè)可執(zhí)行映象中).
當(dāng)一個(gè)用戶空間進(jìn)程調(diào)用 mmap 來映射設(shè)備內(nèi)存到它的地址空間, 系統(tǒng)通過一個(gè)新 VMA 代表那個(gè)映射來響應(yīng). 一個(gè)支持 mmap 的驅(qū)動(并且, 因此, 實(shí)現(xiàn) mmap 方法)需要來幫助那個(gè)進(jìn)程來完成那個(gè) VMA 的初始化. 驅(qū)動編寫者應(yīng)當(dāng), 因此, 為支持 mmap 應(yīng)至少有對 VMA 的最少的理解.
讓我們看再 struct vm_area_struct 中最重要的成員( 在 <linux/mm.h> 中定義). 這些成員應(yīng)當(dāng)被設(shè)備驅(qū)動在它們的 mmap 實(shí)現(xiàn)中使用. 注意內(nèi)核維護(hù) VMA 的鏈表和樹來優(yōu)化區(qū)查找, 并且 vm_area_struct 的幾個(gè)成員被用來維護(hù)這個(gè)組織. 因此, VMA 不是有一個(gè)驅(qū)動任意創(chuàng)建的, 否則這個(gè)結(jié)構(gòu)破壞了. VMA 的主要成員是下面(注意在這些成員和我們剛看到的 /proc 輸出之間的相似)
unsigned long vm_start;unsigned long vm_end;
被這個(gè) VMA 覆蓋的虛擬地址范圍. 這些成員是在 /proc/*/maps中出現(xiàn)的頭 2 個(gè)字段.
struct file *vm_file;
一個(gè)指向和這個(gè)區(qū)(如果有一個(gè))關(guān)聯(lián)的 struct file 結(jié)構(gòu)的指針.
unsigned long vm_pgoff;
文件中區(qū)的偏移, 以頁計(jì). 當(dāng)一個(gè)文件和設(shè)備被映射, 這是映射在這個(gè)區(qū)的第一頁的文件位置.
unsigned long vm_flags;
描述這個(gè)區(qū)的一套標(biāo)志. 對設(shè)備驅(qū)動編寫者最感興趣的標(biāo)志是 VM_IO 和 VM_RESERVUED. VM_IO 標(biāo)志一個(gè) VMA 作為內(nèi)存映射的 I/O 區(qū). 在其他方面, VM_IO 標(biāo)志阻止這個(gè)區(qū)被包含在進(jìn)程核轉(zhuǎn)儲中. VM_RESERVED 告知內(nèi)存管理系統(tǒng)不要試圖交換出這個(gè) VMA; 它應(yīng)當(dāng)在大部分設(shè)備映射中設(shè)置.
struct vm_operations_struct *vm_ops;
一套函數(shù), 內(nèi)核可能會調(diào)用來在這個(gè)內(nèi)存區(qū)上操作. 它的存在指示內(nèi)存區(qū)是一個(gè)內(nèi)核"對象", 象我們已經(jīng)在全書中使用的 struct file.
void *vm_private_data;
驅(qū)動可以用來存儲它的自身信息的成員.
象 struct vm_area_struct, vm_operations_struct 定義于 <linux/mm.h>; 它包括下面列出的操作. 這些操作是唯一需要來處理進(jìn)程的內(nèi)存需要的, 它們以被聲明的順序列出. 本章后面, 一些這些函數(shù)被實(shí)現(xiàn).
void (open)(struct vm_area_struct vma);
open 方法被內(nèi)核調(diào)用來允許實(shí)現(xiàn) VMA 的子系統(tǒng)來初始化這個(gè)區(qū). 這個(gè)方法被調(diào)用在任何時(shí)候有一個(gè)新的引用這個(gè) VMA( 當(dāng)生成一個(gè)新進(jìn)程, 例如). 一個(gè)例外是當(dāng)這個(gè) VMA 第一次被 mmap 創(chuàng)建時(shí); 在這個(gè)情況下, 驅(qū)動的 mmap 方法被調(diào)用來替代.
void (close)(struct vm_area_struct vma);
當(dāng)一個(gè)區(qū)被銷毀, 內(nèi)核調(diào)用它的關(guān)閉操作. 注意沒有使用計(jì)數(shù)關(guān)聯(lián)到 VMA; 這個(gè)區(qū)只被使用它的每個(gè)進(jìn)程打開和關(guān)閉一次.
struct page (nopage)(struct vm_area_struct vma, unsigned long address, int type);
當(dāng)一個(gè)進(jìn)程試圖存取使用一個(gè)有效 VMA 的頁, 但是它當(dāng)前不在內(nèi)存中, nopage 方法被調(diào)用(如果它被定義)給相關(guān)的區(qū). 這個(gè)方法返回 struct page 指針給物理頁, 也許在從第 2 級存儲中讀取它之后. 如果 nopage 方法沒有為這個(gè)區(qū)定義, 一個(gè)空頁由內(nèi)核分配.
int (populate)(struct vm_area_struct vm, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock);
這個(gè)方法允許內(nèi)核"預(yù)錯(cuò)"頁到內(nèi)存, 在它們被用戶空間存取之前. 對于驅(qū)動通常沒有必要來實(shí)現(xiàn)這個(gè)填充方法.
內(nèi)存管理難題的最后部分是進(jìn)程內(nèi)存映射結(jié)構(gòu), 它保持所有其他數(shù)據(jù)結(jié)構(gòu)在一起. 每個(gè)系統(tǒng)中的進(jìn)程(除了幾個(gè)內(nèi)核空間幫助線程)有一個(gè) struct mm_struct ( 定義在 <linux/sched.h>), 它含有進(jìn)程的虛擬內(nèi)存區(qū)列表, 頁表, 和各種其他的內(nèi)存管理管理信息, 包括一個(gè)旗標(biāo)( mmap_sem )和一個(gè)自旋鎖( page_table_lock ). 這個(gè)結(jié)構(gòu)的指針在任務(wù)結(jié)構(gòu)中; 在很少的驅(qū)動需要存取它的情況下, 通常的方法是使用 current->mm. 注意內(nèi)存關(guān)聯(lián)結(jié)構(gòu)可在進(jìn)程之間共享; Linux 線程的實(shí)現(xiàn)以這種方式工作, 例如.
這總結(jié)了我們對 Linux 內(nèi)存管理數(shù)據(jù)結(jié)構(gòu)的總體. 有了這些, 我們現(xiàn)在可以繼續(xù) mmap 系統(tǒng)調(diào)用的實(shí)現(xiàn).
[47] 許多非-x86體系可以有效工作在沒有這里描述的內(nèi)核/用戶空間的劃分, 因此它們可以在 32-位系統(tǒng)使用直到 4-GB 內(nèi)核地址空間. 但是, 本節(jié)描述的限制仍然適用這樣的系統(tǒng)當(dāng)安裝有多于 4GB 內(nèi)存時(shí).
[48] BSS 的名子是來自一個(gè)老的匯編操作符的歷史遺物, 意思是"由符號開始的塊". 可執(zhí)行文件的 BSS 段不存儲在磁盤上, 并且內(nèi)核映射零頁到 BSS 地址范圍.
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: