国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

15.1. Linux 中的內(nèi)存管理

2018-02-24 15:50 更新

15.1.3.?高和低內(nèi)存

邏輯和內(nèi)核虛擬地址之間的不同在配備大量內(nèi)存的 32-位系統(tǒng)中被突出. 用 32 位, 可能尋址 4 G 內(nèi)存. 但是, 直到最近, 在 32-位 系統(tǒng)的 Linux 被限制比那個少很多的內(nèi)存, 因為它建立虛擬地址的方式.

內(nèi)核( 在 x86 體系上, 在缺省配置里) 在用戶空間和內(nèi)核之間劃分 4-G 虛擬地址; 在 2 個上下文中使用同一套映射. 一個典型的劃分分出 3 GB 給用戶空間, 和 1 GB 給內(nèi)核空間. [47]內(nèi)核的代碼和數(shù)據(jù)結構必須要適合這個空間, 但是內(nèi)核地址空間最大的消費者是物理內(nèi)存的虛擬映射. 內(nèi)核不能直接操作沒有映射到內(nèi)核的地址空間. 內(nèi)核, 換句話說, 需要它自己的虛擬地址給任何它必須直接接觸的內(nèi)存. 因此, 多年來, 能夠被內(nèi)核處理的的最大量的物理內(nèi)存是能夠映射到虛擬地址的內(nèi)核部分的數(shù)量, 減去內(nèi)核代碼自身需要的空間. 結果, 基于 x86 的 Linux 系統(tǒng)可以工作在最多稍小于 1 GB 物理內(nèi)存.

為應對更多內(nèi)存的商業(yè)壓力而不破壞 32-位 應用和系統(tǒng)的兼容性, 處理器制造商已經(jīng)增加了"地址擴展"特性到他們的產(chǎn)品中. 結果, 在許多情況下, 即便 32-位 處理器也能夠尋址多于 4GB 物理內(nèi)存. 但是, 多少內(nèi)存可被直接用邏輯地址映射的限制還存在. 這樣內(nèi)存的最低部分(上到 1 或 2 GB, 根據(jù)硬件和內(nèi)核配置)有邏輯地址; 剩下的(高內(nèi)存)沒有. 在存取一個特定高地址頁之前, 內(nèi)核必須建立一個明確的虛擬映射來使這個也在內(nèi)核地址空間可用. 因此, 許多內(nèi)核數(shù)據(jù)結構必須放在低內(nèi)存; 高內(nèi)存用作被保留為用戶進程頁.

術語"高內(nèi)存"對有些人可能是疑惑的, 特別因為它在 PC 世界里有其他的含義. 因此, 為清晰起見, 我們將定義這些術語:

Low memory
邏輯地址在內(nèi)核空間中存在的內(nèi)存. 在大部分每個系統(tǒng)你可能會遇到, 所有的內(nèi)存都是低內(nèi)存.

High memory
邏輯地址不存在的內(nèi)存, 因為它在為內(nèi)核虛擬地址設置的地址范圍之外.

在 i386 系統(tǒng)上, 低和高內(nèi)存之間的分界常常設置在剛剛在 1 GB 之下, 盡管那個邊界在內(nèi)核配置時可被改變. 這個邊界和在原始 PC 中有的老的 640 KB 限制沒有任何關聯(lián), 并且它的位置不是硬件規(guī)定的. 相反, 它是, 內(nèi)核自身設置的一個限制當它在內(nèi)核和用戶空間之間劃分 32-位地址空間時.

我們將指出使用高內(nèi)存的限制, 隨著我們在本章遇到它們時.

15.1.4.?內(nèi)存映射和 struct page

歷史上, 內(nèi)核已使用邏輯地址來引用物理內(nèi)存頁. 高內(nèi)存支持的增加, 但是, 已暴露這個方法的一個明顯的問題 -- 邏輯地址對高內(nèi)存不可用. 因此, 處理內(nèi)存的內(nèi)核函數(shù)更多在使用指向 struct page 的指針來代替(在 <linux/mm.h> 中定義). 這個數(shù)據(jù)結構只是用來跟蹤內(nèi)核需要知道的關于物理內(nèi)存的所有事情.

2.6 內(nèi)核(帶一個增加的補丁)可以支持一個 "4G/4G" 模式在 x86 硬件上, 它以微弱的性能代價換來更大的內(nèi)核和用戶虛擬地址空間.

系統(tǒng)中每一個物理頁有一個 struct page. 這個結構的一些成員包括下列:

