赞
踩
参考:https://www.cnblogs.com/liuhailong0112/p/14465697.html
先看一下整体:
打印函数:
- static void test_func(void)
- {
- printk("VA_BITS %u \n",VA_BITS);
- printk("VA_START %llX \n",VA_START);
- printk("PAGE_OFFSET %llX \n",PAGE_OFFSET);
- printk("KIMAGE_VADDR %llX \n",KIMAGE_VADDR);
- printk("MODULES_END %llX \n",MODULES_END);
- printk("MODULES_VADDR %llX \n",MODULES_VADDR);
- printk("MODULES_VSIZE %llX \n",MODULES_VSIZE);
- printk("VMEMMAP_START %llX \n",VMEMMAP_START);
- printk("VMEMMAP_SIZE %llX \n",VMEMMAP_SIZE);
- printk("PUD_SIZE %llX \n",PUD_SIZE);
- printk("PCI_IO_END %llX \n",PCI_IO_END);
- printk("PCI_IO_START %llX \n",PCI_IO_START);
- printk("FIXADDR_TOP %llX \n",FIXADDR_TOP);
- printk("FIXADDR_SIZE %llX \n",FIXADDR_SIZE);
- printk("FIXADDR_START %llX \n",FIXADDR_START);
- //printk("KERNEL_START %llx \n",KERNEL_START);
- //printk("KERNEL_END %llx \n",KERNEL_END);
- printk("VMALLOC_START %llX \n",VMALLOC_START);
- printk("VMALLOC_END %llX \n",VMALLOC_END);
- printk("KASAN_SHADOW_SIZE %llX \n",KASAN_SHADOW_SIZE);
- printk("PHYS_OFFSET %llX \n",PHYS_OFFSET);
- printk("kimage_voffset %llX \n",kimage_voffset);
- printk("ffffcc39c0000000 pa :%llX \n",virt_to_phys(0xffffcc39c0000000));
- printk("ffff000001470100 pa :%llX \n",virt_to_phys(0xffff000001470100));
- }
相关内容 在arch/arm64/include/asm/memory.h ,arch/arm64/include/asm/pgtable.h
下面是arm64中内核地址转换的函数和宏 :
- //判断是否处于内核线性区域。
- #define __is_lm_address(addr) (!(((u64)addr) & BIT(vabits_actual - 1)))
-
- //实际计算原理
- #define __lm_to_phys(addr) (((addr) & ~PAGE_OFFSET) + PHYS_OFFSET)
- #define __kimg_to_phys(addr) ((addr) - kimage_voffset)
-
- #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 __pa_symbol_nodebug(x) __kimg_to_phys((phys_addr_t)(x))
-
- #ifdef CONFIG_DEBUG_VIRTUAL
- extern phys_addr_t __virt_to_phys(unsigned long x);
- extern phys_addr_t __phys_addr_symbol(unsigned long x);
- #else
- #define __virt_to_phys(x) __virt_to_phys_nodebug(x)
- #define __phys_addr_symbol(x) __pa_symbol_nodebug(x)
- #endif /* CONFIG_DEBUG_VIRTUAL */
-
- #define __phys_to_virt(x) ((unsigned long)((x) - PHYS_OFFSET) | PAGE_OFFSET)
- #define __phys_to_kimg(x) ((unsigned long)((x) + kimage_voffset))
-
- /*
- * Convert a page to/from a physical address
- */
- #define page_to_phys(page) (__pfn_to_phys(page_to_pfn(page)))
- #define phys_to_page(phys) (pfn_to_page(__phys_to_pfn(phys)))
-
- /*
- * Note: Drivers should NOT use these. They are the wrong
- * translation for translating DMA addresses. Use the driver
- * DMA support - see dma-mapping.h.
- */
- #define virt_to_phys virt_to_phys
- static inline phys_addr_t virt_to_phys(const volatile void *x)
- {
- return __virt_to_phys((unsigned long)(x));
- }
-
- #define phys_to_virt phys_to_virt
- static inline void *phys_to_virt(phys_addr_t x)
- {
- return (void *)(__phys_to_virt(x));
- }
-
- /*
- * Drivers should NOT use these either.
- */
- #define __pa(x) __virt_to_phys((unsigned long)(x))
- #define __pa_symbol(x) __phys_addr_symbol(RELOC_HIDE((unsigned long)(x), 0))
- #define __pa_nodebug(x) __virt_to_phys_nodebug((unsigned long)(x))
- #define __va(x) ((void *)__phys_to_virt((phys_addr_t)(x)))
- #define pfn_to_kaddr(pfn) __va((pfn) << PAGE_SHIFT)
- #define virt_to_pfn(x) __phys_to_pfn(__virt_to_phys((unsigned long)(x)))
- #define sym_to_pfn(x) __phys_to_pfn(__pa_symbol(x))
TCR_ELx
: 地址翻译控制寄存器- T0SZ, bits[5:0] : 定义TTBR0_EL1 虚拟内存地址范围.
- TG0, bit[15:14] : 用户态 Page size, 0[4k] 1[64k] 2[16k].
-
- IPS : 物理内存地址宽度.
-
- T1SZ, bits[21:16] : 定义TTBR1_EL1 虚拟内存地址范围.
- TG1, bits[31:30] : 内核态 Page size. 1[16k] 2[4k] 3[64k].
- AS , bit [36] : ASID 大小配置位 0:8bit 1:16bit.
对于ARM64
来说,虚拟地址最大宽度为48bit
. 内核 config
配置中有VA
、PA
和page size
的定义,这些定义最终都体现在TCR_ELx
寄存器上.,(具体设置参考arch/arm64/mm/proc.S:(__cpu_setup)tcr_set_idmap_t0sz/tcr_compute_pa_size函数)
- CONFIG_ARM64_4K_PAGES=y
- CONFIG_ARM64_VA_BITS_48=y
- CONFIG_ARM64_VA_BITS=48
- CONFIG_ARM64_PA_BITS_48=y
- 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 Abort
、Data Abort
、PC对齐中止
引发的异常时,该寄存器会保存出错的虚拟地址.ESR_EL1
: EC = 0x24 时表示 Data abort
, 此时ISS bit[24:0]
编码如下根据 DFSC
字段可知道 Data abort
的错误码,部分错误码枚举如下
由于进程隔离的需要,每个进程只能访问自己的资源,不能访问其余进程的资源,所以进程都是使用虚拟地址,这样操作系统能够控制用户态内存视图,而且可以将碎片物理内存映射成连续的虚拟内存. 进程内存视图和PA
视图如下.
VA and PA view
当MMU
打开时,内核产生和访问的都是虚拟地址,必须经过MMU
翻译成物理地址
才能进行正常的访问.
MMU
MMU
首先会在TLBs 中检查访问的VA
是否有缓存,如果有就是缓存命中
,直接返回对应的物理地址
,地址翻译完成; 如果没有,就是 TLB miss
, 需要依靠页表来完成翻译过程,翻译完成后将该翻译加入TLB
.
TLB
结构如下,针对user task
, 为了处理多个task
中 VA
相同的情况,需要使用 ASID (Address Space ID)
来区分, 不同的task ASID
不同,这样TLB
中就能有多条相同VA
,不同ASID
的缓存. 对于全局(nG=0)映射,无需比较ASID
,只用VA
查询.
如果TLB 命中
会直接返回 PA
以及对应的属性(内存类型, 缓存策略, 访问权限等).
页表项有三种: 描述页表的;描述块的;描述页的,如下图.
页表中每项为8byte
, 关键 bit 解释如下
- bit0 : 页表项是否有效,0 时表示输入的虚拟地址unmapped,任何访问操作会生成 Translation fault.
- bit1 : 1:bit[47:12]给出的是下一级页表地址.
- 0: 给出的是块内存的基地址及其属性.
下一级为页表时,页表项描述结构体如下
下一级为块内存时,页表项描述结构体如下
- AttrIndx[2:0], bits[4:2] : 内存属性 index, 和 MAIR_EL1 寄存器组合使用.
- AF, bit[10] : access flag.
页表等级
由页大小和虚拟地址宽度确定,计算公式如下,例如页大小为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
将tcr_el1
寄存器的 T0SZ
,T1SZ
位设置为16 来配置 VA
位宽为 48; TG0
部分配置为 0 表示用户态page size
为4K; TG1
部分配置为2 表示内核态page size
为4K.
- #define TCR_T0SZ (64 - 48)
- #define TCR_T1SZ ((64 - 48) << 16)
- #define TCR_TG0_4K (0 << 14)
- #define TCR_TG1_4K (2 << 30)
- #define TCR_VALUE (TCR_T0SZ | TCR_T1SZ | TCR_TG0_4K | TCR_TG1_4K)
-
- ldr x0, =(TCR_VALUE)
- msr tcr_el1, x0
定义了两组内存 Attribute: Attr0
, Attr1
.
Attr0
: 0 , 设备内存, Device-nGnRnE memory
;内存访问不合并,按照program order
执行,禁止指令重排;ack 必须来自最终目的地.Attr1
: 0x44 , 正常内存, Inner + Outer Non-cacheable.- #define MT_DEVICE_nGnRnE 0x0
- #define MT_NORMAL_NC 0x1
- #define MT_DEVICE_nGnRnE_FLAGS 0x00
- #define MT_NORMAL_NC_FLAGS 0x44
- #define MAIR_VALUE (MT_DEVICE_nGnRnE_FLAGS << (8 * MT_DEVICE_nGnRnE)) | (MT_NORMAL_NC_FLAGS << (8 * MT_NORMAL_NC))
- ldr x0, =(MAIR_VALUE)
-
- 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
.
- .macro create_pgd_entry, tbl, virt, tmp1, tmp2
- create_table_entry \tbl, \virt, PGD_SHIFT, \tmp1, \tmp2
- create_table_entry \tbl, \virt, PUD_SHIFT, \tmp1, \tmp2
- .endm
-
- .macro create_table_entry, tbl, virt, shift, tmp1, tmp2
- lsr \tmp1, \virt, #\shift
- and \tmp1, \tmp1, #PTRS_PER_TABLE - 1 // table index
- add \tmp2, \tbl, #PAGE_SIZE
- orr \tmp2, \tmp2, #MM_TYPE_PAGE_TABLE
- str \tmp2, [\tbl, \tmp1, lsl #3]
- add \tbl, \tbl, #PAGE_SIZE // next level table page
- .endm
-
- adrp x0, pg_dir
- msr ttbr1_el1, x0
create_pgd_entry
创建了 PGD
和PUD
,但只用了他们的 index0
这一个表项,如下图,实际上这两级页表位置可以不连续. 这两级页表项都还是页表属性,下一级创建的是block entity
5_2_memory_mapping.PNG
将 sctlr_el1
bit0 置位即打开了MMU.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。