当前位置:   article > 正文

arm64内核地址空间(arm64架构linux内核地址转换__pa(x)与__va(x)分析)_arm64 linux kernel 地址空间

arm64 linux kernel 地址空间

参考:https://www.cnblogs.com/liuhailong0112/p/14465697.html 

先看一下整体: 

打印函数: 

  1. static void test_func(void)
  2. {
  3. printk("VA_BITS %u \n",VA_BITS);
  4. printk("VA_START %llX \n",VA_START);
  5. printk("PAGE_OFFSET %llX \n",PAGE_OFFSET);
  6. printk("KIMAGE_VADDR %llX \n",KIMAGE_VADDR);
  7. printk("MODULES_END %llX \n",MODULES_END);
  8. printk("MODULES_VADDR %llX \n",MODULES_VADDR);
  9. printk("MODULES_VSIZE %llX \n",MODULES_VSIZE);
  10. printk("VMEMMAP_START %llX \n",VMEMMAP_START);
  11. printk("VMEMMAP_SIZE %llX \n",VMEMMAP_SIZE);
  12. printk("PUD_SIZE %llX \n",PUD_SIZE);
  13. printk("PCI_IO_END %llX \n",PCI_IO_END);
  14. printk("PCI_IO_START %llX \n",PCI_IO_START);
  15. printk("FIXADDR_TOP %llX \n",FIXADDR_TOP);
  16. printk("FIXADDR_SIZE %llX \n",FIXADDR_SIZE);
  17. printk("FIXADDR_START %llX \n",FIXADDR_START);
  18. //printk("KERNEL_START %llx \n",KERNEL_START);
  19. //printk("KERNEL_END %llx \n",KERNEL_END);
  20. printk("VMALLOC_START %llX \n",VMALLOC_START);
  21. printk("VMALLOC_END %llX \n",VMALLOC_END);
  22. printk("KASAN_SHADOW_SIZE %llX \n",KASAN_SHADOW_SIZE);
  23. printk("PHYS_OFFSET %llX \n",PHYS_OFFSET);
  24. printk("kimage_voffset %llX \n",kimage_voffset);
  25. printk("ffffcc39c0000000 pa :%llX \n",virt_to_phys(0xffffcc39c0000000));
  26. printk("ffff000001470100 pa :%llX \n",virt_to_phys(0xffff000001470100));
  27. }

相关内容 在arch/arm64/include/asm/memory.h  ,arch/arm64/include/asm/pgtable.h

下面是arm64中内核地址转换的函数和宏 :

  1. //判断是否处于内核线性区域。
  2. #define __is_lm_address(addr) (!(((u64)addr) & BIT(vabits_actual - 1)))
  3. //实际计算原理
  4. #define __lm_to_phys(addr) (((addr) & ~PAGE_OFFSET) + PHYS_OFFSET)
  5. #define __kimg_to_phys(addr) ((addr) - kimage_voffset)
  6. #define __virt_to_phys_nodebug(x) ({ \
  7. phys_addr_t __x = (phys_addr_t)(__tag_reset(x)); \
  8. __is_lm_address(__x) ? __lm_to_phys(__x) : __kimg_to_phys(__x); \
  9. })
  10. #define __pa_symbol_nodebug(x) __kimg_to_phys((phys_addr_t)(x))
  11. #ifdef CONFIG_DEBUG_VIRTUAL
  12. extern phys_addr_t __virt_to_phys(unsigned long x);
  13. extern phys_addr_t __phys_addr_symbol(unsigned long x);
  14. #else
  15. #define __virt_to_phys(x) __virt_to_phys_nodebug(x)
  16. #define __phys_addr_symbol(x) __pa_symbol_nodebug(x)
  17. #endif /* CONFIG_DEBUG_VIRTUAL */
  18. #define __phys_to_virt(x) ((unsigned long)((x) - PHYS_OFFSET) | PAGE_OFFSET)
  19. #define __phys_to_kimg(x) ((unsigned long)((x) + kimage_voffset))
  20. /*
  21. * Convert a page to/from a physical address
  22. */
  23. #define page_to_phys(page) (__pfn_to_phys(page_to_pfn(page)))
  24. #define phys_to_page(phys) (pfn_to_page(__phys_to_pfn(phys)))
  25. /*
  26. * Note: Drivers should NOT use these. They are the wrong
  27. * translation for translating DMA addresses. Use the driver
  28. * DMA support - see dma-mapping.h.
  29. */
  30. #define virt_to_phys virt_to_phys
  31. static inline phys_addr_t virt_to_phys(const volatile void *x)
  32. {
  33. return __virt_to_phys((unsigned long)(x));
  34. }
  35. #define phys_to_virt phys_to_virt
  36. static inline void *phys_to_virt(phys_addr_t x)
  37. {
  38. return (void *)(__phys_to_virt(x));
  39. }
  40. /*
  41. * Drivers should NOT use these either.
  42. */
  43. #define __pa(x) __virt_to_phys((unsigned long)(x))
  44. #define __pa_symbol(x) __phys_addr_symbol(RELOC_HIDE((unsigned long)(x), 0))
  45. #define __pa_nodebug(x) __virt_to_phys_nodebug((unsigned long)(x))
  46. #define __va(x) ((void *)__phys_to_virt((phys_addr_t)(x)))
  47. #define pfn_to_kaddr(pfn) __va((pfn) << PAGE_SHIFT)
  48. #define virt_to_pfn(x) __phys_to_pfn(__virt_to_phys((unsigned long)(x)))
  49. #define sym_to_pfn(x) __phys_to_pfn(__pa_symbol(x))

