Detailed analysis of the ARMv8 (aarch64) page table creation process

Publisher:masphiaLatest update time:2020-06-04 Source: elecfansKeywords:ARMv8 Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

1 ARMv8 Memory Management

1.1 Memory layout in Aarch64 Linux

The ARMv8 architecture can support 48-bit virtual addresses and is configured as a 4-level page table (4K pages) or a 3-level page table (64K pages). However, this Linux system only uses 39-bit virtual addresses (512G kernel, 512G user), and is configured as a 3-level page table (4K pages) or a 2-level page table (64K pages).


The address bits 63:39 of user space are set to zero, and the address bits 63:39 of kernel space are set to one. The 63rd bit of the virtual address can be used to select TTBRx. swapper_pg_dir only contains kernel global mappings, and the user's pgd contains user (non-global) mappings. The swapper_pg_dir address is in TTBR1 and is not written to TTBR0.


AArch64Linux memory layout:


Start End Size Use


--------------------------------------------------------------------------------------------------


000000000000000 0000007fffffffff 512GB user


 


ffffff80000000000 ffffffbbfffcffff ~240GB vmalloc


 


ffffffbbfffd0000 ffffffbcfffdffff 64KB [guardpage]


 


ffffffbbfffe0000 ffffffbcfffeffff 64KB PCII/O space


 


ffffffbbffff0000 ffffffbcffffffff 64KB [guard page]


 


ffffffbc00000000 ffffffbdffffffff 8GB vmemmap


 


ffffffbe00000000 ffffffbffbffffff ~8GB [guard,future vmmemap]


 


ffffffbffc000000 ffffffbfffffffff 64MB modules


 


ffffffc000000000 ffffffffffffffff 256GB memory


1.2 AArch64 virtual address format

1.2.1 Virtual Address for 4K Pages

 


1.2.2 Virtual Addresses for 64K Pages


 



2 Analysis of the head.S page table creation process

2.1 Page table creation function __create_page_tables

This function is used to create the page tables required for the FDT (device tree) and kernel image when the kernel is started. After the kernel is running normally, create_mapping needs to be run to create page tables for all physical memory, which will overwrite the page tables created by __create_page_tables.


The page table source file is created when the kernel starts running: arm64/kernel/head.Sline345


/*


 * Setup the initial page tables. We only setup the barest amount which is


 * required to get the kernel running. The following sections are required:


 * -identity mapping to enable the MMU (low address, TTBR0)


 * -first few MB of the kernel linear mapping to jump to once the MMU has


 * been enabled, including the FDT blob (TTBR1)


 */


__create_page_tables:


          pgtbl x25, x26, x24 //idmap_pg_dir and swapper_pg_dir addresses


 


          /*


           * Clear the two newly created page tables TTBR0, TTBR1


           */


          mov x0,x25


          add x6,x26, #SWAPPER_DIR_SIZE


1: stp xzr,xzr, [x0], #16


          stp xzr,xzr, [x0], #16


          stp xzr,xzr, [x0], #16


          stp xzr,xzr, [x0], #16


          cmp x0,x6


          b.lo 1b


 


          ldr x7,=MM_MMUFLAGS


 


          /*


           * Create the identity mapping.


           */


          add x0, x25,#PAGE_SIZE // sectiontable address


          adr x3, __turn_mmu_on // virtual/physical address


          create_pgd_entry x25, x0, x3, x5, x6 //See 1.1.3 for details


          create_block_map x0, x7, x3, x5, x5, idmap=1


 


          /*


           * Map the kernel image (starting withPHYS_OFFSET).


           */


          add x0,x26, #PAGE_SIZE //section table address


          mov x5,#PAGE_OFFSET


          create_pgd_entry x26, x0, x5, x3, x6


          ldr x6,=KERNEL_END - 1


          mov x3,x24 // physoffset


          create_block_map x0, x7, x3, x5, x6


 


          /*


           * Map the FDT blob (maximum 2MB; must be within 512MB of


           * PHYS_OFFSET).


           */


          mov x3,x21 // FDTphys address


          and x3,x3, #~((1 << 21) - 1) // 2MBaligned


          mov x6,#PAGE_OFFSET


          sub x5,x3, x24 //subtract PHYS_OFFSET


          tst x5,#~((1 << 29) - 1) //within 512MB?


          csel x21,xzr, x21, ne // zero the FDTpointer


          b.ne 1f


          add x5,x5,x6 // __va(FDTblob)


          add x6,x5, #1 << 21 // 2MB for the FDT blob


          sub x6,x6, #1 //inclusive range


          create_block_map x0, x7, x3, x5, x6


1:


          ret


ENDPROC(__create_page_tables)


 


 


2.1.1 pgtbl x25, x26, x24 analysis

pgtbl is a macro, defined as follows:


arm64/kernel/head.S line55


          .macro pgtbl,ttb0, ttb1, phys


          add ttb1,phys, #TEXT_OFFSET - SWAPPER_DIR_SIZE


          sub ttb0,ttb1, #IDMAP_DIR_SIZE


          .endm


pgtbl x25,x26, x24 //Expanded as follows


add x26,x24, #TEXT_OFFSET -SWAPPER_DIR_SIZE


sub x25,x26,#IDMAP_DIR_SIZE


 


The variables are defined as follows:


#defineSWAPPER_DIR_SIZE (3 * PAGE_SIZE)


#defineIDMAP_DIR_SIZE (2 * PAGE_SIZE)


illustrate:


1. For an introduction to TTBR0 and TTBR1, see Page B4-1708 of the ARM ARM manual.


2. x25 holds the address of TTBR0 (TTBR0 holds the base address of translation table 0).


3. X26 stores TTBR1 (TTBR1 holds the base address of translation table 1).


4. X24 stores PHYS_OFFSET, /* PHYS_OFFSET - the physical address of the start of memory. */


    #definePHYS_OFFSET ({ memstart_addr; })


5. TEXT_OFFSET is the parameter passed in when the Bootloader is started. It is the offset relative to the RAM start address when the kernel Image is loaded.


6. PAGE_OFFSEST: the virtual address of the start of the kernel image.


 


 


 



Figure 1 pgtbl macro analysis


 


2.1.2 MM_MMUFLAGS explanation

In file arm64/kernel/head.S line71:


/*


 * Initial memory map attributes.


 */


#ifndefCONFIG_SMP


#definePTE_FLAGS PTE_TYPE_PAGE | PTE_AF


#definePMD_FLAGS PMD_TYPE_SECT | PMD_SECT_AF


#else


#definePTE_FLAGS PTE_TYPE_PAGE | PTE_AF | PTE_SHARED


#definePMD_FLAGS PMD_TYPE_SECT | PMD_SECT_AF | PMD_SECT_S


#endif


 


#ifdefCONFIG_ARM64_64K_PAGES


#defineMM_MMUFLAGS PTE_ATTRINDX(MT_NORMAL) | PTE_FLAGS


#defineIO_MMUFLAGS PTE_ATTRINDX(MT_DEVICE_nGnRE) | PTE_XN | PTE_FLAGS


#else


#defineMM_MMUFLAGS PMD_ATTRINDX(MT_NORMAL) |PMD_FLAGS


#defineIO_MMUFLAGS PMD_ATTRINDX(MT_DEVICE_nGnRE) | PMD_SECT_XN | PMD_FLAGS


#endif


 


According to the above macro definition, MM_MMUFLAGS sets the MMU according to the page size (64K or 4K) selected when you compiled the kernel.


2.1.3 create_pgd_entry/create_block_map macro explanation

1. create_pgd_entry


/*


 * Macro to populate the PGD for the corresponding block entry in the next


 * level (tbl) for the given virtual address.


 *


 * Preserves: pgd,tbl,virt


 * Corrupts: tmp1, tmp2


 */


      .macro create_pgd_entry,pgd, tbl, virt, tmp1, tmp2


      lsr tmp1,virt, #PGDIR_SHIFT


      and tmp1,tmp1, #PTRS_PER_PGD - 1 // PGD index


      orr tmp2,tbl, #3 // PGD entrytable type


      str tmp2,[pgd, tmp1, lsl #3]


      .endm


 


According to the above definition, create_pgd_entry x25, x0, x3, x5, x6 are expanded as follows:


lsr x5, x3,# PGDIR_SHIFT //X3 stores the address of __turn_mmu_on, right shift PGDIR_SHIFT (30) bits


and x5, x5, #PTRS_PER_PGD – 1//Set <38:30>


orr x6, x0, #3 //x0 stores the PGD entry (i.e., the lower-level page table address), and the lower three bits are used for the valid bits of the entry


str x6, [ x25, x5, lsl #3] //Store the entry in TTBR0(x25) at the offset x5 and shift left 3 bits (multiply by 8, because entry is 8 bytes).


For easier understanding, see the following figure:




 

                                                                                                                          Figure 2 48-bit virtual address composition for 4K pages



Note: The table name corresponding to the virtual address in the above figure is:


PGD: Global Descriptor Table


PUD: converted to PGD, not used in Linux


PMD: Page Table Middle Descriptor Table


PTE: Page Table


The Linux kernel only uses 39 bits of virtual address




 


 


Figure 3 64-bit page table entry format


 


 

 


 


Figure 4


Similarly, create_pgd_entry x26, x0, x5, x3, x6 are expanded as follows:


lsr x3, x5,#PGDIR_SHIFT //X5 stores PAGE_OFFSET = 0xffffffc000000000, right shift PGDIR_SHIFT bits and store in X3


and x3, x3,#PTRS_PER_PGD – 1//Set <38:30>


orr x6, x0, #3 //x0 stores the next page pointed to by TTBR1. The lower three bits are used for the valid bits of the table entry and stored in x6


str x6, [ x26, x3,lsl #3] //Store the entry in TTBR0(x25) at the position where the offset is x5 and shifted 3 bits to the left


The above content is to fill in the page table entry with an offset of x3*8 (because an entry is 8 bytes) in the page table pointed to by TTBR1, and the content is x6 (that is, the position pointed to by x0 in Figure 4)


 


 


2. create_block_map


/*


 * Macro to populate block entries in the pagetable for the start..end


 * virtual range (inclusive).


 *


 * Preserves: tbl, flags


 * Corrupts: phys, start, end, pstate


 */


      .macro create_block_map,tbl,flags,phys,start,end,idmap=0


      lsr phys,phys, #BLOCK_SHIFT


      .if idmap


      and start,phys, #PTRS_PER_PTE - 1 // table index


      .else


      lsr start,start, #BLOCK_SHIFT


      and start,start, #PTRS_PER_PTE - 1 // table index


      .endif


      orr phys,flags, phys, lsl #BLOCK_SHIFT //table entry


      .ifnc start,end


      lsr end,end, #BLOCK_SHIFT


      and end,end, #PTRS_PER_PTE - 1 // tableend index


      .endif


9999: str phys,[tbl, start, lsl #3] // store the entry


      .ifnc start,end //ifnc: if string1!=string2


      add start,start, #1 // next entry


      add phys,phys, #BLOCK_SIZE // next block


      cmp start,end


      b.ls 9999b


      .endif


      .endm


 


According to the above macro definition, create_block_map x0, x7, x3, x5, x5, idmap=1, the translation is as follows:


lsr x3, x3, # BLOCK_SHIFT


and x5, x3 # PTRS_PER_PTE – 1


orr x3, x7, x3, lsl # BLOCK_SHIFT


9999:


      str x3, [x0, x5, lsl #3]


 


Similarly, create_block_map x0, x7, x3, x5, x6 is expanded as follows:


      lsr x3,x3, #BLOCK_SHIFT


      lsr x5,x5, #BLOCK_SHIFT


      and x5,x5, #PTRS_PER_PTE - 1 // table index


      orr x3,x7, x3, lsl #BLOCK_SHIFT // tableentry


      lsr x6,x6, #BLOCK_SHIFT


      and x6,x6, #PTRS_PER_PTE - 1 // table endindex


9999: str x3,[x0, x5, lsl #3] // store the entry


      add x5,x5, #1 // next entry


      add x3,x3, #BLOCK_SIZE // next block


      cmp x5, x6


      b.ls 9999b


The purpose of create_block_mapx0, x7, x3, x5, x6 macros is to create all the mapping relationships of the kernel image.


     


     


3 Questions and Answers

3.1 How to notify the OS kernel of the physical memory size before TLB is opened?

The bootloader passes the physical memory start address and size to the Linux kernel through the device tree (devicetree.dts file). The size of the physical memory needs to be specified in the bootloader, i.e., the dts file. The memory declaration in the dts file is as follows:

[1] [2]
Keywords:ARMv8 Reference address:Detailed analysis of the ARMv8 (aarch64) page table creation process

Previous article:Several other questions about ARMv8
Next article:Analysis of the working principle of the microcontroller P0 port

Latest Microcontroller Articles
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号