Mips TLB miss实现in Linux
TLB miss是Mips中內存管理的核心流程。上一篇寫了關于Mips中,TLB miss的相關原理,本文關注在Linux kernel中的代碼實現。
TLB Refill初始化
內核啟動過程中,會對TLB Refill異常進行初始化,設置相應的處理接口。主要流程如下(以R3k為例):
start_secondaryper_cpu_trap_inittlb_initbuild_tlb_refill_handlerbuild_r3000_tlb_refill_handler具體設置處理函數的操作在build_r3000_tlb_refill_handler進行,代碼如下:
/** The R3000 TLB handler is simple.*/ /** 標準的TLB Refill代碼,相對比較簡單,只用一級頁表,跟Mips手冊中的基本一致。*/ static void build_r3000_tlb_refill_handler(void) {/* 頁目錄 */long pgdc = (long)pgd_current;u32 *p;/* 初始化tlb處理接口 */memset(tlb_handler, 0, sizeof(tlb_handler));p = tlb_handler;/* 讀取產生異常的虛擬地址 */uasm_i_mfc0(&p, K0, C0_BADVADDR);uasm_i_lui(&p, K1, uasm_rel_hi(pgdc)); /* cp0 delay */uasm_i_lw(&p, K1, uasm_rel_lo(pgdc), K1);uasm_i_srl(&p, K0, K0, 22); /* load delay */uasm_i_sll(&p, K0, K0, 2);uasm_i_addu(&p, K1, K1, K0);/* 從CONTEXT寄存器中讀取信息 */uasm_i_mfc0(&p, K0, C0_CONTEXT);uasm_i_lw(&p, K1, 0, K1); /* cp0 delay */uasm_i_andi(&p, K0, K0, 0xffc); /* load delay */uasm_i_addu(&p, K1, K1, K0);uasm_i_lw(&p, K0, 0, K1);uasm_i_nop(&p); /* load delay *//* 寫ENTRYLO0寄存器,供后面的tlbwr指令使用,將其寫入TLB */uasm_i_mtc0(&p, K0, C0_ENTRYLO0);/* 讀取EPC寄存器,其中保存了異常返回地址 */uasm_i_mfc0(&p, K1, C0_EPC); /* cp0 delay *//* tlbwr指令,根據ENTRYLO0寄存器內容(來源于頁表,通過Context寄存器索引),寫TLB */uasm_i_tlbwr(&p); /* cp0 delay *//* 跳轉到異常返回地址,退出異常 */uasm_i_jr(&p, K1);uasm_i_rfe(&p); /* branch delay */.../** 關鍵點:將tlb_handler(也就是前面設置的TLB refill異常處理函數)內容拷貝到ebase* (Mips為異常分配的物理地址空間(大小為0x80),用ebase寄存器指向,簡單情況下,就是* 物理地址0x0),當TLB Refill異常發生時,硬件會自動跳轉到ebase對應的地址執行。*/memcpy((void *)ebase, tlb_handler, 0x80);... }可以看出,TLB refill異常的處理函數代碼,是內核動態設置的,而不是匯編靜態代碼,由于Mips硬件型號比較多,如此操作更具靈活性。
同時,請注意TLB refill異常處理函數入口位于固定物理地址:0x0。
General Exception初始化
內核啟動過程中,會對General Exception相應的異常處理接口進行初始化,主要流程如下:
start_kerneltrap_inittrap_init完成異常相關初始化操作,主要代碼如下:
void __init trap_init(void) {// General Exception處理接口extern char except_vec3_generic;.../** Copy the generic exception handlers to their final destination.* This will be overriden later as suitable for a particular* configuration.*//** 將General Exception的處理函數(指令)拷貝到0x180(物理地址)中,0x180是r4000以上* 的Mips CPU中General Exception默認入口地址,該段物理地址空間大小為0x80,專門* 預留出來用于General Exception的處理。*/set_handler(0x180, &except_vec3_generic, 0x80);/** Setup default vectors*//×* 為0-31號異常設置默認的處理接口,General Exception對應編號為3,TLB refill異常* 對應編號為1。*/for (i = 0; i <= 31; i++)set_except_vector(i, handle_reserved);.../** General Exception是所有普通優先級異常的總入口,其中會根據異常碼調用不同的處理* 接口,這里就是設置不同的異常嗎對應的處理接口。* 注意:2號異常碼對應為**page fault**異常,即TLB Load異常,處理接口為handle_tlbl*/set_except_vector(0, using_rollback_handler() ? rollback_handle_int: handle_int);set_except_vector(1, handle_tlbm);set_except_vector(2, handle_tlbl);set_except_vector(3, handle_tlbs);set_except_vector(4, handle_adel);set_except_vector(5, handle_ades);set_except_vector(6, handle_ibe);set_except_vector(7, handle_dbe);/* 設置系統調用處理接口 */set_except_vector(8, handle_sys);/** 根據不同的CPU類型,設置相應的General Exception的處理接口地址,對應R4,地址為* 0x180,其它(更老的)為0x80.*/if (cpu_has_vce)/* Special exception: R4[04]00 uses also the divec space. */set_handler(0x180, &except_vec3_r4000, 0x100);else if (cpu_has_4kex)set_handler(0x180, &except_vec3_generic, 0x80);elseset_handler(0x080, &except_vec3_generic, 0x80);... }General Exception處理
General Exception處理在except_vec3_generic函數中,以匯編實現,其中主要工作是:讀取異常類型碼ExcCode,然后調用相應的處理接口。主要代碼如下(genex.S文件中):
/** General exception vector for all other CPUs.** Be careful when changing this, it has to be at most 128 bytes* to fit into space reserved for the exception handler.*/ /** 匯編代碼,最終被拷貝到0x180的物理地址中,注意這段物理地址空間是有大小限制的,最大為128* 字節(0x80),所以處理不能太復雜。*/ NESTED(except_vec3_generic, 0, sp).set push.set noat #if R5432_CP0_INTERRUPT_WARmfc0 k0, CP0_INDEX #endifmfc0 k1, CP0_CAUSE //從CP0_CAUSE寄存器中讀取異常類型碼ExcCodeandi k1, k1, 0x7c #ifdef CONFIG_64BITdsll k1, k1, 1 #endif/** 根據類型碼,條用exception_handlers中對應的接口,exception_handlers中的處理接口* 是在trap_init初始化時設置的。*/PTR_L k0, exception_handlers(k1)jr k0.set popEND(except_vec3_generic)TLB Load異常處理初始化
前面的General Exception初始化流程中,僅設置了General Exception的處理入口和不同的異常碼對應的處理接口,但是并設置具體的異常處理函數內容,即異常發生后具體執行的代碼,其初始化是在另外的流程中:
start_secondaryper_cpu_trap_inittlb_initbuild_tlb_refill_handlerbuild_r3000_tlb_load_handler具體實現在build_r3000_tlb_load_handler函數中,關鍵代碼如下:
static void build_r3000_tlb_load_handler(void) {u32 *p = handle_tlbl;const int handle_tlbl_size = handle_tlbl_end - handle_tlbl;.../* 初始化 */memset(handle_tlbl, 0, handle_tlbl_size * sizeof(handle_tlbl[0]));.../** 寫匯編代碼,相當于j tlb_do_page_fault_0,即直接跳轉到tlb_do_page_fault_0處* 執行,即調用tlb_do_page_fault_0函數*/uasm_i_j(&p, (unsigned long)tlb_do_page_fault_0 & 0x0fffffff);uasm_i_nop(&p);... }TLB Load異常處理(Page Fault)
如前面的代碼分析,tlb load異常的處理接口中,其實就是調用tlb_do_page_fault_0函數,再看看這個函數實現(匯編代碼,在tlbex-fault.S文件中):
/* tlb_do_page_fault_0和tlb_do_page_fault_1一起實現,通過宏控制 */ .macro tlb_do_page_fault, write NESTED(tlb_do_page_fault_\write, PT_SIZE, sp) /* 保存上下文 */ SAVE_ALL /* 讀取發送異常的虛擬地址 */ MFC0 a2, CP0_BADVADDR KMODE move a0, sp REG_S a2, PT_BVADDR(sp) li a1, \write PTR_LA ra, ret_from_exception /* 關鍵點:調用do_page_fault函數 */ j do_page_fault END(tlb_do_page_fault_\write) .endm代碼很簡單,除保存上下文之類的操作外,就是調用do_page_fault函數了,這個就是我們很熟悉的page fault的標準接口了,其中會分配內存,并新增頁表項,建立虛擬地址和物理地址的映射關系。這里就不說的,以前寫過分析文檔。
原文地址:?http://happyseeker.github.io/kernel/2016/12/28/mips-TLB-miss-implemented-in-linux.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的Mips TLB miss实现in Linux的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mips TLB miss异常
- 下一篇: 闲聊Linux内存管理(1)