亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢(xún)客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

上篇分析了RISC-V Linux的匯編啟動(dòng)過(guò)程,其中講到了relocate重定向需要開(kāi)啟MMU,今天分析RISC-V Linux的頁(yè)表創(chuàng)建。

注意:本文基于linux5.10.111內(nèi)核

sv39 mmu

RISC-V Linux支持sv32、sv39、sv48等虛擬地址格式,分別代表32為虛擬地址、38位虛擬地址和48位虛擬地址。RISC-V Linux默認(rèn)也是使用sv39格式,sv39的虛擬地址、物理地址、PTE格式如下:

虛擬地址格式:

物理地址格式:

PTE格式:

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

物理地址使用56位表示,低12位代表page offset,高位是物理頁(yè)P(yáng)PN[0]、PPN[1]和PPN[2]

PTE保存了物理頁(yè)P(yáng)PN[0]、PPN[1]和PPN[2],和物理地址中的PPN相對(duì)應(yīng);PTE的低10位代表物理地址的訪問(wèn)權(quán)限,當(dāng)RWX全為0時(shí),則代表該P(yáng)TE存儲(chǔ)的地址是下一級(jí)頁(yè)表的物理地址,否則代表當(dāng)前頁(yè)表是最后一級(jí)頁(yè)表。

再看看sv39 的頁(yè)表格式,sv39使用的是三級(jí)頁(yè)表,PGDPMDPTE,每一個(gè)級(jí)頁(yè)表使用9bit表示,即每一級(jí)頁(yè)表都有512個(gè)頁(yè)表項(xiàng)。

在代碼中,創(chuàng)建一個(gè)有512個(gè)元素的數(shù)組即代表一個(gè)頁(yè)表。一個(gè)PTE有512個(gè)頁(yè)表項(xiàng),每一個(gè)頁(yè)表項(xiàng)占用8字節(jié),512*8=4096字節(jié),所以一個(gè)PTE代表4K。一個(gè)PMD也是512個(gè)頁(yè)表項(xiàng),每一項(xiàng)可代表一個(gè)PTE,512 *4 K=2M,所以一個(gè)PMD就代表2M。以此類(lèi)推,一個(gè)PGD代表512 * 2M=1G。

重要結(jié)論:PGD代表1G、PMD代表2M、PTE代表4K。sv39默認(rèn)的頁(yè)大小是4K。

三級(jí)頁(yè)表虛擬地址轉(zhuǎn)為物理地址過(guò)程示意圖:

sv39三級(jí)頁(yè)表虛擬地址轉(zhuǎn)為物理地址過(guò)程:

MMU通過(guò)satp寄存器得到PGD的物理地址,結(jié)合PGD index(即V PN[2])找到PMD;找到PMD后,再結(jié)合PMD index(即V PN[1])找到PTE,然后結(jié)合PTE index(即V PN[0])得到VA在PTE索引中的值,從而得到物理地址。

最后在PTE中取出PPN[2]、PPN[1]和PPN[0],再和虛擬地址的低12位offset相加,得到最終的物理地址。

臨時(shí)頁(yè)表分析

MMU開(kāi)啟前,需要建立好kernel、dtb、trampoline等頁(yè)表。以便MMU開(kāi)啟后,并且在內(nèi)存管理模塊運(yùn)行之前,kernel可以正常初始化,dtb可以正常地被解析。這部分頁(yè)表都是臨時(shí)頁(yè)表,最終的頁(yè)表在setup_vm_final()建立。

臨時(shí)頁(yè)表創(chuàng)建順序:

首先為fixmap創(chuàng)建早期的PGD、PMD,這時(shí)PGD使用early_pg_dir。然后對(duì)從kernel開(kāi)始的前2M內(nèi)存建立二級(jí)頁(yè)表,此時(shí)PGD使用trampoline_pg_dir,為這2M建立的頁(yè)表也叫作superpage。再然后,對(duì)整個(gè)kernel創(chuàng)建二級(jí)頁(yè)表,此時(shí)PGD使用early_pg_dir。最后為dtb預(yù)留4M大小創(chuàng)建二級(jí)頁(yè)表。

頁(yè)表創(chuàng)建函數(shù)

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)

登錄后復(fù)制

pgdp:PGD頁(yè)表

va:虛擬地址

pa:物理地址

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

prot:PAGE_KERNEL_EXEC/PAGE_KERNEL表示當(dāng)前是最后一級(jí)頁(yè)表,否則pa代表下一級(jí)頁(yè)表的物理地址

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)

登錄后復(fù)制

pmdp:PMD頁(yè)表

va:虛擬地址

pa:物理地址

sz:映射大小,PMD_SIZE或PAGE_SIZE

prot:權(quán)限,PAGE_KERNEL_EXEC/PAGE_KERNEL表示當(dāng)前是最后一級(jí)頁(yè)表,否則pa代表下一級(jí)頁(yè)表的物理地址

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)

登錄后復(fù)制

ptep:PTE頁(yè)表

va:虛擬地址

pa:物理地址

sz:映射大小,PAGE_SIZE

prot:權(quán)限,PAGE_KERNEL_EXEC/PAGE_KERNEL表示當(dāng)前是最后一級(jí)頁(yè)表,否則pa代表下一級(jí)頁(yè)表的物理地址

使用舉例

例如,將虛擬地址PAGE_OFFSET映射到物理地址pa,映射大小為4K,創(chuàng)建三級(jí)頁(yè)表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);

登錄后復(fù)制

這樣創(chuàng)建后,MMU就會(huì)根據(jù)PAGE_OFFSET在PGD中找到PMD,然后再PMD中找到PTE,最后取出物理地址。

頁(yè)表創(chuàng)建源碼分析

RISC-V Linux啟動(dòng),經(jīng)歷了兩次頁(yè)表創(chuàng)建過(guò)程,第一次使用C函數(shù)setup_vm()創(chuàng)建臨時(shí)頁(yè)表,第二次使用C函數(shù)setup_vm_final()創(chuàng)建最終頁(yè)表。

具體細(xì)節(jié)參考代碼中的注釋?zhuān)旅娴拇a省略了一些不重要的部分。

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加載的其實(shí)物理地址
    //load_sz就是kernel的實(shí)際大小

    //page_offset就是kernel的起始物理地址對(duì)應(yīng)的虛擬地址,va_pa_offset是他們的偏移量
 va_pa_offset = PAGE_OFFSET - load_pa;
    
    //計(jì)算得到kernel起始物理地址的物理頁(yè),PFN_DOWN是將物理地址右移12位,因?yàn)閟v39的物理地址的低12位是pa_offset,所以右移12位,得到pfn
 pfn_base = PFN_DOWN(load_pa);

 map_size = PMD_SIZE;//PMD_SIZE為2M,在當(dāng)前,map_size只能為PGDIR_SIZE或PMD_SIZE。這時(shí)kernel默認(rèn)不允許建立PTE。

 //檢查PAGE_OFFSET是否1G對(duì)齊,以及kernel入口地址是否2M對(duì)齊
 BUG_ON((PAGE_OFFSET % PGDIR_SIZE) != 0);
 BUG_ON((load_pa % map_size) != 0);

    //allc_pte_early里面是BUG(),對(duì)于臨時(shí)頁(yè)表,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
 /* 設(shè)置 early PGD for fixmap */
 create_pgd_mapping(early_pg_dir, FIXADDR_START,
      (uintptr_t)fixmap_pgd_next, PGDIR_SIZE, PAGE_TABLE);


 /* 設(shè)置 fixmap PMD */
 create_pmd_mapping(fixmap_pmd, FIXADDR_START,
      (uintptr_t)fixmap_pte, PMD_SIZE, PAGE_TABLE);
 /* 設(shè)置 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);

 /*
  * 設(shè)置覆蓋整個(gè)內(nèi)核的早期PGD,這將使我們能夠達(dá)到paging_init()。
  * 稍后在下面的 setup_vm_final() 中映射所有內(nèi)存。
  */
 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創(chuàng)建早期的PMD */
 create_pgd_mapping(early_pg_dir, DTB_EARLY_BASE_VA,
      (uintptr_t)early_dtb_pmd, PGDIR_SIZE, PAGE_TABLE);
 /* 為 FDT 早期掃描創(chuàng)建兩個(gè)連續(xù)的 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));
 ......

}

登錄后復(fù)制

setup_vm()在最開(kāi)始就進(jìn)行了kernel入口地址的對(duì)齊檢查,要求入口地址2M對(duì)齊。假設(shè)內(nèi)存起始地址為0x80000000,那么kernel只能放在0x80000000、0x80200000等2M對(duì)齊處。為什么會(huì)有這種對(duì)齊要求呢?

我猜測(cè)單純是為給opensbi預(yù)留了2M空間,因?yàn)閗ernel之前還有opensbi,而opensbi運(yùn)行完之后,默認(rèn)跳轉(zhuǎn)地址就是偏移2M,kernel只是為了跟opensbi對(duì)應(yīng),所以設(shè)置了2M對(duì)齊。

那opensbi需要占用2M這么大?實(shí)際上只需要幾百KB,因此opensbi和kernel中間有一段內(nèi)存是空閑的,沒(méi)有人使用。這個(gè)問(wèn)題我們下篇再講。

setup_vm_final()

在該函數(shù)中開(kāi)始為整個(gè)物理內(nèi)存做內(nèi)存映射,通過(guò)swapper頁(yè)表來(lái)管理,并且清除掉匯編階段的頁(yè)表。

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

 /**
  * 此時(shí)MMU已經(jīng)開(kāi)啟,但是頁(yè)表還沒(méi)完全建立。
  */
 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);

 /* 為整個(gè)物理內(nèi)存創(chuàng)建頁(yè)表 */
 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對(duì)齊或者kernel大小能被2M整除時(shí),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頁(yè)表,這個(gè)是最終的頁(yè)表,匯編階段relocate開(kāi)啟MMU的操作,跟下面這句是一樣的。 */
 csr_write(CSR_SATP, PFN_DOWN(__pa_symbol(swapper_pg_dir)) | SATP_MODE);
 local_flush_tlb_all();//刷新TLB

 ......
}

登錄后復(fù)制

說(shuō)明:

在setup_vm_final()函數(shù)中,通過(guò)swapper_pg_dir頁(yè)表來(lái)管理整個(gè)物理內(nèi)存的訪問(wèn)。并且清除匯編階段的頁(yè)表fixmap_pte和early_pg_dir。(本質(zhì)上就是把該頁(yè)表項(xiàng)的內(nèi)容清0,即賦值為0)

最終把swapper_pg_dir頁(yè)表的物理地址賦值給SATP寄存器。這樣CPU就可以通過(guò)該頁(yè)表訪問(wèn)整個(gè)物理內(nèi)存。

切換頁(yè)表通過(guò)如下實(shí)現(xiàn):

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中的一個(gè)全局變量)

注意:swapper_pg_dir管理的是kernel space的頁(yè)表,即它把物理內(nèi)存映射到的虛擬地址空間是只能kernel訪問(wèn)的。user space不能訪問(wèn),用戶(hù)空間如果訪問(wèn),必須自行建立頁(yè)表,把物理地址映射到user space的虛擬地址空間。kernel線程共享這個(gè)swapper_pg_dir頁(yè)表。

總結(jié)

RISC-V Linux啟動(dòng)時(shí)的頁(yè)表創(chuàng)建相對(duì)來(lái)說(shuō)還是比較容易理解的,都是C語(yǔ)言創(chuàng)建的,代碼也比較少。主要就是setup_vm()和setup_vm_final()兩個(gè)頁(yè)表創(chuàng)建函數(shù)。理解了sv39的一些地址格式后,再去分析源碼就比較容易。不過(guò)不同kernel版本代碼都不一樣,需要具體情況具體分析。

本篇提到了setup_vm()會(huì)檢查kernel入口地址是否2M對(duì)齊,如果不對(duì)齊kernel無(wú)法啟動(dòng),但其實(shí)我們可以解除這個(gè)2M對(duì)齊限制,將這部分空間利用起來(lái),下篇教大家優(yōu)化這部分內(nèi)存。

以上就是RISC-V Linux啟動(dòng)之頁(yè)表創(chuàng)建分析的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注www.92cms.cn其它相關(guān)文章!

分享到:
標(biāo)簽:Linux RISC 分析 創(chuàng)建 啟動(dòng)
用戶(hù)無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定