Chapter-1 Memory Management (section 1.1-1.5)
參考了 《打通 Linux 操作系統(tǒng)和芯片開發(fā)》 書籍的內(nèi)容,實(shí)際也可以說是完全參照加上了個(gè)人的拙見或者是讀書記錄。
和我上一篇說的一樣,我依然還是一個(gè)初學(xué)者,記錄這些是自己梳理,以及想讓文字發(fā)揮一些作用和意義。涉及到代碼的部分實(shí)在是非常非常的枯燥無(wú)味和無(wú)聊,并且由于 Linux 中函數(shù)的分層很多,call stack 特別深,函數(shù)名稱特別相似,
非常容易頭暈眼花了,所以還是應(yīng)該采取從宏觀角度的,抓住關(guān)鍵的函數(shù)調(diào)用鏈來(lái)進(jìn)行分析和理解,不應(yīng)該要求細(xì)節(jié)到某一行代碼的程度,
否則陷入難解的困境了。
操作系統(tǒng)為什么需要內(nèi)存管理?
這應(yīng)該是一個(gè)很經(jīng)典的問題,內(nèi)存池 (Memory Pool) 也可以認(rèn)為是一種內(nèi)存管理的方式,所以關(guān)于內(nèi)存管理四個(gè)字有點(diǎn)像謎底就在謎面上,更多的只是你如何管理的方式。
比如 FreeRTOS 中的好幾種分配方式,常用的只是 heap_4.c 的方式,這種用在 MCU 上的方式可以比較簡(jiǎn)單,而對(duì)于現(xiàn)代的 2025 年的 MCU 可能依然還是比較小的內(nèi)存,
至少?zèng)]有上升到 4GB ,至少我還沒接觸到。并且芯片性能可能不強(qiáng),無(wú)法負(fù)責(zé)和管理這么多的內(nèi)存(后面出現(xiàn)了一個(gè)東西專門輔助此工作),所以操作系統(tǒng)采取了其他的方式來(lái)管理。
MCU 跑的都是在很小的內(nèi)存中,大部分直接都是訪問了物理地址,
所以簡(jiǎn)單的說為什么的原因,就是為了更好的利用和使用內(nèi)存 這個(gè)相對(duì)比較快速的可以存數(shù)據(jù)的東西,才出現(xiàn)了內(nèi)存管理的各種方式,一般在操作系統(tǒng)課程中都會(huì)提到在演進(jìn)中出現(xiàn)的:
- 分段機(jī)制(Segmentation)
- 分頁(yè)機(jī)制(Paging)
兩種經(jīng)典的方式,也可能聽到 段頁(yè)式 就是兩種合并在一起說的。
但 Linux 采取的是分頁(yè)的機(jī)制,同時(shí)根據(jù)書中描述是 四級(jí)頁(yè)表 的形式,關(guān)于分段、分頁(yè)的一些說明和概述及原理性這里就不詳細(xì)說明,可以詢問 ChatGPT 或是查看其他的文章,
這里僅添加一些可能重要的名詞:
| 名詞 | 翻譯 |
|---|---|
| 換入 | Swapping In |
| 換出 | Swapping Out |
| 頁(yè)面 | Page |
| 物理頁(yè)面 | Physical Page |
| 頁(yè)幀 | Page Frame |
| 頁(yè)幀號(hào) | Page Frame Number (PFN) |
| 虛擬頁(yè)幀號(hào) | Virtual Page Frame Number (VPN) |
| 物理頁(yè)幀號(hào) | Physical Page Frame Number (PFN) |
| 虛擬頁(yè)面 | Virtual Page |
| 頁(yè)表 | Page Table (PT) |
| 頁(yè)表項(xiàng) | Page Table Entry (PTE) |
| 內(nèi)存管理單元 | Memory Management Unit (MMU) |
| 虛擬地址 | Virtual Address |
| 物理地址 | Physical Address |
| 轉(zhuǎn)譯后備緩沖器(快表) | Translation Lookaside Buffer (TLB) |
| 頁(yè)全局目錄 | Page Global Direcotry (PGD) |
| 頁(yè)上級(jí)目錄 | Page Upper Directory (PUD) |
| 頁(yè)中間目錄 | Page Middle Direcotry (PMD) |
| 頁(yè)表 | Page Table (PTE) 上面出現(xiàn)了但含義不一樣 這里主要指Linux中多級(jí)頁(yè)表的 |
| 頁(yè)內(nèi)偏移 | Page offset |
上面提到了一個(gè)專門輔助 Linux 做內(nèi)存管理的東西就是 MMU 了(用來(lái)將虛擬地址轉(zhuǎn)換成物理地址),現(xiàn)代的芯片一般都是將 MMU 內(nèi)置在芯片中了,這是 Linux 運(yùn)行的必備條件,所以 區(qū)別一個(gè)芯片能不能運(yùn)行 Linux 系統(tǒng),就是芯片有沒有 MMU 這個(gè)模塊了。 (當(dāng)然現(xiàn)在也可以不用 MMU 也能運(yùn)行 Linux 了只不過是功能受限
關(guān)于 MMU 如何尋址,如何管理,以及頁(yè)表的映射和使用的過程,這里不多贅述,感興趣的可以找操作系統(tǒng)相關(guān)課程學(xué)習(xí),或者找 408 相關(guān)的學(xué)習(xí)視頻參考。
另外重點(diǎn)是 Linux 一個(gè)頁(yè)面的大小為 4KB 至于為什么是 4KB 的大小,AI 給出的答案是歷史原因;還有就是想要運(yùn)行 Linux 就需要 MMU 這個(gè)模塊;
Linux 內(nèi)存架構(gòu)和模型略過,對(duì)理解關(guān)系不大不太重要
一級(jí)、二級(jí)頁(yè)表映射過程
感覺實(shí)在是有必要的畫一個(gè)一級(jí)、二級(jí)的頁(yè)表映射的過程
二級(jí)頁(yè)表只是在一級(jí)的基礎(chǔ)上做了修改,添加了一個(gè)對(duì)一級(jí)頁(yè)表的索引表,這樣能對(duì)應(yīng)的一級(jí)頁(yè)表項(xiàng)就會(huì)更多了,而 Linux 用了四級(jí)來(lái)管理,數(shù)量就不計(jì)算了,同時(shí)這只是大致的示意,不代表就是這樣的尋址。
對(duì)于 Linux 的多級(jí)頁(yè)表管理不準(zhǔn)備畫圖了,只不過是更多級(jí),更復(fù)雜,更多控制位,更長(zhǎng)的地址長(zhǎng)度,但基本的方式是一樣的。
簡(jiǎn)單說明就是首先將一個(gè)包含了虛擬頁(yè)幀號(hào)的虛擬地址(線性地址)通過一系列查表的方式(查表的這個(gè)動(dòng)作也可以是 MMU 在執(zhí)行)轉(zhuǎn)換成一個(gè)包含了物理頁(yè)幀號(hào)的物理地址,(這里假設(shè)用到了 TLB ) 然后 MMU 通過查 TLB 快速的知道了物理頁(yè)幀號(hào)與內(nèi)存的某一塊位置的對(duì)應(yīng)關(guān)系,
然后就使用一下偏移量,在這一頁(yè)中的偏移多少,就知道了這個(gè)虛擬地址的數(shù)據(jù)內(nèi)容是多少了。
另外在這個(gè)過程中可能會(huì)產(chǎn)生一個(gè) 缺頁(yè)中斷 (Page Fault) ,簡(jiǎn)單說明即是在代碼中使用 malloc 申請(qǐng)內(nèi)存空間是在虛擬地址空間中,此時(shí)隨便申請(qǐng),實(shí)際上物理的內(nèi)存條上對(duì)應(yīng)映射的空間并不存在,或者說并沒有數(shù)據(jù)內(nèi)容,
或者當(dāng)訪問的頁(yè)不在任何一個(gè)頁(yè)表中,這個(gè)時(shí)候出現(xiàn)了缺頁(yè)中斷,此時(shí)才會(huì)從存儲(chǔ)中加載到內(nèi)存中,或者是正式的分配內(nèi)存,這時(shí)候內(nèi)存條上就有了空間和數(shù)據(jù)內(nèi)容了,那么這正好也有頁(yè)的換入(Swaping In)和換出(Swaping Out)兩個(gè)動(dòng)作。
memblock 物理內(nèi)存的初始化
這部分有比較多的圖和代碼,太麻煩了,嘗試通過文字來(lái)簡(jiǎn)單的敘述看看
memblock 的作用
Linux 中通過 Buddy 伙伴系統(tǒng)和 slab 分配器來(lái)分配和管理內(nèi)存的,但是在此之前不可用的階段,就由 memblock 來(lái)承擔(dān)了初始化和管理的工作,所以自然的就想到這個(gè)階段的 memblock 直接就是訪問和管理的物理地址,
memblock 是唯一能做早期啟動(dòng)階段管理內(nèi)存的內(nèi)存分配器,由此出現(xiàn)了 early boot memory 階段的名稱,是系統(tǒng)啟動(dòng)中間階段的內(nèi)存管理,這里涉及的內(nèi)存模型不多贅述。
memblock 的數(shù)據(jù)結(jié)構(gòu)及代碼分析
書籍解析了代碼的結(jié)構(gòu),只是簡(jiǎn)單解析了各部分的字段含義,詳細(xì)可以直接讓 AI 生成,注釋內(nèi)容 Gemini 2.5 Flash 生成:
include/linux/memblock.h
struct memblock {
bool bottom_up; /* 是否是自底向上? */
phys_addr_t current_limit; /* 當(dāng)前限制地址 */
struct memblock_type memory; /* 可用內(nèi)存區(qū)域 */
struct memblock_type reserved; /* 保留內(nèi)存區(qū)域 */
};
struct memblock_type {
unsigned long cnt; /* 區(qū)域計(jì)數(shù) */
unsigned long max; /* 最大區(qū)域數(shù) */
phys_addr_t total_size; /* 總大小 */
struct memblock_region *regions; /* 區(qū)域數(shù)組 */
char *name; /* 類型名稱 */
};
struct memblock_region {
phys_addr_t base; /* 區(qū)域基地址 */
phys_addr_t size; /* 區(qū)域大小 */
enum memblock_flags flags; /* 區(qū)域標(biāo)志 */
#ifdef CONFIG_NUMA
int nid; /* NUMA節(jié)點(diǎn)ID */
#endif
};
enum memblock_flags {
MEMBLOCK_NONE = 0x0, /* 無(wú)特殊請(qǐng)求 */
MEMBLOCK_HOTPLUG = 0x1, /* 可熱插拔區(qū)域 */
MEMBLOCK_MIRROR = 0x2, /* 鏡像區(qū)域 */
MEMBLOCK_NOMAP = 0x4, /* 不添加到內(nèi)核直接映射 */
MEMBLOCK_DRIVER_MANAGED = 0x8, /* 總是通過驅(qū)動(dòng)檢測(cè) */
MEMBLOCK_RSRV_NOINIT = 0x10, /* 不初始化struct pages */
};
接著從 stark_kernel 入手,主要關(guān)注了 setup_arch 函數(shù),參數(shù)是 command_line,貼出函數(shù):
// 只給出相對(duì)重要的函數(shù)調(diào)用
arch/arm64/kernel/setup.c
void __init __no_sanitize_address setup_arch(char **cmdline_p)
{
setup_initial_init_mm(_stext, _etext, _edata, _end);
*cmdline_p = boot_command_line;
... ...
early_fixmap_init();
early_ioremap_init();
setup_machine_fdt(__fdt_pointer);
... ...
arm64_memblock_init();
paging_init();
... ...
bootmem_init();
... ...
}
Gemini 2.5 Flash:
-
setup_initial_init_mm- 初始化內(nèi)核的第一個(gè)內(nèi)存描述符
init_mm。 - 主要用于設(shè)置內(nèi)核代碼 (
_stext,_etext) 和數(shù)據(jù)段 (_edata,_end) 的內(nèi)存范圍。
- 初始化內(nèi)核的第一個(gè)內(nèi)存描述符
-
*cmdline_p = boot_command_line- 將系統(tǒng)啟動(dòng)時(shí)傳遞的命令行參數(shù) (
boot_command_line) 賦值給cmdline_p指針。 - 這使得后續(xù)的內(nèi)核組件能夠訪問和解析啟動(dòng)參數(shù)。
- 將系統(tǒng)啟動(dòng)時(shí)傳遞的命令行參數(shù) (
-
early_fixmap_init- 初始化早期固定映射(fixmap)區(qū)域。
- 用于在啟動(dòng)初期為特定的、固定地址的內(nèi)存區(qū)域建立虛擬地址映射,通常用于訪問一些關(guān)鍵的硬件寄存器或數(shù)據(jù)結(jié)構(gòu)。
-
early_ioremap_init- 初始化早期 I/O 內(nèi)存重映射機(jī)制。
- 允許內(nèi)核在啟動(dòng)初期安全地訪問和映射設(shè)備 I/O 空間,例如外設(shè)控制器的寄存器。
-
setup_machine_fdt(__fdt_pointer)- 根據(jù)設(shè)備樹(Flattened Device Tree, FDT)設(shè)置機(jī)器相關(guān)的參數(shù)和配置。
- 解析由
__fdt_pointer指向的設(shè)備樹,從中獲取硬件信息、設(shè)備配置等,以初始化系統(tǒng)。
-
arm64_memblock_init- 初始化 ARM64 架構(gòu)的內(nèi)存塊(memblock)管理機(jī)制。
- 用于在內(nèi)核啟動(dòng)早期跟蹤和管理物理內(nèi)存區(qū)域,包括可用內(nèi)存和保留內(nèi)存。
-
paging_init- 初始化頁(yè)表和內(nèi)存分頁(yè)機(jī)制。
- 建立將物理內(nèi)存映射到虛擬地址空間的頁(yè)表結(jié)構(gòu),這是現(xiàn)代操作系統(tǒng)內(nèi)存管理的基礎(chǔ)。
-
bootmem_init- 初始化 bootmem 分配器。
- 這是一個(gè)簡(jiǎn)單的物理內(nèi)存分配器,在內(nèi)核啟動(dòng)的早期階段(分頁(yè)機(jī)制剛建立,但更復(fù)雜的內(nèi)存管理系統(tǒng)尚未完全初始化時(shí))用于分配物理內(nèi)存。
接著繼續(xù)分析了 setup_machine_fdt 函數(shù):
static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
int size;
void *dt_virt = fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL);
const char *name;
if (dt_virt)
memblock_reserve(dt_phys, size);
if (!early_init_dt_scan(dt_virt, dt_phys)) {
pr_crit("\n"
"Error: invalid device tree blob at physical address %pa (virtual address 0x%px)\n"
"The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
"\nPlease check your bootloader.",
&dt_phys, dt_virt);
while (true)
cpu_relax();
}
/* Early fixups are done, map the FDT as read-only now */
fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);
name = of_flat_dt_get_machine_name();
if (!name)
return;
pr_info("Machine model: %s\n", name);
dump_stack_set_arch_desc("%s (DT)", name);
}
該函數(shù)主要功能是:
- 拿到 DTB 的物理地址后,會(huì)通過
fixmap_remap_fdt()進(jìn)行映射,其中包括pgd、pud、pte等映射(書中這部分是否漏了pmd?),當(dāng)映射完成后會(huì)返回dt_virt,并通過memblock_reserve()添加到memblock.reserved中。 early_init_dt_scan()通過解析DTB文件的memory節(jié)點(diǎn)獲得可用物理內(nèi)存的起始地址和大小,并通過類memblock_add的 API 向memory.regions數(shù)組添加一個(gè)memblock.region實(shí)例,用于管理這個(gè)物理內(nèi)存的區(qū)域。
接著是 arm64_memblock_init 函數(shù),其主要工作是將物理內(nèi)存進(jìn)行整理,將一些特殊區(qū)域添加到 reserved 內(nèi)存中,主要是設(shè)備樹中的:chosen, chosen(cma), reserved-memory, /memreserve, chosen(initrd) 節(jié)點(diǎn)。
這部分的代碼工作大體將物理內(nèi)存進(jìn)行了分區(qū)和簡(jiǎn)單的管理,后續(xù)需要進(jìn)行重要的 內(nèi)存頁(yè)表映射 完成物理地址到虛擬地址的映射,書中說系統(tǒng)完成初始化之后,所有的工作會(huì)移交給 Buddy 系統(tǒng)來(lái)進(jìn)行內(nèi)存管理。
—— juezhong 乙巳年丙戌月戊辰日 戌時(shí)
「夫人神好清,而心擾之,人心好靜,而慾牽之。」
本文來(lái)自博客園,作者:縱然似夢(mèng),轉(zhuǎn)載請(qǐng)注明原文鏈接:http://www.rzrgm.cn/juezhong/p/19167298
浙公網(wǎng)安備 33010602011771號(hào)