上篇分析了RISC-V Linux啟動的頁表創建,提到RISC-V Linux入口地址必須2M對齊,今天講講如何解決2M對齊的問題,或者說如何優化部分內存。
注意:本文基於linux5.10.111核心
#每顆晶片在出廠時,其bootrom就已經固化在晶片內部,假設bootrom的位址是0x0,即上電後,會從0x0位址開始運作程式。
在啟動RISC-V Linux之前,需要先運行opensbi,因此應該把opensbi放到位址0x0
處,這樣晶片上電後,就會從0x0
地址處執行opensbi。 opensbi運行完後,會跳到opensbi運行位址偏移2M的位置去執行下一級boot(這裡下一級boot是kernel),即跳到0x200000
位址處運行kernel,因此應該把kernel放到記憶體的0x200000
處。
記憶體分佈示意圖如下:
對於kernel來說,在啟動時會從自己的kernel載入位址處(即0x200000
)開始建立頁表映射,只有對物理記憶體建立了頁表映射,後面才能存取這些記憶體。而kernel載入位址前面的2M記憶體(即0x0 - 0x200000
)將被kernel忽略,不會對這2M記憶體建立頁表,即kernel無法存取這2M記憶體。
在QEMU上RISC-V Linux的啟動資訊:
#但opensbi實際上不需要使用2M這麼大的範圍,預設是512KB
,opensbi的pmp會保護這512KB
內存,不讓其他程式存取。
因此在Kernel和opensbi之間會存在1.5M
的記憶體空隙,而這部分記憶體空隙沒有程式使用,這就會造成記憶體浪費,那要如何讓kernel將前面的一部分記憶體也利用起來呢?
#對這2M記憶體的最佳化,有兩個方案:
#方案一:將opensbi放到記憶體的最後面,kernel入口位址仍保持2M對齊。
方案二:opensbi仍放到記憶體的起始位置,透過修改核心原始碼,解除2M對齊限制,即可將kernel位址往前移。
我們將opensbi放到記憶體的最後面,kernel入口位址仍然保持2M對齊。
即kernel放到記憶體的最前面,opensbi放到後面:
#例如kernel放到記憶體的0x0
位址處,opensbi放到記憶體的0x10000000
位址處。這樣kernel前面就不會有預留內存,只不過這樣需要修改bootrom的位址,將位址從0x0
修改為0x0x10000000
。這個方案只適合晶片還沒出廠前,因為用戶無法修改bootrom的位址,晶片出廠後,bootrom位址是固定的,假設bootrom位址為0x0
,那麼晶片上電後,就會從0x0
開始執行程序,所以opensbi必須放到0x0
位址處,這樣必然kernel只能往後偏移2M。
我們也可以修改RISC-V Linux的核心源碼,解除2M對齊的限制。我們只需要在setup_vm()
函數中,將原來的二級頁表改為三級頁表,這樣kernel入口位址只需要4K對齊,因此就能將kernel往前挪,從而利用前面的記憶。
路徑:arch/riscv/mm/init.c
註解原來的2M對齊檢查:
對kernel的前2M頁表映射由二級頁表改為三級頁表:
//新增一个PTE pte_t trampoline_pte[PTRS_PER_PTE] __page_aligned_bss; create_pgd_mapping(trampoline_pg_dir,PAGE_OFFSET, (uintptr_t)trampoline_pmd,PGDIR_SIZE,PAGE_TABLE); create_pmd_mapping(trampoline_pmd,PAGE_OFFSET, (uintptr_t)trampoline_pte,PMD_SIZE,PAGE_TABLE); end_va = PAGE_OFFSET + PMD_SIZE; for (va = PAGE_OFFSET; va < end_va; va += PAGE_SIZE) { create_pte_mapping(trampoline_pte,PAGE_OFFSET, load_pa + (va - PAGE_OFFSET), PAGE_SIZE,PAGE_KERNEL_EXEC); }
整個kernel的頁表映射由二級頁表改為三級頁表:
假設kernel大小為4M
//定义三个PTE pte_t load_sz_pte[PTRS_PER_PTE] __page_aligned_bss; pte_t load_sz_pte1[PTRS_PER_PTE] __page_aligned_bss; pte_t load_sz_pte2[PTRS_PER_PTE] __page_aligned_bss; //=======0-2M====== create_pgd_mapping(early_pg_dir,PAGE_OFFSET, (uintptr_t)early_pmd,PGDIR_SIZE,PAGE_TABLE); create_pmd_mapping(early_pmd,PAGE_OFFSET, (uintptr_t)load_sz_pte,PMD_SIZE,PAGE_TABLE); end_va = PAGE_OFFSET + PMD_SIZE; for (va = PAGE_OFFSET; va < end_va; va += PAGE_SIZE) { create_pte_mapping(load_sz_pte,PAGE_OFFSET, load_pa + (va - PAGE_OFFSET), PAGE_SIZE,PAGE_KERNEL_EXEC); } //=======2-4M========== create_pgd_mapping(early_pg_dir,PAGE_OFFSET + PMD_SIZE, (uintptr_t)early_pmd,PGDIR_SIZE,PAGE_TABLE); create_pmd_mapping(early_pmd,PAGE_OFFSET, (uintptr_t)load_sz_pte1,PMD_SIZE,PAGE_TABLE); end_va = PAGE_OFFSET + (PMD_SIZE * 2); for (va = PAGE_OFFSET + PMD_SIZE; va < end_va; va += PAGE_SIZE) { create_pte_mapping(load_sz_pte1,va, load_pa + (va - PAGE_OFFSET), PAGE_SIZE,PAGE_KERNEL_EXEC); } //=======4-6M========== create_pgd_mapping(early_pg_dir,PAGE_OFFSET + (PMD_SIZE*2), (uintptr_t)early_pmd,PGDIR_SIZE,PAGE_TABLE); create_pmd_mapping(early_pmd,PAGE_OFFSET, (uintptr_t)load_sz_pte2,PMD_SIZE,PAGE_TABLE); end_va = PAGE_OFFSET + (PMD_SIZE * 3); for (va = PAGE_OFFSET + (PMD_SIZE*2); va < end_va; va += PAGE_SIZE) { create_pte_mapping(load_sz_pte2,va, load_pa + (va - PAGE_OFFSET), PAGE_SIZE,PAGE_KERNEL_EXEC); }
透過以上的程式碼修改,就能將Kernel入口位址往前挪1.5M,只給opensbi預留512KB,這樣RISC-V Linux啟動之後,可用實體記憶體就會增加。
#RISC-V Linux入口位址2M對齊的操作目前還沒看到有人解釋,不過應該就是為了預留給opensbi2M,於是kernel只建立了二級頁表,使得入口位址必須2M對齊。對這部分記憶體的最佳化解決方案,目前也沒人給出,希望本文的優化方案能夠幫助到有些人,也希望能夠給大家一些啟發。
以上是實戰 | RISC-V Linux入口位址2M預留記憶體優化的詳細內容。更多資訊請關注PHP中文網其他相關文章!