簡體   English   中英

kernel 如何知道物理 memory 基地址?

[英]How does kernel know physical memory base address?

我試圖了解 2 個密切相關的問題。

  1. Kernel 代碼在引導加載程序后和啟用 MMU 之前在物理/身份映射虛擬 memory 中運行。 這段代碼如何在不同的 CPU 之間移植,可能在不同的物理地址范圍內有 DRAM?

  2. For the kernel to manage the page table, it needs some awareness of what physical memory resources are available, including the physical memory base address and available physical memory, so it doesn't assign physical addresses that are out of DRAM range.

我想這在某種程度上依賴於實現,但對不同架構如何處理這個問題的參考將不勝感激。 到目前為止我的一些想法:

  1. 物理地址 DRAM 范圍,或者至少是基地址,在 kernel 編譯時被烘焙。 這意味着即使使用相同的 ISA,不同的 CPU 也需要重新編譯。 這是受到此答案啟發,如果我理解正確,它描述了 kernel 基地址的相同解決方案。 由於基地址在編譯時已知,因此 kernel 代碼引用文字地址而不是與 DRAM/kernel 基地址的偏移量。

  2. DRAM 信息通過物理 memory map 的 rest 從設備樹中讀取和學習。 這是我對至少 Xilinx Zynq SoC 的印象,基於這樣的論壇帖子。 雖然這個解決方案提供了更大的靈活性,並且允許我們只重新編譯引導加載程序而不是將整個 kernel 重新編譯到端口 CPU,但它確實讓我想知道我的 X86 個人計算機如何在運行時檢測我安裝了多少 DRAM。 管理頁表的代碼僅引用與 DRAM 基地址的偏移量,並且可移植,無需跨具有不同 DRAM 物理地址范圍的 CPU 重新編譯。

在啟動時可用的整個物理 memory DIMM 可能不會而且通常不會映射到物理 memory 地址空間的單個連續范圍,因此沒有“基地址”。 在硬重置時,在 CPU 固件完成執行后,將執行平台固件,通常是舊版 BIOS 或 UEFI。 給定的主板僅與一組有限的 CPU collections 兼容,這些 CPU 通常具有相同的方法來發現物理 memory,包括 DIMM 和平台固件 memory 設備。 平台固件的實現使用此方法構建 memory 描述條目表,其中每個條目描述物理 memory 地址范圍。 有關此處理器外觀的更多信息,請參閱: BIOS 如何初始化 DRAM? . 此表存儲在主 memory (DIMM) 中的一個地址,這些地址已知為此目的而保留,並且應該由實際的 memory 支持(系統可以在沒有任何 DIMM 的情況下啟動)。

自 90 年代中期以來,x86 PC BIOS 的大多數實現都提供實模式INT 15h E820h function(15h 是中斷號,E820h 是在AX寄存器中傳遞的參數)。 這是一個特定於供應商的 BIOS function 在 PhoenixBIOS v4.0(1992-1994,我無法確定確切的年份)中首次引入,后來被其他 BIOS 供應商采用。 該接口由 1996 年發布的 ACPI 1.0 規范和后來的 PhoenixBIOS 版本支持 ACPI 進行了擴展。 對應的 UEFI 接口是GetMemoryMap() ,它是 UEFI 啟動時服務(意味着它只能在 UEFI 規范中定義的啟動時調用)。 kernel 可以使用這些接口之一來獲取地址 map 在所有 NUMA 節點上描述 memory。 x86 平台上的其他(舊)方法在檢測 Memory (x86)中討論。 ACPI 規范都以版本開頭? 和 UEFI 規范從版本開始? 支持 DRAM DIMM 和 NVDIMM memory 范圍類型。

例如,考慮 ACPI 兼容的 Linux kernel 如何確定 Z8A9DA7865483C5FD359F3ACEF178D26 平台上可用(即,由實際內存支持)和可用(即,免費)的物理地址范圍。 BIOS 固件將引導加載程序從指定的可引導存儲設備加載到專用於此目的的 memory 位置。 固件執行完成后,跳轉到引導加載程序,該引導加載程序將在存儲介質上找到 kernel 映像,將其加載到 memory,並將控制權移交給 kernel。 引導加載程序本身需要知道當前的 memory map 並為其操作分配一些 memory。 它試圖通過調用E820h function 來獲取 memory map,如果不支持,它將求助於較舊的 PC BIOS 接口。 kernel 引導協議定義了引導加載程序可以使用哪些 memory 范圍以及哪些 memory 范圍必須為 Z538484C0D821EDFDAF1A 保留可用。

