Linux arm64 內存工作原理:頁表與虛擬地址底層邏輯
從開發者角度來說,當你在基于 arm64 平臺開發應用程序時,如果不了解內存管理機制,就如同在黑暗中摸索。比如,在開發一款運行于 arm64 架構的高性能游戲時,若不清楚虛擬地址如何映射到物理內存,可能會導致頻繁的內存訪問沖突,出現卡頓、掉幀等影響玩家體驗的問題。掌握內存工作原理,能夠讓開發者在代碼層面進行更精細的優化,合理分配內存資源,減少內存碎片,提高程序的運行效率和穩定性。
對于系統運維人員,arm64 內存知識更是保障系統穩定運行的關鍵。在服務器運維場景中,當服務器負載過高,內存資源緊張時,如果不了解 arm64 內存的頁表管理,就難以準確判斷內存性能瓶頸所在。比如,無法確定是頁表項缺失導致的頻繁缺頁中斷,還是內存分配不合理造成的內存泄漏。只有深入理解內存工作原理,才能快速定位問題根源,采取有效的措施,如調整內存分配策略、優化頁表結構等,確保系統的高效、穩定運行。
一、Linux 內存管理基礎概念
在深入探究 Linux arm64 內存工作原理之前,先扎實掌握一些基礎概念,就像蓋房子要先打好地基一樣,這些概念是理解后續復雜內容的關鍵。
1.1虛擬內存與物理內存
物理內存,是計算機硬件實實在在擁有的內存空間,由內存條提供,就如同你家里實實在在的房間,空間大小是固定的,比如常見的 8GB、16GB 或 32GB 。它直接與 CPU 交互,數據的讀取和寫入都在這里快速進行,速度非常快,就像你在家中找東西,伸手就能拿到。
而虛擬內存則是一種虛擬出來的內存空間,是操作系統利用磁盤空間模擬出來的一塊邏輯內存 ,類似于你家樓下的儲物間,當家里房間不夠用時,可以把暫時不用的東西放到儲物間。虛擬內存的出現,主要是為了解決物理內存不足的問題。當物理內存被占滿,而程序還需要更多內存時,操作系統就會把物理內存中暫時不用的數據 “挪” 到虛擬內存中,騰出物理內存給更急需的程序使用。當程序再次需要這些數據時,再從虛擬內存中讀取回來。
虛擬內存還有一個非常重要的作用,就是為每個進程提供獨立的地址空間。在多進程環境下,如果沒有虛擬內存,每個進程直接訪問物理內存,就會像一群人在一個沒有劃分區域的大倉庫里找東西,很容易相互干擾,造成內存沖突。有了虛擬內存,每個進程都以為自己擁有一塊獨立且連續的內存空間,就像每個人都有自己獨立的小倉庫,互不干擾。進程使用的是虛擬地址,通過內存管理單元(MMU)將虛擬地址映射到物理地址,這樣既保證了進程間的隔離,又提高了系統的穩定性和安全性 。
1.2內存管理單元(MMU)
內存管理單元(MMU)是計算機系統中負責處理 CPU 內存訪問請求的關鍵硬件組件 ,它就像是一個智能的翻譯官,主要負責虛擬地址到物理地址的轉換工作。當 CPU 想要訪問內存中的數據時,它會給出一個虛擬地址,MMU 接到這個虛擬地址后,會迅速查閱頁表(后面會詳細介紹頁表),找到對應的物理地址,然后將物理地址發送給內存,內存再根據這個物理地址返回相應的數據給 CPU。
圖片
在這個地址轉換的過程中,MMU 中的頁表緩存(TLB,Translation Lookaside Buffer)發揮著重要作用。TLB 是一個高速緩存,它存儲了最近使用的頁表項,就像一個常用物品的快速查找清單。當 MMU 需要進行地址轉換時,它會先在 TLB 中查找,如果找到了對應的頁表項(即 TLB 命中),就可以直接獲取物理地址,大大加快了地址轉換的速度,減少了內存訪問的時間開銷;如果 TLB 中沒有找到(即 TLB 未命中),MMU 才會去訪問內存中的頁表,這個過程相對較慢 。
MMU 除了地址轉換的功能外,還承擔著內存保護的重要職責。它可以設置內存訪問權限,比如將某些內存區域設置為只讀、讀寫或者可執行等權限。當一個進程嘗試以不被允許的方式訪問內存時,比如對只讀內存區域進行寫入操作,MMU 就會觸發異常,操作系統會捕獲這個異常并進行相應的處理,從而避免系統崩潰或數據損壞 ,就像一個嚴格的保安,守護著內存的安全。
二、arm64 系統架構
2.164 位尋址能力
arm64 架構最顯著的特點之一就是其強大的 64 位尋址能力 。相比 32 位架構,arm64 架構在內存尋址空間上實現了質的飛躍。在 32 位架構中,由于地址總線寬度的限制,其最大內存尋址空間通常被限制在 4GB(2 的 32 次方字節) ,就像一個只有有限房間的小旅館,當客人(數據)數量超過房間容量時,就會出現 “住不下” 的情況。這對于現代復雜應用程序和大數據處理來說,顯得捉襟見肘,例如在運行大型數據庫、進行大數據分析時,4GB 的內存常常無法滿足需求,程序會頻繁地進行磁盤讀寫操作來交換數據,大大降低了運行效率。
而 arm64 架構采用 64 位地址總線,理論上可以尋址高達 2 的 64 次方字節的內存空間,雖然在實際應用中,由于硬件成本、操作系統限制等因素,不會使用到如此巨大的內存空間,但這已經極大地擴展了處理器的內存尋址能力,就像將小旅館升級成了大型酒店,有足夠多的房間來容納各種數據 。這使得 arm64 架構能夠輕松應對大數據處理場景,比如在處理海量的基因測序數據時,大數據量可以直接存儲在內存中進行快速分析,避免了頻繁的磁盤 I/O 操作,大大提高了分析速度;在運行復雜的企業級應用程序時,如大型 ERP 系統,arm64 架構能夠為其提供充足的內存空間,保證系統的穩定運行,提升用戶體驗。
2.2寄存器組
arm64 架構擁有豐富的寄存器組,其中包含 31 個 64 位通用寄存器 ,這些寄存器在數據處理和運算過程中發揮著關鍵作用,就像是一個個高速的小倉庫,用于臨時存儲數據和運算結果。相比 32 位架構中數量有限的寄存器,arm64 架構的寄存器組能夠存儲更多的數據,減少了數據在內存和寄存器之間頻繁傳輸的次數。
以一個簡單的矩陣運算為例,在進行矩陣乘法時,需要對大量的矩陣元素進行乘法和累加操作。在 32 位架構下,由于寄存器數量有限,可能需要頻繁地將矩陣元素從內存讀取到寄存器進行運算,運算結果又要頻繁地寫回內存,這個過程會消耗大量的時間在內存訪問上。而在 arm64 架構中,豐富的通用寄存器可以一次性存儲更多的矩陣元素,在寄存器之間直接進行運算,大大減少了內存訪問的開銷,提高了運算效率。此外,這些寄存器還能更好地支持多線程處理,每個線程都可以使用不同的寄存器來存儲數據,避免了線程之間的寄存器沖突,提高了多線程程序的運行效率 。
2.3指令集優化
arm64 架構在指令集方面進行了深度優化,引入了許多新特性和改進 。其中,高級 SIMD(NEON)指令集是其重要的優化成果之一。NEON 指令集是一種單指令多數據(SIMD)技術,它允許在一個指令周期內對多個數據元素同時進行操作,大大提高了數據處理的并行性,就像一個多工位的生產線,可以同時處理多個產品。
在多媒體處理領域,NEON 指令集展現出了強大的優勢。例如在視頻解碼過程中,需要對大量的像素數據進行處理。傳統的標量指令處理方式,每次只能處理一個像素點的數據,效率較低。而使用 NEON 指令集,一次可以對多個像素點的數據進行并行處理,能夠顯著提高視頻解碼的速度,使得視頻播放更加流暢,減少卡頓現象;在音頻處理中,對于音頻信號的濾波、混音等操作,NEON 指令集也能通過并行處理多個音頻樣本,提升音頻處理的效率,改善音頻質量 。在信號處理領域,如無線通信中的數字信號處理,NEON 指令集可以加速對信號的調制、解調、濾波等操作,提高通信系統的性能 。
三、頁表:虛擬地址與物理地址的橋梁
頁表,簡單來說,就是一個記錄虛擬地址與物理地址對應關系的數據結構,如同圖書館的索引目錄,當你想要借閱一本圖書時,通過索引目錄就能快速找到它在書架上的具體位置。在計算機內存管理中,頁表承擔著類似的角色,它幫助內存管理單元(MMU)將 CPU 發出的虛擬地址準確無誤地轉換為物理地址 ,從而實現數據的讀取和寫入。
為了更高效地管理內存,內存空間被劃分成一個個固定大小的塊,在虛擬內存中,這些塊被稱為虛擬頁(Virtual Page);在物理內存中,對應的塊被稱為物理頁框(Physical Page Frame) ,就像把一個大倉庫劃分成一個個小隔間,每個隔間就是一個頁或頁框。頁表中的每一個條目(Page Table Entry,PTE),都存儲著一個虛擬頁到物理頁框的映射關系 ,以及一些額外的控制信息,比如該頁的訪問權限(是只讀、讀寫還是可執行)是否被訪問過、是否被修改過(臟位)等,這些控制信息就像是小隔間上的標簽,告訴系統如何操作這個小隔間里的數據 。
3.1arm64 的頁表結構
arm64 架構支持兩種主要的頁表層次結構:四級頁表和三級頁表 。
四級頁表結構適用于 48 位虛擬地址空間,它就像一個四層的樹形結構,每層都有特定的作用和索引方式 。虛擬地址被拆分成多個部分,分別用于索引各級頁表和確定頁內偏移 。具體來說,虛擬地址的高 36 位(48 - 12,其中 12 位是頁內偏移)被分為四層,每層 9 位 。
最高層是頁全局目錄(Page Global Directory,PGD) ,它是整個頁表結構的入口,就像圖書館索引目錄的總目錄。PGD 中的每個表項指向一個頁上級目錄(Page Upper Directory,PUD)的物理地址 。通過虛擬地址的最高 9 位作為索引,可以在 PGD 中找到對應的 PUD 表項。
第二層是 PUD ,它進一步細化地址映射,PUD 中的每個表項指向一個頁中間目錄(Page Middle Directory,PMD)的物理地址 。利用虛擬地址的次高 9 位作為索引,在 PUD 中定位到相應的 PMD 表項。第三層是 PMD ,PMD 中的表項指向頁表項(Page Table Entry,PTE)所在的頁表的物理地址 。通過虛擬地址的第三高 9 位索引 PMD,找到對應的 PTE 頁表。
最底層是 PTE ,PTE直接映射到物理頁框地址,并包含訪問權限、是否被訪問過、是否被修改過等控制信息 。使用虛擬地址的最低9位作為索引,在 PTE 中找到最終對應的物理頁框號,再結合12位的頁內偏移,就可以得到完整的物理地址 。
三級頁表結構則適用于較小的虛擬地址空間,比如 39 位虛擬地址空間 ,它省略了頁上級目錄(PUD)這一層 。虛擬地址的高 27 位(39 - 12)被分為三層,每層 9 位 ,直接從 PGD 通過 9 位索引找到 PMD,再由 PMD 通過 9 位索引找到 PTE,最后結合 12 位頁內偏移得到物理地址 。這種結構在虛擬地址空間需求較小時,可以減少頁表的層級和內存占用,提高地址轉換的效率 。
3.2頁表的操作與維護
內核在頁表的操作與維護中扮演著至關重要的角色 。在進程創建時,內核會為新進程分配頁表,并初始化各級目錄結構 。具體步驟如下:首先分配 PGD,然后為 PGD 中的每個 PUD 分配內存,接著為 PUD 中的每個 PMD 分配內存,最后為 PMD 中的每個 PTE 分配內存 ,就像搭建一個四層的書架,先搭建最上層的框架,再逐步填充下面的每一層 。
當進程訪問一個虛擬地址時,如果該虛擬地址對應的頁表項不存在(即發生缺頁中斷) ,內核會介入處理。內核會根據內存分配策略,為進程分配一個物理頁框,并更新頁表,建立虛擬地址到物理地址的映射關系 。例如,如果系統采用的是按需分配內存策略,當進程首次訪問某個虛擬地址時,發現對應的物理頁框未分配,內核就會從空閑物理頁框池中選取一個空閑頁框,將其與該虛擬地址建立映射,并更新頁表中的 PTE 。
在進程切換時,由于不同進程擁有獨立的頁表 ,內核需要保存當前進程的頁表狀態,并切換到新進程的頁表 。這個過程就像在圖書館中,一個讀者離開,另一個讀者進來,圖書管理員需要切換到新讀者的借閱索引目錄 。內核會將當前進程的頁表基地址保存起來,然后將新進程的頁表基地址加載到內存管理單元(MMU)的相關寄存器中,這樣 MMU 在進行地址轉換時,就會使用新進程的頁表 。
當進程釋放內存時,內核需要更新頁表,將對應的頁表項標記為無效 ,并回收物理頁框 。例如,當進程調用 free 函數釋放一塊內存時,內核會找到該內存對應的頁表項,將其訪問權限設置為無效,同時將該物理頁框標記為空閑,以便重新分配給其他需要的進程 。在整個過程中,內核通過精心管理頁表,確保內存資源的合理分配和高效利用,保障系統的穩定運行 。
四、虛擬地址的底層邏輯
4.1虛擬地址空間布局
在 arm64 架構下,Linux 系統的虛擬地址空間被清晰地劃分為內核空間和用戶空間兩大部分 ,它們在整個虛擬地址空間中占據著不同的范圍,承擔著不同的職責。
用戶空間是應用程序運行的場所,其地址范圍通常從 0x0000 0000 0000 0000 開始,到 0x0000 FFFF FFFF FFFF 結束(48 位虛擬地址空間的情況) 。在這個空間里,各個進程擁有自己獨立的一套虛擬地址空間,就像一個個獨立的小世界。每個進程的用戶空間包含了代碼段、數據段、堆、棧等不同的內存區域 。代碼段存儲著進程的可執行代碼,就像一本寫滿操作指南的書籍;數據段存放已初始化的全局變量和靜態變量,如同一個存放固定物品的倉庫;堆是進程在運行時動態分配內存的區域,好比一個可以根據需求隨時擴建的儲物間,進程可以通過 malloc 等函數在堆上申請內存;棧則用于函數調用和局部變量的存儲,每當函數被調用時,系統會在棧上為該函數分配一塊空間,用于存放函數的參數、局部變量等信息,當函數返回時,這塊棧空間會被釋放,棧就像一個臨時的工作區,按照后進先出的原則進行操作 。
內核空間則是操作系統內核運行的區域,它的地址范圍從 0xFFFF 0000 0000 0000 到 0xFFFF FFFF FFFF FFFF(48 位虛擬地址空間) 。內核空間是所有進程共享的,它包含了內核代碼、內核數據、內核棧以及一些重要的數據結構 。內核代碼是操作系統的核心指令集,負責處理各種系統調用、中斷處理、內存管理、進程調度等關鍵任務,就像一個城市的交通指揮中心,協調著整個城市的運行;內核數據存儲著內核運行過程中需要使用的各種數據,如系統狀態信息、設備驅動程序的相關數據等;內核棧用于內核函數的調用和執行,與用戶空間的棧類似,但它是為內核函數服務的 。內核空間還包含了頁表等內存管理數據結構,這些數據結構對于虛擬地址到物理地址的轉換起著關鍵作用,就像地圖上的坐標轉換工具,幫助系統準確找到數據的存儲位置 。
這種內核空間和用戶空間的劃分,為系統提供了良好的隔離性和安全性 。用戶空間的進程不能直接訪問內核空間的內容,這就避免了用戶進程對內核數據的非法修改,防止了系統的崩潰和安全漏洞的出現,就像一個戒備森嚴的軍事基地,普通民眾無法隨意進入,保障了基地的安全和穩定運行 。當用戶進程需要訪問內核資源時,必須通過系統調用的方式,由內核來檢查和處理,這就像民眾需要辦理某些特殊事務時,必須通過正規的渠道向相關部門申請,經過審核后才能得到處理 。
4.2虛擬地址到物理地址的轉換過程
當 CPU 訪問內存時,給出的是虛擬地址,而實際的數據存儲在物理內存中,因此需要將虛擬地址轉換為物理地址,這個轉換過程主要由內存管理單元(MMU)借助頁表來完成 。以 arm64 架構的四級頁表結構為例,下面詳細介紹其轉換步驟 :
- 獲取頁全局目錄(PGD)基地址:CPU 中的內存管理單元(MMU)首先會從特定的寄存器中獲取頁全局目錄(PGD)的基地址 ,這個寄存器就像是一個存放著重要索引目錄地址的小盒子,MMU 從中取出 PGD 的起始位置信息 。
- 索引 PGD:虛擬地址的最高 9 位被用作索引值 ,MMU 根據這個索引值在 PGD 中查找對應的表項 。例如,假設虛擬地址為 0x0000 1234 5678 9ABC,其最高 9 位為 0x0000 123,MMU 就會在 PGD 中找到索引為 0x0000 123 的表項 。這個表項中存儲著頁上級目錄(PUD)的物理地址 ,就像在圖書館的總目錄中找到了某個大類書籍所在的書架位置 。
- 獲取 PUD 并索引:根據 PGD 中找到的 PUD 物理地址,MMU 訪問內存獲取 PUD 。然后,虛擬地址的次高 9 位被用于在 PUD 中進行索引 。比如上述虛擬地址的次高 9 位為 0x456,MMU 就在 PUD 中找到索引為 0x456 的表項 ,該表項指向頁中間目錄(PMD)的物理地址 ,如同在大類書架中找到了具體的小類書架 。
- 獲取 PMD 并索引:MMU 根據 PUD 中得到的 PMD 物理地址,再次訪問內存獲取 PMD 。接著,用虛擬地址的第三高 9 位在 PMD 中查找對應的表項 。對于前面的虛擬地址,其第三高 9 位為 0x789,MMU 通過這個索引在 PMD 中找到相應表項,該表項指向頁表項(PTE)所在頁表的物理地址 ,這一步就像在小類書架中找到了具體的某一層書架 。
- 獲取 PTE 并得到物理頁框號:MMU 根據 PMD 找到的頁表物理地址,訪問內存獲取頁表 。最后,使用虛擬地址的最低 9 位在頁表中找到對應的 PTE 。如虛擬地址的最低 9 位為 0xABC,MMU 在頁表中找到索引為 0xABC 的 PTE ,PTE 中存儲著物理頁框號 ,這就找到了數據所在的具體物理頁框 ,就像在書架的某一層找到了具體的書籍 。
- 計算物理地址:得到物理頁框號后,再結合虛擬地址的低 12 位頁內偏移 ,就可以計算出最終的物理地址 。假設物理頁框號為 0x12345,頁內偏移為 0xABC,那么物理地址就是 0x12345ABC ,這樣就完成了從虛擬地址到物理地址的轉換,MMU 就可以根據這個物理地址從物理內存中讀取或寫入數據 。
在這個過程中,如果某一級頁表項不存在,或者頁表項中的訪問權限不允許當前的訪問操作(比如試圖寫入只讀的頁表項) ,MMU 就會觸發異常,由操作系統內核來處理這些異常情況 ,確保系統的穩定和安全 。
虛擬地址到物理地址轉換(頁表分級映射)實現:
#include
<iostream>
#include
<cstdint>
#include
<iomanip>
#include
<array>
// 頁表相關常量定義(x86_64架構標準)
const uint64_t PAGE_SHIFT = 12; // 頁內偏移位數(4KB頁大小)
const uint64_t PAGE_SIZE = 1ULL << PAGE_SHIFT; // 頁大小:4096字節
const uint64_t PTE_SHIFT = 9; // 每個頁表層級的索引位數
const uint64_t PTE_MASK = (1ULL << PTE_SHIFT) - 1; // 索引掩碼
// 頁表項結構體(簡化版,僅保留物理地址字段)
struct PageTableEntry {
uint64_t physical_addr; // 指向下級頁表/物理頁框的物理地址
bool valid; // 表項是否有效
PageTableEntry() : physical_addr(0), valid(false) {}
PageTableEntry(uint64_t addr) : physical_addr(addr), valid(true) {}
};
// 模擬物理內存(存儲各級頁表數據)
class PhysicalMemory {
private:
// 簡化:物理內存按頁劃分,每頁存儲一個頁表(PGD/PUD/PMD/PTE)
std::array<PageTableEntry, 1ULL << PTE_SHIFT> memory_pages[1024];
uint64_t next_free_page = 0; // 下一個可用物理頁框號
public:
// 分配物理頁框,返回頁框號
uint64_t alloc_page() {
if (next_free_page >= 1024) {
std::cerr << "物理內存耗盡" << std::endl;
return 0;
}
return next_free_page++;
}
// 獲取指定物理地址的頁表項
PageTableEntry& get_pte(uint64_t phys_addr) {
uint64_t page_num = phys_addr >> PAGE_SHIFT;
uint64_t offset = phys_addr & (PAGE_SIZE - 1);
return memory_pages[page_num][offset];
}
// 設置頁表項內容
void set_pte(uint64_t phys_addr, const PageTableEntry& pte) {
uint64_t page_num = phys_addr >> PAGE_SHIFT;
uint64_t offset = phys_addr & (PAGE_SIZE - 1);
memory_pages[page_num][offset] = pte;
}
};
// MMU內存管理單元:實現虛擬地址到物理地址轉換
class MMU {
private:
PhysicalMemory& phys_mem;
uint64_t pgd_base; // PGD基地址(存儲在CPU專用寄存器)
// 提取虛擬地址各層級索引
void extract_vaddr_indices(uint64_t vaddr, uint64_t& pgd_idx, uint64_t& pud_idx,
uint64_t& pmd_idx, uint64_t& pte_idx, uint64_t& offset) {
// 拆分64位虛擬地址(x86_64僅使用低48位)
uint64_t vaddr_48 = vaddr & ((1ULL << 48) - 1);
pgd_idx = (vaddr_48 >> (PAGE_SHIFT + 3 * PTE_SHIFT)) & PTE_MASK;
pud_idx = (vaddr_48 >> (PAGE_SHIFT + 2 * PTE_SHIFT)) & PTE_MASK;
pmd_idx = (vaddr_48 >> (PAGE_SHIFT + 1 * PTE_SHIFT)) & PTE_MASK;
pte_idx = (vaddr_48 >> PAGE_SHIFT) & PTE_MASK;
offset = vaddr_48 & (PAGE_SIZE - 1);
}
public:
MMU(PhysicalMemory& mem) : phys_mem(mem) {
// 初始化PGD基地址:分配第一個物理頁作為PGD
pgd_base = phys_mem.alloc_page() << PAGE_SHIFT;
std::cout << "PGD基地址已初始化:0x" << std::hex << pgd_base << std::dec << std::endl;
}
// 構建頁表映射(模擬內核設置頁表)
void map_virtual_to_physical(uint64_t vaddr, uint64_t phys_page) {
uint64_t pgd_idx, pud_idx, pmd_idx, pte_idx, offset;
extract_vaddr_indices(vaddr, pgd_idx, pud_idx, pmd_idx, pte_idx, offset);
// 1. 初始化PGD表項:指向PUD物理地址
uint64_t pud_phys = phys_mem.alloc_page() << PAGE_SHIFT;
phys_mem.set_pte(pgd_base + (pgd_idx * sizeof(PageTableEntry)), PageTableEntry(pud_phys));
// 2. 初始化PUD表項:指向PMD物理地址
uint64_t pmd_phys = phys_mem.alloc_page() << PAGE_SHIFT;
phys_mem.set_pte(pud_phys + (pud_idx * sizeof(PageTableEntry)), PageTableEntry(pmd_phys));
// 3. 初始化PMD表項:指向PTE頁表物理地址
uint64_t pte_table_phys = phys_mem.alloc_page() << PAGE_SHIFT;
phys_mem.set_pte(pmd_phys + (pmd_idx * sizeof(PageTableEntry)), PageTableEntry(pte_table_phys));
// 4. 初始化PTE表項:指向物理頁框
phys_mem.set_pte(pte_table_phys + (pte_idx * sizeof(PageTableEntry)),
PageTableEntry(phys_page << PAGE_SHIFT));
}
// 核心:虛擬地址轉換為物理地址
uint64_t translate_virtual_to_physical(uint64_t vaddr) {
std::cout << "\n開始轉換虛擬地址:0x" << std::hex << vaddr << std::dec << std::endl;
// 步驟1:提取各層級索引和頁內偏移
uint64_t pgd_idx, pud_idx, pmd_idx, pte_idx, offset;
extract_vaddr_indices(vaddr, pgd_idx, pud_idx, pmd_idx, pte_idx, offset);
std::cout << "虛擬地址拆分:" << std::endl;
std::cout << " PGD索引:0x" << std::hex << pgd_idx << std::dec << std::endl;
std::cout << " PUD索引:0x" << std::hex << pud_idx << std::dec << std::endl;
std::cout << " PMD索引:0x" << std::hex << pmd_idx << std::dec << std::endl;
std::cout << " PTE索引:0x" << std::hex << pte_idx << std::dec << std::endl;
std::cout << " 頁內偏移:0x" << std::hex << offset << std::dec << std::endl;
// 步驟2:從PGD基地址獲取PUD物理地址
PageTableEntry pgd_entry = phys_mem.get_pte(pgd_base + (pgd_idx * sizeof(PageTableEntry)));
if (!pgd_entry.valid) {
std::cerr << "PGD表項無效" << std::endl;
return 0;
}
uint64_t pud_phys = pgd_entry.physical_addr;
std::cout << "從PGD索引找到PUD物理地址:0x" << std::hex << pud_phys << std::dec << std::endl;
// 步驟3:從PUD獲取PMD物理地址
PageTableEntry pud_entry = phys_mem.get_pte(pud_phys + (pud_idx * sizeof(PageTableEntry)));
if (!pud_entry.valid) {
std::cerr << "PUD表項無效" << std::endl;
return 0;
}
uint64_t pmd_phys = pud_entry.physical_addr;
std::cout << "從PUD索引找到PMD物理地址:0x" << std::hex << pmd_phys << std::dec << std::endl;
// 步驟4:從PMD獲取PTE頁表物理地址
PageTableEntry pmd_entry = phys_mem.get_pte(pmd_phys + (pmd_idx * sizeof(PageTableEntry)));
if (!pmd_entry.valid) {
std::cerr << "PMD表項無效" << std::endl;
return 0;
}
uint64_t pte_table_phys = pmd_entry.physical_addr;
std::cout << "從PMD索引找到PTE頁表物理地址:0x" << std::hex << pte_table_phys << std::dec << std::endl;
// 步驟5:從PTE頁表獲取物理頁框號
PageTableEntry pte_entry = phys_mem.get_pte(pte_table_phys + (pte_idx * sizeof(PageTableEntry)));
if (!pte_entry.valid) {
std::cerr << "PTE表項無效" << std::endl;
return 0;
}
uint64_t phys_page = pte_entry.physical_addr >> PAGE_SHIFT;
std::cout << "從PTE索引找到物理頁框號:0x" << std::hex << phys_page << std::dec << std::endl;
// 步驟6:計算最終物理地址(頁框號 + 頁內偏移)
uint64_t phys_addr = (phys_page << PAGE_SHIFT) | offset;
std::cout << "計算最終物理地址:0x" << std::hex << phys_addr << std::dec << std::endl;
return phys_addr;
}
};
int main() {
// 初始化物理內存和MMU
PhysicalMemory phys_mem;
MMU mmu(phys_mem);
// 模擬:構建虛擬地址到物理地址的映射
uint64_t virtual_addr = 0x0000123456789ABC; // 示例虛擬地址
uint64_t physical_page = 0x12345; // 目標物理頁框號
mmu.map_virtual_to_physical(virtual_addr, physical_page);
// 執行虛擬地址到物理地址的轉換
uint64_t physical_addr = mmu.translate_virtual_to_physical(virtual_addr);
std::cout << "\n轉換完成:虛擬地址 0x" << std::hex << virtual_addr
<< " → 物理地址 0x" << physical_addr << std::dec << std::endl;
return 0;
}- 地址拆分:嚴格按照 x86_64 架構的 48 位虛擬地址規則,拆分為 PGD/PUD/PMD/PTE 四層索引(各 9 位)+ 12 位頁內偏移;
- 物理內存模擬:通過PhysicalMemory類模擬物理頁框分配和頁表項存儲,貼合硬件內存的分頁管理方式;
- 頁表映射構建:map_virtual_to_physical方法模擬內核初始化頁表的過程,為地址轉換提供基礎;
- 地址轉換流程:translate_virtual_to_physical方法完整復現 MMU 的四級頁表尋址邏輯,每一步輸出關鍵地址信息。
編譯運行:
# 編譯(支持C++11及以上,跨平臺)
g++ -std=c++11 mmu_address_translate.cpp -o mmu_translate
# 運行
./mmu_translate執行流程輸出示例:
PGD基地址已初始化:0x0
開始轉換虛擬地址:0x123456789abc
虛擬地址拆分:
PGD索引:0x123
PUD索引:0x456
PMD索引:0x789
PTE索引:0xabc
頁內偏移:0xabc
從PGD索引找到PUD物理地址:0x1000
從PUD索引找到PMD物理地址:0x2000
從PMD索引找到PTE頁表物理地址:0x3000
從PTE索引找到物理頁框號:0x12345
計算最終物理地址:0x12345abc
轉換完成:虛擬地址 0x123456789abc → 物理地址 0x12345abc4.3缺頁異常處理
當虛擬地址訪問的頁面不在物理內存中時,就會產生缺頁異常 。缺頁異常的產生原因主要有兩種:一是首次訪問某個虛擬地址,該虛擬地址對應的物理頁面尚未分配;二是之前已經分配的物理頁面被操作系統換出到磁盤(Swap)上,以騰出物理內存空間給更急需的進程使用 。
當缺頁異常發生時,硬件會將控制權交給操作系統內核的缺頁異常處理程序 。內核首先會對缺頁異常進行詳細的分析,確定缺頁的具體原因 。如果是因為頁面尚未分配,內核會根據內存分配策略,從系統的空閑物理頁面池中選取一個空閑的物理頁面 。例如,如果系統采用的是伙伴系統內存分配策略,內核會在伙伴系統的空閑頁面鏈表中查找合適大小的空閑頁面 。
找到空閑物理頁面后,內核會將該物理頁面與引發缺頁異常的虛擬地址建立映射關系 。具體來說,內核會更新頁表,在相應的頁表項(PTE)中填入新分配的物理頁面的頁框號,并設置正確的訪問權限等控制信息 。如果是因為頁面被換出到磁盤,內核需要將該頁面從磁盤上的 Swap 分區讀取回物理內存 。這涉及到磁盤 I/O 操作,會比較耗時 。內核會通過文件系統的相關接口,找到磁盤上對應的 Swap 文件或分區,將頁面數據讀入到選定的物理頁面中 。
在完成頁面的分配或從磁盤讀回操作,并更新頁表建立好映射關系后,內核會喚醒因為缺頁異常而被阻塞的進程,讓它繼續執行被中斷的指令 。此時,進程再次訪問之前引發缺頁異常的虛擬地址時,就可以通過頁表找到對應的物理地址,順利完成內存訪問操作 。整個缺頁異常處理過程是操作系統內存管理的關鍵環節,它確保了進程能夠在有限的物理內存條件下,高效、穩定地運行 。
五、實際應用與案例分析
5.1內存分配與釋放的案例
在 arm64 平臺上,應用程序通常借助系統調用進行內存分配與釋放操作,其中 malloc 和 mmap 是較為常用的函數 。下面通過具體代碼示例來深入了解這一過程 。
#include
<stdio.h>
#include
<stdlib.h>
#include
<sys/mman.h>
#include
<fcntl.h>
#include
<unistd.h>
#include
<sys/types.h>
#include
<sys/stat.h>
#define
MAP_SIZE 4096UL // 映射大小,通常為一頁大小
int main() {
// 使用malloc分配內存
int *malloc_ptr = (int *)malloc(10 * sizeof(int));
if (malloc_ptr == NULL) {
perror("malloc failed");
return 1;
}
// 使用分配的內存
for (int i = 0; i < 10; i++) {
malloc_ptr[i] = i;
}
// 輸出內存內容
printf("malloc allocated memory content:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", malloc_ptr[i]);
}
printf("\n");
// 釋放malloc分配的內存
free(malloc_ptr);
// 使用mmap進行內存映射
int fd = open("testfile", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror("open failed");
return 1;
}
// 擴展文件大小到MAP_SIZE
if (lseek(fd, MAP_SIZE - 1, SEEK_SET) == -1) {
perror("lseek failed");
close(fd);
return 1;
}
if (write(fd, "", 1) != 1) {
perror("write failed");
close(fd);
return 1;
}
char *mmap_ptr = (char *)mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mmap_ptr == MAP_FAILED) {
perror("mmap failed");
close(fd);
return 1;
}
// 使用mmap映射的內存
for (int i = 0; i < MAP_SIZE; i++) {
mmap_ptr[i] = 'A' + (i % 26);
}
// 輸出內存內容
printf("mmap mapped memory content:\n");
for (int i = 0; i < MAP_SIZE; i++) {
printf("%c", mmap_ptr[i]);
}
printf("\n");
// 解除mmap映射
if (munmap(mmap_ptr, MAP_SIZE) == -1) {
perror("munmap failed");
}
// 關閉文件
close(fd);
return 0;
}在上述代碼中,首先使用 malloc 函數分配了一段可以存儲 10 個整數的內存空間 。malloc 函數是 C 標準庫提供的內存分配函數,它在堆上分配內存 。在 arm64 平臺下,當調用 malloc 時,底層會通過 brk 或 sbrk 系統調用(當分配內存較小時)或者 mmap 系統調用(當分配內存較大時,具體閾值因 glibc 版本而異,一般 128KB 為界)來向操作系統申請內存 。申請到內存后,程序對這段內存進行初始化并輸出其中的內容,最后使用 free 函數釋放內存 ,free 函數會將 malloc 分配的內存歸還給堆,以便后續重新分配 。
接著,使用 mmap 函數進行內存映射 。mmap 函數將文件(這里是名為 testfile 的文件)映射到進程的虛擬地址空間 。在 arm64 架構下,mmap 系統調用會與內核進行交互,內核會為進程創建新的虛擬內存區域,并建立虛擬地址到物理地址的映射關系 。代碼中先打開文件,擴展文件大小,然后使用 mmap 進行映射 ,將文件內容映射到虛擬地址空間后,對映射的內存進行初始化并輸出內容 。最后,使用 munmap 函數解除映射,將映射的內存區域從進程的虛擬地址空間中移除 ,同時關閉文件 。
5.2性能優化案例
在 arm64 架構下,基于對內存工作原理的深刻理解,可以進行一系列有效的性能優化 。以下是一些實際案例 。
案例一:大頁內存的使用
大頁內存(Huge Pages)是一種比普通頁(如 4KB 頁)更大的內存頁,常見的大頁大小有 2MB、1GB 等 。在某些場景下,使用大頁內存可以顯著提高性能 。以數據庫系統為例,數據庫通常需要頻繁地訪問內存中的數據和索引 。假設一個數據庫系統在普通 4KB 頁的內存環境下運行,由于數據庫的數據量龐大,會產生大量的頁表項 。當進行數據查詢時,頻繁的內存訪問會導致內存管理單元(MMU)頻繁地查找頁表,產生較多的 TLB(Translation Lookaside Buffer)未命中情況 ,每次 TLB 未命中都需要訪問內存中的頁表,這會增加內存訪問的延遲 。
而如果使用 2MB 的大頁內存,由于大頁內存的尺寸更大,同樣的數據量占用的頁數量會大幅減少 ,相應的頁表項數量也會減少 。例如,原本需要 1000 個 4KB 頁來存儲的數據,使用 2MB 大頁內存可能只需要 2 個大頁 。這樣,MMU 在進行地址轉換時,TLB 命中的概率會大大提高 ,減少了頁表查找的次數,從而降低了內存訪問延遲 ,提高了數據庫系統的查詢性能 。在實際配置中,可以通過修改系統參數(如在 Linux 系統中,通過修改/etc/sysctl.conf文件,設置vm.nr_hugepages參數來指定系統預留的大頁數量)來啟用和配置大頁內存 。
大頁內存對數據庫性能優化實現:
#include
<iostream>
#include
<cstdint>
#include
<iomanip>
#include
<chrono>
#include
<vector>
#include
<unordered_set>
// 內存頁相關常量定義
const uint64_t NORMAL_PAGE_SIZE = 4 * 1024; // 普通頁大小:4KB
const uint64_t HUGE_PAGE_SIZE = 2 * 1024 * 1024; // 大頁大小:2MB
const uint64_t DB_DATA_SIZE = 2 * 1024 * 1024 * 1024;// 數據庫數據量:2GB
// TLB(地址轉換后備緩沖器)模擬結構
struct TLB {
std::unordered_set<uint64_t> cached_page_entries; // 緩存的頁表項
uint64_t hit_count = 0; // TLB命中次數
uint64_t miss_count = 0; // TLB未命中次數
// 檢查TLB是否命中指定頁的頁表項
bool check_hit(uint64_t page_number) {
if (cached_page_entries.find(page_number) != cached_page_entries.end()) {
hit_count++;
return true;
}
miss_count++;
// 未命中時將頁表項加入TLB(模擬TLB填充)
cached_page_entries.insert(page_number);
return false;
}
// 重置TLB狀態
void reset() {
cached_page_entries.clear();
hit_count = 0;
miss_count = 0;
}
// 計算TLB命中率
double get_hit_rate() const {
uint64_t total = hit_count + miss_count;
return total == 0 ? 0.0 : (static_cast<double>(hit_count) / total) * 100;
}
};
// 數據庫內存訪問模擬
class DatabaseMemoryAccess {
private:
TLB tlb; // 系統TLB
// 計算指定頁大小下的頁數量
uint64_t calculate_page_count(uint64_t page_size) {
return DB_DATA_SIZE / page_size;
}
// 模擬數據庫隨機訪問內存頁
void access_memory_pages(uint64_t page_size, const std::string& page_type) {
tlb.reset();
uint64_t page_count = calculate_page_count(page_size);
std::cout << "\n=== " << page_type << "(" << (page_size / 1024) << "KB)===" << std::endl;
std::cout << "數據總量:" << (DB_DATA_SIZE / (1024 * 1024)) << "MB,總頁數:" << page_count << std::endl;
// 模擬10000次隨機內存訪問
auto start_time = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000; ++i) {
// 生成隨機頁號
uint64_t random_page = rand() % page_count;
// 檢查TLB命中情況
tlb.check_hit(random_page);
}
auto end_time = std::chrono::high_resolution_clock::now();
auto cost = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();
// 輸出性能數據
std::cout << "內存訪問耗時:" << cost << "微秒" << std::endl;
std::cout << "TLB命中次數:" << tlb.hit_count << ",未命中次數:" << tlb.miss_count << std::endl;
std::cout << "TLB命中率:" << std::fixed << std::setprecision(2) << tlb.get_hit_rate() << "%" << std::endl;
}
public:
// 對比普通頁與大頁的內存訪問性能
void compare_page_performance() {
// 1. 使用普通4KB頁訪問
access_memory_pages(NORMAL_PAGE_SIZE, "普通頁");
// 2. 使用2MB大頁訪問
access_memory_pages(HUGE_PAGE_SIZE, "大頁");
}
};
// 系統大頁配置模擬(對應Linux sysctl配置)
void configure_huge_pages(uint64_t hugepage_count) {
std::cout << "配置系統大頁內存:" << std::endl;
std::cout << "1. 修改/etc/sysctl.conf,設置vm.nr_hugepages = " << hugepage_count << std::endl;
std::cout << "2. 執行sysctl -p使配置生效" << std::endl;
std::cout << "3. 掛載大頁內存文件系統:mount -t hugetlbfs hugetlbfs /dev/hugepages" << std::endl;
std::cout << "系統已預留 " << hugepage_count << " 個2MB大頁,總預留大頁內存:"
<< (hugepage_count * 2) << "MB" << std::endl;
}
int main() {
// 1. 配置大頁內存(模擬系統參數設置)
uint64_t required_hugepages = DB_DATA_SIZE / HUGE_PAGE_SIZE; // 2GB數據需要1024個2MB大頁
configure_huge_pages(required_hugepages);
// 2. 模擬數據庫內存訪問,對比普通頁與大頁性能
DatabaseMemoryAccess db_access;
db_access.compare_page_performance();
return 0;
}編譯運行:
# 編譯(Linux環境,需C++11及以上)
g++ -std=c++11 hugepage_perf.cpp -o hugepage_perf
# 運行
./hugepage_perf執行流程輸出示例:
配置系統大頁內存:
1. 修改/etc/sysctl.conf,設置vm.nr_hugepages = 1024
2. 執行sysctl -p使配置生效
3. 掛載大頁內存文件系統:mount -t hugetlbfs hugetlbfs /dev/hugepages
系統已預留 1024 個2MB大頁,總預留大頁內存:2048MB
=== 普通頁(4KB)===
數據總量:2048MB,總頁數:524288
內存訪問耗時:892微秒
TLB命中次數:19,未命中次數:9981
TLB命中率:0.19%
=== 大頁(2048KB)===
數據總量:2048MB,總頁數:1024
內存訪問耗時:125微秒
TLB命中次數:9876,未命中次數:124
TLB命中率:98.76%案例二:頁表優化
優化頁表結構和管理策略也能提升性能 。在一些對實時性要求較高的嵌入式系統中,如工業控制系統 ,系統需要快速響應外部事件 。如果頁表結構不合理,在進程切換或內存訪問時,可能會導致較長的延遲 。通過優化頁表,可以減少這種延遲 。例如,采用預分頁(Pre - Paging)技術,在進程啟動或執行某些操作之前,提前將可能訪問的頁面加載到內存中,并更新頁表 。假設一個工業控制程序在響應外部傳感器信號時,需要讀取特定內存區域的數據 。如果沒有預分頁,當信號到來時,才發現所需頁面不在內存中,就會觸發缺頁中斷,導致處理信號的延遲 。而通過預分頁,提前將相關頁面加載到內存并更新頁表,當信號到來時,系統可以快速訪問內存中的數據,及時響應外部事件 ,提高了系統的實時性 。還可以對頁表項的訪問權限設置進行優化,根據不同的內存區域和訪問需求,合理設置讀寫權限、執行權限等,避免不必要的權限檢查開銷 ,進一步提升系統性能 。
#include
<iostream>
#include
<cstdint>
#include
<unordered_map>
#include
<chrono>
#include
<vector>
#include
<bitset>
// 內存頁相關常量定義
const uint64_t PAGE_SIZE = 4 * 1024; // 頁大小:4KB
const uint64_t SENSOR_DATA_REGION = 0x100000; // 傳感器數據內存區域起始地址
const uint64_t REGION_SIZE = 16 * 1024; // 傳感器數據區域大小:16KB(4個頁)
// 頁表項結構體(含權限控制)
struct PageTableEntry {
uint64_t physical_addr; // 物理頁地址
bool present; // 頁面是否在內存中(缺頁判斷)
// 權限位:bit0-讀,bit1-寫,bit2-執行
std::bitset<3> permissions;
PageTableEntry() : physical_addr(0), present(false), permissions(0) {}
PageTableEntry(uint64_t addr, uint8_t perm)
: physical_addr(addr), present(true), permissions(perm) {}
};
// 頁表管理器(含預分頁、權限優化)
class PageTableManager {
private:
// 頁表:虛擬頁號 -> 頁表項
std::unordered_map<uint64_t, PageTableEntry> page_table;
uint64_t next_phys_page = 0; // 下一個可用物理頁框號
// 計算虛擬地址對應的虛擬頁號
uint64_t get_virtual_page_num(uint64_t vaddr) {
return vaddr / PAGE_SIZE;
}
// 缺頁中斷處理(耗時操作)
void handle_page_fault(uint64_t vpage) {
// 模擬缺頁中斷的磁盤IO和頁表更新耗時
std::this_thread::sleep_for(std::chrono::microseconds(500));
// 分配物理頁并更新頁表
uint64_t phys_addr = (next_phys_page++) * PAGE_SIZE;
page_table[vpage] = PageTableEntry(phys_addr, 0b100); // 默認只讀權限
std::cout << "缺頁中斷處理完成:虛擬頁" << vpage << "加載到物理頁" << next_phys_page - 1 << std::endl;
}
public:
// 預分頁:提前加載指定內存區域的頁面到內存
void pre_paging(uint64_t start_addr, uint64_t size) {
std::cout << "\n執行預分頁操作:提前加載傳感器數據區域(0x" << std::hex << start_addr << "-0x"
<< start_addr + size << std::dec << ")" << std::endl;
uint64_t start_vpage = get_virtual_page_num(start_addr);
uint64_t page_count = size / PAGE_SIZE;
for (uint64_t i = 0; i < page_count; ++i) {
uint64_t vpage = start_vpage + i;
// 分配物理頁并更新頁表(提前加載)
uint64_t phys_addr = (next_phys_page++) * PAGE_SIZE;
// 優化權限:傳感器數據僅需讀權限,關閉寫/執行權限
page_table[vpage] = PageTableEntry(phys_addr, 0b100);
std::cout << "預加載虛擬頁" << vpage << "到物理頁" << next_phys_page - 1
<< ",權限:只讀" << std::endl;
}
}
// 內存訪問(含權限檢查)
uint64_t access_memory(uint64_t vaddr, uint8_t access_type) {
uint64_t vpage = get_virtual_page_num(vaddr);
auto start_time = std::chrono::high_resolution_clock::now();
// 1. 檢查頁面是否存在(缺頁判斷)
if (page_table.find(vpage) == page_table.end() || !page_table[vpage].present) {
std::cout << "訪問虛擬地址0x" << std::hex << vaddr << std::dec << "觸發缺頁中斷" << std::endl;
handle_page_fault(vpage);
}
// 2. 權限檢查(優化:僅檢查必要權限,無冗余判斷)
PageTableEntry& pte = page_table[vpage];
if (!(pte.permissions.test(access_type))) {
std::cerr << "權限不足:虛擬頁" << vpage << "不允許"
<< (access_type == 0 ? "讀" : access_type == 1 ? "寫" : "執行") << "操作" << std::endl;
return 0;
}
// 3. 計算物理地址并返回
uint64_t offset = vaddr % PAGE_SIZE;
uint64_t phys_addr = pte.physical_addr + offset;
auto end_time = std::chrono::high_resolution_clock::now();
auto cost = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();
std::cout << "訪問虛擬地址0x" << std::hex << vaddr << "→物理地址0x" << phys_addr
<< std::dec << ",耗時:" << cost << "微秒" << std::endl;
return phys_addr;
}
// 優化頁表權限:批量更新指定區域權限
void optimize_permissions(uint64_t start_addr, uint64_t size, uint8_t perm) {
std::cout << "\n優化內存區域權限:0x" << std::hex << start_addr << "-0x"
<< start_addr + size << std::dec << ",權限設置為:"
<< (perm == 0b100 ? "只讀" : perm == 0b110 ? "讀寫" : "執行") << std::endl;
uint64_t start_vpage = get_virtual_page_num(start_addr);
uint64_t page_count = size / PAGE_SIZE;
for (uint64_t i = 0; i < page_count; ++i) {
uint64_t vpage = start_vpage + i;
if (page_table.find(vpage) != page_table.end()) {
page_table[vpage].permissions = perm;
}
}
}
};
// 工業控制系統:響應傳感器信號
class IndustrialControlSystem {
private:
PageTableManager pt_mgr;
// 處理傳感器信號(實時性要求高)
void process_sensor_signal(uint64_t data_addr) {
std::cout << "\n=== 處理外部傳感器信號 ===" << std::endl;
// 讀取傳感器數據(訪問指定內存地址)
pt_mgr.access_memory(data_addr, 0); // 0=讀權限
}
public:
// 系統初始化(含預分頁配置)
void init() {
// 步驟1:預分頁加載傳感器數據區域
pt_mgr.pre_paging(SENSOR_DATA_REGION, REGION_SIZE);
// 步驟2:優化頁表權限(按需調整)
pt_mgr.optimize_permissions(SENSOR_DATA_REGION, REGION_SIZE, 0b100);
}
// 模擬傳感器信號觸發
void trigger_sensor_signal() {
// 場景1:預分頁后訪問(無缺頁中斷)
process_sensor_signal(SENSOR_DATA_REGION + 0x123);
// 場景2:訪問非預分頁區域(觸發缺頁中斷)
process_sensor_signal(0x200000 + 0x456);
}
};
int main() {
IndustrialControlSystem ics;
// 系統初始化:預分頁+權限優化
ics.init();
// 模擬外部傳感器信號觸發
ics.trigger_sensor_signal();
return 0;
}編譯運行:
# 編譯(嵌入式/Linux環境,需C++11及以上)
g++ -std=c++11 pagetable_optimization.cpp -o pagetable_opt -lpthread
# 運行
./pagetable_opt執行流程輸出示例:
執行預分頁操作:提前加載傳感器數據區域(0x100000-0x110000)
預加載虛擬頁64到物理頁0,權限:只讀
預加載虛擬頁65到物理頁1,權限:只讀
預加載虛擬頁66到物理頁2,權限:只讀
預加載虛擬頁67到物理頁3,權限:只讀
優化內存區域權限:0x100000-0x110000,權限設置為:只讀
=== 處理外部傳感器信號 ===
訪問虛擬地址0x100123→物理地址0x123,耗時:1微秒
=== 處理外部傳感器信號 ===
訪問虛擬地址0x200456觸發缺頁中斷
缺頁中斷處理完成:虛擬頁128加載到物理頁4
訪問虛擬地址0x200456→物理地址0x4456,耗時:502微秒

























