<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                ## 15.1.3.?高和低內存 邏輯和內核虛擬地址之間的不同在配備大量內存的 32-位系統中被突出. 用 32 位, 可能尋址 4 G 內存. 但是, 直到最近, 在 32-位 系統的 Linux 被限制比那個少很多的內存, 因為它建立虛擬地址的方式. 內核( 在 x86 體系上, 在缺省配置里) 在用戶空間和內核之間劃分 4-G 虛擬地址; 在 2 個上下文中使用同一套映射. 一個典型的劃分分出 3 GB 給用戶空間, 和 1 GB 給內核空間. [[47](#)]內核的代碼和數據結構必須要適合這個空間, 但是內核地址空間最大的消費者是物理內存的虛擬映射. 內核不能直接操作沒有映射到內核的地址空間. 內核, 換句話說, 需要它自己的虛擬地址給任何它必須直接接觸的內存. 因此, 多年來, 能夠被內核處理的的最大量的物理內存是能夠映射到虛擬地址的內核部分的數量, 減去內核代碼自身需要的空間. 結果, 基于 x86 的 Linux 系統可以工作在最多稍小于 1 GB 物理內存. 為應對更多內存的商業壓力而不破壞 32-位 應用和系統的兼容性, 處理器制造商已經增加了"地址擴展"特性到他們的產品中. 結果, 在許多情況下, 即便 32-位 處理器也能夠尋址多于 4GB 物理內存. 但是, 多少內存可被直接用邏輯地址映射的限制還存在. 這樣內存的最低部分(上到 1 或 2 GB, 根據硬件和內核配置)有邏輯地址; 剩下的(高內存)沒有. 在存取一個特定高地址頁之前, 內核必須建立一個明確的虛擬映射來使這個也在內核地址空間可用. 因此, 許多內核數據結構必須放在低內存; 高內存用作被保留為用戶進程頁. 術語"高內存"對有些人可能是疑惑的, 特別因為它在 PC 世界里有其他的含義. 因此, 為清晰起見, 我們將定義這些術語: Low memory 邏輯地址在內核空間中存在的內存. 在大部分每個系統你可能會遇到, 所有的內存都是低內存. High memory 邏輯地址不存在的內存, 因為它在為內核虛擬地址設置的地址范圍之外. 在 i386 系統上, 低和高內存之間的分界常常設置在剛剛在 1 GB 之下, 盡管那個邊界在內核配置時可被改變. 這個邊界和在原始 PC 中有的老的 640 KB 限制沒有任何關聯, 并且它的位置不是硬件規定的. 相反, 它是, 內核自身設置的一個限制當它在內核和用戶空間之間劃分 32-位地址空間時. 我們將指出使用高內存的限制, 隨著我們在本章遇到它們時. ### 15.1.4.?內存映射和 struct page 歷史上, 內核已使用邏輯地址來引用物理內存頁. 高內存支持的增加, 但是, 已暴露這個方法的一個明顯的問題 -- 邏輯地址對高內存不可用. 因此, 處理內存的內核函數更多在使用指向 struct page 的指針來代替(在 <linux/mm.h> 中定義). 這個數據結構只是用來跟蹤內核需要知道的關于物理內存的所有事情. 2.6 內核(帶一個增加的補丁)可以支持一個 "4G/4G" 模式在 x86 硬件上, 它以微弱的性能代價換來更大的內核和用戶虛擬地址空間. 系統中每一個物理頁有一個 struct page. 這個結構的一些成員包括下列: atomic_t count; 這個頁的引用數. 當這個 count 掉到 0, 這頁被返回給空閑列表. void *virtual; 這頁的內核虛擬地址, 如果它被映射; 否則, NULL. 低內存頁一直被映射; 高內存頁常常不是. 這個成員不是在所有體系上出現; 它通常只在頁的內核虛擬地址無法輕易計算時被編譯. 如果你想查看這個成員, 正確的方法是使用 page_address 宏, 下面描述. unsigned long flags; 一套描述頁狀態的一套位標志. 這些包括 PG_locked, 它指示該頁在內存中已被加鎖, 以及 PG_reserved, 它防止內存管理系統使用該頁. 有很多的信息在 struct page 中, 但是它是內存管理的更深的黑魔法的一部分并且和驅動編寫者無關. 內核維護一個或多個 struct page 項的數組來跟蹤系統中所有物理內存. 在某些系統, 有一個單個數組稱為 mem_map. 但是, 在某些系統, 情況更加復雜. 非一致內存存取( NUMA )系統和那些有很大不連續的物理內存的可能有多于一個內存映射數組, 因此打算是可移植的代碼在任何可能時候應當避免直接對數組存取. 幸運的是, 只是使用 struct page 指針常常是非常容易, 而不用擔心它們來自哪里. 有些函數和宏被定義來在 struct page 指針和虛擬地址之間轉換: struct page *virt_to_page(void *kaddr); 這個宏, 定義在 <asm/page.h>, 采用一個內核邏輯地址并返回它的被關聯的 struct page 指針. 因為它需要一個邏輯地址, 它不使用來自 vmalloc 的內存或者高內存. struct page *pfn_to_page(int pfn); 為給定的頁幀號返回 struct page 指針. 如果需要, 它在傳遞給 pfn_to_page 之前使用 pfn_valid 來檢查一個頁幀號的有效性. void *page_address(struct page *page); 返回這個頁的內核虛擬地址, 如果這樣一個地址存在. 對于高內存, 那個地址僅當這個頁已被映射才存在. 這個函數在 <linux/mm.h> 中定義. 大部分情況下, 你想使用 kmap 的一個版本而不是 page_address. #include <linux/highmem.h>void *kmap(struct page *page);void kunmap(struct page *page); kmap 為系統中的任何頁返回一個內核虛擬地址. 對于低內存頁, 它只返回頁的邏輯地址; 對于高內存, kmap 在內核地址空間的一個專用部分中創建一個特殊的映射. 使用 kmap 創建的映射應當一直使用 kunmap 來釋放;一個有限數目的這樣的映射可用, 因此最好不要在它們上停留太長時間. kmap 調用維護一個計數器, 因此如果 2 個或 多個函數都在同一個頁上調用 kmap, 正確的事情發生了. 還要注意 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 的調用者必須在 type 參數中告知系統使用這些插口中的哪個. 對驅動有意義的唯一插口是 KM_USER0 和 KM_USER1 (對于直接從來自用戶空間的調用運行的代碼), 以及 KM_IRQ0 和 KM_IRQ1(對于中斷處理). 注意原子的 kmaps 必須被原子地處理; 你的代碼不能在持有一個時睡眠. 還要注意內核中沒有什么可以阻止 2 個函數試圖使用同一個插口并且相互干擾( 盡管每個 CPU 有獨特的一套插口). 實際上, 對原子的 kmap 插口的競爭看來不是個問題. 在本章后面和后續章節中當我們進入例子代碼時, 我們看到這些函數的一些使用, ### 15.1.5.?頁表 在任何現代系統上, 處理器必須有一個機制來轉換虛擬地址到它的對應物理地址. 這個機制被稱為一個頁表; 它本質上是一個多級樹型結構數組, 包含了虛擬-到-物理的映射和幾個關聯的標志. Linux 內核維護一套頁表即便在沒有直接使用這樣頁表的體系上. 設備驅動通常可以做的許多操作能涉及操作頁表. 幸運的是對于驅動作者, 2.6 內核已經去掉了任何直接使用頁表的需要. 結果是, 我們不描述它們的任何細節; 好奇的讀者可能想讀一下 Understanding The Linux Kernel 來了解完整的內容, 作者是 Daniel P. Bovet 和 Marco Cesati (O' Reilly). ### 15.1.6.?虛擬內存區 虛擬內存區( VMA )用來管理一個進程的地址空間的獨特區域的內核數據結構. 一個 VMA 代表一個進程的虛擬內存的一個同質區域: 一個有相同許可標志和被相同對象(如, 一個文件或者交換空間)支持的連續虛擬地址范圍. 它松散地對應于一個"段"的概念, 盡管可以更好地描述為"一個有它自己特性的內存對象". 一個進程的內存映射有下列區組成: - 給程序的可執行代碼(常常稱為 text)的一個區. - 給數據的多個區, 包括初始化的數據(它有一個明確的被分配的值, 在執行開始), 未初始化數據(BBS), [[48](#)]以及程序堆棧. - 給每個激活的內存映射的一個區域. 一個進程的內存區可看到通過 /proc/<pid/maps>(這里 pid, 當然, 用一個進程的 ID 來替換). /proc/self 是一個 /proc/id 的特殊情況, 因為它常常指當前進程. 作為一個例子, 這里是幾個內存映射(我們添加了簡短注釋) ~~~ # 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 這個內存區的開始和結束虛擬地址. perm 帶有內存區的讀,寫和執行許可的位掩碼. 這個成員描述進程可以對屬于這個區的頁做什么. 成員的最后一個字符要么是給"私有"的 p 要么是給"共享"的 s. offset 內存區在它被映射到的文件中的起始位置. 0 偏移意味著內存區開始對應文件的開始. major minor 持有已被映射文件的設備的主次編號. 易混淆地, 對于設備映射, 主次編號指的是持有被用戶打開的設備特殊文件的磁盤分區, 不是設備自身. inode 被映射文件的 inode 號. image 已被映射的文件名((常常在一個可執行映象中). #### 15.1.6.1.?vm_area_struct 結構 當一個用戶空間進程調用 mmap 來映射設備內存到它的地址空間, 系統通過一個新 VMA 代表那個映射來響應. 一個支持 mmap 的驅動(并且, 因此, 實現 mmap 方法)需要來幫助那個進程來完成那個 VMA 的初始化. 驅動編寫者應當, 因此, 為支持 mmap 應至少有對 VMA 的最少的理解. 讓我們看再 struct vm_area_struct 中最重要的成員( 在 <linux/mm.h> 中定義). 這些成員應當被設備驅動在它們的 mmap 實現中使用. 注意內核維護 VMA 的鏈表和樹來優化區查找, 并且 vm_area_struct 的幾個成員被用來維護這個組織. 因此, VMA 不是有一個驅動任意創建的, 否則這個結構破壞了. VMA 的主要成員是下面(注意在這些成員和我們剛看到的 /proc 輸出之間的相似) unsigned long vm_start;unsigned long vm_end; 被這個 VMA 覆蓋的虛擬地址范圍. 這些成員是在 /proc/*/maps中出現的頭 2 個字段. struct file *vm_file; 一個指向和這個區(如果有一個)關聯的 struct file 結構的指針. unsigned long vm_pgoff; 文件中區的偏移, 以頁計. 當一個文件和設備被映射, 這是映射在這個區的第一頁的文件位置. unsigned long vm_flags; 描述這個區的一套標志. 對設備驅動編寫者最感興趣的標志是 VM_IO 和 VM_RESERVUED. VM_IO 標志一個 VMA 作為內存映射的 I/O 區. 在其他方面, VM_IO 標志阻止這個區被包含在進程核轉儲中. VM_RESERVED 告知內存管理系統不要試圖交換出這個 VMA; 它應當在大部分設備映射中設置. struct vm_operations_struct *vm_ops; 一套函數, 內核可能會調用來在這個內存區上操作. 它的存在指示內存區是一個內核"對象", 象我們已經在全書中使用的 struct file. void *vm_private_data; 驅動可以用來存儲它的自身信息的成員. 象 struct vm_area_struct, vm_operations_struct 定義于 <linux/mm.h>; 它包括下面列出的操作. 這些操作是唯一需要來處理進程的內存需要的, 它們以被聲明的順序列出. 本章后面, 一些這些函數被實現. void (*open)(struct vm_area_struct *vma); open 方法被內核調用來允許實現 VMA 的子系統來初始化這個區. 這個方法被調用在任何時候有一個新的引用這個 VMA( 當生成一個新進程, 例如). 一個例外是當這個 VMA 第一次被 mmap 創建時; 在這個情況下, 驅動的 mmap 方法被調用來替代. void (*close)(struct vm_area_struct *vma); 當一個區被銷毀, 內核調用它的關閉操作. 注意沒有使用計數關聯到 VMA; 這個區只被使用它的每個進程打開和關閉一次. struct page *(*nopage)(struct vm_area_struct *vma, unsigned long address, int *type); 當一個進程試圖存取使用一個有效 VMA 的頁, 但是它當前不在內存中, nopage 方法被調用(如果它被定義)給相關的區. 這個方法返回 struct page 指針給物理頁, 也許在從第 2 級存儲中讀取它之后. 如果 nopage 方法沒有為這個區定義, 一個空頁由內核分配. int (*populate)(struct vm_area_struct *vm, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock); 這個方法允許內核"預錯"頁到內存, 在它們被用戶空間存取之前. 對于驅動通常沒有必要來實現這個填充方法. ### 15.1.7.?進程內存映射 內存管理難題的最后部分是進程內存映射結構, 它保持所有其他數據結構在一起. 每個系統中的進程(除了幾個內核空間幫助線程)有一個 struct mm_struct ( 定義在 <linux/sched.h>), 它含有進程的虛擬內存區列表, 頁表, 和各種其他的內存管理管理信息, 包括一個旗標( mmap_sem )和一個自旋鎖( page_table_lock ). 這個結構的指針在任務結構中; 在很少的驅動需要存取它的情況下, 通常的方法是使用 current->mm. 注意內存關聯結構可在進程之間共享; Linux 線程的實現以這種方式工作, 例如. 這總結了我們對 Linux 內存管理數據結構的總體. 有了這些, 我們現在可以繼續 mmap 系統調用的實現. [[47](#)] 許多非-x86體系可以有效工作在沒有這里描述的內核/用戶空間的劃分, 因此它們可以在 32-位系統使用直到 4-GB 內核地址空間. 但是, 本節描述的限制仍然適用這樣的系統當安裝有多于 4GB 內存時. [[48](#)] BSS 的名子是來自一個老的匯編操作符的歷史遺物, 意思是"由符號開始的塊". 可執行文件的 BSS 段不存儲在磁盤上, 并且內核映射零頁到 BSS 地址范圍.
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看