atomic_t count;
這個頁的引用數(shù). 當這個 count 掉到 0, 這頁被返回給空閑列表.

void *virtual;
這頁的內(nèi)核虛擬地址, 如果它被映射; 否則, NULL. 低內(nèi)存頁一直被映射; 高內(nèi)存頁常常不是. 這個成員不是在所有體系上出現(xiàn); 它通常只在頁的內(nèi)核虛擬地址無法輕易計算時被編譯. 如果你想查看這個成員, 正確的方法是使用 page_address 宏, 下面描述.

unsigned long flags;
一套描述頁狀態(tài)的一套位標志. 這些包括 PG_locked, 它指示該頁在內(nèi)存中已被加鎖, 以及 PG_reserved, 它防止內(nèi)存管理系統(tǒng)使用該頁.

有很多的信息在 struct page 中, 但是它是內(nèi)存管理的更深的黑魔法的一部分并且和驅動編寫者無關.

內(nèi)核維護一個或多個 struct page 項的數(shù)組來跟蹤系統(tǒng)中所有物理內(nèi)存. 在某些系統(tǒng), 有一個單個數(shù)組稱為 mem_map. 但是, 在某些系統(tǒng), 情況更加復雜. 非一致內(nèi)存存取( NUMA )系統(tǒng)和那些有很大不連續(xù)的物理內(nèi)存的可能有多于一個內(nèi)存映射數(shù)組, 因此打算是可移植的代碼在任何可能時候應當避免直接對數(shù)組存取. 幸運的是, 只是使用 struct page 指針常常是非常容易, 而不用擔心它們來自哪里.

有些函數(shù)和宏被定義來在 struct page 指針和虛擬地址之間轉換:

struct page virt_to_page(void kaddr);
這個宏, 定義在 <asm/page.h>, 采用一個內(nèi)核邏輯地址并返回它的被關聯(lián)的 struct page 指針. 因為它需要一個邏輯地址, 它不使用來自 vmalloc 的內(nèi)存或者高內(nèi)存.

struct page *pfn_to_page(int pfn);
為給定的頁幀號返回 struct page 指針. 如果需要, 它在傳遞給 pfn_to_page 之前使用 pfn_valid 來檢查一個頁幀號的有效性.

void page_address(struct page page);
返回這個頁的內(nèi)核虛擬地址, 如果這樣一個地址存在. 對于高內(nèi)存, 那個地址僅當這個頁已被映射才存在. 這個函數(shù)在 <linux/mm.h> 中定義. 大部分情況下, 你想使用 kmap 的一個版本而不是 page_address.

include <linux/highmem.h>void kmap(struct page page);void kunmap(struct page *page);

kmap 為系統(tǒng)中的任何頁返回一個內(nèi)核虛擬地址. 對于低內(nèi)存頁, 它只返回頁的邏輯地址; 對于高內(nèi)存, kmap 在內(nèi)核地址空間的一個專用部分中創(chuàng)建一個特殊的映射. 使用 kmap 創(chuàng)建的映射應當一直使用 kunmap 來釋放;一個有限數(shù)目的這樣的映射可用, 因此最好不要在它們上停留太長時間. kmap 調(diào)用維護一個計數(shù)器, 因此如果 2 個或 多個函數(shù)都在同一個頁上調(diào)用 kmap, 正確的事情發(fā)生了. 還要注意 kmap 可能睡眠當沒有映射可用時.

include <linux/highmem.h>#include <asm/kmap_types.h>void kmap_atomic(struct page page, enum km_type type);void kunmap_atomic(void *addr, enum km_type type);

kmap_atomic 是 kmap 的一種高性能形式. 每個體系都給原子的 kmaps 維護一小列插口( 專用的頁表項); 一個 kmap_atomic 的調(diào)用者必須在 type 參數(shù)中告知系統(tǒng)使用這些插口中的哪個. 對驅動有意義的唯一插口是 KM_USER0 和 KM_USER1 (對于直接從來自用戶空間的調(diào)用運行的代碼), 以及 KM_IRQ0 和 KM_IRQ1(對于中斷處理). 注意原子的 kmaps 必須被原子地處理; 你的代碼不能在持有一個時睡眠. 還要注意內(nèi)核中沒有什么可以阻止 2 個函數(shù)試圖使用同一個插口并且相互干擾( 盡管每個 CPU 有獨特的一套插口). 實際上, 對原子的 kmap 插口的競爭看來不是個問題.

在本章后面和后續(xù)章節(jié)中當我們進入例子代碼時, 我們看到這些函數(shù)的一些使用,

