关于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)<<PTE_T_LOG2) -> k0; // k0&0xFF8,低8位保持为0
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:
.macro SAVE_ALL
SAVE_SOME
SAVE_AT
SAVE_TEMP
SAVE_STATIC
.endm
asmmacro-64.h:
.macro cpu_save_nonscratch thread
LONG_S s0, THREAD_REG16(\thread)
LONG_S s1, THREAD_REG17(\thread)
LONG_S s2, THREAD_REG18(\thread)
LONG_S s3, THREAD_REG19(\thread)
LONG_S s4, THREAD_REG20(\thread)
LONG_S s5, THREAD_REG21(\thread)
LONG_S s6, THREAD_REG22(\thread)
LONG_S s7, THREAD_REG23(\thread)
LONG_S sp, THREAD_REG29(\thread)
LONG_S fp, THREAD_REG30(\thread)
.endm
.macro cpu_restore_nonscratch thread
LONG_L s0, THREAD_REG16(\thread)
LONG_L s1, THREAD_REG17(\thread)
LONG_L s2, THREAD_REG18(\thread)
LONG_L s3, THREAD_REG19(\thread)
LONG_L s4, THREAD_REG20(\thread)
LONG_L s5, THREAD_REG21(\thread)
LONG_L s6, THREAD_REG22(\thread)
LONG_L s7, THREAD_REG23(\thread)
LONG_L sp, THREAD_REG29(\thread)
LONG_L fp, THREAD_REG30(\thread)
LONG_L ra, THREAD_REG31(\thread)
struct thread_struct {
/* Saved main processor registers. */
unsigned long reg16;
unsigned long reg17, reg18, reg19, reg20, reg21, reg22, reg23;
unsigned long reg29, reg30, reg31;
/* Saved cp0 stuff. */
unsigned long cp0_status;
/* Saved fpu/fpu emulator stuff. */
struct mips_fpu_struct fpu;
/* Saved state of the DSP ASE, if available. */
struct mips_dsp_state dsp;
/* Other stuff associated with the thread. */
unsigned long cp0_badvaddr; /* Last user fault */
unsigned long cp0_baduaddr; /* Last kernel fault accessing USEG */
unsigned long error_code;
unsigned long trap_no;
#define MF_FIXADE 1 /* Fix address errors in software */
#define MF_LOGADE 2 /* Log address errors to syslog */
#define MF_32BIT_REGS 4 /* also implies 16/32 fprs */
#define MF_32BIT_ADDR 8 /* 32-bit address space (o32/n32) */
#define MF_FPUBOUND 0x10 /* thread bound to FPU-full CPU set */
unsigned long mflags;
unsigned long irix_trampoline; /* Wheee... */
unsigned long irix_oldctx;
#ifdef CONFIG_CPU_CAVIUM_OCTEON
struct octeon_cop2_state cp2 __attribute__ ((__aligned__(128)));
struct octeon_cvmseg_state cvmseg __attribute__ ((__aligned__(128)));
#endif
#ifdef CONFIG_HARDWARE_WATCHPOINTS
/* Saved watch register state, if available. */
union mips_watch_reg_state watch;
#endif
struct mips_abi *abi;
};
struct thread_info {
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
unsigned long flags; /* low level flags */
unsigned long tp_value; /* thread pointer */
__u32 cpu; /* current CPU */
int preempt_count; /* 0 => preemptable, <0 => BUG */
mm_segment_t addr_limit; /* thread address space:
0-0xBFFFFFFF for user-thead
0-0xFFFFFFFF for kernel-thread
*/
struct restart_block restart_block;
struct pt_regs *regs;
};
octeon_switch.S中的resume调用上述两个宏来操作task_struct中的thread_struct结构,从而实现堆栈切换以给restore使用。
struct pt_regs {
/* Saved main processor registers. */
unsigned long regs[32];
/* Saved special registers. */
unsigned long cp0_status;
unsigned long hi;
unsigned long lo;
unsigned long cp0_badvaddr;
unsigned long cp0_cause;
unsigned long cp0_epc;
#ifdef CONFIG_CPU_CAVIUM_OCTEON
unsigned long mpl[3]; /* MTM{0,1,2} */
unsigned long mtp[3]; /* MTP{0,1,2} */
#endif
} __attribute__ ((aligned (8)));
arch/mips/kernel/asm-offsets.c:
void output_ptreg_defines(void)
{
text("/* MIPS pt_regs offsets. */");
offset("#define PT_R0 ", struct pt_regs, regs[0]);
offset("#define PT_R1 ", struct pt_regs, regs[1]);
offset("#define PT_R2 ", struct pt_regs, regs[2]);
offset("#define PT_R3 ", struct pt_regs, regs[3]);
offset("#define PT_R4 ", struct pt_regs, regs[4]);
offset("#define PT_R5 ", struct pt_regs, regs[5]);
offset("#define PT_R6 ", struct pt_regs, regs[6]);
offset("#define PT_R7 ", struct pt_regs, regs[7]);
offset("#define PT_R8 ", struct pt_regs, regs[8]);
offset("#define PT_R9 ", struct pt_regs, regs[9]);
offset("#define PT_R10 ", struct pt_regs, regs[10]);
offset("#define PT_R11 ", struct pt_regs, regs[11]);
offset("#define PT_R12 ", struct pt_regs, regs[12]);
offset("#define PT_R13 ", struct pt_regs, regs[13]);
offset("#define PT_R14 ", struct pt_regs, regs[14]);
offset("#define PT_R15 ", struct pt_regs, regs[15]);
offset("#define PT_R16 ", struct pt_regs, regs[16]);
offset("#define PT_R17 ", struct pt_regs, regs[17]);
offset("#define PT_R18 ", struct pt_regs, regs[18]);
offset("#define PT_R19 ", struct pt_regs, regs[19]);
offset("#define PT_R20 ", struct pt_regs, regs[20]);
offset("#define PT_R21 ", struct pt_regs, regs[21]);
offset("#define PT_R22 ", struct pt_regs, regs[22]);
offset("#define PT_R23 ", struct pt_regs, regs[23]);
offset("#define PT_R24 ", struct pt_regs, regs[24]);
offset("#define PT_R25 ", struct pt_regs, regs[25]);
offset("#define PT_R26 ", struct pt_regs, regs[26]);
offset("#define PT_R27 ", struct pt_regs, regs[27]);
offset("#define PT_R28 ", struct pt_regs, regs[28]);
offset("#define PT_R29 ", struct pt_regs, regs[29]);
offset("#define PT_R30 ", struct pt_regs, regs[30]);
offset("#define PT_R31 ", struct pt_regs, regs[31]);
offset("#define PT_LO ", struct pt_regs, lo);
offset("#define PT_HI ", struct pt_regs, hi);
offset("#define PT_EPC ", struct pt_regs, cp0_epc);
offset("#define PT_BVADDR ", struct pt_regs, cp0_badvaddr);
offset("#define PT_STATUS ", struct pt_regs, cp0_status);
offset("#define PT_CAUSE ", struct pt_regs, cp0_cause);
#ifdef CONFIG_CPU_CAVIUM_OCTEON
offset("#define PT_MPL ", struct pt_regs, mpl);
offset("#define PT_MTP ", struct pt_regs, mtp);
#endif
size("#define PT_SIZE ", struct pt_regs);
}
mips64(无RI&XI) pte entry格式:
#define _PAGE_PRESENT (1<<0) /* implemented in software */
#define _PAGE_READ (1<<1) /* implemented in software */
#define _PAGE_WRITE (1<<2) /* implemented in software */
#define _PAGE_ACCESSED (1<<3) /* implemented in software */
#define _PAGE_MODIFIED (1<<4) /* implemented in software */
#define _PAGE_FILE (1<<4) /* set:pagecache unset:swap */
#define _PAGE_R4KBUG (1<<5) /* workaround for r4k bug */
#define _PAGE_HUGE (1<<5) /* huge tlb page */
#define _PAGE_GLOBAL (1<<6)
#define _PAGE_VALID (1<<7)
#define _PAGE_SILENT_READ (1<<7) /* synonym */
#define _PAGE_DIRTY (1<<8) /* The MIPS dirty bit */
#define _PAGE_SILENT_WRITE (1<<8)
#define _CACHE_MASK (7<<9)
pte entry>>6为entry lo寄存器内容
场景:
系统中存在两个用户任务t1和t2,t2任务优先级比t1任务高,t2任务不停地被某个频繁的中断唤醒;t1,t2和中断都位于同一个cpu上运行;可强占CGEL3.0内核。
1. 当前t1任务由于此中断从用户态进入内核态
2. 此中断唤醒t2任务运行,于是中断返回后t1任务被高优先级t2任务强占运行;
3. t2任务运行完后睡眠,在schedule()中切换为t1任务的内核栈后开中断;
4. 刚开中断就来了中断(此中断比较频繁),于是t1被此中断打断(中断会继续保存当前寄存器现场到t1任务的内核栈中),中断执行完毕后唤醒t2任务;
转至步骤2,如此反复。
现针对octeon mips64系统分析此场景中t1任务的状态以及堆栈切换的详细流程:
基础知识:
进入handle_int()中后的流程:
SAVE_ALL
关中断
s0 <- TI_REGS(gp) (gp已指向当前thread_info结构)
sp -> TI_REGS(gp) // sp在SAVE_SOME中已经被指向当前应该使用的内核栈-PT_SIZE
ra <- ret_from_irq
j plat_irq_dispatch
下面来看SAVE_ALL都保存了哪些寄存器(保存32个通用寄存器,但除了k0和k1;HI, LO, EPC, STATUS, CAUSE):
.macro SAVE_ALL
SAVE_SOME // zero, v0~v1, a0~a3, t0~t1, t9, gp, ra, sp, EPC, STATUS, CAUSE
SAVE_AT // at
SAVE_TEMP // t2~t8, HI, LO
SAVE_STATIC // s0~s7, fp
.endm
/******** SAVE_SOME BEGIN *********/
SAVE_SOME的作用(注:有一个很重要的作用就是切换sp寄存器为应该用的堆栈):
如果STATUS.CU0为0表示从用户态进来
get_saved_sp // 从CP0_CONTEXT取出当前cpu号,kernelsp[NR_CPUS]中存有各cpu的当前任务内核栈,放入k1中
如果STATUS.CU0为1表示从内核态进来的不用切换栈(可能是中断也可能是其他任务的内核态上下文),k1 <- sp
上面两种情况,k1中均存着当前应该使用的内核堆栈
k0 <- sp // k0中存放当前sp
sp <- (k1-PT_SIZE) // 在当前应该使用的堆栈上保留出PT_SIZE大小的空间,用以保存现场即PT_REGS结构,PT_SIZE为pt_regs的大小
k0 -> PT_R29(sp) // 将当前sp寄存器的值存放在PT_REGS中对应的sp的位置
v1 -> PT_R3(sp) // 将当前v1寄存器的值存放在PT_REGS中对应的v1的位置
$0 -> PT_RO(sp) // 将当前$0寄存器的值存放在PT_REGS中对应的$0的位置
v0 -> PT_R2(sp) // 将当前v0寄存器的值存放在PT_REGS中对应的v0的位置
a0 -> PT_R4(sp) // 将当前a0寄存器的值存放在PT_REGS中对应的a0的位置
a1 -> PT_R5(sp) // 将当前a1寄存器的值存放在PT_REGS中对应的a1的位置
STATUS -> PT_STATUS(sp) // 将当前CP0_STATUS寄存器的值存放在PT_REGS中对应的STATUS的位置
a2 -> PT_R6(sp) // 将当前a2寄存器的值存放在PT_REGS中对应的a2的位置
a3 -> PT_R7(sp) // 将当前a3寄存器的值存放在PT_REGS中对应的a3的位置
CAUSE -> PT_CAUSE(sp) // 将当前CP0_CAUSE寄存器的值存放在PT_REGS中对应的CAUSE的位置
t0 -> PT_R8(sp) // 将当前t0寄存器的值存放在PT_REGS中对应的t0的位置
t1 -> PT_R9(sp) // 将当前t1寄存器的值存放在PT_REGS中对应的t1的位置
t9 -> PT_R25(sp) // 将当前t9寄存器的值存放在PT_REGS中对应的t9的位置
gp -> PT_R28(sp) // 将当前gp寄存器的值存放在PT_REGS中对应的gp的位置
ra -> PT_R31(sp) // 将当前ra寄存器的值存放在PT_REGS中对应的ra的位置
EPC -> PT_EPC(sp) // 将当前CP0_EPC寄存器的值存放在PT_REGS中对应的EPC的位置
gp <- sp所表示的堆栈起始地址即thread_info结构(THREAD_MASK低位清0)
对于octeon,需要保存pt_regs中的如下寄存器:
#ifdef CONFIG_CPU_CAVIUM_OCTEON
unsigned long mpl[3]; /* MTM{0,1,2} */
unsigned long mtp[3]; /* MTP{0,1,2} */
#endif
/******** SAVE_SOME END ********((*/
/******** SAVE_AT BEGIN *********/
at -> PT_R1(sp) // 将当前at寄存器的值存放在PT_REGS中对应的at的位置
/********* SAVE_AT END *********/
/******** SAVE_TEMP BEGIN *********/
t2 -> PT_R10(sp) // 将当前t2寄存器的值存放在PT_REGS中对应的t2的位置
t3 -> PT_R11(sp) // 将当前t3寄存器的值存放在PT_REGS中对应的t3的位置
t4 -> PT_R12(sp) // 将当前t4寄存器的值存放在PT_REGS中对应的t4的位置
HI -> PT_HI(sp) // 将当前CP0_HI寄存器的值存放在PT_REGS中对应的HI的位置
t5 -> PT_R13(sp) // 将当前t5寄存器的值存放在PT_REGS中对应的t5的位置
t6 -> PT_R14(sp) // 将当前t6寄存器的值存放在PT_REGS中对应的t6的位置
t7 -> PT_R15(sp) // 将当前t7寄存器的值存放在PT_REGS中对应的t7的位置
t8 -> PT_R24(sp) // 将当前t8寄存器的值存放在PT_REGS中对应的t8的位置
LO -> PT_LO(sp) // 将当前CP0_LO寄存器的值存放在PT_REGS中对应的LO的位置
/******** SAVE_TEMP END *********/
/******** SAVE_STATIC BEGIN *********/
s0 -> PT_R16(sp) // 将当前s0寄存器的值存放在PT_REGS中对应的s0的位置
s1 -> PT_R17(sp) // 将当前s1寄存器的值存放在PT_REGS中对应的s1的位置
s2 -> PT_R18(sp) // 将当前s2寄存器的值存放在PT_REGS中对应的s2的位置
s3 -> PT_R19(sp) // 将当前s3寄存器的值存放在PT_REGS中对应的s3的位置
s4 -> PT_R20(sp) // 将当前s4寄存器的值存放在PT_REGS中对应的s4的位置
s5 -> PT_R21(sp) // 将当前s5寄存器的值存放在PT_REGS中对应的s5的位置
s6 -> PT_R22(sp) // 将当前s6寄存器的值存放在PT_REGS中对应的s6的位置
s7 -> PT_R23(sp) // 将当前s7寄存器的值存放在PT_REGS中对应的s7的位置
fp -> PT_R30(sp) // 将当前fp寄存器的值存放在PT_REGS中对应的fp的位置
/********* SAVE_STATIC END *********/
/************ switch_to -> resume begein ****************/
switch prev to next
将CP0_STATUS, s0~s7, sp, fp, ra寄存器存入prev任务的thread_struct结构中;
将next的内核栈起始地址(即next->thread_info)赋给gp寄存器;
将next任务的thread_struct结构中的s0~s7, sp, fp, ra存入相应寄存器中;
把next任务的内核栈顶-32处的地址存入当前cpu的kernelsp[NR_CPUS]中,以供next任务在用户态运行时例如被中断打断后进入内核中断流程查找内核堆栈使用;
把next任务的thread_struct结构中的STATUS存入CP0_STATUS寄存器中;
jr ra; // 完成切换
/************ switch_to -> resume end ****************/
/************ restore_all begin ****************/
(小写restore_all function symbol 恢复了对应的SAVE_ALL的32个通用寄存器,但除了$0, k0和k1;HI, LO, ECP, STATUS,没有恢复CAUSE)
.set noat
RESTORE_TEMP // LO, HI, t2~t8
RESTORE_AT // at
RESTORE_STATIC // s0~s7, fp
RESOTRE_SOME // t0, t1, t9, a0~a3, v0~v1, gp, ra
RESTORE_SP_AND_RET // 恢复sp后执行eret指令 (对比:大写的RESTORE_ALL宏用的是RESTORE_SP)
/************ restore_all end ****************/
/************ RESTORE_TEMP begin ****************/
CP0_LO <- PT_LO(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的lo的值放入寄存器CP0_LO
CP0_HI <- PT_HI(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的hi的值放入寄存器CP0_HI
t2 <- PT_R10(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的t2的值放入寄存器t2
t3 <- PT_R11(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的t3的值放入寄存器t3
t4 <- PT_R12(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的t4的值放入寄存器t4
t5 <- PT_R13(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的t5的值放入寄存器t5
t6 <- PT_R14(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的t6的值放入寄存器t6
t7 <- PT_R15(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的t7的值放入寄存器t7
t8 <- PT_R24(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的t8的值放入寄存器t8
/************ RESTORE_TEMP end ****************/
/************ RESTORE_AT begin ****************/
at <- PT_R1(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的at的值放入寄存器at
/************ RESTORE_AT end ****************/
/************ RESTORE_STATIC begin ****************/
s0 <- PT_R16(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的s0的值放入寄存器s0
s1 <- PT_R17(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的s1的值放入寄存器s1
s2 <- PT_R18(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的s2的值放入寄存器s2
s3 <- PT_R19(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的s3的值放入寄存器s3
s4 <- PT_R20(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的s4的值放入寄存器s4
s5 <- PT_R21(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的s5的值放入寄存器s5
s6 <- PT_R22(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的s6的值放入寄存器s6
s7 <- PT_R23(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的s7的值放入寄存器s7
fp <- PT_R30(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的fp的值放入寄存器fp
/************ RESTORE_STATIC end ****************/
/************ RESTORE_SOME begin ****************/
对于octeon,需要保存pt_regs中的如下寄存器:
#ifdef CONFIG_CPU_CAVIUM_OCTEON
unsigned long mpl[3]; /* MTM{0,1,2} */
unsigned long mtp[3]; /* MTP{0,1,2} */
#endif
CP0_STATUS <- PT_STATUS(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的STATUS的值放入寄存器CP0_STATUS
CP0_EPC <- PT_EPC(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的ECP的值放入寄存器CP0_EPC
ra <- PT_R31(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的ra的值放入寄存器ra
gp <- PT_R28(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的gp的值放入寄存器gp
t9 <- PT_R25(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的t9的值放入寄存器t9
t0 <- PT_R8(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的t0的值放入寄存器t0
t1 <- PT_R9(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的t1的值放入寄存器t1
v0 <- PT_R2(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的v0的值放入寄存器v0
v1 <- PT_R3(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的v1的值放入寄存器v1
a0 <- PT_R4(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的a0的值放入寄存器a0
a1 <- PT_R5(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的a1的值放入寄存器a1
a2 <- PT_R6(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的a2的值放入寄存器a2
a3 <- PT_R7(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的a3的值放入寄存器a3
/************ RESTORE_SOME end ****************/
/************ RESTORE_SP_AND_RET begin ****************/
sp <- PT_R29(sp) // 将当前sp寄存器的值所指向的PT_REGS结构中对应的sp的值放入寄存器sp
eret;
/************ RESTORE_SP_AND_RET end ****************/
gp初始化总结:
a. 在switch_to中的resume分支,每次将next的thread_info起始地址赋给gp寄存器;
b. 在中断或异常的SAVE_ALL->SOME_SOME宏中,将当前应该用的内核栈的sp与栈大小对齐赋给gp寄存器。
总之,gp都将指向当前内核堆栈的栈底。
3. 顺带再来个syscall的,注意:mips64分N64, N32, O32等ABI
分析futex_lock_pi()被信号打断后返回错误码为ERESTARTNOINTR的情形:
// ABI n64
#define __NR_64_Linux 5000
mips64进入内核态时(EXL或ERL置位)均自动关中断,系统调用发生是EXL置位
handle_sys64 // v0中存放系统调用号
SAVE_SOME // 保存zero, v0~v1, a0~a3, t0~t1, t9, gp, ra, sp, EPC, STATUS, CAUSE至当前任务内核堆栈PT_REGS结构中,sp寄存器此时指向sp_top-32-PT_SIZE处
STI // 开中断
将当前sp所指的PT_SIZE内存中的EPC值加4,使得在返回用户态时跳过此系统调用
sd a3, PT_R26(sp) // save a3 for syscall restarting
jar 跳转至相应系统调用函数处
将函数是否有错误发生的标志存入PT_R7(sp)中(R7为a3)
v0 = -v0 // 取反
sd v0, PT_R0(sp) // regs->regs[0]中存放错误码
将函数的返回值存入PT_R2(sp)中(R2为v0)
转至n64_syscall_exit(此时gp寄存器指向当前任务内核栈的底部):
有信号转至
n64_syscall_exit_work:
j syscall_exit_work_partial
syscall_exit_work_partial:
SAVE_STATIC // s0~s7, fp
继续跳转至work_pending->work_notifysig->do_signal()->resume_userspace->restore_all
进入信号处理时,regs->regs[0]中值为ERESTARTNOINTR
handle_signal()中:
case ERESTARTNOINTR: /* Userland will reload $v0. */
regs->regs[7] = regs->regs[26];
regs->cp0_epc -= 8;
}
regs->regs[0] = 0; /* Don't deal with this again. */
之后,
regs->regs[0]为0
regs->regs[7]为a3,regs->cp0_epc为重新执行系统调用的地方,regs->regs[2](v0)中为ERESTARTNOINTR
场景分析:
1. t1由于中断,从用户态进入内核态
t1任务在用户态运行时,kernelsp[smp_processor_id()]中已存放有t1任务的内核堆栈指针的栈顶sp_t1减32B的位置(即sp_t1-32B);
此时来了一个中断将其打断进入内核态,执行SAVE_ALL将sp_t1-32-PT_SIZE存入sp寄存器中,并在其中保存t1任务的用户态寄存器现场(保存32个通用寄存器,但除了k0和k1;HI, LO, EPC, STATUS, CAUSE);
gp在此时保存有sp_t1-THREAD_SIZE即栈底(即指向thread_info结构);
2. 中断唤醒t2任务运行,于是中断返回后t1任务被高优先级t2任务强占运行
进入中断处理handle_int()->plat_irq_dispatch()->do_IRQ()->... 唤醒更高优先级t2任务(在try_to_wake_up()中非同步唤醒t2任务,sync=0),于是t1任务的thread_info->flags被置上了NEED_RESCHED标志;
中断处理完成后返回ret_from_irq->resume_userspace(因为t1在中断前处于用户态,取的是当前sp寄存器所指的PT_REGS中保存的STATUS值进行的判断)->work_pending->work_resched->schedule();
schedule中调用switch_to将当前堆栈切换成t2任务的内核栈顶sp_t2-32B位置,并将当前寄存器信息保存至t1任务的thread_struct结构中,从t2任务的thread_struct中恢复出其内核现场信息至s0~s7, sp, fp, ra, STATUS等寄存器中,gp中含有sp_t2-THREAD_SIZE即t2任务的thread_info栈底结构指针,jr ra继续t2任务;
3. t2任务运行完后睡眠,在schedule()中切换为t1任务的内核栈后开中断;
t2运行完毕后,执行schedule()切换成t2任务,schedule()末尾执行spin_unlock_irq(&rq->lock)开中断时,此时switch_to已经切换为t1任务的上下文即s0~s7, fp, ra, STATUS和sp
(sp=sp_t1-32-PT_SIZE),但此时就又会来中断;
4. 刚开中断就来了中断(此中断比较频繁),于是t1被此中断打断(中断会继续保存当前寄存器现场到t1任务的内核栈中);
中断进入SAVE_ALL中,判断已经在内核态,就不会kernelsp[smp_processor_id()]中取堆栈信息,而是直接使用sp寄存器中的值(即在switch_to的resume中从t1任务的thread_struct中恢复出来的sp值,即sp_t1-32-PT_SIZE),从将现场信息(STATUS寄存器的中断标志是打开的)保存至sp-PT_SIZE即(sp_t1-32-PT_SIZE-PT_SIZE)处,并将此位置值存入sp寄存器中(见SAVE_SOME宏的实现)。
进入中断处理handle_int()->plat_irq_dispatch()->do_IRQ()->... 唤醒更高优先级t2任务(在try_to_wake_up()中非同步唤醒t2任务,sync=0),于是t1任务被置上了NEED_RESCHED标志;
中断处理完成后返回ret_from_irq->resume_kernel(PT_STATUS(sp)中此时变成了内核态的STATUS,t2处于返回内核态了):
lw t0, TI_PRE_COUNT($28) // 此时可以见PREEMPT_ACTIVE标志的作用,防止层层嵌套以至t1任务的内核栈在此场景下被SAVE_ALL操作要放的PT_REGS结构溢出,在preempt_schedule_irq()中置位
bnez t0, restore_all
此时PREEMPT_ACTIVE标志未置,可以被抢占,于是进入resume_kernel->need_resched->preempt_schedule_irq(PT_STATUS(sp)中断位为1):
当前任务t1的thread_info->preempt_count置PREEMPT_ACTIVE,preempt_schedule_irq()末尾会schedule()后关闭本地中断。
之后再发生中断,就不会进入preempt_schedule_irq()被t2任务抢占了,而是执行restore_all了。
3. tlb异常代码都是用C语言生成的机器码,修改tlb refill handler举例。
build_r4000_tlb_refill_handler()中添加监视tlb miss的调试代码
unsigned long tlbmiss_epc;
unsigned long tlbmiss_badvaddr;
static int tlbmiss_high16(unsigned long address) {
return ((address & 0xffff0000) >> 16);
}
static int tlbmiss_low16(unsigned long address) {
return (address & 0xffff);
}
build_r4000_tlb_refill_handler()开头生成调试指令,这样就会在ebase+0x80处存放这些指令了。
#define CP0_CONTEXT 4, 0
i_dmfc0(&p, K0, CP0_CONTEXT);
i_dsrl(&p, K0, K0, 23);
i_dsll(&p, K0, K0, 13);
i_lui(&p, K1, n);
/* only catch tlb miss on CPUn;
* NOTE: offset is 32 not 8, this instruction implementation here,
don't comply with mips64 handbook
*/
i_bne(&p, K0, K1, 32);
i_lui(&p, K0, pxl_high16((unsigned long)(&tlbmiss_epc)) | 0x8000); /* 0xffffffff_8xxxxxxx */
i_ori(&p, K0, K0, pxl_low16((unsigned long)(&tlbmiss_epc)));
i_dmfc0(&p, K1, C0_EPC);
i_sd(&p, K1, 0, K0);
i_lui(&p, K0, pxl_high16((unsigned long)(&tlbmiss_badvaddr)) | 0x8000);
i_ori(&p, K0, K0, pxl_low16((unsigned long)(&tlbmiss_badvaddr)));
i_dmfc0(&p, K1, C0_BADVADDR);
i_sd(&p, K1, 0, K0);
4.打印某个CPU上的tlb entry举例
int tlbdump_switch = 0;
int tlbdump_cpu = 0;
int tlbr_thread(void * unused)
{
while (!kthread_should_stop()) {
while (1) {
msleep(1000);
if (tlbdump_switch == 1) {
collect_tlb_entries();
tlbdump_switch = 2;
}
}
}
return 0;
}
void tlbr_init( )
{
struct task_struct *tlbr_worker;
unsigned long flags;
struct rq *rq;
tlbr_worker = kthread_create(tlbr_thread, NULL, "tlb_reader");
if (IS_ERR(tlbr_worker)) {
printk("tlb_reader create failed!\n");
return;
}
/* bind this thread to tlbdump_cpu */
kthread_bind(tlbr_worker, tlbdump_cpu);
rq = task_rq_lock(tlbr_worker, &flags);
__setscheduler(rq, tlbr_worker, SCHED_FIFO, MAX_RT_PRIO-1);
task_rq_unlock(rq, &flags);
wake_up_process(tlbr_worker);
}
struct mips64_tlb_entry {
unsigned int mask;
unsigned long entryhi;
unsigned long entrylo0;
unsigned long entrylo1;
};
#define TLB_ENTRIES 16
struct mips64_tlb_entry tlb_entry_array[TLB_ENTRIES] = {0};
static inline void save_old_entry(struct mips64_tlb_entry *entry)
{
entry->mask = read_c0_pagemask();
entry->entryhi = read_c0_entryhi();
entry->entrylo0 = read_c0_entrylo0();
entry->entrylo1 = read_c0_entrylo1();
}
static inline void restore_old_entry(struct mips64_tlb_entry *entry)
{
write_c0_pagemask(entry->mask);
write_c0_entryhi(entry->entryhi);
write_c0_entrylo0(entry->entrylo0);
write_c0_entrylo1(entry->entrylo1);
}
static inline void collect_tlb_entry(struct mips64_tlb_entry *entry)
{
save_old_entry(entry);
}
void collect_tlb_entries( )
{
unsigned long flags;
unsigned int index, old_index;
int entries = ARRAY_SIZE(tlb_entry_array);
struct mips64_tlb_entry *p = tlb_entry_array;
struct mips64_tlb_entry old_entry;
local_irq_save(flags);
save_old_entry(&old_entry);
old_index = read_c0_index();
for (index = 0; index < entries; index++) {
write_c0_index(index);
mtc0_tlbw_hazard();
tlb_read();
mtc0_tlbw_hazard();
collect_tlb_entry(p+index);
}
restore_old_entry(&old_entry);
write_c0_index(old_index);
mtc0_tlbw_hazard();
local_irq_restore(flags);
}
static inline const char *msk2str(unsigned int mask)
{
switch (mask) {
case PM_4K: return "4kb";
case PM_16K: return "16kb";
case PM_64K: return "64kb";
case PM_256K: return "256kb";
case PM_1M: return "1Mb";
case PM_4M: return "4Mb";
case PM_16M: return "16Mb";
case PM_64M: return "64Mb";
case PM_256M: return "256Mb";
}
return NULL;
}
static void show_tlb_entries( )
{
int i;
struct mips64_tlb_entry *p = tlb_entry_array;
unsigned long entrylo0, entrylo1, entryhi, pagemask;
unsigned int c0, c1;
printk("mips64 tlb entries:\n");
for (i = 0; i < ARRAY_SIZE(tlb_entry_array); i++) {
pagemask = p[i].mask;
entryhi = p[i].entryhi;
entrylo0 = p[i].entrylo0;
entrylo1 = p[i].entrylo1;
printk("Index: %2d pgmask=%s ", i, msk2str(pagemask));
c0 = (entrylo0 >> 3) & 7;
c1 = (entrylo1 >> 3) & 7;
printk("va=%011lx asid=%02lx\n",
(entryhi & ~0x1fffUL),
entryhi & 0xff);
printk("\t[pa=%011lx c=%d d=%d v=%d g=%ld] ",
(entrylo0 << 6) & PAGE_MASK, c0,
(entrylo0 & 4) ? 1 : 0,
(entrylo0 & 2) ? 1 : 0,
(entrylo0 & 1));
printk("[pa=%011lx c=%d d=%d v=%d g=%ld]\n",
(entrylo1 << 6) & PAGE_MASK, c1,
(entrylo1 & 4) ? 1 : 0,
(entrylo1 & 2) ? 1 : 0,
(entrylo1 & 1));
memset(p+i, 0, sizeof(struct mips64_tlb_entry));
}
}
static void tlbdump_show(void)
{
if (tlbdump_switch == 0) {
tlbdump_switch = 1;
} else if (tlbdump_switch == 2) {
show_tlb_entries();
tlbdump_switch = 0;
}
return 0;
}