引導加載程序本身不會修改 memory map 或將 map 提供給 Z50484C19F21AFDAF3842ZA0D8。 Instead, when the kernel starts executing, it calls the E820h function and passes to it a 20-bit pointer (in ES:DI ) to a buffer that the kernel knows to be free on x86 platforms according to the boot protocol. 每次調用都會返回一個 memory 范圍描述符,其大小至少為 20 字節。 有關詳細信息,請參閱 ACPI 規范的最新版本。 大多數 BIOS 實現都支持 ACPI。

假設 Linux kernel 具有上游默認引導參數,您可以使用命令dmesg | grep 'BIOS-provided\|e820' dmesg | grep 'BIOS-provided\|e820'查看返回的 memory 范圍描述符表。 在我的系統上,它看起來像這樣:

[    0.000000] BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x00000000000917ff] usable
[    0.000000] BIOS-e820: [mem 0x0000000000091800-0x000000000009ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000000e0000-0x00000000000fffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000d2982fff] usable
[    0.000000] BIOS-e820: [mem 0x00000000d2983000-0x00000000d2989fff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x00000000d298a000-0x00000000d2db9fff] usable
[    0.000000] BIOS-e820: [mem 0x00000000d2dba000-0x00000000d323cfff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000d323d000-0x00000000d7eeafff] usable
[    0.000000] BIOS-e820: [mem 0x00000000d7eeb000-0x00000000d7ffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000d8000000-0x00000000d875ffff] usable
[    0.000000] BIOS-e820: [mem 0x00000000d8760000-0x00000000d87fffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000d8800000-0x00000000d8fadfff] usable
[    0.000000] BIOS-e820: [mem 0x00000000d8fae000-0x00000000d8ffffff] ACPI data
[    0.000000] BIOS-e820: [mem 0x00000000d9000000-0x00000000da718fff] usable
[    0.000000] BIOS-e820: [mem 0x00000000da719000-0x00000000da7fffff] ACPI NVS
[    0.000000] BIOS-e820: [mem 0x00000000da800000-0x00000000dbe11fff] usable
[    0.000000] BIOS-e820: [mem 0x00000000dbe12000-0x00000000dbffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000dd000000-0x00000000df1fffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000f8000000-0x00000000fbffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fec00000-0x00000000fec00fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fed00000-0x00000000fed03fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fed1c000-0x00000000fed1ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000ff000000-0x00000000ffffffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000041edfffff] usable
[    0.002320] e820: update [mem 0x00000000-0x00000fff] usable ==> reserved
[    0.002321] e820: remove [mem 0x000a0000-0x000fffff] usable
[    0.002937] e820: update [mem 0xdd000000-0xffffffff] usable ==> reserved
[    0.169287] e820: reserve RAM buffer [mem 0x00091800-0x0009ffff]
[    0.169288] e820: reserve RAM buffer [mem 0xd2983000-0xd3ffffff]
[    0.169289] e820: reserve RAM buffer [mem 0xd2dba000-0xd3ffffff]
[    0.169289] e820: reserve RAM buffer [mem 0xd7eeb000-0xd7ffffff]
[    0.169289] e820: reserve RAM buffer [mem 0xd8760000-0xdbffffff]
[    0.169290] e820: reserve RAM buffer [mem 0xd8fae000-0xdbffffff]
[    0.169291] e820: reserve RAM buffer [mem 0xda719000-0xdbffffff]
[    0.169291] e820: reserve RAM buffer [mem 0xdbe12000-0xdbffffff]
[    0.169292] e820: reserve RAM buffer [mem 0x41ee00000-0x41fffffff]

