
默认情况下,发生TLB miss后,hardware page table walk自动启动开始扫描内存中的pagetable,若找到相应PTE(page table entry),则自动完成TLB entry的重填工作;如果找不到,则发出一个page fault异常,然后OS接管处理page fault。内核中有do_page_fault函数,该函数从硬盘中调换页面进内存,更新页表,然后重新执行发生TLB miss的那条指令,hardware page table walk重新执行,完成TLB重填的工作。
这里关心的是关闭hardware pagetable walk后,再发生TLB miss后的处理例程。如果发生这种情况,ARM CPU会发出一个translation fault(If translation table walksare disabled, for example, PD0 or EPD0 is set to 1 for TTBR0, or PD1 or EPD1 isset to 1 for TTBR1, the processor returns a Translation fault.见cortex-A15TRM p 5-5)。OS处理该异常的流程如下。
首先发生translation fault后,CPU会发出一个abort异常,然后跳转到该异常地址处(以发生指令预取中止异常为例,跳转到0x00000010)去执行,该地址处存放的是一个跳转指令 (W(b) vector_pabt +stubs_offset),然后,通过判断,若发生该异常的指令处于usr模式,则跳转到__pabt_usr函数去执行,该函数中有条跳转指令bl CPU_PABORT_HANDLER,CPU_PABORT_HANDLER是个宏定义,对于ARMv7,该定义是:# define CPU_PABORT_HANDLER v7_pabort,
v7_pabort函数中就读取了IFSR和IFAR两个寄存器的值:
//pabort-v7.S
/*
*Function: v6_pabort
*
*Params : r0 = address of aborted instruction
*
*Returns : r0 = address of abort
* : r1 = IFSR
*
*Purpose : obtain information about current prefetch abort.
*/
.align5
ENTRY(v7_pabort)
mrc p15,0, r0, c6, c0, 2 @ get IFAR
mrc p15,0, r1, c5, c0, 1 @ get IFSR
mov pc,lr
ENDPROC(v7_pabort)
IFAR中存储了发生异常的指令地址,IFSR中存储的是一个32位数,其中某些位表明异常类型等(参考Cortex-A15TRM p4-76)
剩余的工作就是根据以上两个寄存器提取出来的信息,调用相应函数(do_PrefetchAbort——>do_translation_fault)进行处理。OS接管后的 *** 作是(do_translation_fault函数),首先判断发生TLBmiss的那条指令是用户指令还是系统指令,如果是系统指令则剩余工作是对页全局目录(pgd),页上级目录(pud),页中间目录(pmd)进行 *** 作;如果是用户指令,则调用do_page_fault函数,剩下的工作就是page fault的处理过程,根据不同情况判断,包括权限检查,分配页面,发送SIGSEGV信号给进程,直接杀死进程等。不管哪种 *** 作,OS都没有对TLB进行重填。
对于page fault的处理过程如下:在取数或者取指令时,发生指令或者数据的地址不存在的情况,则发生中止异常。
以取指发生异常为例。发生指令预取中止异常后,CPU自动跳转到0x0000000C(可配置成0xfffffffc,这里不考虑)去执行,该地址处是一个跳转指令 (W(b)vector_pabt + stubs_offset),然后,通过判断,若发生该异常的指令处于usr模式,则跳转到__pabt_usr函数去执行,该函数中有条跳转指令bl CPU_PABORT_HANDLER,CPU_PABORT_HANDLER是个宏定义,对于ARMv7,该定义是:# defineCPU_PABORT_HANDLER v7_pabort,v7_pabort函数中就读取了IFSR和IFAR两个寄存器的值:
//pabort-v7.S
/*
* Function: v6_pabort
*
* Params : r0 = address ofaborted instruction
*
* Returns : r0 = address of abort
*: r1 = IFSR
*
* Purpose : obtain information aboutcurrent prefetch abort.
*/
.align 5
ENTRY(v7_pabort)
mrc p15, 0, r0, c6, c0, 2@ get IFAR
mrc p15, 0, r1, c5, c0, 1@ get IFSR
mov pc, lr
ENDPROC(v7_pabort)
IFAR中存储了发生异常的指令地址,IFSR中存储的是一个32位数,其中某些位表明异常类型等(参考Cortex-A15 TRM p4-76)
剩余的工作就是根据以上两个寄存器提取出来的信息,调用相应函数(do_PrefetchAbort——>do_page_fault)进行处理。
页表用来把虚拟页映射到物理页,并且存放页的保护位(即访问权限)。
在Linux4.11版本以前,Linux内核把页表分为4级:
页全局目录表(PGD)、页上层目录(PUD)、页中间目录(PMD)、直接页表(PT) 。
4.11版本把页表扩展到5级,在页全局目录和页上层目录之间增加了 页四级目录(P4D) 。
各处处理器架构可以选择使用5级,4级,3级或者2级页表,同一种处理器在页长度不同的情况可能选择不同的页表级数。可以使用配置宏CONFIG_PGTABLE_LEVELS配置页表的级数,一般使用默认值。
如果选择4级页表,那么使用PGD,PUD,PMD,PT;如果使用3级页表,那么使用PGD,PMD,PT;如果选择2级页表,那么使用PGD和PT。 如果不使用页中间目录 ,那么内核模拟页中间目录,调用函数pmd_offset 根据页上层目录表项和虚拟地址获取页中间目录表项时 , 直接把页上层目录表项指针强制转换成页中间目录表项 。
每个进程有独立的页表,进程的mm_struct实例的成员pgd指向页全局目录,前面四级页表的表项存放下一级页表的起始地址,直接页表的页表项存放页帧号(PFN) 。
内核也有一个页表, 0号内核线程的进程描述符init_task的成员active_mm指向内存描述符init_mm,内存描述符init_mm的成员pgd指向内核的页全局目录swapper_pg_dir 。
ARM64处理器把页表称为转换表,最多4级。ARM64处理器支持三种页长度:4KB,16KB,64KB。页长度和虚拟地址的宽度决定了转换表的级数,在虚拟地址的宽度为48位的条件下,页长度和转换表级数的关系如下所示:
ARM64处理器把表项称为描述符,使用64位的长描述符格式。描述符的0bit指示描述符是不是有效的:0表示无效,1表示有效。第1位指定描述符类型。
在块描述符和页描述符中,内存属性被拆分为一个高属性和一个低属性块。
处理器的MMU负责把虚拟地址转换成物理地址,为了改进虚拟地址到物理地址的转换速度,避免每次转换都需要查询内存中的页表,处理器厂商在管理单元里加了称为TLB的高速缓存,TLB直译为转换后备缓冲区,意译为页表缓存。
页表缓存用来缓存最近使用过的页表项, 有些处理器使用两级页表缓存 : 第一级TLB分为指令TLB和数据TLB,好处是取指令和取数据可以并行;第二级TLB是统一TLB,即指令和数据共用的TLB 。
不同处理器架构的TLB表项的格式不同。ARM64处理器的每条TLB表项不仅包含虚拟地址和物理地址,也包含属性:内存类型、缓存策略、访问权限、地址空间标识符(ASID)和虚拟机标识符(VMID)。 地址空间标识符区分不同进程的页表项 , 虚拟机标识符区分不同虚拟机的页表项 。
如果内核修改了可能缓存在TLB里面的页表项,那么内核必须负责使旧的TLB表项失效,内核定义了每种处理器架构必须实现的函数。
当TLB没有命中的时候,ARM64处理器的MMU自动遍历内存中的页表,把页表项复制到TLB,不需要软件把页表项写到TLB,所以ARM64架构没有提供写TLB的指令。
为了减少在进程切换时清空页表缓存的需要,ARM64处理器的页表缓存使用非全局位区分内核和进程的页表项(nG位为0表示内核的页表项), 使用地址空间标识符(ASID)区分不同进程的页表项 。
ARM64处理器的ASID长度是由具体实现定义的,可以选择8位或者16位。寄存器TTBR0_EL1或者TTBR1_EL1都可以用来存放当前进程的ASID,通常使用寄存器TCR_EL1的A1位决定使用哪个寄存器存放当前进程的ASID,通常使用寄存器 TTBR0_EL1 。寄存器TTBR0_EL1的位[63:48]或者[63:56]存放当前进程的ASID,位[47:1]存放当前进程的页全局目录的物理地址。
在SMP系统中,ARM64架构要求ASID在处理器的所有核是唯一的。假设ASID为8位,ASID只有256个值,其中0是保留值,可分配的ASID范围1~255,进程的数量可能超过255,两个进程的ASID可能相同,内核引入ASID版本号解决这个问题。
(1)每个进程有一个64位的软件ASID, 低8位存放硬件ASID,高56位存放ASID版本号 。
(2) 64位全局变量asid_generation的高56位保存全局ASID版本号 。
(3) 当进程被调度时,比较进程的ASID版本号和全局版本号 。如果版本号相同,那么直接使用上次分配的ASID,否则需要给进程重新分配硬件ASID。
存在空闲ASID,那么选择一个分配给进程。不存在空闲ASID时,把全局ASID版本号加1,重新从1开始分配硬件ASID,即硬件ASID从255回绕到1。因为刚分配的硬件ASID可能和某个进程的ASID相同,只是ASID版本号不同,页表缓存可能包含了这个进程的页表项,所以必须把所有处理器的页表缓存清空。
引入ASID版本号的好处是:避免每次进程切换都需要清空页表缓存,只需要在硬件ASID回环时把处理器的页表缓存清空 。
虚拟机里面运行的客户 *** 作系统的虚拟地址转物理地址分两个阶段:
(1) 把虚拟地址转换成中间物理地址,由客户 *** 作系统的内核控制 ,和非虚拟化的转换过程相同。
(2) 把中间物理地址转换成物理地址,由虚拟机监控器控制 ,虚拟机监控器为每个虚拟机维护一个转换表,分配一个虚拟机标识符,寄存器 VTTBR_EL2 存放当前虚拟机的阶段2转换表的物理地址。
每个虚拟机有独立的ASID空间 ,页表缓存使用 虚拟机标识符 区分不同虚拟机的转换表项,避免每次虚拟机切换都要清空页表缓存,在虚拟机标识符回绕时把处理器的页表缓存清空。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)