目錄
create_pmd_mapping()" >create_pmd_mapping()
create_pte_mapping()" >create_pte_mapping()
使用举例" >使用举例
页表创建源码分析 " >页表创建源码分析
setup_vm()" >setup_vm()
setup_vm_final()" >setup_vm_final()
首頁 運維 linux運維 RISC-V Linux啟動之頁表建立分析

RISC-V Linux啟動之頁表建立分析

Aug 01, 2023 pm 03:39 PM
linux

上篇分析了RISC-V Linux的彙編啟動過程,其中講到了relocate重定向需要開啟MMU,今天分析RISC-V Linux的頁表建立。

注意:本文基於linux5.10.111核心

#sv39 mmu

RISC-V Linux啟動之頁表建立分析

RISC-V Linux支援
sv32RISC-V Linux啟動之頁表建立分析
sv39

sv48
等虛擬位址格式,分別代表32為虛擬位址、38位元虛擬位址和48位元虛擬地址。 RISC-V Linux預設也是使用sv39格式,sv39的虛擬位址、實體位址、PTE格式如下:RISC-V Linux啟動之頁表建立分析
虛擬位址格式:

#######實體位址格式:###############PTE格式:################虛擬位址使用39位表示,其中低12位元代表page offset,高位為了三個部分劃分:VP N[0]、VP N[1]和VP N[2],分別代表虛擬位址VA在PTE、PMD和PGD中的索引。 ###

物理位址使用56位元表示,低12位元代表page offset,高位元是物理頁PPN[0]、PPN[1]和PPN[2]

PTE保存了物理頁PPN[0] 、PPN[1]和PPN[2],和物理位址中的PPN相對應;PTE的低10位元代表物理位址的存取權限,當RWX全為0時,則代表該PTE儲存的位址是下一級頁表的實體位址,否則代表目前頁表是最後一級頁表

再看看sv39 的頁表格式,sv39使用的是三級頁表,PGDPMDPTE,每一個級頁表使用9bit表示,即每一級頁表都有512個頁表項。

在程式碼中,建立一個有512個元素的陣列即代表一個頁表。一個PTE有512個頁表項,每個頁表項佔用8字節,512*8=4096字節,所以一個PTE代表4K。一個PMD也是512個頁表項,每一項可代表一個PTE,512 *4 K=2M,所以一個PMD就代表2M。以此類推,一個PGD代表512 * 2M=1G。

重要結論:PGD代表1G、PMD代表2M、PTE代表4K。 sv39預設的頁大小是4K

三級頁表虛擬位址轉為實體位址流程示意圖:RISC-V Linux啟動之頁表建立分析

sv39三級頁表虛擬位址轉為實體位址過程:

MMU透過satp暫存器得到PGD的實體位址,結合PGD index(即V PN[2])找到PMD;找到PMD後,再結合PMD index(即V PN[1])找到PTE,然後結合PTE index(即V PN[0 ])得到VA在PTE索引中的值,從而得到實體位址。

最後在PTE中取出PPN[2]、PPN[1]和PPN[0],再和虛擬位址的低12位元offset相加,得到最終的實體位址。

臨時頁表分析

MMU開啟前,需要建立好kernel、dtb、trampoline等頁表。以便MMU開啟後,並且在記憶體管理模組運行之前,kernel可以正常初始化,dtb可以正常地被解析。這部分頁表都是臨時頁表,最終的頁表在setup_vm_final()建立。

臨時頁表建立順序:

首先為fixmap建立早期的PGD、PMD,這時PGD使用early_pg_dir。接著對從kernel開始的前2M記憶體建立二級頁表,此時PGD使用trampoline_pg_dir,為這2M建立的頁表也叫作superpage。再然後,對整個kernel建立二級頁表,此時PGD使用early_pg_dir。最後為dtb預留4M大小建立二級頁表。

頁表建立函數

#create_pgd_mapping()

#
void __init create_pgd_mapping(pgd_t *pgdp,
          uintptr_t va, phys_addr_t pa,
          phys_addr_t sz, pgprot_t prot)
登入後複製

pgdp:PGD頁表

#va:虛擬位址

##pa:實體位址

sz:映射大小,PGDIR_SIZE或PMD_SIZE或PTE_SIZE

prot:PAGE_KERNEL_EXEC/PAGE_KERNEL表示当前是最后一级页表,否则pa代表下一级页表的物理地址

create_pmd_mapping()

static void __init create_pmd_mapping(pmd_t *pmdp,
          uintptr_t va, phys_addr_t pa,
          phys_addr_t sz, pgprot_t prot)
登入後複製

pmdp:PMD页表

va:虚拟地址

pa:物理地址