15.1.5.?頁表

在任何現(xiàn)代系統(tǒng)上, 處理器必須有一個機制來轉換虛擬地址到它的對應物理地址. 這個機制被稱為一個頁表; 它本質上是一個多級樹型結構數(shù)組, 包含了虛擬-到-物理的映射和幾個關聯(lián)的標志. Linux 內(nèi)核維護一套頁表即便在沒有直接使用這樣頁表的體系上.

設備驅動通常可以做的許多操作能涉及操作頁表. 幸運的是對于驅動作者, 2.6 內(nèi)核已經(jīng)去掉了任何直接使用頁表的需要. 結果是, 我們不描述它們的任何細節(jié); 好奇的讀者可能想讀一下 Understanding The Linux Kernel 來了解完整的內(nèi)容, 作者是 Daniel P. Bovet 和 Marco Cesati (O' Reilly).

15.1.6.?虛擬內(nèi)存區(qū)

虛擬內(nèi)存區(qū)( VMA )用來管理一個進程的地址空間的獨特區(qū)域的內(nèi)核數(shù)據(jù)結構. 一個 VMA 代表一個進程的虛擬內(nèi)存的一個同質區(qū)域: 一個有相同許可標志和被相同對象(如, 一個文件或者交換空間)支持的連續(xù)虛擬地址范圍. 它松散地對應于一個"段"的概念, 盡管可以更好地描述為"一個有它自己特性的內(nèi)存對象". 一個進程的內(nèi)存映射有下列區(qū)組成:

  • 給程序的可執(zhí)行代碼(常常稱為 text)的一個區(qū).

  • 給數(shù)據(jù)的多個區(qū), 包括初始化的數(shù)據(jù)(它有一個明確的被分配的值, 在執(zhí)行開始), 未初始化數(shù)據(jù)(BBS), [48]以及程序堆棧.

  • 給每個激活的內(nèi)存映射的一個區(qū)域.

一個進程的內(nèi)存區(qū)可看到通過 /proc/<pid/maps>(這里 pid, 當然, 用一個進程的 ID 來替換). /proc/self 是一個 /proc/id 的特殊情況, 因為它常常指當前進程. 作為一個例子, 這里是幾個內(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 

每個在 /proc/*/maps (出來映象的名子) 對應 struct vm_area_struct 中的一個成員:

start end
這個內(nèi)存區(qū)的開始和結束虛擬地址.

perm
帶有內(nèi)存區(qū)的讀,寫和執(zhí)行許可的位掩碼. 這個成員描述進程可以對屬于這個區(qū)的頁做什么. 成員的最后一個字符要么是給"私有"的 p 要么是給"共享"的 s.

offset
內(nèi)存區(qū)在它被映射到的文件中的起始位置. 0 偏移意味著內(nèi)存區(qū)開始對應文件的開始.

major minor
持有已被映射文件的設備的主次編號. 易混淆地, 對于設備映射, 主次編號指的是持有被用戶打開的設備特殊文件的磁盤分區(qū), 不是設備自身.

inode
被映射文件的 inode 號.

image
已被映射的文件名((常常在一個可執(zhí)行映象中).

15.1.6.1.?vm_area_struct 結構

當一個用戶空間進程調(diào)用 mmap 來映射設備內(nèi)存到它的地址空間, 系統(tǒng)通過一個新 VMA 代表那個映射來響應. 一個支持 mmap 的驅動(并且, 因此, 實現(xiàn) mmap 方法)需要來幫助那個進程來完成那個 VMA 的初始化. 驅動編寫者應當, 因此, 為支持 mmap 應至少有對 VMA 的最少的理解.

讓我們看再 struct vm_area_struct 中最重要的成員( 在 <linux/mm.h> 中定義). 這些成員應當被設備驅動在它們的 mmap 實現(xiàn)中使用. 注意內(nèi)核維護 VMA 的鏈表和樹來優(yōu)化區(qū)查找, 并且 vm_area_struct 的幾個成員被用來維護這個組織. 因此, VMA 不是有一個驅動任意創(chuàng)建的, 否則這個結構破壞了. VMA 的主要成員是下面(注意在這些成員和我們剛看到的 /proc 輸出之間的相似)

unsigned long vm_start;unsigned long vm_end;
被這個 VMA 覆蓋的虛擬地址范圍. 這些成員是在 /proc/*/maps中出現(xiàn)的頭 2 個字段.

struct file *vm_file;
一個指向和這個區(qū)(如果有一個)關聯(lián)的 struct file 結構的指針.

unsigned long vm_pgoff;
文件中區(qū)的偏移, 以頁計. 當一個文件和設備被映射, 這是映射在這個區(qū)的第一頁的文件位置.

unsigned long vm_flags;
描述這個區(qū)的一套標志. 對設備驅動編寫者最感興趣的標志是 VM_IO 和 VM_RESERVUED. VM_IO 標志一個 VMA 作為內(nèi)存映射的 I/O 區(qū). 在其他方面, VM_IO 標志阻止這個區(qū)被包含在進程核轉儲中. VM_RESERVED 告知內(nèi)存管理系統(tǒng)不要試圖交換出這個 VMA; 它應當在大部分設備映射中設置.

struct vm_operations_struct *vm_ops;
一套函數(shù), 內(nèi)核可能會調(diào)用來在這個內(nèi)存區(qū)上操作. 它的存在指示內(nèi)存區(qū)是一個內(nèi)核"對象", 象我們已經(jīng)在全書中使用的 struct file.

void *vm_private_data;
驅動可以用來存儲它的自身信息的成員.

象 struct vm_area_struct, vm_operations_struct 定義于 <linux/mm.h>; 它包括下面列出的操作. 這些操作是唯一需要來處理進程的內(nèi)存需要的, 它們以被聲明的順序列出. 本章后面, 一些這些函數(shù)被實現(xiàn).

void (open)(struct vm_area_struct vma);
open 方法被內(nèi)核調(diào)用來允許實現(xiàn) VMA 的子系統(tǒng)來初始化這個區(qū). 這個方法被調(diào)用在任何時候有一個新的引用這個 VMA( 當生成一個新進程, 例如). 一個例外是當這個 VMA 第一次被 mmap 創(chuàng)建時; 在這個情況下, 驅動的 mmap 方法被調(diào)用來替代.

void (close)(struct vm_area_struct vma);
當一個區(qū)被銷毀, 內(nèi)核調(diào)用它的關閉操作. 注意沒有使用計數(shù)關聯(lián)到 VMA; 這個區(qū)只被使用它的每個進程打開和關閉一次.

struct page (nopage)(struct vm_area_struct vma, unsigned long address, int type);
當一個進程試圖存取使用一個有效 VMA 的頁, 但是它當前不在內(nèi)存中, nopage 方法被調(diào)用(如果它被定義)給相關的區(qū). 這個方法返回 struct page 指針給物理頁, 也許在從第 2 級存儲中讀取它之后. 如果 nopage 方法沒有為這個區(qū)定義, 一個空頁由內(nèi)核分配.

int (populate)(struct vm_area_struct vm, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock);
這個方法允許內(nèi)核"預錯"頁到內(nèi)存, 在它們被用戶空間存取之前. 對于驅動通常沒有必要來實現(xiàn)這個填充方法.

15.1.7.?進程內(nèi)存映射

內(nèi)存管理難題的最后部分是進程內(nèi)存映射結構, 它保持所有其他數(shù)據(jù)結構在一起. 每個系統(tǒng)中的進程(除了幾個內(nèi)核空間幫助線程)有一個 struct mm_struct ( 定義在 <linux/sched.h>), 它含有進程的虛擬內(nèi)存區(qū)列表, 頁表, 和各種其他的內(nèi)存管理管理信息, 包括一個旗標( mmap_sem )和一個自旋鎖( page_table_lock ). 這個結構的指針在任務結構中; 在很少的驅動需要存取它的情況下, 通常的方法是使用 current->mm. 注意內(nèi)存關聯(lián)結構可在進程之間共享; Linux 線程的實現(xiàn)以這種方式工作, 例如.

這總結了我們對 Linux 內(nèi)存管理數(shù)據(jù)結構的總體. 有了這些, 我們現(xiàn)在可以繼續(xù) mmap 系統(tǒng)調(diào)用的實現(xiàn).

[47] 許多非-x86體系可以有效工作在沒有這里描述的內(nèi)核/用戶空間的劃分, 因此它們可以在 32-位系統(tǒng)使用直到 4-GB 內(nèi)核地址空間. 但是, 本節(jié)描述的限制仍然適用這樣的系統(tǒng)當安裝有多于 4GB 內(nèi)存時.

[48] BSS 的名子是來自一個老的匯編操作符的歷史遺物, 意思是"由符號開始的塊". 可執(zhí)行文件的 BSS 段不存儲在磁盤上, 并且內(nèi)核映射零頁到 BSS 地址范圍.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號