armv8 mmu 支持4K,16K,64K分页,在SylixOS目前实现了4K和6K分页。根据查看代码SylixOS目前使用的是Non-secure EL1、stage 1 translation、VA和PA的地址宽度都是48个bit。所以分析代码不包含arm提供的其他功能。
代码位于SylixOS/arch/arm64/mm/mmu 文件夹。
在SylixOS封装了mmu操作函数集,这样虽然不同体系结构只需要实现对应的函数功能,不影响内核。
arm64MmuMemInit函数是初始化,为pgd,pmd,pts,pte分配空间。在4K模式下使用四级分页。
/*********************************************************************************************************
** 函数名称: arm64MmuMemInit
** 功能描述: 初始化 MMU 页表内存区
** 输 入 : pmmuctx mmu 上下文
** 输 出 : ERROR or OK
** 全局变量:
** 调用模块:
** 注 意 : ARM 体系结构要求: 一级页表基地址需要保持 16 KByte 对齐, 单条目映射 1 MByte 空间.
二级页表基地址需要保持 1 KByte 对齐, 单条目映射 4 KByte 空间.
*********************************************************************************************************/
static INT arm64MmuMemInit (PLW_MMU_CONTEXT pmmuctx)
{
#define PGD_BLOCK_SIZE (4 * LW_CFG_KB_SIZE)
#define PMD_BLOCK_SIZE (4 * LW_CFG_KB_SIZE)
#define PTS_BLOCK_SIZE (4 * LW_CFG_KB_SIZE)
#define PTE_BLOCK_SIZE (4 * LW_CFG_KB_SIZE)
PVOID pvPgdTable;
PVOID pvPmdTable;
PVOID pvPtsTable;
PVOID pvPteTable;
ULONG ulPgdNum = bspMmuPgdMaxNum();
ULONG ulPmdNum = bspMmuPmdMaxNum();
ULONG ulPtsNum = bspMmuPtsMaxNum();
ULONG ulPteNum = bspMmuPteMaxNum();
pvPgdTable = __KHEAP_ALLOC_ALIGN((size_t)ulPgdNum * PGD_BLOCK_SIZE, PGD_BLOCK_SIZE);
pvPmdTable = __KHEAP_ALLOC_ALIGN((size_t)ulPmdNum * PMD_BLOCK_SIZE, PMD_BLOCK_SIZE);
pvPtsTable = __KHEAP_ALLOC_ALIGN((size_t)ulPtsNum * PTS_BLOCK_SIZE, PTS_BLOCK_SIZE);
pvPteTable = __KHEAP_ALLOC_ALIGN((size_t)ulPteNum * PTE_BLOCK_SIZE, PTE_BLOCK_SIZE);
if (!pvPgdTable || !pvPmdTable || !pvPtsTable || !pvPteTable) {
_DebugHandle(__ERRORMESSAGE_LEVEL, "can not allocate page table.\r\n");
return (PX_ERROR);
}
_G_hPGDPartition = API_PartitionCreate("pgd_pool", pvPgdTable, ulPgdNum, PGD_BLOCK_SIZE,
LW_OPTION_OBJECT_GLOBAL, LW_NULL);
_G_hPMDPartition = API_PartitionCreate("pmd_pool", pvPmdTable, ulPmdNum, PMD_BLOCK_SIZE,
LW_OPTION_OBJECT_GLOBAL, LW_NULL);
_G_hPTSPartition = API_PartitionCreate("pts_pool", pvPtsTable, ulPtsNum, PTS_BLOCK_SIZE,
LW_OPTION_OBJECT_GLOBAL, LW_NULL);
_G_hPTEPartition = API_PartitionCreate("pte_pool", pvPteTable, ulPteNum, PTE_BLOCK_SIZE,
LW_OPTION_OBJECT_GLOBAL, LW_NULL);
if (!_G_hPGDPartition || !_G_hPMDPartition || !_G_hPTSPartition || !_G_hPTEPartition) {
_DebugHandle(__ERRORMESSAGE_LEVEL, "can not allocate page pool.\r\n");
return (PX_ERROR);
}
return (ERROR_NONE);
}
可看到首先重bsp函数中获得需要分配的pgd,pmd,pte,pts数量。根据不同的硬件可以指定当前使用的数量,但是都是以4K空间为单位。在armv8A手册中规定了采用4级后各使用地址位数。
上图是手册中VA48位时4K分页传输时的结构。在armv8 可以指定地址线位数,SylixOS目前使用的是虚拟地址48位。
上图来自蜗窝科技 的ARM64的启动过程之(二),很好的解释了虚拟地址的翻译过程。
在4k分页对应关系是下表
地址翻译过程如下图:
在arm64MmuMemInit函数中创建了pgd,pmd,pts,pte的池,这个池就是为四级提前在堆栈中分配的空间。每使用一个entry就是从池中分配出来。在arm64中每个entry是8个字节。
主要是做了与硬件相关的初始化。
/*********************************************************************************************************
** 函数名称: arm64MmuGlobalInit
** 功能描述: 调用 BSP 对 MMU 初始化
** 输 入 : pcMachineName 使用的机器名称
** 输 出 : ERROR or OK
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static INT arm64MmuGlobalInit (CPCHAR pcMachineName)
{
archCacheReset(pcMachineName);
arm64MmuInvalidateTLB();
arm64MmuSetTCR(0x580823510); /* T0SZ = 2 ^ 48 */
arm64MmuSetMAIR();
return (ERROR_NONE);
}
archCacheRest函数是无效cache清除cache。实现调用汇编参考SylixOS armv8启动分析
arm64MmuInvalidateTlB函数是无效mmu的TLB表。在mmu中存在一个类似于cache的快表。
FUNC_DEF(arm64MmuInvalidateTLB) ;/* 快表中的所有项无效 */
TLBI VMALLE1IS ;/* TLBIAll, EL1, IS */
ARM_DSB()
ARM_ISB()
RET
FUNC_END()
TLBI VMALLE1IS 指令在armv8手册中定义如图。ARM_DSB(),ARM_ISB()是arm dsb,isb指令,是指令屏障,保证前面指令执行完,后面执行。
arm64MmuSetTCR函数是直接将值赋值给TCR_EL1寄存器。TCR_ELx 是mmu控制相关的寄存器。
FUNC_DEF(arm64MmuSetTCR)
MSR TCR_EL1 , X0
RET
FUNC_END()
TCR控制寄存器包含很多系统相关的内容:
T0SZ, bits [5:0]定义了虚拟地址的宽度。
EPD0 控制当虚拟地址使用TLB块表查找错误时,是否使用TTBR0_EL1 进行查找。Translation table walk 指的是从虚拟地址查找到物理地址的过程。TTBR0_EL1 存放的的pgd的基地址。当从TLB查找失败时需要使用pgd->pmd->pts->pts查找过程。
IRGN1, bits [25:24]和IRGN0, bits [9:8]用来控制页表所在memory的inner cachebility attribute的。
ORGN1, bits [27:26]和ORGN0, bits [11:10]用来控制页表所在memory的outercachebility attribute的。
SH1, bits [29:28]和SH0, bits [13:12]是用来控制页表所在memory的Shareability attribute。
TG1,bits [31:30]和TG0,bits [15:14]是用来控制page size的,可以是4K,16K或者64K。
TBI1,bit[38]和TBI0,bit[37]用来控制是否忽略地址的高8位(TBI就是Top Byte ignored的意思),如果允许忽略地址的高8位,那么MMU的硬件在进行地址比对,匹配的时候忽略高八位,这样软件可以自由的使用这个byte,例如对于一个指向动态分配内存的对象指针,可以通过高8位来表示reference counter,从而可以跟踪其使用情况,reference count等于0的时候,可以释放内存。AS bit[36]用来定义ASID(address space ID)的size,A1, bit [22]用来控制是kernel space还是user space使用ASID。ASID是和TLB操作相关,一般而言,地址翻译的时候并不是直接查找页表,而是先看TLB是否命中,具体判断的标准是虚拟地址+ASID,ASID是每一个进程分配一个,标识自己的进程地址空间。这样在切换进程的时候不需要flush TLB,从而有助于performance。 这里参考了ARM64的启动过程之(三):为打开MMU而进行的CPU初始化。
在SylixOS中在TCR寄存器设置的值为0x580823510 所以bit[38]和bit[37]都为0,不忽略高8位。控制选择4K,虚拟地址长度是48位。IPS设置为48bit,IPS(Intermediate Physical Address Size)查资料这里和虚拟化有关,具体还没搞懂。
最后是arm64MmuSetMAIR函数,MAIR_EL1是内存属性的寄存器
;/*********************************************************************************************************
; 设置域属性
;*********************************************************************************************************/
#define MAIR(attr, mt) ((attr) << ((mt) * 8))
#define MT_DEVICE_nGnRnE 0
#define MT_DEVICE_nGnRE 1
#define MT_DEVICE_nGRE 2
#define MT_DEVICE_GRE 3
#define MT_NORMAL_NC 4
#define MT_NORMAL 5
#define MT_NORMAL_WT 6
FUNC_DEF(arm64MmuSetMAIR)
LDR X0 , =MAIR(0x00, MT_DEVICE_nGnRnE) | \
MAIR(0x04, MT_DEVICE_nGnRE) | \
MAIR(0x08, MT_DEVICE_nGRE) | \
MAIR(0x0c, MT_DEVICE_GRE) | \
MAIR(0x44, MT_NORMAL_NC) | \
MAIR(0xff, MT_NORMAL) | \
MAIR(0xbb, MT_NORMAL_WT)
MSR MAIR_EL1 , X0
RET
FUNC_END()
下面在armv8A手册中对MAIR_EL寄存的介绍
根据arm官方介绍内存分为两种类型,分别是device和normal,也就是设备使用的内存和正常的内存。在arm中将寄存器操作映射到内存当中这部分内存,由于对寄存器读些映射关系呗称为设备内存。
“我们知道,ARMv8采用了weakly-order内存模型,也就是说,通俗的讲就是处理器实际对内存访问(load and store)的执行序列和program order不一定保持严格的一致,处理器可以对内存访问进行reorder。例如:对于写操作,processor可能会合并两个写的请求。处理器这么任性当然是从性能考虑,不过这大大加大了软件的复杂度(软件工程师需要理解各种memory barrier操作,例如ISB/DSB/DMB,以便控制自己程序的内存访问的order)。
地址空间那么大,是否都任由processor胡作非为呢?当然不是,例如对于外设的IO地址,处理必须要保持其order。因此memory被分成两个基本的类型:normal memory和devicememory。除了基本的memory type,还有memory attribute(例如:cacheability,shareability)来进一步进行描述,我们在下一节描述。
标识为normal memory type的memory就是我们常说的内存而已,对其访问没有副作用(side effect),也就是说第n次和第n+1次访问没有什么差别。device memory就不会这样,对一些状态寄存器有可能会read clear,因此n和n+1的内存访问结果是不一样的。正因为如此,processor可以对这些内存操作进行reorder、repeat或者merge。我们可以把程序代码和数据所在的memory设定为normal memory type,这样可以获取更高的性能。例如,在代码执行过程中,processor可能进行分支预测,从而提前加载某些代码进入pipeline(而实际上,program不一定会fetch那些指令),如果设定了不正确的memory type,那么会阻止processor进行reorder的动作,从而阻止了分支预测,进而影响性能。
对于那些外设使用的IO memory,对其的访问是有side effect的,很简单的例子就是设备的FIFO,其地址是固定不变的,但是每次访问,内部的移位寄存器就会将下一个数据移出来,因此每次访问同一个地址实际上返回的数据是不一样的。device不存在cache的设定,总是no cache的,处理器访问device memory的时候,限制会比普通memory多,例如不能进行Speculative data accesses(所谓不能进行Speculative data accesses就是说cpu对memory的访问必须由顺序执行的执行产生,不能由于自己想加快性能而投机的,提前进行某些数据访问)。” ---引用自蜗窝科技
在device和normal内存中又进行了细分,在MAIR寄存器被分为了8组,每组8位,分别代表了不同属性的内存。看手册中MAIR寄存器的定义
可以看到每一组中当4-7bit是0时为设备内存。
以下来自蜗窝科技:“
对于device type,其总是non cacheable的,而且是outer shareable,因此它的attribute不多,主要有下面几种附加的特性:
(1)Gathering 或者non Gathering (G or nG)。这个特性表示对多个memory的访问是否可以合并,如果是nG,表示处理器必须严格按照代码中内存访问来进行,不能把两次访问合并成一次。例如:代码中有2次对同样的一个地址的读访问,那么处理器必须严格进行两次read transaction。
(2)Re-ordering (R or nR)。这个特性用来表示是否允许处理器对内存访问指令进行重排。nR表示必须严格执行program order。
(3)Early Write Acknowledgement (E or nE)。PE访问memory是有问有答的(更专业的术语叫做transaction),对于write而言,PE需要write ack操作以便确定完成一个write transaction。为了加快写的速度,系统的中间环节可能会设定一些write buffer。nE表示写操作的ack必须来自最终的目的地而不是中间的write buffer。
对于normal memory,可以是non-cacheable的,也可以是cacheable的,这样就需要进一步了解Cacheable和shareable atrribute,具体如下:
(1)是否cacheable
(2)write through or write back
(3)Read allocate or write allocate
(4)transient or non-transient cache
最后一点要说明的是由于cache hierararchy的存在,memory的属性可以针对inner和outer cache分别设定,具体如何区分inner和outer cache是和具体实现相关,但通俗的讲,build in在processor内的cache是inner的,而outer cache是processor通过bus访问的。NC是no cache,也就是说MT_NORMAL_NC的memory是normal memory,但是对于这种类型的memory的访问不需要通过cache系统”
write through or write back解释如下(来自链接):
Write Back:Cache Line中的数据被CPU核修改时并不立刻写回内存,Cache Line和内存中的数据会暂时不一致,在Cache Line中有一个Dirty位标记这一情况。当一条Cache Line要被其它VA的数据替换时,如果不是Dirty的就直接替换掉,如果是Dirty的就先写回内存再替换。
Write Through:每当CPU核修改Cache Line中的数据时就立刻写回内存,Cache Line和内存中的数据总是一致的。如果有多个CPU或设备同时访问内存,例如采用双口RAM,那么Cache中的数据和内存保持一致就非常重要了,这时相关的内存页面通常配置为Write Through模式
Read allocate or write allocate 解释(来自链接):
在有cache的单机系统中,通常有两种写策略:write through和write back。这两种写策略都是针对写命中(write hit)情况而言的:write through是既写cache也写main memory;write back是只写cache,并使用dirty标志位记录cache的修改,直到被修改的cache 块被替换时,才把修改的内容写回main memory。那么在写失效(write miss)时,即所要写的地址不在cache中,该怎么办呢?一种办法就是把要写的内容直接写回main memory,这种办法叫做no write allocate policy;另一种办法就是把要写的地址所在的块先从main memory调入cache中,然后写cache,这种办法叫做write allocate policy
可以看到SylixOS给MAIR_EL1中七个条目设置属性。
第一个是MT_DEVICE_nGnRnE 代表是设备内存,必须保证严格按照代码中的访问顺序来,不允许合并对内存的访问,不允许对指令重排,写操作的ack必须来自最终的目的地而不是中间的write buffer。
第二个是MT_DEVICE_nGnRE 必须保证严格按照代码中的访问顺序来,不允许合并对内存的访问,不允许对指令重排,但是写操作的ack不必须来自最终的目的地而不是中间的write buffer。
第三个 MT_DEVICE_nGRE是不能对内存访问合并,但是指令可以重排,ack不必须来自最终的目的地。
第四个MT_DEVICE_GRE 是对内存访问可以合并,指令可以重排,ack不必须来自最终的目的地。
第五个 MT_NORMAL_NC是不带cache的内存。
第六个MT_NORMAL 是正常的内存。
第七个MT_NORMAL_WT 是带 write Through的。
MAIR寄存器含有8组,最后使用时在描述符属性里的AttrIndx[2:0]里指定使用MAIR寄存器中那一组属性。
上图是armv8手册中定义属性位置, 但是SylixOS有一层自己封装的属性,在最后pte和物理地址绑定时将SylixOS属性转化为上图所示的属性。
此函数是分配一个pgd 基地址。也就是从pgd池中分配出一个表的首地址。然后将表清空。pgd比较特殊因为它是第一个,所以没有去把地址填入过程。并且直接返回相应的entry。
/*********************************************************************************************************
** 函数名称: arm64MmuPgdAlloc
** 功能描述: 分配 PGD 项
** 输 入 : pmmuctx mmu 上下文
** ulAddr 虚拟地址 (参数 0 即偏移量为 0 , 需要返回页表基地址)
** 输 出 : 分配 PGD 地址
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static LW_PGD_TRANSENTRY *arm64MmuPgdAlloc (PLW_MMU_CONTEXT pmmuctx, addr_t ulAddr)
{
REGISTER LW_PGD_TRANSENTRY *p_pgdentry;
REGISTER ULONG ulPgdNum;
p_pgdentry = (LW_PGD_TRANSENTRY *)API_PartitionGet(_G_hPGDPartition);
if (!p_pgdentry) {
return (LW_NULL);
}
lib_bzero(p_pgdentry, PGD_BLOCK_SIZE); /* 新的 PGD 无有效的页表项 */
ulAddr &= LW_CFG_VMM_PGD_MASK;
ulPgdNum = ulAddr >> LW_CFG_VMM_PGD_SHIFT;
p_pgdentry = (LW_PGD_TRANSENTRY *)((addr_t)p_pgdentry |
(ulPgdNum * sizeof(LW_PGD_TRANSENTRY))); /* 获得一级页表描述符地址 */
return (p_pgdentry);
}
首先获取pgd池的基地址,然后保留ulAddr 地址的39到47位。通过右移39位,求出当前地址在pgd页表中的的位置。求出一个entry的地址。
主要是分配一个pmd表的首地址,将这个首地址填入到pgd 条目中。然后计算当前地址在pmd表中的entry,返回当前的pmd条目
/*********************************************************************************************************
** 函数名称: arm64MmuPmdAlloc
** 功能描述: 分配 PMD 项
** 输 入 : pmmuctx mmu 上下文
** p_pgdentry pgd 入口地址
** ulAddr 虚拟地址
** 输 出 : 分配 PMD 地址
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static LW_PMD_TRANSENTRY *arm64MmuPmdAlloc (PLW_MMU_CONTEXT pmmuctx,
LW_PGD_TRANSENTRY *p_pgdentry,
addr_t ulAddr)
{
#if LW_CFG_CACHE_EN > 0
INTREG iregInterLevel;
#endif /* LW_CFG_CACHE_EN > 0 */
LW_PMD_TRANSENTRY *p_pmdentry = (LW_PMD_TRANSENTRY *)API_PartitionGet(_G_hPMDPartition);
if (!p_pmdentry) {
return (LW_NULL);
}
lib_bzero(p_pmdentry, PMD_BLOCK_SIZE);
*p_pgdentry = arm64MmuBuildPgdEntry((addr_t)p_pmdentry, /* 设置一级页表描述符 */
ARM64_MMU_NS_SECURE,
ARM64_MMU_AP_NO_EFFECT,
ARM64_MMU_XN_NO_EFFECT,
ARM64_MMU_PXN_NO_EFFECT,
ARM64_PGD_TYPE_TABLE);
#if LW_CFG_CACHE_EN > 0
iregInterLevel = KN_INT_DISABLE();
arm64DCacheFlush((PVOID)p_pgdentry, (PVOID)p_pgdentry, 32); /* 第三个参数无影响 */
KN_INT_ENABLE(iregInterLevel);
#endif /* LW_CFG_CACHE_EN > 0 */
return (arm64MmuPmdOffset(p_pgdentry, ulAddr));
}
pts,pte分配和pmd分配相同。arm64MmuBuildPgdEntry函数在这里保留了地址的12-47位,其他位加入了系统需要的标志信息。
arm64MmuPgdIsOk 判断是否pgd当前 entry有效
/*********************************************************************************************************
** 函数名称: arm64MmuPgdIsOk
** 功能描述: 判断 PGD 项的描述符是否正确
** 输 入 : pgdentry PGD 项描述符
** 输 出 : 是否正确
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static BOOL arm64MmuPgdIsOk (LW_PGD_TRANSENTRY pgdentry)
{
return (((pgdentry & ARM64_PGD_TYPE_MASK) == ARM64_PGD_TYPE_TABLE) ? LW_TRUE : LW_FALSE);
}
判断方式很简单 就是检测ARM64_PMD_TYPE_MASK 这个掩码,这个掩码上面pmd分配函数里,在把pmd地址写入到pgd entry时加入一些标志位,检测到这个存在说明当前pgd被填写了pmd数据。pmdOk等函数与这原理相同。
/*********************************************************************************************************
** 函数名称: arm64MmuPgdOffset
** 功能描述: 通过虚拟地址计算 PGD 项
** 输 入 : pmmuctx mmu 上下文
** ulAddr 虚拟地址
** 输 出 : 对应的 PGD 表项地址
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static LW_PGD_TRANSENTRY *arm64MmuPgdOffset (PLW_MMU_CONTEXT pmmuctx, addr_t ulAddr)
{
REGISTER LW_PGD_TRANSENTRY *p_pgdentry = pmmuctx->MMUCTX_pgdEntry;
REGISTER ULONG ulPgdNum;
ulAddr &= LW_CFG_VMM_PGD_MASK;
ulPgdNum = ulAddr >> LW_CFG_VMM_PGD_SHIFT; /* 计算 PGD 号 */
p_pgdentry = (LW_PGD_TRANSENTRY *)((addr_t)p_pgdentry |
(ulPgdNum * sizeof(LW_PGD_TRANSENTRY))); /* 获得一级页表描述符地址 */
return (p_pgdentry);
}
全部的偏移函数都是首先获得当前自己的表的基地址,上面函数是首先获得 自己表的首地址,然后保留39-47数值,通过
(ulPgdNum * sizeof(LW_PGD_TRANSENTRY)) 这句话算出在表内的偏移值。返回对应的entry。
arm64MmuPtePhysGet 是将当前的pte存放地址丢掉低12bit,返回。
arm64MmuMakeTrans
此函数的主要作用是将物理地址和虚拟地址表中的pte建立联系。也就是需要填充pte表。上面的alloc函数最终只把pts填充了pte的地址。所以在这里填充pte。
/*********************************************************************************************************
** 函数名称: arm64MmuMakeTrans
** 功能描述: 设置页面映射关系
** 输 入 : pmmuctx mmu 上下文
** p_pteentry 对应的页表项
** ulVirtualAddr 虚拟地址
** paPhysicalAddr 物理地址
** ulFlag 对应的类型
** 输 出 : NONE
** 全局变量:
** 调用模块:
** 注 意 : 这里不需要清除快表 TLB, 因为 VMM 自身会作此操作.
*********************************************************************************************************/
static VOID arm64MmuMakeTrans (PLW_MMU_CONTEXT pmmuctx,
LW_PTE_TRANSENTRY *p_pteentry,
addr_t ulVirtualAddr,
phys_addr_t paPhysicalAddr,
ULONG ulFlag)
{
UINT8 ucGuard; /* 严格的权限检查 */
UINT8 ucXN; /* 存储权限 */
UINT8 ucPXN; /* 域 */
UINT8 ucContiguous; /* CACHE 与缓冲区控制 */
UINT8 ucnG; /* 存储权限 */
UINT8 ucAF; /* CACHE 与缓冲区控制 */
UINT8 ucSH; /* 永不执行位 */
UINT8 ucAP;
UINT8 ucNS;
UINT8 ucAttrIndx;
UINT8 ucType;
if (ulFlag & LW_VMM_FLAG_ACCESS) {
ucType = ARM64_PTE_TYPE_PAGE;
} else {
ucType = ARM64_PTE_TYPE_FAULT; /* 访问将失效 */
}
if (arm64MmuFlags2Attr(ulFlag,
&ucGuard,
&ucXN, &ucPXN,
&ucContiguous,
&ucnG, &ucAF,
&ucSH, &ucAP,
&ucNS, &ucAttrIndx) < 0) { /* 无效的映射关系 */
return;
}
*p_pteentry = arm64MmuBuildPtentry((addr_t)paPhysicalAddr,
ucGuard,
ucXN, ucPXN,
ucContiguous,
ucnG, ucAF,
ucSH, ucAP,
ucNS, ucAttrIndx,
ucType);
#if LW_CFG_CACHE_EN > 0
arm64DCacheFlush((PVOID)p_pteentry, (PVOID)p_pteentry, 32); /* 第三个参数无影响 */
#endif /* LW_CFG_CACHE_EN > 0 */
}
首先将SylixoS标志位转换为物理地址的标志位。这里的ucAttrIndx 就是上面MAIR寄存器讲到有8个属性条目,其他可以在上面的图中看到。物理地址因为是4K页对齐所以也是保留 12-47位。最后根据虚拟地址中的offset计算出当前实际访问物理地址。
arm64MmuMakeCurCtx
此函数用来设置TTBR0_EL1寄存器。TTBR0_EL1 寄存器器用来保存pgd表的基地址。
arm64MmuInvTLB
此函数使用来实现无线Tlb块表的,通过传入地址和无效的数量,当无效数量大于16时直接使用arm64MmuInvalidateTLB函数无效全部TLB。当无效数量小于16时调用arm64MmuInvalidateTLBMVA函数,将虚拟地址低12位抛弃,然后无效当前地址的。
FUNC_DEF(arm64MmuInvalidateTLBMVA)
LSR X0 , X0 , #12 ;/* 虚拟地址移出取低 12 位 */
TLBI VAE1IS , X0 ;/* TLBIVA, All ASID, EL1, IS */
ARM_DSB()
ARM_ISB()
RET
FUNC_END()
使能和关闭mmu都是通过设置SCTLR_EL1系统控制寄存器。
arm64MmuFlagGet 是通过虚拟地址四级查表获得pte存放的物理地址值,然后解析当前值对应的属性位。
arm64MmuFlagSet 函数是通过四级页表查找获得pte存放的物理地址,然后将SylixOS系统内存属性解析为硬件mmu的属性。然后将新设置的值写入到pte当中。
上面是mmu操作函数集,封装好了后还需要调用来建立mmu映射,在SylixOS中操作函数使用了宏定义,类似于下图