sz:映射大小,PMD_SIZE或PAGE_SIZE

prot:权限,PAGE_KERNEL_EXEC/PAGE_KERNEL表示当前是最后一级页表,否则pa代表下一级页表的物理地址

create_pte_mapping()

static void __init create_pte_mapping(pte_t *ptep,
          uintptr_t va, phys_addr_t pa,
          phys_addr_t sz, pgprot_t prot)
登入後複製

ptep:PTE页表

va:虚拟地址

pa:物理地址

sz:映射大小,PAGE_SIZE

prot:权限,PAGE_KERNEL_EXEC/PAGE_KERNEL表示当前是最后一级页表,否则pa代表下一级页表的物理地址

使用举例

例如,将虚拟地址PAGE_OFFSET映射到物理地址pa,映射大小为4K,创建三级页表PGD、PMD和PTE:

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)early_pte,PGDIR_SIZE,PAGE_TABLE);
create_pte_mapping(early_pte,PAGE_OFFSET,
                   (uintptr_t)pa,PAGE_SIZE,PAGE_KERNEL_EXEC);
登入後複製

这样创建后,MMU就会根据PAGE_OFFSET在PGD中找到PMD,然后再PMD中找到PTE,最后取出物理地址。

页表创建源码分析

RISC-V Linux启动,经历了两次页表创建过程,第一次使用C函数setup_vm()创建临时页表,第二次使用C函数setup_vm_final()创建最终页表。

具体细节参考代码中的注释,下面的代码省略了一些不重要的部分。

setup_vm()

asmlinkage void __init setup_vm(uintptr_t dtb_pa)
{
 uintptr_t va, pa, end_va;
 uintptr_t load_pa = (uintptr_t)(&_start);
 uintptr_t load_sz = (uintptr_t)(&_end) - load_pa;
 uintptr_t map_size;
 //load_pa就是kernel加载的其实物理地址
    //load_sz就是kernel的实际大小

    //page_offset就是kernel的起始物理地址对应的虚拟地址,va_pa_offset是他们的偏移量
 va_pa_offset = PAGE_OFFSET - load_pa;
    
    //计算得到kernel起始物理地址的物理页,PFN_DOWN是将物理地址右移12位,因为sv39的物理地址的低12位是pa_offset,所以右移12位,得到pfn
 pfn_base = PFN_DOWN(load_pa);

 map_size = PMD_SIZE;//PMD_SIZE为2M,在当前,map_size只能为PGDIR_SIZE或PMD_SIZE。这时kernel默认不允许建立PTE。

 //检查PAGE_OFFSET是否1G对齐,以及kernel入口地址是否2M对齐
 BUG_ON((PAGE_OFFSET % PGDIR_SIZE) != 0);
 BUG_ON((load_pa % map_size) != 0);

    //allc_pte_early里面是BUG(),对于临时页表,kernel不允许我们建立PTE
 pt_ops.alloc_pte = alloc_pte_early;
 pt_ops.get_pte_virt = get_pte_virt_early;
#ifndef __PAGETABLE_PMD_FOLDED
 pt_ops.alloc_pmd = alloc_pmd_early;
 pt_ops.get_pmd_virt = get_pmd_virt_early;
#endif
 /* 设置 early PGD for fixmap */
 create_pgd_mapping(early_pg_dir, FIXADDR_START,
      (uintptr_t)fixmap_pgd_next, PGDIR_SIZE, PAGE_TABLE);


 /* 设置 fixmap PMD */
 create_pmd_mapping(fixmap_pmd, FIXADDR_START,
      (uintptr_t)fixmap_pte, PMD_SIZE, PAGE_TABLE);
 /* 设置 trampoline PGD and PMD */
 create_pgd_mapping(trampoline_pg_dir, PAGE_OFFSET,
      (uintptr_t)trampoline_pmd, PGDIR_SIZE, PAGE_TABLE);
 create_pmd_mapping(trampoline_pmd, PAGE_OFFSET,
      load_pa, PMD_SIZE, PAGE_KERNEL_EXEC);

 /*
  * 设置覆盖整个内核的早期PGD,这将使我们能够达到paging_init()。
  * 稍后在下面的 setup_vm_final() 中映射所有内存。
  */
 end_va = PAGE_OFFSET + load_sz;
 for (va = PAGE_OFFSET; va < end_va; va += map_size)
  create_pgd_mapping(early_pg_dir, va,
       load_pa + (va - PAGE_OFFSET),
       map_size, PAGE_KERNEL_EXEC);

 /* 为dtb创建早期的PMD */
 create_pgd_mapping(early_pg_dir, DTB_EARLY_BASE_VA,
      (uintptr_t)early_dtb_pmd, PGDIR_SIZE, PAGE_TABLE);
 /* 为 FDT 早期扫描创建两个连续的 PMD 映射 */
 pa = dtb_pa & ~(PMD_SIZE - 1);
 create_pmd_mapping(early_dtb_pmd, DTB_EARLY_BASE_VA,
      pa, PMD_SIZE, PAGE_KERNEL);
 create_pmd_mapping(early_dtb_pmd, DTB_EARLY_BASE_VA + PMD_SIZE,
      pa + PMD_SIZE, PMD_SIZE, PAGE_KERNEL);
 dtb_early_va = (void *)DTB_EARLY_BASE_VA + (dtb_pa & (PMD_SIZE - 1));
 ......

}
登入後複製