該表中描述了以“BIOS-e820”開頭的 memory 范圍。 第一行清楚地告訴你這些信息的來源。 此信息的確切格式取決於 Linux kernel 版本。 在任何情況下,您都會在每個條目中看到一個范圍和一個類型。 以“e820”開頭的行(沒有“BIOS-”部分)是 kernel 本身對表所做的更改。 E820h的實現可能有問題,或者在不同條目中獲得的范圍之間可能存在重疊。 kernel 執行必要的檢查並進行相應的更改。 kernel 可以免費使用標記為“可用”的范圍,但 ACPI 規范中討論的例外情況以及 kernel 知道的例外情況除外。 絕大多數 PC BIOS 實現最多返回 128 個 memory 范圍描述符。 舊版本的 Linux kernel 最多只能處理 128 個 memory 范圍,因此從第 128 個E820h返回的任何條目都將被忽略。 從 version? 開始,這個限制被放寬了。 有關更多信息,請參閱 kernel 系列補丁,標題為“x86 boot: pass E820 memory map entries more than 128 via setup data.”鏈接列表。

usable類型和ACPI data的范圍。 reserved的類型范圍由 DRAM DIMM 支持,或者由 CPU 或平台固件為 MMIO 截斷。 ACPI NVS類型的范圍由固件 memory 支持。 據固件所知,實際 memory 並未支持所有其他范圍。 請注意,固件可能會選擇不對所有已安裝的 DRAM DIMM 或 NVDIMM 進行 map。 如果不支持物理 memory 配置,或者如果固件由於 DIMM 中的問題而無法從已安裝的 DIMM 獲取信息,則可能會發生這種情況。

您可以計算出安裝的 DRAM DIMM 和 NVDIMM 中有多少 memory 可通過固件提供給 kernel。 在我的系統上,我安裝了 16 GB 的 DRAM DIMM。 因此,除非某些 DIMM 安裝不正確、運行不正常、固件中存在錯誤,或者平台或處理器不支持,否則 kernel 的可用空間應該少於 16 GB。

所有usable范圍加起來為 0x3FA42B800 字節。 請注意,范圍的最后一個地址是包含在內的,這意味着它指向一個字節位置,該位置是該范圍的一部分。 物理安裝的 DIMM 總量為 16 GB 或 0x400000000 字節。 因此,對於 kernel 不可用的已安裝 memory 的總量為 0x400000000 - 0x3FA42B800 或總共 16 GB 的大約 92 MB。 這個 memory 被一些reserved范圍和所有ACPI data范圍占用。 如果 DRAM DIMM 或 NVDIMM 中的某些位置被平台固件確定為不可靠,它們也將被截斷為reserved

請注意,根據 ACPI 規范, E820 memory map 中沒有描述范圍 0x000a0000-0x000fffff。 這是 640KB-1MB 上部 memory 區域。 kernel 打印一條消息,表示它已從可用的 memory 區域中刪除了此范圍,以保持與古代系統的兼容性。

此時,尚未分配用於大多數 PCIe 設備的 MMIO 的 memory。 我的處理器支持 39 位物理地址空間,這意味着 0 到 2^39 之間的地址可用於映射。 到目前為止,只有這個空間的最底部 16.5 GB 被映射到某個東西。 請注意,此范圍內仍有未映射的差距。 kernel可以利用這些間隙(幾百MB)和物理地址空間的rest(約495.5GB)為ZCF3882F1C43AB22BFF2DB0BD9D8設備分配地址范圍。 kernel 最終會發現 PCIe 設備,並且對於每個設備,如果可用,它將嘗試加載兼容的驅動程序。 The driver then determines how much memory the device needs and any restrictions on the memory addresses imposed by the device and request from the kernel to allocate memory for the device and configure it as an MMIO memory owned by the device. 您可以使用命令sudo cat /proc/iomem查看最終的 memory map。

在某些情況下,您需要手動更改現有 memory 范圍的 memory 類型(例如,用於測試),創建一個新范圍(例如, 用於模擬持久性 ZCD69B4957F06CD818D7BF3D61980E2或如果固件無法發現所有available memory for whatever reason), reduce the amount of memory usable by the kernel (eg, to prevent a bare-metal hypervisor for using memory beyond a limit and make the rest available for guests), or even completely override the entire table returned from E820h memmemmap kernel 參數可用於此類目的。 當這些參數中的一個或多個指定為有效值時,kernel 將首先讀取 BIOS 提供的 memory map 並進行相應更改。 kernel 會將最終的 memory map 打印為“用戶定義的物理 RAM Z1D78DC8ED51214E5018B5114”。 在 kernel 消息環形緩沖區中。 您可以使用dmesg | grep user:查看這些消息。 dmesg | grep user:每個 memory 范圍行以“用戶:”開頭)。 這些消息將在“BIOS-e820”消息之后打印。

