关于tlb的描述可参考mips run2以及mips64官方手册。
tlb条目:tlb entry
寄存器:entryHi, entryLo0, entryLo1, mask, index, wired
指令:tlbr, tlbwi, tlbwr, tlbprobe
使用时请注意配合hazard,防止遇险;
请先了解tlb entry与entryHi, entryLo0, entryLo1,mask的对应关系。
每个core以及每个core的vCPU都有独立的tlb(vCPU还可配置共享tlb,一般配置的都是独立的;L1[L2]cache也类似);
mips64 tlb miss入口地址为ebase+0x80,但使用了ebase偏移[0x0,0x100)的空间(ebase+0x0为mips32的tlb miss入口,Linux中被mips64利用了);可参考tlbex.c中的build_r4000_tlb_refill_handler()生成的tlb miss处理;
tlb miss后从页表中加载物理地址并生成tlb entry,之后可能会发生tlb invalid或tlb modify异常;tlb invalid包括tlb load/tlb store两种情况;
tlb modify表示tlb entry是有效的,但权限为只读;可用于COW机制;
tlb refill handler处理过程中可能再次会发生异常(如页表不在内存中)
tlb load/tlb store/tlb modify都会进do_page_fault();
huge tlb可用于提高性能,如对于大容量的数据访问,可将大块数据生成一个huge tlb entry;
还有一点重要的区别,tlb miss异常走的是ebase异常向量入口(,其它tlb异常走的是generic向量入口即ebase+0x180,见genex.S中的实现,通常的实现为except_vec3_generic;
tlb异常不会调用SAVE_SOME宏,关于此宏见后面的描述,但如果进入do_page_fault()就会调SAME_SOME,见tlbex-fault.S中关于tlb_do_page_fault宏的实现。
内核态的地址一般用的是unmapped区域,不会发生tlb miss,除非在内核态访问了mapped空间。
如内核态使用0x98000000_00000000开始处,cached unmapped区域;来个mips64的地址空间图:
由于mips处理器的型号比较多,tlb处理方式也不尽相同,TLB处理在Linux中的实现用的是C代码生成机器码的实现;主要位于tlb-r4k.c和tlbex.c中,其实也很简单,就是实现了mips64手册中关于指令机器码字段的解析,主要数据结构为enum opcode和struct insn insn_table[],build_insn()根据这个表生成指令机器码;另外,这套框架中还提供了一些label的生成方式,用于生成跳转偏移地址;
per_cpu_trap_init()调用下面函数构建出来各自的tlb异常处理:
tlb miss -> build_r4000_tlb_refill_handler();
tlb load -> build_r4000_tlb_load_handler();
tlb store -> build_r4000_tlb_store_handler();
tlb modify -> build_r4000_tlb_modify_handler();
最后拷至[ebase+0x0, ebase+0x100)中。
这些代码,Linux标准实现中,是多个CPU共享的。
关于r4k,请参考mips run2:
MIPS III: The 64-bit instruction set introduced by the R4000.
介绍之前再来个mips寄存器的图:
k0,k1专用于中断和异常中;gp在内核中用于存放thread_info起始地址即kernel stack base;
1. tlb refill handler(处理tlb miss的函数)
current_cpu_data.cputype为CPU_CAVIUM_OCTEON为例进行讲解
在static __initdata u32 tlb_handler[128]中产生机器指令,最终拷至static __initdata u32 final_handler[64]中
(每一个异常向量允许32条指令,每条指令4 bytes)
build_r4000_tlb_refill_handler()
一、build_get_pmde64()
功能:获得bad address的pmd entry存入ptr中。(注:tmp即k0, ptr即k1)
从全局变量unsigned long pgd_current[NR_CPUS]中获取pgd指针 // 此值和CP0_CONTEXT值已在per_cpu_trap_init中的TLBMISS_HANDLER_SETUP初始化
C0_BADVADDR -> k0;
if k0 < 0, goto label_vmalloc;
C0_CONTEXT -> k1;
k1 >> 23; //C0_CONTEXT中存有当前smp id
lui(rel_hi) -> k0;
k1+k0 -> k1
C0_BADVADDR -> k0;
ld rel_lo+k1 -> k1; //得到当前smp id的pgd地址
build label: label_vmalloc_done以用于relocs重定向此地址(branch到某个lable的指令需要重定向到此目标地址);
(k0>>(PGDIR_SHIFT-3)) -> k0; // 从bad address中得到其在pgd中的偏移
(k0&(PTRS_PER_PGD-1)<<3) -> k0;
k1+k0 -> k1; // 获得其pgd的物理地址,下面开始找pmd
C0_BADVADDR -> k0; // 再次取得bad address,没办法,寄存器不够用
ld k1 -> k1; // k1中存放pmd的地址
(k0>>(PMD_SHIFT-3)) -> k0; // 从bad address中得到其在pmd中的偏移
(k0&(PTRS_PER_PMD-1)<<3) -> k0;
k1+k0 -> k1; // 获得bad address的pmd entry(即pte的地址)存在k1中
二、build_get_ptep()
C0_XCONTEXT -> k0;
[k1] -> k1; // 用的是lw指令
k0&0xff0 -> k0;
k1+k0 -> k1; // k1中存放的是pte的地址
三、build_update_entries()
偶数pte项 -> k0; // 用的是ld指令
奇数pte项 -> k1; // 用的是ld指令
k0 >> 6;
k1 >> 6; // 转化成entrylo格式
k0 -> C0_ENTRYLO0;
k1 -> C0_ENTRYLO1;
四、build_tlb_write_entry()
tlbwr; // 将C0_ENTRYLO0, C0_ENTRYLO1和C0_ENTRYHi写入random所指示的tlb项中
五、收尾工作
build label: label_leave;
eret;
2. tlb load
current_cpu_data.cputype为CPU_CAVIUM_OCTEON为例进行讲解
u32 __tlb_handler_align handle_tlbl[128]; // handle_tlbl最终在trap_init()中被设置成通用异常向量入口:
// set_except_vector(2, handle_tlbl);
从except_vec3_generic进入:
一、build_r4000_tlbchange_handler_head()
build_get_pmde64()将pmd存入k1中; // pte: k0, ptr: k1
C0_BADVADDR -> k0;
[k1] -> k1; // lw指令获得pmd中的pte指针
k0>>(PAGE_SHIFT+PTE_ORDER-PTE_T_LOG2) -> k0; // k0>>12+0-3即k0>>9
k0&((PTRS_PER_PTE-1)
k0+k1 -> k1; // 获得pte项的指针即页面的地址
build label: label_smp_pgtable_change;
[k1] -> k0; // ld指令获得偶数pte项
tlbp;
二、build_pte_present()
k0&(_PAGE_PRESENT|_PAGE_READ) -> k0;
k0^(_PAGE_PRESENT|_PAGE_READ) -> k0;
如果k0有值说明pte不在内存中或者不能读,则跳转至label_nopage_tlbl;
[k1] -> k0; // 在delay slot中重新load页面项,ld指令获得偶数pte项
三、build_make_valid()
将k0中的pte置上_PAGE_VALID和_PAGE_ACCESSED标志;
四、build_r4000_tlbchange_handler_tail()
build_update_entries();
build_tlb_write_entry(); // tlb_indexed,注意index寄存器在tlbp的时候已经设置,在tlb load场景中一定存在这样的index值;
// 在tlb refiller中,请注意使用tlbw_random;
eret;
五、
生成label_nopage_tlbl;
j tlb_do_page_fault_0; // tlbex-fault.S中的宏实现,最终调用do_page_fault();
// ra中存放ret_from_exception,do_page_fault()返回后会执行;
// ret_from_exception,在entry.S中; 如果配有强占,则#define __ret_from_irq ret_from_exception
附录:
stackframe.h: