linux之 內存管理(1)-armv8 內核啟動頁表建立過程

一、內核啟動時,頁表映射有哪些?

Linux初始化過程,會依次建立如下頁表映射:

1.恒等映射:頁表基地址idmap_pg_dir;

2.粗粒度內核鏡像映射:頁表基地址init_pg_dir;

3.fixmap映射:頁表基地址為init_pg_dir;

4.內核主表映射,包含細粒度內核鏡像映射和線性映射,頁表基地址為swapper_pg_dir;

5.用戶空間頁表映射:頁表基地址task->mm->pgd;

二、恒等映射

????????在我們將uboot和kernel image燒寫到ram后,uboot完成相關硬件的初始化后,最后跳轉到kernel的入口函數(假設地址為0x40000000,那么就會將PC設置為0x40000000),此時,由于MMU尚未開啟,所以程序一直運行在物理地址空間,也就是PC的值,也就是指令地址,都是物理地址。

????????一旦MMU開始后,地址就會經過MMU轉換為物理地址,也就是所有地址就會被認為是虛擬地址,但是,現代處理器大多支持多級流水線,處理器會提前預取多條指令到流水線中,當打開MMU時,CPU已經預取多條指令到流水線中,并且這些指令都是用物理地址預取的;MMU開啟后,將以虛擬地址訪問,這樣繼續訪問流水線中預取的指令(按物理地址預取,比如0x40000488,這個地址就會被認為是虛擬地址),就很容易出錯。由此,便引出了恒等映射,所謂恒等,就是將虛擬地址映射到完全相等的物理地址上去(比如前面地址0x40000488映射到物理地址還是0x40000488),這樣就解決了這個問題。當然這個映射的區域是很小的,恒等映射完成后,啟動MMU,CPU就開啟虛擬地址訪問階段。

簡單看下啟動部分的匯編代碼(head.S)

SYM_CODE_START(primary_entry)   //入口函數bl  preserve_boot_args   // 保存啟動參數到boot_args數組bl  el2_setup           // w0=cpu_boot_mode,切換到EL1模式adrp    x23, __PHYS_OFFSETand x23, x23, MIN_KIMG_ALIGN - 1    // KASLR offset, defaults to 0bl  set_cpu_boot_mode_flag  //設置__boot_cpu_mode全局變量bl  __create_page_tables  //創建恒等映射和內核映像映射頁表/** The following calls CPU setup code, see arch/arm64/mm/proc.S for* details.* On return, the CPU will be ready for the MMU to be turned on and* the TCR will have been set.*/bl  __cpu_setup         // 為打開MMU做準備b   __primary_switch    //啟動MMU,最后跳轉到start_kernel(內核C語言部分)
SYM_CODE_END(primary_entry)

其中__create_page_tables函數的功能就是創建恒等映射和內核映像映射頁表,這里先看創建恒等映射頁表。

2.1?__create_page_tables

/** Create the identity mapping.*/adrp    x0, idmap_pg_diradrp    x3, __idmap_text_start      // __pa(__idmap_text_start)mov x5, #VA_BITS_MIN
1:adr_l   x6, vabits_actualstr x5, [x6]dmb sydc  ivac, x6        // Invalidate potentially stale cache lineadrp    x5, __idmap_text_endclz x5, x5cmp x5, TCR_T0SZ(VA_BITS)   // default T0SZ small enough?b.ge    1f          // .. then skip VA range extensionadr_l   x6, idmap_t0szstr x5, [x6]dmb sydc  ivac, x6        // Invalidate potentially stale cache linemov x4, #1 << (PHYS_MASK_SHIFT - PGDIR_SHIFT)str_l   x4, idmap_ptrs_per_pgd, x51:ldr_l   x4, idmap_ptrs_per_pgdmov x5, x3              // __pa(__idmap_text_start)adr_l   x6, __idmap_text_end        // __pa(__idmap_text_end)map_memory x0, x1, x3, x6, x7, x3, x4, x10, x11, x12, x13, x14

這段匯編的作用的就是創建恒等映射頁表,在調用map_memory的時候,各個參數的值分別如下:

x0:idmap_pg_dir

x3:idmap_text_start

x6:idmap_text_end

x7:SWAPPER_MM_MMUFLAGS

x4:PTRS_PER_PGD

map_memory

其它的作為輸出。再看下宏map_memory的定義

.macro map_memory, tbl, rtbl, vstart, vend, flags, phys, pgds, istart, iend, tmp, count, svadd \rtbl, \tbl, #PAGE_SIZEmov \sv, \rtblmov \count, #0compute_indices \vstart, \vend, #PGDIR_SHIFT, \pgds, \istart, \iend, \countpopulate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmpmov \tbl, \svmov \sv, \rtbl#if SWAPPER_PGTABLE_LEVELS > 3compute_indices \vstart, \vend, #PUD_SHIFT, #PTRS_PER_PUD, \istart, \iend, \countpopulate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmpmov \tbl, \svmov \sv, \rtbl
#endif#if SWAPPER_PGTABLE_LEVELS > 2compute_indices \vstart, \vend, #SWAPPER_TABLE_SHIFT, #PTRS_PER_PMD, \istart, \iend, \countpopulate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmpmov \tbl, \sv
#endifcompute_indices \vstart, \vend, #SWAPPER_BLOCK_SHIFT, #PTRS_PER_PTE, \istart, \iend, \countbic \count, \phys, #SWAPPER_BLOCK_SIZE - 1populate_entries \tbl, \count, \istart, \iend, \flags, #SWAPPER_BLOCK_SIZE, \tmp.endm

宏各個參數的含義如下:

tbl:頁表起始地址,頁表基地址

rtbl:下一級頁表起始地址,一般是tbl + PAGE_SIZE

vstart:要映射的虛擬地址的起始地址

vend:要映射的虛擬地址的結束地址

flags:最后一級頁表的屬性

phys:要映射的物理地址

pgds:PGD頁表的個數

首先用compute_indices計算虛擬地址對應的pgd頁表的索引,然后用populate_entries填充相關頁表項。

compute_indices

.macro compute_indices, vstart, vend, shift, ptrs, istart, iend, countlsr \iend, \vend, \shiftmov \istart, \ptrssub \istart, \istart, #1and \iend, \iend, \istart   // iend = (vend >> shift) & (ptrs - 1)mov \istart, \ptrsmul \istart, \istart, \countadd \iend, \iend, \istart   // iend += (count - 1) * ptrs// our entries span multiple tableslsr \istart, \vstart, \shiftmov \count, \ptrssub \count, \count, #1and \istart, \istart, \countsub \count, \iend, \istart
.endm

各個入參含義:

vstart:起始虛擬地址

vend:結束虛擬地址

shift:各級頁表在虛擬地址中的偏移

ptrs:頁表項的個數

istart:vstart索引值

iend:vend索引值

populate_entries

.macro populate_entries, tbl, rtbl, index, eindex, flags, inc, tmp1
.Lpe\@: phys_to_pte \tmp1, \rtblorr \tmp1, \tmp1, \flags    // tmp1 = table entrystr \tmp1, [\tbl, \index, lsl #3]add \rtbl, \rtbl, \inc  // rtbl = pa next leveladd \index, \index, #1cmp \index, \eindexb.ls    .Lpe\@
.endm

各參數含義:

tbl:頁表基地址

rtbl:下級頁表基地址

index:寫入頁表的起始索引

flags:頁表屬性

三、粗粒度內核鏡像映射

????????一般情況下,物理地址都是低端地址(不會超過256T),所以,恒等映射的時候,其實是將用戶空間的一段地址與物理地址建立了映射,所以,MMU啟動后,idmap_pg_dir會寫入TTBR0;然內核鏈接的地址都是高端地址(0xFFFF_xxxx_xxxx_xxxx),它的頁表基地址需要寫入TTBR1,所以還需要創建一張表來映射整個內核鏡像。 這張表的頁表基地址是init_pg_dir。

/** Map the kernel image (starting with PHYS_OFFSET).
*/adrp    x0, init_pg_dirmov_q   x5, KIMAGE_VADDR        // compile time __va(_text)add x5, x5, x23         // add KASLR displacementmov x4, PTRS_PER_PGDadrp    x6, _end            // runtime __pa(_end)adrp    x3, _text           // runtime __pa(_text)sub x6, x6, x3          // _end - _textadd x6, x6, x5          // runtime __va(_end)map_memory x0, x1, x5, x6, x7, x3, x4, x10, x11, x12, x13, x14

這里還是調用map_memory宏,前面已經看過。內核鏡像的映射后如下圖:

這里有幾個點需要注意:

(1).idmap.text本身也是屬于內核鏡像的一部分,所以,這部分,其實是映射了兩次;

(2)兩張頁表idmap_pg_dir和init_pg_dir,也都在內核鏡像當中,從vmlinux.lds.S能夠看到其定義:

idmap_pg_dir = .;
. += IDMAP_DIR_SIZE;
idmap_pg_end = .;.... = ALIGN(PAGE_SIZE);
init_pg_dir = .;
. += INIT_DIR_SIZE;
init_pg_end = .;

(3) 細心的人可能會注意到上面那張映射的圖好像有問題,明明是4級映射,怎么只有PGD,PUD和PMD三張表,這是因為目前我們項目的內核使能了section map機制,在這種機制下,idmap和init_pg_dir的創建會比實際設置值減少一級,將最后一級頁表取消,所以最后一級的頁表每一項能夠映射2M,整張表的映射大小達到了1G,這樣,一張表就能映射整個內核了。

這個最后一級能夠映射2M,和整張表可以映射1G,對于不懂虛擬地址轉換成物理地址的人,可能是一團漿糊,下面簡單介紹一下,為什么?

第一、64位地址,四級頁表,每個頁表4k,就是一個page,能存儲512個entry,每個entry 占用8字節,這是最常見的情況。

內核將一個進程的內存映射表建立好之后,在該進程被調度運行的時候,會將PGD的物理地址放置到MMU的頁表基地址寄存器中,在X86_64架構下,該寄存器為CR3,ARM64架構下,該寄存器為ttbr0_el1和ttbr1_el1,接下來的尋址過程中,就不需要linux來干預了,MMU會通過PGD-PUD-PMD-PTE-PAGE-OFFSET這個過程,根據虛擬地址,找到其對應的物理地址。

第二、拆分一下,64位的地址,MMU如何計算虛擬地址 得出物理地址;

  • ? ?ttbr0_el1寄存器中保存著頁表的基地址,也就是存放PGD頁表起始的物理地址,在內核中是申請了一個page,共4096字節,每個表項占8字節,可以存512個表項;要取哪個表項,需要根據虛擬地址 39-47 的PGD的索引,就可以得到下一級PUD的頁表基地址;
  • 39-47bit為PGD的索引,該索引可以找到PUD的頁基地址,2的9次方,共512個表項,每個表項占8字節,共4096字節。
  • 30-38bit為PUG的索引,該索引可以找到PMD的頁基地址,2的9次方,共512個表項,每個表項占8字節,共4096字節;
  • 21-29bit為PMD的索引,該索引可以找到PTE的頁基地址,2的9次方,共512個表項,每個表項占8字節,共4096字節;
  • 12-20bit為PTE的索引,該索引可以找到物理內存頁面的基地址,2的9次方,共512個表項,每個表項占8字節,共4096字節;
  • 0-11bit為頁內偏移地址,根據頁基地址+偏移量找到對應的物理內存;