On an x86 platform booted with UEFI firmware that supports the Compatibility Support Module (refer to the CSM specification for more information, which is separate from UEFI), the legacy real-mode E820h interface is supported and the Linux kernel by default still uses it. 如果內核運行在具有不支持 CSM 的 UEFI 的 x86 平台上,則E820h接口可能無法提供所有或任何 memory 范圍。 在此類平台上可能需要使用add_efi_memmap kernel 參數。 可以在UEFI Memory V E820 Memory中找到一個示例。 當從GetMemoryMap()提供一個或多個 memory 范圍時,kernel 會將這些范圍與來自E820h接口的范圍合並。 生成的 memory map 可以使用dmesg | grep 'efi:'查看 dmesg | grep 'efi:' Another UEFI-related kernel parameter that affects the memory map is efi_fake_mem .

當 IO 或 DIMM 設備在任何 S 狀態下從系統中插入或移除時,ACPI 規范(第 6.3 節)提供通知機制以通知 kernel。 (不過,我不知道是否有任何主板支持在任何 S 狀態下移除 DIMM。這通常僅在 G3 state 和可能 S4 和/或 S5 中才有可能)發生此類事件時,kernel 或固件相應地對 memory map 進行更改。 這些更改反映在sudo cat /proc/iomem

pc 相對尋址是指一種編程技術,您的程序可以在任何地址上運行。 由於重定位寄存器(例如段)已經過時,大多數 pc 相關編程都是顯式執行的。 這是通用機器代碼中的示例:

.text
entry:
    call reloc  /* call is pc relative */
reloc:
    pop %r0     /* r0 now contains physical address of reloc */
    sub $reloc, %r0, %r14  /* r14 contains difference between link address of reloc */
/* At this point, r14 is a relocation register.  A virtual address + r14 == the corresponding physical address. */
    add $proot, %r14, %r0  /* physical address of page table root */
    add $entry, %r14, %r1  /* entry is where we were loaded into ram */
    test $0xfff, %r1   /* someone is being funny and not page aligning us */
    jnz bad_alignment
    or   $0x7, %r1     /* put mythical page protection bits in r1 */
    mov $1024, %r2     /* number of pages in r2 */
loop:
    store %r1, (%r0)   /* store a page table entry */
    add $0x1000, %r1   /* setup next one 4096 bytes farther */
    add $4, %r0        /* point to next page table entry */
    sub $1, r2         /* are we done? */
    cmp %0, r2
    jne loop           /* nope, setup next entry */
    add $proot, %r14, %r0
    loadsysreg %r0, page_table_base_register
    mov $1, %r0
    mov $v_entry, %r1
    loadsysreg %r0, page_table_enabled
    jmp %r1
v_entry:
        /* now we are virtually addressed */
    call main
1:  jmp 1b   /* main shouldn't return. */


.data
.align 12   /* 4096 byte pages */
proot:
.zero 4096
.text

這個神話機器非常簡單,只有一個平面頁表,kernel 鏈接在地址 0,但可以從前 4M(1024 * 4096)中的任何位置運行。 真正的機器只是這個的更詳細的版本。 通常,在您完成初始地址空間設置之前,您甚至無法信任C之類的系統語言。 一旦它是,其中的代碼可以構建更復雜的頁表,並查詢設備樹等數據庫,甚至像 apic/uefi 這樣的怪物,以獲取有關 ram 布局等的更多信息。

在內部節點采用與葉節點兼容的格式(例如 x86-classic)的前向映射頁表體系結構中,您可以遞歸地使用單個頁表以允許更靈活的鏈接地址。 例如,如果您將 proot 中的最后一個條目(即 proot[1023])指向 proot,那么您可以將您的操作系統鏈接到 0xffffc000,並且此代碼將正常工作(一旦轉換為 x86)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM