MMU简单处理TLB例外
TLB是translation lookaside buffer的简称,也可称为快表,页表的Cache。
首先,我们知道MMU的作用是把虚拟地址转换成物理地址。虚拟地址和物理地址的映射关系存储在页表中,而现在页表又是分级的。64位系统一般都是3~5级, 常见的配置是4级页表,就以4级页表为例说明: 分别是PGD、PUD、PMD、PTE四级页表。在硬件上会有一个叫做页表基地址寄存器,它存储PGD页表的首地址。MMU就是根据页表基地址寄存器从PGD页表一路查到PTE,最终找到物理地址(PTE页表中存储物理地址)。
这就像在地图上显示你的家在哪一样,我为了找到你家的地址,先确定你是中国,再确定你是某个省,继续往下某个市,最后找到你家是一样的原理。一级一级找下去。这个过程非常繁琐;如果第一次查到你家的具体位置,我如果记下来你的姓名和你家的地址。下次查找时,是不是只需要跟我说你的姓名是什么,我就直接能够告诉你地址,而不需要一级一级查找。四级页表查找过程需要四次内存访问, 延时可想而知,非常影响性能, 而记下来本次查找页表命中的数据就可以称为缓存到TLB.
本章节的内容主要是通过TlbRefill来将一个VA映射成PA,即VA有效.
TLB Miss
在固件下我们没有实现页表的操作,地址都是通过窗口命中来转换成物理地址, 所以我们有了0x8000000000000000(uncache) 和 0x9000000000000000(cache) 两个窗口地址,并且将窗口下的地址进一步划分;
比如:CPU->Chipset 间HT空间的地址 0x80000e0000000000 -> 0x0e0000000000 (HT1_LO的物理地址空间).
基本上在高位的窗口命中下划分的VA->PA都按照1-1做了映射关系.
正因为有了以上的窗口地址,我们的代码中的有效地址基本上都是以0x8000000000000000 和 0x9000000000000000来划分,即Mem空间通过Cache窗口,非Mem通过Uncache窗口进行访问; 但如果我们访问的地址不落在窗口命中范围,就会去TLB表项内查找是否存在映射关系(MTLB & STLB),由于我们没有增加任何Table Entry,所以其余地址必定TLB Miss, 从而触发TLB 例外, 下面以一个VA为例:
下面在不静态添加TLB表项的前提下,就TLBRefill来动态实现VA有效。
TLB Refill
我们目前的 Exception Entry在内存的0地址,故异常初始化的时候将Exception Section拷贝到了0地址,作为软件入口点。
简谈TLB Refill执行逻辑:
- 1 保存上下文,即所有寄存器的状态,为了灵活架构提供了TLB时可以作为暂存的寄存器:
- 2 检测触发例外是中断还是异常, 我们以TLB重填例外为例 0x8a Bit0 置1:
- 3 重填TLB表项: TLBRELO0、 TLBRELO1 、 TLBREHI.
- 4 开中断恢复寄存器状态.
- 5 ERTN返回:
重填TLB入口点
Exception_Handler ( 保存上文数据 ) -> { la t1, mException; jirl zero, t1, 0;} (跳转异常具体处理).
0x00: 当前模式信息 (CRMD)
0x01: 例外当前模式信息 (PRMD)
0x04: 例外配置 (ECTL)
0x05: 例外状态 (ESTAT)
0x06: 例外程序计数器 (EPC)
0x07: 出错虚地址 (BADV)
0x08: 出错指令 (BADI)
0x10: TLB索引 (TLBIDX):
0x11: TLB表项高位 (TLBEHI)
0x12: TLB表项低位0 (TLBELO0)
0x13: TLB表项低位1 (TLBELO1)
0x1b: 全局目录基址 (PGD)
0x1e: STLB页大小 (STLBPS)
0x30 + n (0->15): 数据保存 (SAVEn)
0x88 : TLB重填例外入口地址 TLBRENTRY :填写异常入口点,即0地址异常处理函数.
0x89 : TLB重填例外出错虚地址 TLBRBADV : 触发TLB例外时,出错虚拟地址(读写出错BadV为读写无效地址,加载执行出错为pc无效地址).
0x8a : TLB重填例外程序计数器 TLBREPC : 触发TLB例外时, 当前PC已跳入异常,EPC记录触发异常前的PC.
0x8b : TLB重填例外数据保存 TLBRSAVE 暂存上下文数据
0x8c : TLB重填例外表项低位0 (TLBRELO0) TLB表项的奇偶页:PPN0(物理页号)
0x8d : TLB重填例外表项低位1 (TLBRELO1) TLB表项的奇偶页:PPN1(物理页号)
0x8e : TLB重填例外表项高位 (TLBREHI) TLB表项的虚页号VPN
以上这些CSR寄存器都是调试必须了解的,关于本章节的所有寄存器的信息都可参考 龙芯架构参考手册 卷一:基础架构。
相关手册文档已开源github,附下载地址: https://github.com/loongson/LoongArch-Documentation,欢迎有兴趣的小伙伴收藏转发学习 !
单个TLB表项的比较部分:
TLBRefill Code实例:
150 VOID
151 AddTlbEntry (
152 IN TLB_REGS *Regs
153 )
154 {
155 DbgPrint (DEBUG_INFO, "Add TLB Entry:\n");
156 DbgPrint (DEBUG_INFO, "\tTLBELO0 = 0x%lx\n", Regs->TLBELO0);
157 DbgPrint (DEBUG_INFO, "\tTLBELO1 = 0x%lx\n", Regs->TLBELO1);
158 // Csrxchg (Regs->PageSize << 24, 0x3f000000, 0x10); // CSR.TLBRERA.IsTLBR = 0, is not tlbrefill
159 Csrxchg (Regs->Index, 0xfff, 0x10); // Index
160 // Csrxchg (0, 0x80000000, 0x10); // NP CSR.TLBRERA.IsTLBR = 0,bit = 1;
161 CsrWrite (Regs->PageSize, 0x1e); // PS
162 CsrWrite (Regs->TLBEHI, 0x8e); // TLBEHI
163 CsrWrite (Regs->TLBELO0, 0x8c); // TLBELO0
164 CsrWrite (Regs->TLBELO1, 0x8d); // TLBELO1
165 TlbWr();
166 }
167
168 #define VTLB_INDEX_BASE 2048U
169
170 static int num = 1;
171
172 VOID
173 TlbRefill (
174 VOID
175 )
176 {
177 TLB_REGS Regs;
178
179 Regs.PageSize = 0xc; // PageSize: 4k
180 Regs.TLBEHI = CsrRead64(0x89); // BadV
181 UINT64 Tmp = CsrRead64(0x89);
182 if (Tmp & 0x0e0000000000) {
183 Tmp |= 0x43; //uncache attribute
184 } else {
185 Tmp |= 0x53; //cache attribute
186 }
187 Regs.TLBELO0 = Tmp ;
188 Regs.TLBELO1 = Tmp | (1ULL << Regs.PageSize); //Single PageSize
189
190 Regs.Index = VTLB_INDEX_BASE + num;
191 num++;
192
193 AddTlbEntry (&Regs);
194
195 }
Code execution:
Kernel TlbRefill
关于内核下发生TlbRefill的情况:用户态地址都是通过页表映射后的地址,当创建进程时,都为每个进城增加了页表的对应关系。
假设进程A和进程B都需要物理内存PM的资源,而增加的页表AT就是A通往内存PM的通道,增加的BT就是B通往内存PM的通道,同一块物理内存资源,代码中的有效地址确实多对一的关系,这样我们可以保证每个进程都可以访问更多的资源,当然少不了PGD的参与,保证每个进程都存储整个空间。
简单梳理了下内核,发现如果发生TLB 例外来Refill时,重填的页大小与内存分配的最小页一致,4K or 16K or 64K, 当然也为了保证对齐,内核下的对应关系挺复杂的,我们以上固件下只简单介绍了VA->PA 1-1映射的关系。
以上关于LoongArch架构的资源均来自https://github.com/loongson/,欢迎有需要的小伙伴下载学习,共同交流 !