setup_vm()在最开始就进行了kernel入口地址的对齐检查,要求入口地址2M对齐。假设内存起始地址为0x80000000,那么kernel只能放在0x80000000、0x80200000等2M对齐处。为什么会有这种对齐要求呢?

我猜测单纯是为给opensbi预留了2M空间,因为kernel之前还有opensbi,而opensbi运行完之后,默认跳转地址就是偏移2M,kernel只是为了跟opensbi对应,所以设置了2M对齐。

那opensbi需要占用2M这么大?实际上只需要几百KB,因此opensbi和kernel中间有一段内存是空闲的,没有人使用。这个问题我们下篇再讲。

setup_vm_final()

在该函数中开始为整个物理内存做内存映射,通过swapper页表来管理,并且清除掉汇编阶段的页表。

static void __init setup_vm_final(void)
{
 uintptr_t va, map_size;
 phys_addr_t pa, start, end;
 u64 i;

 /**
  * 此时MMU已经开启,但是页表还没完全建立。
  */
 pt_ops.alloc_pte = alloc_pte_fixmap;
 pt_ops.get_pte_virt = get_pte_virt_fixmap;
#ifndef __PAGETABLE_PMD_FOLDED
 pt_ops.alloc_pmd = alloc_pmd_fixmap;
 pt_ops.get_pmd_virt = get_pmd_virt_fixmap;
#endif
 /* Setup swapper PGD for fixmap */
 create_pgd_mapping(swapper_pg_dir, FIXADDR_START,
      __pa_symbol(fixmap_pgd_next),
      PGDIR_SIZE, PAGE_TABLE);

 /* 为整个物理内存创建页表 */
 for_each_mem_range(i, &start, &end) {
  if (start >= end)
   break;
  if (start <= __pa(PAGE_OFFSET) &&
      __pa(PAGE_OFFSET) < end)
   start = __pa(PAGE_OFFSET);

        //best_map_size是选择合适的映射大小,kernel入口地址2M对齐或者kernel大小能被2M整除时,map_size就是2M,否则就是4K。
  map_size = best_map_size(start, end - start);
  for (pa = start; pa < end; pa += map_size) {
   va = (uintptr_t)__va(pa);
   create_pgd_mapping(swapper_pg_dir, va, pa,
        map_size, PAGE_KERNEL_EXEC);
  }
 }

 /* 清除fixmap的PMD和PTE */
 clear_fixmap(FIX_PTE);
 clear_fixmap(FIX_PMD);

 /* 切换到swapper页表,这个是最终的页表,汇编阶段relocate开启MMU的操作,跟下面这句是一样的。 */
 csr_write(CSR_SATP, PFN_DOWN(__pa_symbol(swapper_pg_dir)) | SATP_MODE);
 local_flush_tlb_all();//刷新TLB

 ......
}
登入後複製

说明:

在setup_vm_final()函数中,通过swapper_pg_dir页表来管理整个物理内存的访问。并且清除汇编阶段的页表fixmap_pte和early_pg_dir。(本质上就是把该页表项的内容清0,即赋值为0)

最终把swapper_pg_dir页表的物理地址赋值给SATP寄存器。这样CPU就可以通过该页表访问整个物理内存。

切换页表通过如下实现:

csr_write(CSR_SATP,PFN_DOWN(_pa(swapper_pg_dir))|SATP_MODE);

在swapper_pg_dir管理的kernel space中,其虚拟地址与物理地址空间的偏移是固定的,为va_pa_offset(定义在arch/riscv/mm/init.c中的一个全局变量)

注意:swapper_pg_dir管理的是kernel space的页表,即它把物理内存映射到的虚拟地址空间是只能kernel访问的。user space不能访问,用户空间如果访问,必须自行建立页表,把物理地址映射到user space的虚拟地址空间。kernel线程共享这个swapper_pg_dir页表。

總結

RISC-V Linux啟動時的頁表建立相對來說還是比較容易理解的,都是C語言創建的,程式碼也比較少。主要就是setup_vm()和setup_vm_final()兩個頁表建立函式。理解了sv39的一些位址格式後,再去分析原始碼就比較容易。不過不同kernel版本程式碼都不一樣,需要具體情況具體分析。

本篇提到了setup_vm()會檢查kernel入口位址是否2M對齊,如果不對齊kernel無法啟動,但其實我們可以解除這個2M對齊限制,將這部分空間利用起來,下篇教大家優化這部分記憶體。

#

以上是RISC-V Linux啟動之頁表建立分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

centos和ubuntu的區別 centos和ubuntu的區別 Apr 14, 2025 pm 09:09 PM

CentOS 和 Ubuntu 的關鍵差異在於:起源(CentOS 源自 Red Hat,面向企業;Ubuntu 源自 Debian,面向個人)、包管理(CentOS 使用 yum,注重穩定;Ubuntu 使用 apt,更新頻率高)、支持週期(CentOS 提供 10 年支持,Ubuntu 提供 5 年 LTS 支持)、社區支持(CentOS 側重穩定,Ubuntu 提供廣泛教程和文檔)、用途(CentOS 偏向服務器,Ubuntu 適用於服務器和桌面),其他差異包括安裝精簡度(CentOS 精

centos如何安裝 centos如何安裝 Apr 14, 2025 pm 09:03 PM

CentOS 安裝步驟:下載 ISO 映像並刻錄可引導媒體;啟動並選擇安裝源;選擇語言和鍵盤佈局;配置網絡;分區硬盤;設置系統時鐘;創建 root 用戶;選擇軟件包;開始安裝;安裝完成後重啟並從硬盤啟動。

Centos停止維護2024 Centos停止維護2024 Apr 14, 2025 pm 08:39 PM

CentOS將於2024年停止維護,原因是其上游發行版RHEL 8已停止維護。該停更將影響CentOS 8系統,使其無法繼續接收更新。用戶應規劃遷移,建議選項包括CentOS Stream、AlmaLinux和Rocky Linux,以保持系統安全和穩定。

Centos停止維護後的選擇 Centos停止維護後的選擇 Apr 14, 2025 pm 08:51 PM

CentOS 已停止維護,替代選擇包括:1. Rocky Linux(兼容性最佳);2. AlmaLinux(與 CentOS 兼容);3. Ubuntu Server(需要配置);4. Red Hat Enterprise Linux(商業版,付費許可);5. Oracle Linux(與 CentOS 和 RHEL 兼容)。在遷移時,考慮因素有:兼容性、可用性、支持、成本和社區支持。

docker原理詳解 docker原理詳解 Apr 14, 2025 pm 11:57 PM

Docker利用Linux內核特性,提供高效、隔離的應用運行環境。其工作原理如下:1. 鏡像作為只讀模板,包含運行應用所需的一切;2. 聯合文件系統(UnionFS)層疊多個文件系統,只存儲差異部分,節省空間並加快速度;3. 守護進程管理鏡像和容器,客戶端用於交互;4. Namespaces和cgroups實現容器隔離和資源限制;5. 多種網絡模式支持容器互聯。理解這些核心概念,才能更好地利用Docker。

centos停止維護後怎麼辦 centos停止維護後怎麼辦 Apr 14, 2025 pm 08:48 PM

CentOS 停止維護後,用戶可以採取以下措施應對:選擇兼容髮行版:如 AlmaLinux、Rocky Linux、CentOS Stream。遷移到商業發行版:如 Red Hat Enterprise Linux、Oracle Linux。升級到 CentOS 9 Stream:滾動發行版,提供最新技術。選擇其他 Linux 發行版:如 Ubuntu、Debian。評估容器、虛擬機或云平台等其他選項。

docker desktop怎麼用 docker desktop怎麼用 Apr 15, 2025 am 11:45 AM

如何使用 Docker Desktop? Docker Desktop 是一款工具,用於在本地機器上運行 Docker 容器。其使用步驟包括:1. 安裝 Docker Desktop;2. 啟動 Docker Desktop;3. 創建 Docker 鏡像(使用 Dockerfile);4. 構建 Docker 鏡像(使用 docker build);5. 運行 Docker 容器(使用 docker run)。

vscode需要什麼電腦配置 vscode需要什麼電腦配置 Apr 15, 2025 pm 09:48 PM

VS Code 系統要求:操作系統:Windows 10 及以上、macOS 10.12 及以上、Linux 發行版處理器:最低 1.6 GHz,推薦 2.0 GHz 及以上內存:最低 512 MB,推薦 4 GB 及以上存儲空間:最低 250 MB,推薦 1 GB 及以上其他要求:穩定網絡連接,Xorg/Wayland(Linux)

See all articles