第1章 分级存储架构 通常为了保证计算机的整体性能,内存和CPU之间的通信需保证很高的传输速率,然而这受限制于内存的大小和昂贵的硬件实现,传输速率和内存容量大小的关系遵循“Smaller is faster”原则,使用更大容量的内存势必会增加传输延迟降低性能。为优化计算机整体性能,ARMv8存储系统中提供了多级Cache用于达到内存容量大小和传输延迟之间的平衡。可以从以下两个方面来描述典型的分级存储架构 。 。PE发出虚拟地址VA转换需求 | 。虚拟地址VA经过Address translation的TTW机制试图得到物理地址PA. | 。物理地址PA有可能在Level1 Cache、Level2 Cache或者Main Memroy中. | 。若PA既不在Cache 也不在Main Memroy,将出现Page fault,然后由OS实现重新加载. | 。若顺利找到PA,则给回PE继续执行. | 寄存器 <=> Cache=> | 程序猿/编译器 | Cache <=> Main Memroy=> | Cache 控制器,由HW实现 | Main Memroy <=> Disk=> | OS 操作系统 | Disk <=> Tape=> | 用户 | 1.1.3 Cache数据一致性拓扑结构 PoU | 指当前CPU的指令Cache、数据Cache、TTW所共享一个存储点,每个CPU在Cache L1都有独立的IC和DC,Cache L2对于同一个CPU集群是共享的,POU一般值的是L2范围内的内存. | PoC | 指所有CPU的指令Cache、数据Cache、TTW所共享一个存储点,不同的CPU集群有对于不同的Cache L2,所以对于不同的CPU集群而言,他们共同的存储点是内存,这就是POC拓扑结构. | 1.2 系统层内存模型 属性 | Note | Normal (普通) | 。读写经过Cache 。支持乱序,内存访问顺序同编程顺序可能不一致 。支持预读取? 。支持内存非对齐访问 | Device (设备) | 。读写不经过Cache 。不支持乱序内存访问 。不支持预读取 。不支持内存非对齐访问 | Shareability (可共享性) | 指当前内存页表项的数据是否可以同步到其它CPU上,多核CPU调用带有该属性页表项的数据,一旦某个CPU修改了数据,那么系统将自动更新到其它CPU的数据拷贝,实现内存数据一致性. | Cacheability (可缓存性) | 指当前内存页表项对于的数据是否可以加载到Cache当中. | 地址类型 | 最大支持位宽 | 寄存器配置 | PA(Physical address) | 48 bit | ID_AA64MMFR0_ELx.PARange | OA(Output address) | 48 bit | TCR_ELx.IPS | IA(Input address) | 48 bit | TCR_ELx.T0SZ、TCR_ELx.T0SZ | IPA(Intermediate Physical address) | 48 bit | VTTBR_EL2.T0SZ | · 两种存储类型 · 寄存器配置 异常等级 | 精确数据访问 | Stage 1 TTW | Stage 2 TTW | EL0 | SCTLR_EL1.E0E | SCTLR_EL1.EE | SCTLR_EL2.EE | EL1 | SCTLR_EL1.EE | SCTLR_EL1.EE | SCTLR_EL2.EE | EL2 | SCTLR_EL2EE | SCTLR_EL2.EE | N/A | EL3 | SCTLR_EL3.EE | SCTLR_EL3.EE | N/A | 2.1 VMSAv8-64 · VMSA的基本思想是程序、数据、堆栈的总和内存大小可以超过物理存储器的大小,OS把当前使用的部分送入到内存中,而把其他未被使用的部分保存在磁盘上。例如,对一个16MB的程序和一个内存只有4MB的机器,OS通过调度,可以决定各个时刻将哪4M的内容送入内存中,并在需要时在内存和磁盘间交换程序片段,这样就可以把这个16M的程序运行在一个只具有4M内存机器上了。 · VMSA 提供MMU(Memory Management Unit)用于实现PE访问内存的VA->PA地址转换和控制、访问权限、内存属性决定和检查等。 · 相关命名解析 · 地址类型 VA(虚拟地址) | 我们可看到的地址都是虚拟地址,最大宽度支持48bit,AArch64下VA地址空间分为顶部VA和底部VA两个子区域,每个VA子区域最大支持256TB. 。底部VA:0x0000_0000_0000_0000 =>0x0000_FFFF_FFFF_FFFF 。顶部VA:0xFFFF_0000_0000_0000 =>0xFFFF_FFFF_FFFF_FFFF 。VA[55]决定使用top VA还是bottom VA | IPA(中间物理地址) | 如果不支持Stage 2转换,那么IPA == PA。如果支持Stage2,那么IPA: 。 Stage 1 的OA (Output address) 。 Stage 2 的IA (Input address) 。 最大支持48bit | PA(物理地址) | 物理内存单元映射中的地址,可以看做是PE到内存系统的输出地址(OA),PA最大支持48bit | · 地址转换规则(EL3 use AArch64) 。Secure状态下只支持Stage 1地址转换 | 。Non-secure EL1/EL0既支持Stage 1 也支持Stage 2地址转换. | 。只有EL2才支持Stage 2,所以Stage 2只为EL2服务的 | · 转换表格式支持 。使用64bit descriptor entries(描述符实例) | 。最高支持4个Level的地址查找 | 。Input address (IA)最高支持48bit | 。Output address (OA)最高支持48bit | 。转换颗粒尺寸支持三种大小:4KB\16KB\64KB (和Page size概念类似) | · 三种转换粒度解析 属性 | 4KB granule | 16KB granule | 64KB granule | 转换表中有最大条目数 (项) | 512 | 2048 | 8192 | 每级lookup最大可解析地址位 | 9-bit | 11-bit | 13-bit | Page Offset | VA[11,0]=PA[11,0] | VA[13,0]=PA[13,0] | VA[15,0]=PA[15,0] | 寄存器配置 (x = 0,1,2,3) | TCR_ELx.TG0 =’00’ | TCR_ELx.TG0 =’10’ | TCR_ELx.TG0 =’01’ | · 4KB转换粒度图解 · 16KB转换粒度图解 。最大支持4个Level 的Lookup ,Level1~Level3每个层级的查找最大可以解析11-bit数据 | 。Level 0 是IA[47],只有1个bit宽,比较特殊. | 。Page Offset IA[13:0] == OA[13:0] | 。Translation Table 所需内存Size中等 | · 64KB转换粒度图解 2.1.3 Address Translation Stage · Stage 1(一阶地址转换)图解 · Stage 2 (二阶地址转换)图解 。Stage 2有级联table的概念,可以减少level的级数 | 。所谓级联就是假如有IA[40:0],而Level1解析地址段为IA[38:30],超过了2个bit,而2^40 = 2^2*2^38,所以相当于要2^2个这样的translation table来实现级联解析。ARMv8规定,Stage 2最多支持4-bit级联,也就是最大级联2^4 == 16个translation table级联解析.以达到减少查找level的目的. | 。VTTBR_EL2寄存器提供初始Level查找基地址,Stage 2只为EL2服务 | 。同Stage 1,Level 1支持1GB的内存block,Level 2支持2MB的内存block | 2.1.4 描述符格式(descriptor format) · Level 0~Level 2描述符类型图解 类型 | Bit[1:0] | Note | Invalid | X0 | 表明无效描述符,出现Page fault | Block | 01 | 表明是匹配到一块Block内存范围基地址 | Table | 11 | 表明是匹配到下一级Translation Table基地址 | · ARMv8 Level 3描述符类型图解 类型 | Bit[1:0] | Note | Invalid | X0 | 表明无效描述符,出现Page fault | Reserved | 01 | 保留 | Page | 11 | 表明是个Page基地址,给出内存页的输出地址.bit[47:12] | · 各个层级Block 转换粒度 | Note | 4KB | 。Level 0不支持block 转换 。Level 1 转换表block描述符映射关联1GB的IA范围 。Level 2 描述符映射关联2MB的IA范围 | 16KB | 。Level 0/1不支持block 转换 。Level 2 转换表block描述符映射关联32MB的IA范围 | 64KB | 。Level 1不支持block 转换 。Level 2 转换表block描述符映射关联512MB的IA范围 | NSTable | Stage 2 不支持 | 安全模式标志位 | APTable | 下一级levellookup的access permission | XNTable | 下一级level lookup的执行权限 | PXNTable | 限制XN的特权bit,P是指特权. | 2.2 VMSAv8-32 · 地址类型 VA(虚拟地址) | 保存在PC,LR,SP中的看得到地址都是虚拟地址VA 。VA最大支持32bit宽,地址空间最大到4GB 。地址范围:0x00000000~0xFFFFFFFF | IPA(中间物理地址) | 如果不支持Stage 2转换,那么IPA == PA。如果支持Stage2,那么IPA: 。 Stage 1 的OA (Output address) 。 Stage 2 的IA (Input address) 。 AArch32的IPA最大支持40bit宽(使用16MB转换颗粒) | PA(物理地址) | 物理内存单元映射中的地址,可以看做是PE到内存系统的输出地址(OA),PA最大支持32bit宽 | · 地址转换规则 。Secure状态下只支持Stage 1地址转换 | 。Non-secure EL1/EL0既支持Stage 1 也支持Stage 2地址转换. | 。只有EL2存在才支持Stage 2,所以Stage 2只为EL2服务的 | · 短描述符,转换表中使用32bit描述符entries,提供如下: 。支持最大2级address lookup(地址查找) | 。32bit IA | 。OA最大支持到40bit | 。当使用Page使用Supersection(16MB 转换颗粒)时候,支持PA超过32bit,最大到40bit | 。支持 不访问、代理和domain管理? | · 长描述符,转换表中使用64bit描述符entries,提供如下: 。支持最大3级address lookup(地址查找) | 。当使用Stage2转换 ,最大支持40bit IA | 。OA最大支持到40bit | 。固定4KB的转换颗粒大小(PAGE_SIZE),不支持domain | · 短描述符格式分为4类 Supersection | 超节 | 由16MB的memroy block组成,可选支持 | Section | 节 | 由1MB内存块组成 | Large Pages | 大页 | 由64KB内存块组成 | Small Pages | 小页 | 由4KB内存块组成 | · 转换表中,短描述符是下面之一情况 。一个无效的fault entry | 00 | 。一个page table 条目,指向下一级转换表地址 | 01 | 。一个page或者section 条目,定义了内存访问属性 | 1x | · Level 1描述符格式 无效 | 00 | Page table | 01 | Section/Supersection | 1x | · Level 2描述符格式 无效 | 00 | Large page | 01 | Small page | 1x | NS | 安全模式标志位 | AP | 下一级levellookup的access permission | XN | 下一级level lookup的执行权限 | PX | 限制XN的特权bit,P是指特权. | Domain | Domain其实就是页表权限之上再加一层开关 00:忽略页表权限,访问产生page fault 01:看页表权限访问 11:忽略页表权限正常访问 | S | Shareable(可共享位) | nG | 非全局,决定转换是否标记在TLB中 | TEX[2:0] | 内存范围属性位 | 。TTW(Translation table walk)包含1个或者多个Translation table的查找 | 。目的:提供一种机制去实现 虚拟地址VA => 物理地址PA | 。Non-secure & EL0/1,包含Stage 1、Stage 2 (一阶、二阶) 地址转换 | 。TTBR0_ELx提供user space的初级查找基地址,TTBR1_ELx提供kernel space的初级查找基地址 | 。每一级的translation table lookup返回一个descriptor,如果是最后一级查找,那么返回包含OA和相关内存访问权限属性;如果不是最后一级查找,则包含了下一 level 转换表的基地址. | · TTW结构流程图 · TTW实现流程图 。TTBRn_ELx[47:12]寄存器获取基地址和IA[47:39]做ORR运算得到地址作为输入,开始一阶转换的Level0查找,得到OA作为二级转换的IA(IPA) | 。将IPA作为二级查找的IA,递归TTW过程,得到二级转换OA | 。二级转换OA作为一阶转换Level1查找基地址,开始Level2查找..进入二级转换,重复上面操作 | 。 Level3转换完成后得到物理页框地址[47:12],然后和Page offset做ORR运算,得到最终PA[47:0] | 。如下详细图解 | · TTW结构流程图 · TTW实现流程图 2.4 Cache 支持 Alignment fault | 未对齐故障,发生在Memroy access过程中 | Permission fault | 权限故障(可以发生在TTW的任一Level 的查找中) | Translation fault | 转换故障,发生在TTW过程中 | Address size fault | 地址宽度故障 | Synchronous external abort | 同步外部终止,包括Data abort 和Instruction abort | Access flag fault | 访问标记故障 | TLB confict abort | TLB冲突终止,通常会关联到translation table | 3.2 Kernel如何处理读取空指针? 。kernel函数读取空指针 => ARM发生同步Data abort异常,首先被MMU拦截进入MMU处理,若MMU处理不了,再交由系统处理。如果是32位机,系统将进入Data abort模式处理异常,下面是异常入口函数: 64位:do_mem_abort (arch/arm64/mm/fault.c) 32位:do_DataAbort (arch/arm/mm/fault.c) | 。传入esr寄存器保存的异常的类型,先进入inf->fn()处理,若MMU处理成功就直接返回,否则跳过进入arm64_notify_die /arm_notify_die系统处理 | · AArch64 · AArch 32 3.2.1 Kernel如何处理? 。如下图从fault_info分支中看到同步Data abort会进入do_bad()处理函数,而do_bad()函数固定返回1,说明MMU无法处理,交回由ARM处理. | 。可见,MMU只能处理这几类异常:Address size fault、 Access flag fault、Premission fault、Translation fault、Page fault等. | · AArch 64 · AArch 32 。Vmalloc提供建立连续虚拟内存和离散物理内存直接的映射机制,消除物理内存碎片化 | 。Vmalloc区为非连续内存分区,地址范围是:VMALLOC_START ~ VMALLOC_END 之间. | 。由若干个vmalloc子区域组成,每个vmalloc子区域间隔4KB(PAGE_SIZE),作为安全隔离区 | 。用vm_struct表示每个vmalloc子区域,每次调用vmalloc()在内核成功申请一段连续虚拟内存后,都会对应一个vm_struct子区域. | 。所有的vmalloc内存子区域使用一个链表链接起来. | · Vmalloc 区域结构图 · vm_struct 数据结构(kernel3.10) next | 所有的vm_struct子区域组成一个vmlist链表,next指针指向下一个vm_struct 节点地址 | addr | vmalloc() 最终在内核空间申请一个vm_struct 内存子区域,addr指向该内存子区域首地址 | size | 表示该vm_struct子区域的大小 | flags | 表示该非连续内存区的类型标记 | pages | 指针数组成员是struct page*类型指针,每个成员都关联一个映射到该虚拟内存区的物理页框 | nr_pages | 指针数组pages中page结构的总数 | phys_addr | 通常为0,当使用ioremap()映射一个硬件设备的物理内存时才填充此字段 | caller | 返回地址 | 。size 修正为PAGE_SIZE的整数倍,保证对齐 | 。在vmalloc内存范围内查找一块合适的虚拟地址子内存空间,存储到vm_struct结构中. | 。为申请到的vm_struct 子内存空间分配不连续的物理页框(Physical Frame) | 。建立连续的vm_struct子内存空间到非连续的物理页框(Physical Frame)之间的映射. | 4.3 代码实现分析 · vmallo()内部封装__vmalloc_node_range函数 size | 需要申请子内存的大小,通过vmalloc()传过来 | align | 表示将所申请的内存区分为几个部分,1表示size大小的虚拟内存区作为一个整体 | start | vmalloc区域范围从 VMALLOC_START 开始 | end | vmalloc区域范围到 VMALLOC_END 结束 | gfp_mask | 页面分配标志,GFP_KERNEL | __GFP_HIGHMEM 表示将从内核高端内存区分配内存空间 | prot | 描述当前页的保护标志 | node | 表示在哪个节点上(struct pg_data_t)为这段子内存区分配空间,-1表示在当前节点分配 | caller | 返回地址 | · __vmalloc_node_range函数分析 LINE 9 | 修正获取内存的size,PAGE_ALIGN 将size的大小修改成PAGE_SIZE的整数倍,假设要申请1KB的内存区,那么实际上分配的是PAGE_SIZE(4KB)大小 的区域,然后进行size合法性检查,若不合法则返回NULL,申请内存失败 | LINE 13 | 在vmalloc区域VMALLOC_START,VMALLOC_END 范围内申请一块合适大小的子内存区vm_struct,这部分由函数__get_vm_area_node()来实现 | LINE 18 | 为申请到的子内存区vm_struct 分配物理页框(physical frame),将不连续的physical frame分别映射到连续的vm_struct子内存区中,这部分由函数__vmalloc_area_node()来实现 | LINE22~24 | 新分配的vm_struct区域都有VM_UNLIST标记表明未完全初始化,这里初始化完成后,清除掉此标记,由函数clear_vm_unlist()来实现,表明已经完成VA->PA的映射,kmemleak_alloc()函数是调试用,最后返回addr. | · __get_vm_area_node函数分析 LINE 20 | 修正获取内存的size,修正为PAGE_SIZE的整数倍大小,实现页对齐; | LINE 24 | 使用kmalloc分配一段连续内存,用于存储vm_struct 结构 | LINE 28 | 每个vm_struct子区域之间有4KB大小的安全间隙,所以需要加上PAGE_SIZE的偏移 | LINE 30 | 将在vmalloc整个非连续内存区域范围内查找一块size大小的子内存区,该函数先遍历整个vmap_area_list链表,依次比对链表中每个vmap_area子区域大小,直到找到合适的内存区域为止, 由 alloc_vmap_area()函数实现 | LINE 37 | 将查找到的vmap_area 加载到vm_struct子内存中,然后将这个子vm_struct子内存区插入到整个vmlist链表中,由setup_vmalloc_vm()函数实现 | · alloc_vmap_area函数分析 LINE 17-27 | 红黑树结构,留作后续进阶学习 | · __vmalloc_area_node函数分析 当__get_vm_area_node()创建完新的vm_struct子内存区后,需要通过__vmalloc_area_node()为这个字内存区域分配物理页. LINE 9-10 | 根据PAGE_SIZE计算所需要的内存映射页框(frame)数,保存在nr_pages中,根据nr_pages计算出pages指针数组的大小array_size,pages指针数组每个元素指向一个用于描述物理页框(frame)的page 结构. | LINE 15-19 | 如果pages指针数组大小超过一个PAGE_SIZE的大小(4kB), 那么将进入递归__vmalloc_node()为其分配空间,否则通过kmalloc_node()为pages指针数组分配一段连续内存空间,此段内存空间位于kernel空间的物理内存线性映射区域. pages指针数组用于存放非连续物理页框page结构地址 | LINE 29-43 | 通过一个循环,为pages指针数组中的每个page结构分配物理页框(frame),这里的page结构只是用于描述物理页框结构,不是代表物理内存,如果node<>,说明未指定物理内存所在节点,使用alloc_page()分配一个页框,否则通过alloc_page_node()在指定的节点上分配物理页框,然后把刚刚分配的page装载到pages[i]中. | LINE 46 | 上面完成了分配所需要的物理页框(frame),然后由map_vm_area()完成pages数组中每个非连续物理页框到vmalloc子区的连续虚拟地址映射关系. | 5.1 User内存布局 · 以32位系统为例 · 内存段解析 内存段 | Note | Text | 可执行代码、字符串面值、只读变量 | Data | 已经初始化为0的全局变量和静态局部变量 | BSS | 未经初始化为0的全局变量和静态局部变量 | Stack(栈) | 局部变量,函数参数,返回地址等,效率比堆高很多 | Heap(堆) | Malloc等动态分配的内存,申请到的内存按PAGE_SIZE对齐,机制比较复杂 | Mem maping | 通过mmap系统调用映射进来,包括动态库和文件,可以向上/向下增长. 起始地址由TASK_UNMAPPED_BASE决定,给新的process分配内存 | · 一个简单例子助于理解 Creat一个新的process(Mem mapping),process内有一个函数func(Text),函数内分别定义一个char型变量i(Stack),一个未初始化的static int类型数组j[5](BSS),一个已经初始化的static long型变量k(Data)。func内调用malloc()分配了一段内存(Heap). 5.1.1 Mem mapping内存分配方式(64位) 当系统Create一个新的Process(进程)的时候,使用mmap给它分配内存有两种实现方式,一种是从一个固定的基地址TASK_UNMAPPED_BASE开始向上增长,一种是以某一个基地址为基准的固定范围内偏移位地址向下增长,起始地址是非固定值,这样可以提高安全性能,如下代码描述 | 若mmap_is_legacy() == true,则每一个新的mmap地址是由低=》高增长的,属于老的方式,起始地址是固定值TASK_UNMAPPED_BASE,不同位宽的kernel,起始地址有所不同,如下图描述 | 若mmap_is_legacy() == false,则和上面相反,每一个新的mmap地址是由低《= 高增长的,属于新的方式,起始地址是固定值+固定范围内随机偏移,相比老的方式更加安全,如下图描述 | 5.2.1 ARM64内核内存布局 · 两种Page size内存布局对比,如图: · 内存段解析 内存段 | Note | Kernel driver | 内核驱动模块空间,大小为64MB,起始地址如下 MODULES_END == 0XFFFF-FFC0-0000-0000 MODULES_VADDR == 0XFFFF-FFBF-0000-0000 | Vmalloc | Vmalloc空间,大小约~239GB,用于非连续物理内存到连续虚拟内存直接的映射,起始地址如下 VMALLOC_START == 0XFFFF-FF80-0000-0000 VMALLOC_END == 0XFFFF-FFBB-BBBB-0000 | Linear maping | 线性映射区,包括三段:Kernel.text(程序段)、Kernel.init、Kernel.data(数据段),Kernel.text段起始地址为 0XFFFF-FFC0-0008-0000 | · 相关代码定义 VA_BITS | 文件路径:kernel/arch/arm64/include/asm/memory.h #define VA_BITS (39) #define TASK_SIZE_64 (UL(1) <> | MODULES | 文件路径:kernel/arch/arm64/include/asm/memory.h #define PAGE_OFFSET (UL(0xffff-ffc0-0000-0000)) #define MODULES_END PAGE_OFFSET #define MODULES_VADDR (MODULES_END –SZ_64M) | VMALLOC | 文件路径:kernel/arch/arm64/include/asm/pgtable.h #define VMALL0C_START UL(0xffff-ff80-0000-0000) #define VMALL0C_END (PAGE_OFFSET- UL(0x4-0000-0000)- SZ_64K) | 5.3 各内存域和物理内存映射关系 内存域 | 与物理内存之间的映射关系 | VMALLOC | PA = f(VA), f是页表机制映射法则 | MODULES | 和vmalloc映射机制相同 | Liner maping | PA = VA + OFFSET | BSS\Text\Data | 和vmalloc映射机制相同 | Heap(堆) | 和vmalloc映射机制相同 | Stack(栈) | 和vmalloc映射机制相同 | Mem maping | 和vmalloc映射机制相同 |
|