文章目录
- 头文件与宏定义概览
- aarch64架构特定头文件
- 通用头文件
- 和页表无关的公共定义
- kernel config
- 头文件内容说明
- arch/arm64/include/asm/memory.h
- include/asm-generic/page.h
- arch/arm64/include/asm/pgtable-types.h
- arch/arm64/include/asm/pgtable.h
- pgtable-hwdef.h
- page-def.h
- include/asm-generic/pgtable-nop4d.h
- goto pgtable-hwdef.h
- include/linux/pgtable.h
- arch/arm64/mm/mmu.c
- arch/arm64/include/asm/memory.h
头文件与宏定义概览
aarch64架构特定头文件
arch/arm64/include/asm/memory.h
arch/arm64/include/asm/pgtable.h
arch/arm64/include/asm/pgtable-types.h
arch/arm64/include/asm/pgtable-hwdef.h
arch/arm64/include/asm/page-def.h
通用头文件
include/linux/pgtable.h
include/asm-generic/page.h
include/asm-generic/pgtable-nop4d.h
和页表无关的公共定义
#define __AC(X,Y) (X##Y)
#define _AC(X,Y) __AC(X,Y)
#define _UL(x) (_AC(x, UL))
#define _AT(T,X) ((T)(X)) //作用是强制类型转换
kernel config
CONFIG_PGTABLE_LEVELS=4 # 页表级别为4
CONFIG_ARM64_PAGE_SHIFT=12 # 4k页是这样的
CONFIG_ARM64_VA_BITS=48 # 物理地址位宽
# CONFIG_DEBUG_VIRTUAL # 不知道是啥
头文件内容说明
arch/arm64/include/asm/memory.h
#define VA_BITS (CONFIG_ARM64_VA_BITS)
#define _PAGE_OFFSET(va) (-(UL(1) << (va)))
#define PAGE_OFFSET (_PAGE_OFFSET(VA_BITS))
#define KIMAGE_VADDR (MODULES_END)
#define MODULES_END (MODULES_VADDR + MODULES_VSIZE)
#define MODULES_VADDR (_PAGE_END(VA_BITS_MIN))
#define MODULES_VSIZE (SZ_2G)
#define VMEMMAP_START (-(UL(1) << (VA_BITS - VMEMMAP_SHIFT)))
#define VMEMMAP_END (VMEMMAP_START + VMEMMAP_SIZE)
#define PCI_IO_END (VMEMMAP_START - SZ_8M)
#define PCI_IO_START (PCI_IO_END - PCI_IO_SIZE)
#define FIXADDR_TOP (VMEMMAP_START - SZ_32M)
其中
VA_BITS = 48
PAGE_OFFSET = 0xffff-0000-0000-0000
include/asm-generic/page.h
#define pte_val(x) ((x).pte)
#define pmd_val(x) ((&x)->pmd[0])
#define pgd_val(x) ((x).pgd)
#define pgprot_val(x) ((x).pgprot)#define __pte(x) ((pte_t) { (x) } )
#define __pmd(x) ((pmd_t) { (x) } )
#define __pgd(x) ((pgd_t) { (x) } )
#define __pgprot(x) ((pgprot_t) { (x) } )
arch/arm64/include/asm/pgtable-types.h
typedef u64 pteval_t;
typedef u64 pmdval_t;
typedef u64 pudval_t;
typedef u64 p4dval_t;
typedef u64 pgdval_t;typedef struct { pteval_t pte; } pte_t;
#define pte_val(x) ((x).pte)
#define __pte(x) ((pte_t) { (x) } )typedef struct { pgdval_t pgd; } pgd_t; // 声明了一个匿名结构体, 只有pgd_t这个类型, 没有struct pgd_t
#define pgd_val(x) ((x).pgd)
#define __pgd(x) ((pgd_t) { (x) } )
小结
__pgd就是把一个值强转成pgd_t类型
pgd_val宏就是获取 pgd_t类型的结构体的pgdval_t
arch/arm64/include/asm/pgtable.h
#define __pte_to_phys(pte) (pte_val(pte) & PTE_ADDR_MASK)
#define __p4d_to_phys(p4d) __pte_to_phys(p4d_pte(p4d))#define pte_valid(pte) (!!(pte_val(pte) & PTE_VALID))
__pte_to_phys(pte)
pte_val是获取pte_t结构体的pteval_t(u64)的值
__pte_to_phys(pte.pteval_t & 0xffff-ffff-f000)
pgtable-hwdef.h:
// 这是个标签,等会要goto回来
pgtable-hwdef.h
#define ARM64_HW_PGTABLE_LEVELS(va_bits) (((va_bits) - 4) / (PAGE_SHIFT - 3))
#define ARM64_HW_PGTABLE_LEVEL_SHIFT(n) ((PAGE_SHIFT - 3) * (4 - (n)) + 3)#define PGDIR_SHIFT ARM64_HW_PGTABLE_LEVEL_SHIFT(4 - CONFIG_PGTABLE_LEVELS)
#define PGDIR_SIZE (_AC(1, UL) << PGDIR_SHIFT)
#define PGDIR_MASK (~(PGDIR_SIZE-1))
#define PTRS_PER_PGD (1 << (VA_BITS - PGDIR_SHIFT))#define PUD_SHIFT ARM64_HW_PGTABLE_LEVEL_SHIFT(1)
#define PUD_SIZE (_AC(1, UL) << PUD_SHIFT)
#define PUD_MASK (~(PUD_SIZE-1))
#define PTRS_PER_PUD (1 << (PAGE_SHIFT - 3))#define PTRS_PER_PTE (1 << (PAGE_SHIFT - 3))// 没有配置CONFIG_ARM64_PA_BITS_52的情况下(48位地址时)
#define PTE_ADDR_LOW (((_AT(pteval_t, 1) << (48 - PAGE_SHIFT)) - 1) << PAGE_SHIFT)
#define PTE_ADDR_MASK PTE_ADDR_LOW
对于4k页来说,PAGE_SHIFT就是12
PTE_ADDR_MASK = PTE_ADDR_LOW = (1 << 36 - 1) << 12 = 0xffff-ffff-f000
page-def.h
#define PAGE_SHIFT CONFIG_ARM64_PAGE_SHIFT
#define PAGE_SIZE (_AC(1, UL) << PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE-1))
include/asm-generic/pgtable-nop4d.h
static inline p4d_t *p4d_offset(pgd_t *pgd, unsigned long address)
{ return (p4d_t *)pgd;}
aarch64没有实现5级页表,linux内核也没有相应的支持,所以配到4级页表之后会引入pgtable-nop4d.h来消掉p4d的影响
goto pgtable-hwdef.h
回看到前面标签那里
PAGE_SHIFT = CONFIG_ARM64_PAGE_SHIFT = 12
CONFIG_PGTABLE_LEVELS=4
所以
PGD_SHIFT = ARM64_HW_PGTABLE_LEVEL_SHIFT(4 - 4) = 9 * 4 + 3
这个公式的含义是:
页大小/目录描述符大小 * 目录级别 + 3
以4级页表-4k页-PGD为例:页大小4k(12位) / 目录描述符大小8字节(3位) * 目录级别(4级页表中PGD是4) + 3 = 9 * 4 + 3 = 39
以3级页表-64k页-PGD为例:64k(16位) / 8(3) * 3 + 3 = 42
PGD_SHIFT的含义:PGD最低的那个bit到0的位数, 就是PUD(如果有的话) + PMD + PTE + PAGE的宽度和
在aarch64的4级页表中:
PGDIR_SHIFT = 39
P4D_SHIFT = PGDIR_SHIFT = 39
PTRS_PER_PGD = (1 << (VA_BITS - PGDIR_SHIFT)) = 1 << 9 = 512
linuxpgtableh_170">include/linux/pgtable.h
#define pgd_offset(mm, address) pgd_offset_pgd((mm)->pgd, (address))
= mm->pgd + pgd_index(address);#define pgd_index(a) (((a) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))= mm->pgd + (address >> PGDIR_SHIFT) & (512 - 1)= mm-pgd + (address >> 39) & 0x1ff
pud_offsetp4d_pgtable(*p4d) + pud_index(address)
pud_index
= (address >> PUD_SHIFT) & (PTRS_PER_PUD - 1)pmd_offsetpud_pgtable(*pud) + pmd_index(address)
pmd_index
= (address >> PMD_SHIFT) & (PTRS_PER_PMD - 1)pte_offset_kernel(pte_t *)pmd_page_vaddr(*pmd) + pte_index(address)
pte_index
= (address >> PAGE_SHIFT) & (PTRS_PER_PTE - 1)
小结
pte_index就是取出地址中第13-21位的数据(1-12是PAGE)
pgd_index就是取出第40-48位的数据
那么
pgd_offset就是mm->pgd + pgd所在的那9个bit位
arch/arm64/mm/mmu.c
u64 kimage_vaddr __ro_after_init = (u64)&_text;
EXPORT_SYMBOL(kimage_vaddr);u64 kimage_voffset __ro_after_init;
EXPORT_SYMBOL(kimage_voffset);
arch/arm64/include/asm/memory.h
// 判断一个虚拟地址是否位于线性映射区
#define __is_lm_address(addr) (((u64)(addr) - PAGE_OFFSET) < (PAGE_END - PAGE_OFFSET))
// 将线性映射区的虚拟地址转为物理地址
#define __lm_to_phys(addr) (((addr) - PAGE_OFFSET) + PHYS_OFFSET)
// 将内核镜像的虚拟地址转为物理地址 (kimage_offset是内核镜像的虚拟地址和物理地址之间的便宜量,这个值在内核启动时计算得出
#define __kimg_to_phys(addr) ((addr) - kimage_voffset)// typedef u64 phys_addr_t;
#define __virt_to_phys_nodebug(x) ({ \phys_addr_t __x = (phys_addr_t)(__tag_reset(x)); \__is_lm_address(__x) ? __lm_to_phys(__x) : __kimg_to_phys(__x); \
})#define __virt_to_phys(x) __virt_to_phys_nodebug(x)
#define __phys_to_virt(x) ((unsigned long)((x) - PHYS_OFFSET) | PAGE_OFFSET)
#define __phys_to_kimg(x) ((unsigned long)((x) + kimage_voffset))// pa和va宏,用于获取物理/虚拟地址
#define __pa(x) __virt_to_phys((unsigned long)(x))
#define __va(x) ((void *)__phys_to_virt((phys_addr_t)(x)))
小结:__va宏展开后可以看出, 从物理地址到虚拟地址的偏移只是常量
物理地址转虚拟地址只需要加偏移
虚拟地址转物理地址需要判断是线性映射区还是内核镜像的地址