aarch64中寄存器:

  • TCR_ELx: 地址翻译控制寄存器
  1. T0SZ, bits[5:0] : 定义TTBR0_EL1 虚拟内存地址范围.
  2. TG0, bit[15:14] : 用户态 Page size, 0[4k] 1[64k] 2[16k].
  3. IPS : 物理内存地址宽度.
  4. T1SZ, bits[21:16] : 定义TTBR1_EL1 虚拟内存地址范围.
  5. TG1, bits[31:30] : 内核态 Page size. 1[16k] 2[4k] 3[64k].
  6. AS , bit [36] : ASID 大小配置位 0:8bit 1:16bit.

对于ARM64 来说,虚拟地址最大宽度为48bit. 内核 config 配置中有VAPA 和page size 的定义,这些定义最终都体现在TCR_ELx寄存器上.,(具体设置参考arch/arm64/mm/proc.S:(__cpu_setup)tcr_set_idmap_t0sz/tcr_compute_pa_size函数)

  1. CONFIG_ARM64_4K_PAGES=y
  2. CONFIG_ARM64_VA_BITS_48=y
  3. CONFIG_ARM64_VA_BITS=48
  4. CONFIG_ARM64_PA_BITS_48=y
  5. CONFIG_ARM64_PA_BITS=48

代码中用的是VA_BITS 宏来体现虚拟空间的大小.

#define VA_BITS            (CONFIG_ARM64_VA_BITS)
  • TTBR0_ELx: 地址翻译表基址寄存器, 对应的输入虚拟地址范围 [0, 0x0000ffffffffffff]ASID, bits [63:48] : ASID值BADDR, bits [47:1] :地址翻译表基址
  • TTBR1_ELx: 地址翻译表基址寄存器, 对应的输入虚拟地址范围 [0xffff000000000000, 0xffffffffffffffff].

Virtual Address Space

  • MAIR_ELx: 内存属性寄存器. 提供可供AttrIndx 选择的内存属性,AttrIndx 为block descriptor 或者L3 table descriptor 中的成员. 具体属性含义可参考 AArch64-Reference-Manual 第 2609 页.
  • FAR_ELx: 当Instruction AbortData AbortPC对齐中止引发的异常时,该寄存器会保存出错的虚拟地址.
  • ESR_EL1: EC = 0x24 时表示 Data abort, 此时ISS bit[24:0] 编码如下

根据 DFSC 字段可知道 Data abort的错误码,部分错误码枚举如下


The Memory Management Unit

由于进程隔离的需要,每个进程只能访问自己的资源,不能访问其余进程的资源,所以进程都是使用虚拟地址,这样操作系统能够控制用户态内存视图,而且可以将碎片物理内存映射成连续的虚拟内存. 进程内存视图和PA 视图如下.

VA and PA view

MMU 打开时,内核产生和访问的都是虚拟地址,必须经过MMU翻译成物理地址 才能进行正常的访问.

MMU

MMU首先会在TLBs 中检查访问的VA 是否有缓存,如果有就是缓存命中 ,直接返回对应的物理地址,地址翻译完成; 如果没有,就是 TLB miss, 需要依靠页表来完成翻译过程,翻译完成后将该翻译加入TLB.

TLB 结构

TLB 结构如下,针对user task, 为了处理多个task 中 VA 相同的情况,需要使用 ASID (Address Space ID) 来区分, 不同的task ASID不同,这样TLB 中就能有多条相同VA ,不同ASID 的缓存. 对于全局(nG=0)映射,无需比较ASID,只用VA查询.

如果TLB 命中会直接返回 PA 以及对应的属性(内存类型, 缓存策略, 访问权限等).

Translation Tables 格式

页表项有三种: 描述页表的;描述块的;描述页的,如下图.

页表中每项为8byte, 关键 bit 解释如下

  1. bit0 : 页表项是否有效,0 时表示输入的虚拟地址unmapped,任何访问操作会生成 Translation fault.
  2. bit1 : 1bit[47:12]给出的是下一级页表地址.
  3. 0: 给出的是块内存的基地址及其属性.

下一级为页表时,页表项描述结构体如下

下一级为块内存时,页表项描述结构体如下

  1. AttrIndx[2:0], bits[4:2] : 内存属性 index, 和 MAIR_EL1 寄存器组合使用.
  2. AF, bit[10] : access flag.

VA 翻译

页表等级由页大小和虚拟地址宽度确定,计算公式如下,例如页大小为4K,虚拟地址宽度为48bit 时,使用4级页表.

#define ARM64_HW_PGTABLE_LEVEL_SHIFT(n) ((PAGE_SHIFT - 3) * (4 - (n)) + 3)

各等级的索引 如下

translation table

  • **页内偏移**:相对于页地址的偏移量, 最大为page size,所以占PAGE_SHIFT bit; 页大小为4K时, PAGE_SHIFT=12.
  • PT index :页表索引. 页表项格式如上面章节描述, 占8个字节,页表总大小为一个page size,则页表有page size/8 个项, 所以索引占用PAGE_SHIFT - 3 bit; 页大小为4K 时,页表有 512 项, 索引占用 9bit.
  • PMD index : 页表中级目录索引,和PT index类似,占用PAGE_SHIFT - 3 bit; 页大小为4K 时, 占用 9bit.
  • PUD index : 页表高级目录索引,和PMD index类似,占用PAGE_SHIFT - 3 bit; 页大小为4K 时, 占用 9bit.
  • PGD index : 页表全局目录索引,和PUD index类似,占用PAGE_SHIFT - 3 bit; 页大小为4K 时, 占用 9bit.

multi translation table

初始化

VA、PA、Page Size 配置

tcr_el1 寄存器的 T0SZ,T1SZ 位设置为16 来配置 VA 位宽为 48; TG0 部分配置为 0 表示用户态page size 为4K; TG1 部分配置为2 表示内核态page size 为4K.

  1. #define TCR_T0SZ (64 - 48)
  2. #define TCR_T1SZ ((64 - 48) << 16)
  3. #define TCR_TG0_4K (0 << 14)
  4. #define TCR_TG1_4K (2 << 30)
  5. #define TCR_VALUE (TCR_T0SZ | TCR_T1SZ | TCR_TG0_4K | TCR_TG1_4K)
  6. ldr x0, =(TCR_VALUE)
  7. msr tcr_el1, x0

内存属性配置

定义了两组内存 Attribute: Attr0 , Attr1.

  • Attr0: 0 , 设备内存, Device-nGnRnE memory;内存访问不合并,按照program order执行,禁止指令重排;ack 必须来自最终目的地.
  • Attr1: 0x44 , 正常内存, Inner + Outer Non-cacheable.
  1. #define MT_DEVICE_nGnRnE 0x0
  2. #define MT_NORMAL_NC 0x1
  3. #define MT_DEVICE_nGnRnE_FLAGS 0x00
  4. #define MT_NORMAL_NC_FLAGS 0x44
  5. #define MAIR_VALUE (MT_DEVICE_nGnRnE_FLAGS << (8 * MT_DEVICE_nGnRnE)) | (MT_NORMAL_NC_FLAGS << (8 * MT_NORMAL_NC))
  6. ldr x0, =(MAIR_VALUE)
  7. msr mair_el1, x0

这些属性在页表初始化时通过 index 来引用.

多级页表创建

BCM2837 地址分布如下,我们先只关注0x40000000 (1G) 以下的空间, 这部分分成两部分

  • 0x3f000000 以下为常规内存.
  • [0x3f000000 ~ 0x40000000] 这段物理地址是留给外设的.

1G 空间的映射只需要PGD,PUD,PMD 即可完成.

bcm 2837 memory map

内核虚拟地址从0xffff000000000000 开始, bit[63:48] 全部为1, 使用TTBR1_EL1来缓存PGD地址. 下面代码调用create_pgd_entry 宏在pg_dir内存 处创建填充了页表,然后将pg_dir 的地址存入ttbr1_el1.

  1. .macro create_pgd_entry, tbl, virt, tmp1, tmp2
  2. create_table_entry \tbl, \virt, PGD_SHIFT, \tmp1, \tmp2
  3. create_table_entry \tbl, \virt, PUD_SHIFT, \tmp1, \tmp2
  4. .endm
  5. .macro create_table_entry, tbl, virt, shift, tmp1, tmp2
  6. lsr \tmp1, \virt, #\shift
  7. and \tmp1, \tmp1, #PTRS_PER_TABLE - 1 // table index
  8. add \tmp2, \tbl, #PAGE_SIZE
  9. orr \tmp2, \tmp2, #MM_TYPE_PAGE_TABLE
  10. str \tmp2, [\tbl, \tmp1, lsl #3]
  11. add \tbl, \tbl, #PAGE_SIZE // next level table page
  12. .endm
  13. adrp x0, pg_dir
  14. msr ttbr1_el1, x0

create_pgd_entry创建了 PGD 和PUD,但只用了他们的 index0 这一个表项,如下图,实际上这两级页表位置可以不连续. 这两级页表项都还是页表属性,下一级创建的是block entity

5_2_memory_mapping.PNG

MMU Enable

将 sctlr_el1 bit0 置位即打开了MMU.

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/106442
推荐阅读
相关标签
  

闽ICP备14008679号