学习ARM64页表转换流程

Posted Loopers

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习ARM64页表转换流程相关的知识,希望对你有一定的参考价值。

在引入虚拟地址概念以后,程序员和CPU看到的都是虚拟地址。当CPU尝试去访问某个虚拟地址的时候,这时候硬件单元MMU就会将此虚拟地址转化为物理地址,然后CPU再去访问。

而在Linux中存储虚拟地址到物理地址转化的关系的表称为页表。

 

目前最新的linux内核已经支持了5级页表。下图是一个4级页表的转化关系图。

  • PGD(Page Global Directory )页全局目录
  • PUD(Page Upper Directory)页上级目录
  • PMD(Page Middle Directory)页中级目录
  • PTE(Page Table Entry)   页表

 

如果是5级页表的话,会在PGD和PUD之间增加一个level叫P4D。

LINUX目前是支持5级页表,当然也可以通过config(CONFIG_PAGE_LEVELS)去配置的,目前手上的模拟板使用的是三级页表,如果使用三级页表的话,PUD等于PMD。

 

通过如下一个实例来展示下整个虚拟到物理地址的转化过程。

 

(struct_task_struct_*)(NSD:0xFFFFFFC5FACA4880)_=_0xFFFFFFC5FACA4880 -> (
  thread_info = (flags = 0, padding = (0, 0, 0, 0, 0, 0, 0), addr_limit = 549755813887, preempt_co
  state = 1,
  stack = 0xFFFFFF8008058000,
  usage = (counter = 4),
  flags = 1077952768,
  ptrace = 0,
  wake_entry = (next = 0x0),
  on_cpu = 0,
  cpu = 5,

虚拟地址是0xFFFFFFC5FACA4880,通过MMU可以看到它对应的物理地址为000000017ACA4880,那虚拟地址到物理地址是如何转化的呢? 我们来详细推到下转化过程

Target_Address_|________________logical|_physical______________|
               |     C:FFFFFFC5FACA4880|     A:000000017ACA4880
 

是通过MMU看到的一个虚拟地址对应的物理地址。前期条件是目前配置的是3级页表。 目前此地址是线性地址,转化关系比较简单。

270#define virt_to_phys virt_to_phys
271static inline phys_addr_t virt_to_phys(const volatile void *x)
272
273 return __virt_to_phys((unsigned long)(x));
274
 
 
248#ifdef CONFIG_DEBUG_VIRTUAL
249extern phys_addr_t __virt_to_phys(unsigned long x);
250extern phys_addr_t __phys_addr_symbol(unsigned long x);
251#else
252#define __virt_to_phys(x)    __virt_to_phys_nodebug(x)
253#define __phys_addr_symbol(x)    __pa_symbol_nodebug(x)
254#endif
 
 
CONFIG_DEBUG_VIRTUAL is not set
 
 
235#define __is_lm_address(addr)    (!!((addr) & BIT(VA_BITS - 1)))
236
237#define __lm_to_phys(addr)   (((addr) & ~PAGE_OFFSET) + PHYS_OFFSET)
238#define __kimg_to_phys(addr) ((addr) - kimage_voffset)
239
240#define __virt_to_phys_nodebug(x) (                 \\
241 phys_addr_t __x = (phys_addr_t)(x);             \\
242 __is_lm_address(__x) ? __lm_to_phys(__x) :          \\
243                __kimg_to_phys(__x);         \\
244)
 
 
189extern s64           memstart_addr;
190/* PHYS_OFFSET - the physical address of the start of memory. */
191#define PHYS_OFFSET      ( VM_BUG_ON(memstart_addr & 1); memstart_addr; )

如果是线性地址的话转化就是:(((addr) & ~PAGE_OFFSET) + PHYS_OFFSET) = 0x000000017ACA4880

 

对于不是线性地址我们在下节(手动玩转虚拟地址到物理地址转化)举例说明,如何去转化

模拟板目前的配置是虚拟地址位数为39位(VA_BITS=39),页表的大小是4K(CONFIG_ARM64_PAGE_SHIFT=12、CONFIG_ARM64_4K_PAGES=y),页表转化是3级(CONFIG_PAGE_LEVELS=3),所以我们需要详细描述出39位是如何划分的。

内核定义了各级页表索引在虚拟地址中的偏移:

  • PGDIR_SHIFT ==> 页全局目录索引的偏移
  • P4D_SHIFT  ==>  页四级目录索引的偏移
  • PUD_SHIFT  ==> 页上级目录索引的偏移
  • PMD_SHIFT ==> 页中级目录索引的偏移
  • PAGE_SHIFT ==> 页表内的偏移

当前模拟板是只有三级页表,则就没有P4D和PUD,这样的话PGD=PMD了。

#msm-4.19/include/asm-generic/pgtable-nop4d.h
 
 
typedef struct  pgd_t pgd;  p4d_t;
static inline p4d_t *p4d_offset(pgd_t *pgd, unsigned long address)

    return (p4d_t *)pgd;

当没有p4d的时候,则pgd就等于p4d

#msm-4.19/include/asm-generic/pgtable-nopud.h
 
 
typedef struct  p4d_t p4d;  pud_t;
static inline pud_t *pud_offset(p4d_t *p4d, unsigned long address)

    return (pud_t *)p4d;

当没有pud的时候,pud等于p4d,则pgd=p4d=pud。如下图

 接下来就需要确认的是每个level的偏移位是多少,也就是确认PGDIR_SHIFT, PMD_SHIFT, PAGE_SHIFT代码如下:

39/*
40 * Size mapped by an entry at level n ( 0 <= n <= 3)
41 * We map (PAGE_SHIFT - 3) at all translation levels and PAGE_SHIFT bits
42 * in the final page. The maximum number of translation levels supported by
43 * the architecture is 4. Hence, starting at at level n, we have further
44 * ((4 - n) - 1) levels of translation excluding the offset within the page.
45 * So, the total number of bits mapped by an entry at level n is :
46 *
47 *  ((4 - n) - 1) * (PAGE_SHIFT - 3) + PAGE_SHIFT
48 *
49 * Rearranging it a bit we get :
50 *   (4 - n) * (PAGE_SHIFT - 3) + 3
51 */
52#define ARM64_HW_PGTABLE_LEVEL_SHIFT(n)   ((PAGE_SHIFT - 3) * (4 - (n)) + 3)
53
54#define PTRS_PER_PTE      (1 << (PAGE_SHIFT - 3))
55
56/*
57 * PMD_SHIFT determines the size a level 2 page table entry can map.
58 */
59#if CONFIG_PGTABLE_LEVELS > 2
60#define PMD_SHIFT     ARM64_HW_PGTABLE_LEVEL_SHIFT(2)
61#define PMD_SIZE      (_AC(1, UL) << PMD_SHIFT)
62#define PMD_MASK      (~(PMD_SIZE-1))
63#define PTRS_PER_PMD      PTRS_PER_PTE
64#endif
65
66/*
67 * PUD_SHIFT determines the size a level 1 page table entry can map.
68 */
69#if CONFIG_PGTABLE_LEVELS > 3
70#define PUD_SHIFT     ARM64_HW_PGTABLE_LEVEL_SHIFT(1)
71#define PUD_SIZE      (_AC(1, UL) << PUD_SHIFT)
72#define PUD_MASK      (~(PUD_SIZE-1))
73#define PTRS_PER_PUD      PTRS_PER_PTE
74#endif
75
76/*
77 * PGDIR_SHIFT determines the size a top-level page table entry can map
78 * (depending on the configuration, this level can be 0, 1 or 2).
79 */
80#define PGDIR_SHIFT       ARM64_HW_PGTABLE_LEVEL_SHIFT(4 - CONFIG_PGTABLE_LEVELS)
81#define PGDIR_SIZE        (_AC(1, UL) << PGDIR_SHIFT)
82#define PGDIR_MASK        (~(PGDIR_SIZE-1))
83#define PTRS_PER_PGD      (1 << (VA_BITS - PGDIR_SHIFT))
  • PGDIR_SHIFT = ARM64_HW_PGTABLE_LEVEL_SHIFT(4 - CONFIG_PGTABLE_LEVELS) = ARM64_HW_PGTABLE_LEVEL_SHIFT(1)= ((12 - 3) * (3) + 3)  = 9*3+3=30
  • PMD_SHIFT = ARM64_HW_PGTABLE_LEVEL_SHIFT (2) = (9 * 2 + 3) = 21
  • PAGE_SHIFT = 12

以上是关于学习ARM64页表转换流程的主要内容,如果未能解决你的问题,请参考以下文章

学习ARM64页表转换流程

arm-linux内存管理学习笔记-内核临时页表的建立

ARM32 页表映射

ARM64 Linux 内核页表的块映射

arm-linux内存管理学习笔记-内核临时页表的建立

arm-linux内存管理学习笔记-内核临时页表的建立