第三、上面使用section map 建立頁表到PMD,可以映射2M,是 如何計算?

假如 21-29bit 的索引值計算出來為A,根據A,找到PTE的頁基地址,PTE有512個表項,每個表項代表一個頁面基地址,每個頁4K大小,可以得出公式:

2M = 512* 4K?

第四、1G 的大小是如何計算出?

固定PGD 的索引值,得出固定的PUG索引值,可以得到PMD 的基地址,PMD的基地址開始保存512個表項,一個PMD的表項可以有2M的物理地址范圍,那么就是

1G = 512* 2M

所以init_pg_dir 的映射表,一整張表就能映射整個內核 是指一個PMD?,即一個PMD的頁,512個表項*2M。

四、fixmap映射

????????為什么需要fixmap? 內核啟動流程,首先由uboot將kernel image和dtb拷貝到內存中,并且將dtb物理地址告訴內核。內核啟動后,在mmu已經啟動(之后soc只能通過虛擬地址訪問內存),paging_init還沒完成調用之前,內核啟動過程需要訪問解析dtb此時就需要將虛擬地址和物理地址進行映射。這就是fixmap機制的產生原因

fixmap的區域是在編譯階段就確定好的,在fixmap.h能夠看到代碼定義

enum fixed_addresses {FIX_HOLE,#define FIX_FDT_SIZE        (MAX_FDT_SIZE + SZ_2M)FIX_FDT_END,FIX_FDT = FIX_FDT_END + FIX_FDT_SIZE / PAGE_SIZE - 1,FIX_EARLYCON_MEM_BASE,FIX_TEXT_POKE0,#ifdef CONFIG_UNMAP_KERNEL_AT_EL0FIX_ENTRY_TRAMP_DATA,FIX_ENTRY_TRAMP_TEXT,#define TRAMP_VALIAS        (__fix_to_virt(FIX_ENTRY_TRAMP_TEXT))#endif /* CONFIG_UNMAP_KERNEL_AT_EL0 */__end_of_permanent_fixed_addresses,#define NR_FIX_BTMAPS       (SZ_256K / PAGE_SIZE)#define FIX_BTMAPS_SLOTS    7#define TOTAL_FIX_BTMAPS    (NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS)FIX_BTMAP_END = __end_of_permanent_fixed_addresses,FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1,FIX_PTE,FIX_PMD,FIX_PUD,FIX_PGD,__end_of_fixed_addresses};

再看下fixmap的虛擬地址是怎么計算的:

#define __fix_to_virt(x)    (FIXADDR_TOP - ((x) << PAGE_SHIFT))

這里面的x就對應上面的枚舉值,所以兩個相差為1的枚舉間隔的內存大小剛好是一頁,即4K。

fixmap的映射大致可以分為三類:固定映射,動態映射,查找映射。

固定映射就是在內核啟動過程中,用于分配給指定物理地址設定的映射關系,持續在系統啟動到關機的整個生命周期;

動態映射就是在內核啟動過程中,或啟動完成后,動態地給模塊分配虛擬地址,模塊退出后釋放該虛擬內存,即過程中動態建立映射關系;

查找映射,用于 paing_init,通過 pgd_set_fixmap 給頁目錄表的全局表項 swapper_pg_dir 做虛擬內存映射,映射到具體的 pte 的表項,然后后續就可以根據這個頁表項所映射的內存空間建立模塊的映射關系。

處理流程

映射框架建立:early_fixmap_init

void __init early_fixmap_init(void){pgd_t *pgdp;pud_t *pudp;pmd_t *pmdp;unsigned long addr = FIXADDR_START; //(1)pgdp = pgd_offset_k(addr);  // (2)__p4d_populate(pgdp, __pa_symbol(bm_pud), PUD_TYPE_TABLE); // (3)pudp = fixmap_pud(addr);   // (4)__pud_populate(pudp, __pa_symbol(bm_pmd), PMD_TYPE_TABLE); //(5)pmdp = fixmap_pmd(addr);  //(6)__pmd_populate(pmdp, __pa_symbol(bm_pte), PMD_TYPE_TABLE); //(7)}

(1)拿到FIXMAP的起始地址;

(2)這里獲取addr地址對應pgd全局頁表中的entry,這個pgd全局頁表就是init_pg_dir全局頁表;

#define pgd_offset_k(address)       pgd_offset(&init_mm, (address))
#define INIT_MM_CONTEXT(name)   .pgd = init_pg_dir,
struct mm_struct init_mm = {.mm_rb      = RB_ROOT,.pgd        = swapper_pg_dir,...INIT_MM_CONTEXT(init_mm)
}

完成后如下圖所示

early_ioremap_setup

void __init early_ioremap_setup(void){int i;for (i = 0; i < FIX_BTMAPS_SLOTS; i++)slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i);}

這個比較簡單,就是將FIX_BTMAP_BEGIN ~ FIX_BTMAP_END的虛擬內存做初始化,依序將各個slots的起始地址填入slot_virt數組中,目的是為后續使用這部分虛擬內存的時候,只需要調用這個數組對應的數組項即可,這段虛擬內存,既被稱為臨時映射虛擬內存區域。

dtb映射

????????內核初始化需要讀取dtb,dtb在燒寫的時候會燒寫到物理內存中的一塊區域,現在開啟MMU后,需要訪問的話,就必須要先建立起映射。 dtb的虛擬地址空間為4M。由于linux規定dtb的size需要小于2M,理論上用一個2M的虛擬地址空間即可完成映射。但是建立dtb頁表需要使用section map,即其最后一級頁表會直接指向2M block為邊界的物理地址。此時若dtb的位置橫跨物理地址2M邊界,就需要為上下兩個2M block都創建頁表才能訪問完整的image,正是基于這個考慮,此處內核為dtb保留了4M的虛擬地址空間。 當前fixmap的映射情況如上面圖6所示,注意此時bm_pte中還沒有填充,若要訪問fixmap的虛擬地址,首先要填充bm_pte。

dtb映射的函數如下:

void *__init fixmap_remap_fdt(phys_addr_t dt_phys, int *size, pgprot_t prot)   //(1){const u64 dt_virt_base = __fix_to_virt(FIX_FDT);   //(2)int offset;void *dt_virt;offset = dt_phys % SWAPPER_BLOCK_SIZE;dt_virt = (void *)dt_virt_base + offset;create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE),dt_virt_base, SWAPPER_BLOCK_SIZE, prot); //(3)return dt_virt;}

(1)入參dt_phys就是dtb的物理起始地址;

(2)獲取fixmap中FIX_FDT的虛擬起始地址;

(3)調用建立映射函數create_mapping_noalloc。

create_mapping_noalloc函數主要調用了__create_pgd_mapping

static void __create_pgd_mapping(pgd_t *pgdir, phys_addr_t phys,unsigned long virt, phys_addr_t size,pgprot_t prot,phys_addr_t (*pgtable_alloc)(int),int flags){unsigned long addr, end, next;pgd_t *pgdp = pgd_offset_pgd(pgdir, virt);phys &= PAGE_MASK;addr = virt & PAGE_MASK;end = PAGE_ALIGN(virt + size);do {next = pgd_addr_end(addr, end);  // (1)alloc_init_pud(pgdp, addr, next, phys, prot, pgtable_alloc,flags);   //(2)phys += next - addr;} while (pgdp++, addr = next, addr != end);}

由于fixmap大小只有4M,所以共享同一個pgd項,這里獲取的pgdp也就是圖6中的pgdp。 循環就是在根據每一個pgd項創建pud頁表

static void alloc_init_pud(pgd_t *pgdp, unsigned long addr, unsigned long end,phys_addr_t phys, pgprot_t prot,phys_addr_t (*pgtable_alloc)(int),int flags){unsigned long next;pud_t *pudp;p4d_t *p4dp = p4d_offset(pgdp, addr);p4d_t p4d = READ_ONCE(*p4dp);pudp = pud_set_fixmap_offset(p4dp, addr);  //(1)do {pud_t old_pud = READ_ONCE(*pudp);next = pud_addr_end(addr, end);if (use_1G_block(addr, next, phys) &&(flags & NO_BLOCK_MAPPINGS) == 0) {pud_set_huge(pudp, phys, prot);BUG_ON(!pgattr_change_is_safe(pud_val(old_pud),READ_ONCE(pud_val(*pudp))));} else {alloc_init_cont_pmd(pudp, addr, next, phys, prot,pgtable_alloc, flags);BUG_ON(pud_val(old_pud) != 0 &&pud_val(old_pud) != READ_ONCE(pud_val(*pudp)));}phys += next - addr;} while (pudp++, addr = next, addr != end);pud_clear_fixmap();}

這里有個關鍵點需要注意,就是對pudp的訪問,首先看(1),pudp獲得是什么

void __set_fixmap(enum fixed_addresses idx,phys_addr_t phys, pgprot_t flags){unsigned long addr = __fix_to_virt(idx);  //(2)pte_t *ptep;ptep = fixmap_pte(addr);   //(3)if (pgprot_val(flags)) {set_pte(ptep, pfn_pte(phys >> PAGE_SHIFT, flags));  //(4)} else {pte_clear(&init_mm, addr, ptep);flush_tlb_kernel_range(addr, addr+PAGE_SIZE);}}#define __set_fixmap_offset(idx, phys, flags)               \({                                  \unsigned long ________addr;                 \__set_fixmap(idx, phys, flags);                 \ ________addr = fix_to_virt(idx) + ((phys) & (PAGE_SIZE - 1));   \________addr;     //(5)                      \})#define set_fixmap_offset(idx, phys) \__set_fixmap_offset(idx, phys, FIXMAP_PAGE_NORMAL)    #define pud_offset_phys(dir, addr)  (p4d_page_paddr(READ_ONCE(*(dir))) + pud_index(addr) * sizeof(pud_t))   //(1)#define pud_set_fixmap(addr)        ((pud_t *)set_fixmap_offset(FIX_PUD, addr))#define pud_set_fixmap_offset(p4d, addr)    pud_set_fixmap(pud_offset_phys(p4d, addr))

(1)計算出FIX_FDT對應的pud項地址,也就是圖7中的entryU的物理地址

(2)獲得FIX_PUD的虛擬地址

(3)計算FIX_PUD的虛擬地址對應的pte項

(4)將entryU的物理地址寫入對應的pte項

(5)返回一個地址,這個地址是fixmap中FIX_PUD中的一個地址,在FIX_PUD中的偏移對應的是圖7中entryU在bm_pud中的偏移

返回的地址就是圖中的pudp,我們知道這是一個虛擬地址,如果頁表沒有相應的映射,訪問是會失敗的。在early_fixmap_init函數中,只是建立了一個映射的框架,并沒有填充pte,所以直接訪問pudp會失敗。剛剛的過程就是在為其建立映射。

現在再來看訪問地址pudp,mmu是怎么映射的,首先FIXMAP共享同一個pgd項和pud項,再看FIXMAP_START和FIX_PUD也屬于同一個pmd項(一個pmd項對應2M空間,FIXMAP_START和FIX_PUD在同一個2M范圍內),而剛剛又為pudp填充了對應的pte項,所以,這個地址是能夠映射成功的,并且是將FIX_PUD與bm_pud建立起了映射。

同樣對FIX_PMD建立映射后如圖8所示

static void init_pmd(pud_t *pudp, unsigned long addr, unsigned long end,phys_addr_t phys, pgprot_t prot,phys_addr_t (*pgtable_alloc)(int), int flags){unsigned long next;pmd_t *pmdp;pmdp = pmd_set_fixmap_offset(pudp, addr); //(1)do {pmd_t old_pmd = READ_ONCE(*pmdp);next = pmd_addr_end(addr, end);if (((addr | next | phys) & ~SECTION_MASK) == 0 &&(flags & NO_BLOCK_MAPPINGS) == 0) {pmd_set_huge(pmdp, phys, prot);  //(2)} else {alloc_init_cont_pte(pmdp, addr, next, phys, prot,pgtable_alloc, flags);}phys += next - addr;} while (pmdp++, addr = next, addr != end);pmd_clear_fixmap();   //(3)}

上述代碼完成(1)后,就如圖8所示;

(2)dtb的映射使能了2M的block映射,也就是最后一級為pmd,且其相應的entry會設置為2M對齊的物理地址與prot組合的值,所以會進入到這個分支; 完成后如圖9所示,pte中設置的實際值上面的圖中沒有說明清楚,這里完善了下

(3)清FIX_PMD對應的pte項,因為最后一級已經設置在了pmd,所以pte項也就不再需要了,同樣FIX_PUD對應的pte項也不需要了;完成后如圖10所示

?到這里就完成了對dtb的映射,就可以正常讀取dtb的信息了,dtb中就包含了內存信息,地址范圍,大小等等。然后就會初始化物理頁面分配器,即初始化伙伴系統;有了物理頁面分配器,內核主頁表就可以建立動態映射頁表。

五、內核主頁表建立

內核主表的建立包含了細粒度內核鏡像映射,線性映射。 主要在函數paging_init中完成

void __init paging_init(void){pgd_t *pgdp = pgd_set_fixmap(__pa_symbol(swapper_pg_dir)); //(1)map_kernel(pgdp); //內核細粒度映射map_mem(pgdp);   //線性映射pgd_clear_fixmap();cpu_replace_ttbr1(lm_alias(swapper_pg_dir));init_mm.pgd = swapper_pg_dir;memblock_free(__pa_symbol(init_pg_dir),__pa_symbol(init_pg_end) - __pa_symbol(init_pg_dir));memblock_allow_resize();}

細粒度內核鏡像映射

細粒度映射就是為內核的每個段建立頁表

static void __init map_kernel(pgd_t *pgdp){static struct vm_struct vmlinux_text, vmlinux_rodata, vmlinux_inittext,vmlinux_initdata, vmlinux_data;pgprot_t text_prot = rodata_enabled ? PAGE_KERNEL_ROX : PAGE_KERNEL_EXEC;if (arm64_early_this_cpu_has_bti())text_prot = __pgprot_modify(text_prot, PTE_GP, PTE_GP);map_kernel_segment(pgdp, _text, _etext, text_prot, &vmlinux_text, 0,VM_NO_GUARD);map_kernel_segment(pgdp, __start_rodata, __inittext_begin, PAGE_KERNEL,&vmlinux_rodata, NO_CONT_MAPPINGS, VM_NO_GUARD);map_kernel_segment(pgdp, __inittext_begin, __inittext_end, text_prot,&vmlinux_inittext, 0, VM_NO_GUARD);map_kernel_segment(pgdp, __initdata_begin, __initdata_end, PAGE_KERNEL,&vmlinux_initdata, 0, VM_NO_GUARD);map_kernel_segment(pgdp, _data, _end, PAGE_KERNEL, &vmlinux_data, 0, 0);if (!READ_ONCE(pgd_val(*pgd_offset_pgd(pgdp, FIXADDR_START)))) {set_pgd(pgd_offset_pgd(pgdp, FIXADDR_START),READ_ONCE(*pgd_offset_k(FIXADDR_START)));} else if (CONFIG_PGTABLE_LEVELS > 3) {pgd_t *bm_pgdp;p4d_t *bm_p4dp;pud_t *bm_pudp;BUG_ON(!IS_ENABLED(CONFIG_ARM64_16K_PAGES));bm_pgdp = pgd_offset_pgd(pgdp, FIXADDR_START);bm_p4dp = p4d_offset(bm_pgdp, FIXADDR_START);bm_pudp = pud_set_fixmap_offset(bm_p4dp, FIXADDR_START);pud_populate(&init_mm, bm_pudp, lm_alias(bm_pmd));pud_clear_fixmap();} else {BUG();}kasan_copy_shadow(pgdp);}

看函數map_kernel_segment

static void __init map_kernel_segment(pgd_t *pgdp, void *va_start, void *va_end,pgprot_t prot, struct vm_struct *vma,int flags, unsigned long vm_flags){phys_addr_t pa_start = __pa_symbol(va_start);unsigned long size = va_end - va_start;__create_pgd_mapping(pgdp, pa_start, (unsigned long)va_start, size, prot,early_pgtable_alloc, flags);if (!(vm_flags & VM_NO_GUARD))size += PAGE_SIZE;vma->addr   = va_start;vma->phys_addr  = pa_start;vma->size   = size;vma->flags  = VM_MAP | vm_flags;vma->caller = __builtin_return_address(0);vm_area_add_early(vma);}

頁表建立過程不再細說,這里在建立之前頁表如上面圖10所示 paging_init的第一步是為swapper_pg_dir的pgd表建立映射,也就是FIX_PGDswapper_pg_dir的pgd表建立映射,與swapper_pg_dir是完成內核映射的頁表基地址。 注意此時pgdp已經指向了swapper_pg_dir,所以后面的pud頁表、pmd頁和pte表都需要動態創建。

線性映射

內核鏡像映射僅僅是對內核鏡像空間做了映射,為了方便內核自由訪問所有物理內存,內核還做了一個線性映射。

static void __init map_mem(pgd_t *pgdp){phys_addr_t kernel_start = __pa_symbol(_text);phys_addr_t kernel_end = __pa_symbol(__init_begin);phys_addr_t start, end;int flags = 0;u64 i;if (rodata_full || debug_pagealloc_enabled())flags = NO_BLOCK_MAPPINGS | NO_CONT_MAPPINGS;memblock_mark_nomap(kernel_start, kernel_end - kernel_start);/* map all the memory banks */for_each_mem_range(i, &start, &end) {if (start >= end)break;__map_memblock(pgdp, start, end, PAGE_KERNEL_TAGGED, flags);}__map_memblock(pgdp, kernel_start, kernel_end,PAGE_KERNEL, NO_CONT_MAPPINGS);memblock_clear_nomap(kernel_start, kernel_end - kernel_start);}

核心就是__map_memblock

static void __init __map_memblock(pgd_t *pgdp, phys_addr_t start,phys_addr_t end, pgprot_t prot, int flags){__create_pgd_mapping(pgdp, start, __phys_to_virt(start), end - start,prot, early_pgtable_alloc, flags);}

具體的建立過程和內核鏡像建立過程類似,完成映射后,基本如下圖所示

完成映射后,將init_mm.pgd設置成swapper_pg_dir,同時將TTBR1寄存器改寫成swapper_pg_dir,自此,后面內核的頁表基地址就是swapper_pg_dir了。

六、用戶空間頁表創建

每個進程都有自己的用戶空間,所以每個進程都有自己獨立的頁表。用戶進程創建頁表發生在三個時候:創建進程時,缺頁異常時,進程切換時。

進程創建時

第一步,分配pgd物理頁面 調用順序copy_process->copy_mm->dup_mm->mm_init->mm_alloc_pgd

static inline int mm_alloc_pgd(struct mm_struct *mm){mm->pgd = pgd_alloc(mm);if (unlikely(!mm->pgd))return -ENOMEM;return 0;}pgd_t *pgd_alloc(struct mm_struct *mm){gfp_t gfp = GFP_PGTABLE_USER;if (PGD_SIZE == PAGE_SIZE)return (pgd_t *)__get_free_page(gfp);elsereturn kmem_cache_alloc(pgd_cache, gfp);}

第二步,拷貝父進程頁表,依次拷貝vma 調用路徑 copy_process->copy_mm->dup_mm->dup_mmap->copy_page_range

intcopy_page_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma){pgd_t *src_pgd, *dst_pgd;unsigned long next;unsigned long addr = src_vma->vm_start;unsigned long end = src_vma->vm_end;struct mm_struct *dst_mm = dst_vma->vm_mm;struct mm_struct *src_mm = src_vma->vm_mm;struct mmu_notifier_range range;int ret;ret = 0;dst_pgd = pgd_offset(dst_mm, addr);src_pgd = pgd_offset(src_mm, addr);do {next = pgd_addr_end(addr, end);if (pgd_none_or_clear_bad(src_pgd))continue;if (unlikely(copy_p4d_range(dst_vma, src_vma, dst_pgd, src_pgd,addr, next))) {ret = -ENOMEM;break;}} while (dst_pgd++, src_pgd++, addr = next, addr != end);return ret;}

copy_p4d_range里依次拷貝pud,pmd和pte。

缺頁異常

頁表拷貝完成后,等進程發生寫時拷貝,觸發缺頁異常,在異常服務處理里真正分配物理頁面。

static vm_fault_t wp_page_copy(struct vm_fault *vmf){struct vm_area_struct *vma = vmf->vma;struct mm_struct *mm = vma->vm_mm;struct page *old_page = vmf->page;struct page *new_page = NULL;pte_t entry;int page_copied = 0;struct mmu_notifier_range range;if (is_zero_pfn(pte_pfn(vmf->orig_pte))) {new_page = alloc_zeroed_user_highpage_movable(vma,vmf->address);if (!new_page)goto oom;} else {new_page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma,vmf->address);  //分配一個新的物理頁面,并把old_page復制到new_pageif (!new_page)goto oom;if (!cow_user_page(new_page, old_page, vmf)) {put_page(new_page);if (old_page)put_page(old_page);return 0;}}if (mem_cgroup_charge(new_page, mm, GFP_KERNEL))goto oom_free_new;cgroup_throttle_swaprate(new_page, GFP_KERNEL);__SetPageUptodate(new_page);mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, vma, mm,vmf->address & PAGE_MASK,(vmf->address & PAGE_MASK) + PAGE_SIZE);mmu_notifier_invalidate_range_start(&range);vmf->pte = pte_offset_map_lock(mm, vmf->pmd, vmf->address, &vmf->ptl);if (likely(pte_same(*vmf->pte, vmf->orig_pte))) {if (old_page) {if (!PageAnon(old_page)) {dec_mm_counter_fast(mm,mm_counter_file(old_page));inc_mm_counter_fast(mm, MM_ANONPAGES);}} else {inc_mm_counter_fast(mm, MM_ANONPAGES);}flush_cache_page(vma, vmf->address, pte_pfn(vmf->orig_pte));entry = mk_pte(new_page, vma->vm_page_prot);entry = pte_sw_mkyoung(entry);entry = maybe_mkwrite(pte_mkdirty(entry), vma);ptep_clear_flush_notify(vma, vmf->address, vmf->pte);page_add_new_anon_rmap(new_page, vma, vmf->address, false);lru_cache_add_inactive_or_unevictable(new_page, vma);set_pte_at_notify(mm, vmf->address, vmf->pte, entry);update_mmu_cache(vma, vmf->address, vmf->pte);if (old_page) {page_remove_rmap(old_page, false);}new_page = old_page;page_copied = 1;} else {update_mmu_tlb(vma, vmf->address, vmf->pte);}if (new_page)put_page(new_page);pte_unmap_unlock(vmf->pte, vmf->ptl);mmu_notifier_invalidate_range_only_end(&range);if (old_page) {if (page_copied && (vma->vm_flags & VM_LOCKED)) {lock_page(old_page);    /* LRU manipulation */if (PageMlocked(old_page))munlock_vma_page(old_page);unlock_page(old_page);}put_page(old_page);}return page_copied ? VM_FAULT_WRITE : 0;oom_free_new:put_page(new_page);oom:if (old_page)put_page(old_page);return VM_FAULT_OOM;}

進程切換

主要完成兩件事

(1)設置進程的

ASID到TTBR1_EL1

(2)設置mm->pgd到TTBR0_EL1完成地址空間切換

調用路徑:context_switch->switch_mm_irqs_off->switch_mm->__switch_mm->check_and_switch_context->cpu_switch_mm->cpu_do_switch_mm

void cpu_do_switch_mm(phys_addr_t pgd_phys, struct mm_struct *mm){unsigned long ttbr1 = read_sysreg(ttbr1_el1);unsigned long asid = ASID(mm);unsigned long ttbr0 = phys_to_ttbr(pgd_phys);/* Skip CNP for the reserved ASID */if (system_supports_cnp() && asid)ttbr0 |= TTBR_CNP_BIT;/* SW PAN needs a copy of the ASID in TTBR0 for entry */if (IS_ENABLED(CONFIG_ARM64_SW_TTBR0_PAN))ttbr0 |= FIELD_PREP(TTBR_ASID_MASK, asid);/* Set ASID in TTBR1 since TCR.A1 is set */ttbr1 &= ~TTBR_ASID_MASK;ttbr1 |= FIELD_PREP(TTBR_ASID_MASK, asid);write_sysreg(ttbr1, ttbr1_el1); //(1)isb();write_sysreg(ttbr0, ttbr0_el1); //(2)isb();post_ttbr_update_workaround();}

(1) ASID寫到TTBR1_EL1;

(2) 新進程頁表基地址pgd,填入ttbr0_el1

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/73873.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/73873.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/73873.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【面試問題】Java 接口與抽象類的區別

引言 在 Java 面向對象編程中&#xff0c;接口&#xff08;Interface&#xff09;和抽象類&#xff08;Abstract Class&#xff09;是兩個重要的抽象工具。它們都能定義未實現的方法&#xff0c;但設計目標和使用場景截然不同。本文將通過語法、特性和實際案例&#xff0c;深入…

【資料分享】全志科技T113-i全國產(1.2GHz雙核A7 RISC-V)工業核心板規格書

核心板簡介 創龍科技SOM-TLT113 是一款基于全志科技T113-i 雙核ARM Cortex-A7 玄鐵C906 RISC-V HiFi4 DSP 異構多核處理器設計的全國產工業核心板&#xff0c;ARM Cortex-A7 處理單元主頻高達1.2GHz。核心板 CPU、ROM、RAM、電源、晶振等所有元器件均采用國產工業級方案&…

R語言高效數據處理-自定義格式EXCEL數據輸出

注&#xff1a;以下代碼均為實際數據處理中的筆記摘錄&#xff0c;所以很零散&#xff0c; 將就看吧&#xff0c;這一篇只是代表著我還在&#xff0c;所以可能用處不大&#xff0c;這一段時間都很煎熬&#xff01; 在實際數據處理中為了提升效率&#xff0c;將Excel報表交付給…

LeetCode 30 —— 30.串聯所有單詞的子串

題目&#xff1a; 給定一個字符串 s 和一些長度相同的單詞 words。找出 s 中恰好可以由 words 中所有單詞串聯形成的子串的起始位置。 注意子串要與 words 中的單詞完全匹配&#xff0c;中間不能有其他字符&#xff0c;但不需要考慮 words 中單詞串聯的順序。 示例 1&#xff…

《算法筆記》9.2小節——數據結構專題(2)->二叉樹的遍歷 問題 A: 復原二叉樹(同問題 C: 二叉樹遍歷)

題目描述 小明在做數據結構的作業&#xff0c;其中一題是給你一棵二叉樹的前序遍歷和中序遍歷結果&#xff0c;要求你寫出這棵二叉樹的后序遍歷結果。 輸入 輸入包含多組測試數據。每組輸入包含兩個字符串&#xff0c;分別表示二叉樹的前序遍歷和中序遍歷結果。每個字符串由…

SpringBoot-2整合MyBatis以及基本的使用方法

目錄 1.引入依賴 2.數據庫表的創建 3.數據源的配置 4.編寫pojo類 5.編寫controller類 6.編寫接口 7.編寫接口的實現類 8.編寫mapper 1.引入依賴 在pom.xml引入依賴 <!-- mysql--><dependency><groupId>com.mysql</groupId><artifac…

Unity Shader Graph高級節點邏輯設計:程序化噪聲生成技術詳解

一、程序化噪聲的核心價值 程序化噪聲生成是Shader開發中的關鍵核心技術&#xff0c;通過數學算法直接生成紋理信息&#xff0c;相較于傳統位圖紋理具有以下優勢&#xff1a; 無限分辨率&#xff1a;可動態適應任意顯示精度 參數化控制&#xff1a;實時調整噪聲頻率、振幅等屬…

[藍橋杯 2023 省 B] 飛機降落(不會dfs的看過來)

[藍橋杯 2023 省 B] 飛機降落 題目描述 N N N 架飛機準備降落到某個只有一條跑道的機場。其中第 i i i 架飛機在 T i T_{i} Ti? 時刻到達機場上空&#xff0c;到達時它的剩余油料還可以繼續盤旋 D i D_{i} Di? 個單位時間&#xff0c;即它最早可以于 T i T_{i} Ti? 時刻…

英偉達GTC 2025大會產品全景剖析與未來路線深度洞察分析

【完整版】3月19日&#xff0c;黃仁勛Nvidia GTC 2025 主題演講&#xff5c;英偉達 英偉達GTC 2025大會產品全景剖析與未來路線深度洞察分析 一、引言 1.1 分析內容 本研究主要采用了文獻研究法、數據分析以及專家觀點引用相結合的方法。在文獻研究方面&#xff0c;廣泛收集了…

強化學習 - PPO控制無人機

PPO&#xff08;Proximal Policy Optimization&#xff0c;近端策略優化&#xff09;是一種強化學習算法&#xff0c;用于訓練智能體&#xff08;無人機&#xff09;如何在環境中做出決策。它本質上是 策略梯度&#xff08;Policy Gradient&#xff09;方法 的一種改進&#xf…

YOLO11報錯:AttributeError: module ‘torch‘ has no attribute ‘OutOfMemoryError‘

事情是這樣的&#xff1a;前幾天YOLO11的代碼還是可以訓練的&#xff0c;昨天訓練了一天&#xff0c;今天換模型就報這個錯。 AttributeError: module torch has no attribute OutOfMemoryError我查了一下&#xff1a;YOLO11官方代碼issues里面也有人有同樣的問題&#xff0c;…

Prometheus使用

介紹&#xff1a;Prometheus 是一個開源的 監控與告警系統&#xff0c;主要用于采集和存儲時間序列數據&#xff08;Time Series Data&#xff09; Prometheus的自定義查詢語言PromQL Metric類型 為了能夠幫助用戶理解和區分這些不同監控指標之間的差異&#xff0c;Prometheu…

ESG報告評級標準解讀

ESG&#xff08;環境、社會、治理&#xff09;報告評級標準用于評估企業在環境、社會和公司治理方面的表現。以下是主要評級標準的解讀&#xff1a; 1. 環境&#xff08;Environmental&#xff09; 碳排放&#xff1a;評估企業的溫室氣體排放及減排措施。 能源使用&#xff1…

清晰易懂的 PHP 安裝與配置教程

初學者也能看懂的 PHP 安裝與配置教程 本教程將手把手教你如何在 Windows 系統上安裝 PHP&#xff0c;并配置 Composer&#xff08;PHP 的依賴管理工具&#xff09;的緩存位置&#xff0c;即使你是零基礎小白&#xff0c;也能輕松完成&#xff01; 一、準備工作 操作系統&…

Zabbix監控自動化(Zabbix Mnitoring Automation)

??????zabbix監控自動化 1、自動化監控(網絡發現與自動注冊只能用其一) 1.1 ansible安裝zabbix agent 新采購100臺服務器&#xff1a; 1、安裝操作系統 2、初始化操作系統 3、安裝zabbix agent 1.手動部暑 2.腳本部暑(shell expect) 3.ansible 4、納入監控 1.…

Android Launcher3 首屏圖標鎖定技術方案解析

一、需求背景與技術挑戰 在Android 13系統定制開發中&#xff0c;需實現Launcher首屏圖標固定功能。該需求需在以下技術維度進行突破&#xff1a; 拖拽事件攔截機制&#xff1a;需精準識別拖拽目標區域 布局層級判定&#xff1a;準確識別第一屏的布局標識 跨屏操作限制&…

Spring Framework 中 BeanDefinition 是什么

BeanDefinition 是 Spring Framework 中一個核心的接口&#xff0c;它描述了一個 Bean 的定義。你可以把它看作是 Spring IoC 容器中 Bean 的“藍圖”或“配置元數據”。它包含了 Spring 容器創建、配置和管理 Bean 所需的所有信息。 BeanDefinition 中包含的信息&#xff1a;…

QtCreator16創建WebAssembly工程在瀏覽器中顯示圖片

顯示效果&#xff1a; 實現過程&#xff1a; 添加qrc資源文件 輸入文件名&#xff1a; 選擇模板為Qt Resource File 在工程目錄下創建res文件夾&#xff0c;復制圖片文件到res中 編輯qrc文件 添加資源前綴 添加圖片資源 選擇圖片資源添加別名 復制資源URL 使用別名調用資源 居…

openpnp - 如果安裝面的鈑金接觸面不平,可以嘗試加墊片

文章目錄 openpnp - 如果安裝面的鈑金接觸面不平&#xff0c;可以嘗試加墊片概述吐槽備注END openpnp - 如果安裝面的鈑金接觸面不平&#xff0c;可以嘗試加墊片 概述 在X軸導軌上&#xff0c;架上百分表&#xff0c;打設備的工作平面的平面度&#xff0c;發現工作平面不平(和…

人工智能之數學基礎:線性方程組

本文重點 線性方程組是由兩個或兩個以上的線性方程組成的方程組,其中每個方程都是關于兩個或兩個以上未知數的線性方程。 記憶恢復 我們先從小學學習的線性方程組找到感覺 解答過程: 將第二個方程乘以2,得到: 2x?2y=2 將第一個方程減去新得到的方程,消去x: (2x+y)?…