日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > windows >内容正文

windows

MIPS中的异常处理和系统调用

發(fā)布時(shí)間:2023/12/20 windows 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 MIPS中的异常处理和系统调用 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。 原文鏈接:https://blog.csdn.net/jasonchen_gbd/article/details/44044091

異常入口

系統(tǒng)調(diào)用是用戶態(tài)和內(nèi)核態(tài)通信的一種方式,用戶程序可以直接調(diào)用系統(tǒng)調(diào)用的接口陷入內(nèi)核中執(zhí)行相關(guān)任務(wù),完成后返回用戶態(tài)繼續(xù)運(yùn)行。

應(yīng)用程序使用系統(tǒng)調(diào)用很簡(jiǎn)單,直接調(diào)用C庫(kù)提供的系統(tǒng)調(diào)用接口即可。在C庫(kù)中,對(duì)用戶傳入的參數(shù)進(jìn)行分析和保存,然后通過(guò)syscall指令引發(fā)系統(tǒng)調(diào)用異常,之后便陷入內(nèi)核。

內(nèi)核處理根據(jù)系統(tǒng)調(diào)用號(hào)執(zhí)行相應(yīng)的處理函數(shù),并將結(jié)果返回到用戶態(tài)。

?

圖1 系統(tǒng)調(diào)用大體流程

當(dāng)發(fā)生異常時(shí),協(xié)處理器0的Cause寄存器會(huì)記錄發(fā)生了什么種類的異常。Cause寄存器的每個(gè)域如圖2所示。其中bit6-2(ExcCode)位中保存了具體發(fā)生了什么異常,系統(tǒng)可以根據(jù)異常種類決定調(diào)用哪一個(gè)異常處理例程。

圖2 Cause寄存器

所有的異常入口都位于mips內(nèi)存映射中不需要地址轉(zhuǎn)換的區(qū)域——非緩存的kseg1段和緩存的kseg0段。如圖3所示,RAM中的異常入口點(diǎn)的起始地址為BASE+0x000,BASE表示EBase寄存器編程的異常基地址。一些特殊的異常的處理例程有單獨(dú)的地址存放其異常處理例程,如緩存異常和TLB重填等,其他異常處理例程都放在BASE+0x180地址處。

圖3 異常處理入口

BASE+0x180共存放了32種異常的入口函數(shù)地址,圖4中顯示了部分異常類型對(duì)應(yīng)的ExcCode值,可以看到其中系統(tǒng)調(diào)用對(duì)應(yīng)的ExcCode等于8。當(dāng)發(fā)生系統(tǒng)調(diào)用時(shí),內(nèi)核就可以根據(jù)Cause寄存器查看異常類型,然后跳轉(zhuǎn)到BASE+0x180地址處執(zhí)行,執(zhí)行的結(jié)果就是找到對(duì)應(yīng)的處理函數(shù)并跳轉(zhuǎn)到處理函數(shù)的地址去執(zhí)行。

圖4 異常類型

這些異常處理函數(shù)的注冊(cè)在trap_init()函數(shù)中完成,該函數(shù)將上面所說(shuō)的32個(gè)異常的處理函數(shù)地址放到一個(gè)全局?jǐn)?shù)組exception_handlers中,這個(gè)全局變量定義為:

unsigned long exception_handlers[32];

這個(gè)全局變量是unsigned long型,每個(gè)元素的值就是一種異常向量處理函數(shù)的入口地址。

那BASE+0x180地址處的代碼如何找到異常對(duì)應(yīng)的處理函數(shù)呢。trap_init()函數(shù)中將except_vec3_generic拷貝到了BASE+0x180,這是一個(gè)函數(shù),其實(shí)現(xiàn)如下:

NESTED(except_vec3_generic, 0, sp).set push.set noatmfc0 k1, CP0_CAUSE #讀取協(xié)處理器0的cause寄存器保存到k1中。andi k1, k1, 0x7c #取得k1的2-6位,即excCode#取得exception_handlers[excCode]的值PTR_L k0, exception_handlers(k1)jr k0 #跳轉(zhuǎn)到excCode對(duì)應(yīng)的處理函數(shù)去執(zhí)行.set popEND(except_vec3_generic)

由except_vec3_generic的實(shí)現(xiàn)可知,它負(fù)責(zé)讀取Cause寄存器并跳轉(zhuǎn)到異常處理函數(shù)。

產(chǎn)生異常時(shí),MIPS CPU所要做的主要工作為:

  • 設(shè)置EPC,指向異常返回的位置。
  • 置Status寄存器的EXL位,迫使CPU進(jìn)入內(nèi)核模式(高特權(quán)級(jí))并且禁用中斷。
  • 設(shè)置Cause寄存器,使得軟件可以看到異常的原因。
  • CPU從異常處理入口點(diǎn)取指執(zhí)行,即執(zhí)行異常處理程序。
  • 異常處理的流程主要包括以下步驟:

  • 保護(hù)現(xiàn)場(chǎng),將各個(gè)寄存器的值壓棧,以便處理完之后回到原來(lái)的指令流。
  • 根據(jù)硬件設(shè)置的寄存器標(biāo)志,判斷是什么異常并執(zhí)行具體的異常處理函數(shù)。
  • ?恢復(fù)現(xiàn)場(chǎng),將棧里保存的寄存器的值再寫回。
  • 跳轉(zhuǎn)到正常指令流斷點(diǎn),回到CPU正常的指令流。
  • 以系統(tǒng)調(diào)用為例,用戶程序執(zhí)行系統(tǒng)調(diào)用后,C庫(kù)通過(guò)執(zhí)行syscall指令產(chǎn)生一個(gè)軟件異常,進(jìn)行上面的一系列工作后會(huì)定位到系統(tǒng)調(diào)用的處理函數(shù)handle_sys

    系統(tǒng)調(diào)用代碼分析

    系統(tǒng)調(diào)用表

    內(nèi)核支持的系統(tǒng)調(diào)用都放在一張全局的系統(tǒng)調(diào)用表sys_call_table中,所有的系統(tǒng)調(diào)用按照系統(tǒng)調(diào)用號(hào)從大到小的順序存放。這個(gè)表中每個(gè)條目的大小為8字節(jié),定義如下:

    .macro sys function, nargsPTR \functionLONG (\nargs << 2) - (5 << 2) .endm

    可以看出,系統(tǒng)調(diào)用表中每個(gè)條目由兩部分組成,前4個(gè)字節(jié)是處理函數(shù)function的地址,后4個(gè)字節(jié)為(\nargs << 2) -(5 << 2),nargs是系統(tǒng)調(diào)用的參數(shù)個(gè)數(shù),這個(gè)表達(dá)式的結(jié)果用來(lái)判斷參數(shù)個(gè)數(shù)是否超過(guò)4個(gè)。

    下面通過(guò)分析handle_sys函數(shù)的實(shí)現(xiàn)介紹系統(tǒng)調(diào)用在內(nèi)核態(tài)所做的工作。

    備份通用寄存器

    將異常發(fā)生時(shí)的當(dāng)前進(jìn)程的通用寄存器的值保存起來(lái),并確定異常返回地址epc的值,使其可以正常返回。

    在C庫(kù)執(zhí)行syscall指令之前,會(huì)先把系統(tǒng)調(diào)用號(hào)存放到寄存器v0中,并將需要傳遞的參數(shù)放到a0-a4中,如果參數(shù)個(gè)數(shù)大于四個(gè),就需要保存在棧里面。

    handle_sys的開(kāi)頭先通過(guò)SAVE_SOME宏將當(dāng)前進(jìn)程的通用寄存器的值備份到進(jìn)程棧中:

    NESTED(handle_sys, PT_SIZE, sp).set noatSAVE_SOME # 見(jiàn)下面對(duì)該函數(shù)的分析TRACE_IRQS_ON_RELOAD # not implementedSTI #進(jìn)入內(nèi)核模式,使能全局中斷.set atlw t1, PT_EPC(sp) # 取出epc的值。這時(shí)應(yīng)該指向syscall指令/* v0中存放著系統(tǒng)調(diào)用號(hào),由于系統(tǒng)調(diào)用號(hào)是從4000開(kāi)始的,所以將v0修改為實(shí)際的序號(hào): v0 = v0 – 4000 */subu v0, v0, __NR_O32_Linux/* 判斷系統(tǒng)調(diào)用號(hào)的合法性 */sltiu t0,v0, __NR_O32_Linux_syscalls + 1addiu t1, 4 #skip to next instructionsw t1, PT_EPC(sp) # 跳過(guò)syscall指令,這樣返回時(shí)可以繼續(xù)執(zhí)行beqz t0, illegal_syscall # if(t0== 0) illegal syscall.

    SAVE_SOME宏的定義如下:

    .macro SAVE_SOME.set push.set noat.set reordermfc0 k0, CP0_STATUSsll k0, 3 /* k0 = k0 << 3,即CU0成了最高位 */.set noreorder/* 最高位是1就是負(fù)數(shù),小于0。CU0=1則得到用戶特權(quán)級(jí)別 */bltz k0, 8f /*if k0 < 0, goto 8: */move k1, sp /* 延遲槽,如果是內(nèi)核態(tài)進(jìn)來(lái)的,直接獲取sp的值放到k1中 */.set reorder/* 如果是從用戶態(tài)進(jìn)來(lái)的,則需要使用kernel中保存的sp */get_saved_sp /* 讀取全局kernelsp中的sp的值到k1中。 */8: move k0, sp /* 把原來(lái)的sp的值放到k0中保存。 *//* sp = k1 - sizeof(struct pt_regs),由于kernelsp存放的是sp + _THREAD_SIZE - 32,所以這里得到的sp就是進(jìn)程地址空間的棧頂。 */PTR_SUBU sp, k1, PT_SIZE/* 將k0的值(即剛保存的sp)保存到進(jìn)程的pt_regs.regs[29] */LONG_S k0, PT_R29(sp)LONG_S $3, PT_R3(sp) /* 保存v1的值 *//** You might think that you don't need to save$0,* but the FPU emulator and gdb remote debugstub* need it to operate correctly*/LONG_S $0, PT_R0(sp) /* 保存$0的值 */mfc0 v1, CP0_STATUSLONG_S $2, PT_R2(sp) /* 保存v0的值 */LONG_S v1, PT_STATUS(sp) /* 保存cp0_status的值 */LONG_S $4, PT_R4(sp) /* 保存a0的值 */mfc0 v1, CP0_CAUSELONG_S $5, PT_R5(sp) /* 保存a1的值 */LONG_S v1, PT_CAUSE(sp) /* 保存cp0_cause的值 */LONG_S $6, PT_R6(sp) /* 保存a2的值 */MFC0 v1, CP0_EPCLONG_S $7, PT_R7(sp) /* 保存a3的值 */LONG_S v1, PT_EPC(sp) /* 保存cp0_epc的值 */LONG_S $25, PT_R25(sp) /* 保存t9的值 */LONG_S $28, PT_R28(sp) /* 保存gp的值 */LONG_S $31, PT_R31(sp) /* 保存ra的值 */ori $28, sp, _THREAD_MASK /* gp = sp | 0x1FFF *//* gp = gp ^ 0x1FFF,即sp的末13位清0賦值給gp,內(nèi)核棧的大小就是8K,所以,這里的結(jié)果就是gp指向棧頂。 */xori $28, _THREAD_MASK.set pop .endm

    這里需要說(shuō)明一下內(nèi)核線程的內(nèi)核棧空間,內(nèi)核棧是從高地址向下延伸的,大小為兩個(gè)頁(yè),即8K。為了方便的定位到進(jìn)程的task_struct結(jié)構(gòu),進(jìn)程的thread_info結(jié)構(gòu)被放在棧底(低地址),這樣,在進(jìn)程地址空間內(nèi)的任何地址,只需將末13位清零就是thread_info的位置,再通過(guò)thread_info結(jié)構(gòu)體的task指針可以很快找到進(jìn)程的task_struct結(jié)構(gòu)。

    在創(chuàng)建進(jìn)程時(shí),在棧頂(高地址)會(huì)預(yù)留32字節(jié)的空間,這32字節(jié)目前沒(méi)有被使用,可能是為了防止溢出而導(dǎo)致覆蓋了進(jìn)程的重要信息。在32字節(jié)下面是一個(gè)struct pt_regs結(jié)構(gòu)體,它的目的是為了在發(fā)生系統(tǒng)調(diào)用或其他異常時(shí),保存進(jìn)程的重要寄存器的值,如通用寄存器和CP0的寄存器。在距離棧頂32Bytes +sizeof(struct pt_regs)的位置才是sp的初始位置。

    獲得參數(shù)個(gè)數(shù)并執(zhí)行處理程序

    根據(jù)系統(tǒng)調(diào)用號(hào)在sys_call_table中找到該系統(tǒng)調(diào)用需要幾個(gè)參數(shù)。

    # v0左移3位。因?yàn)閟ys_call_table中每個(gè)條目占用8字節(jié)。sll t0, v0, 3 la t1, sys_call_table # t1中存放sys_call_table的地址addu t1, t0 # t1 = t1 + t0。得到要找的系統(tǒng)調(diào)用的地址。lw t2, (t1) # 把處理函數(shù)地址放到t2中l(wèi)w t3, 4(t1) # t3中存放是否參數(shù)個(gè)數(shù)大于4beqz t2, illegal_syscall # 如果找不到處理函數(shù),非法sw a3, PT_R26(sp) # save a3for syscall restartingbgez t3, stackargs # 如果t3>=0,則參數(shù)大于4個(gè),需要棧

    在上面的代碼中,t2中保存了系統(tǒng)調(diào)用處理函數(shù)的地址。而t3的值就有兩層意思:

    1.???如果t3小于0,說(shuō)明系統(tǒng)調(diào)用的參數(shù)少于或等于4個(gè)。

    2.???如果t3大于等于0,那t3的取值可能是0,4,8,16,分別對(duì)應(yīng)5,6,7,8個(gè)參數(shù)的情況。這里t3賦值成4的倍數(shù)是為了兩個(gè)相鄰值之間相差一個(gè)指令的長(zhǎng)度,在下面獲取參數(shù)時(shí)利用了這一點(diǎn)。

    如果參數(shù)個(gè)數(shù)小于等于4個(gè),就不需要使用棧保存參數(shù),那處理很簡(jiǎn)單:如果需要跟蹤系統(tǒng)調(diào)用,在執(zhí)行系統(tǒng)調(diào)用之前,需要通知父進(jìn)程。一般情況下,我們不需要跟蹤系統(tǒng)調(diào)用,所以直接跳轉(zhuǎn)到系統(tǒng)調(diào)用的處理函數(shù)。

    stack_done: lw t0, TI_FLAGS($28) # 得到進(jìn)程的thread_info.flagsli t1, _TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDITand t0, t1 # thread_info.flags是否設(shè)置了上面兩個(gè)標(biāo)志bnez t0, syscall_trace_entry # 如果設(shè)置了,跳到處理函數(shù)jalr t2 # 進(jìn)入系統(tǒng)調(diào)用處理函

    如果參數(shù)個(gè)數(shù)大于4個(gè),我們需要在棧用獲取多余的參數(shù),然后再跳轉(zhuǎn)到上面的stack_done調(diào)用處理函數(shù)。

    stackargs:lw t0, PT_R29(sp) # get olduser stack pointer/** We intentionally keep the kernel stack alittle below the * top of userspace so we don't have to do a slower byteaccurate check here.*/lw t5, TI_ADDR_LIMIT($28) # 獲得thread_info.addr_limitaddu t4, t0, 32 # sp + 32就是棧的高地址and t5, t4/** addr_limit有兩種:* 0-0x7FFFFFFF for user-thead* 0-0xFFFFFFFF for kernel-thread*/bltz t5, bad_stack # t5 < 0即位于了內(nèi)核態(tài),不合法/* Ok, copy the args fromthe luser stack to the kernel stack.* t3 is the precomputed number of instructionbytes needed to* load or store arguments 6-8.*/la t1, 5f # load up to 3arguments# 通過(guò)上面賦值,t3可能取0, 4, 8, 16對(duì)應(yīng)5,6, 7, 8個(gè)參數(shù)。subu t1, t3 1: lw t5, 16(t0) # argument #5from usp 取出#5.set push.set noreorder.set nomacrojr t1 # 根據(jù)參數(shù)個(gè)數(shù)跳轉(zhuǎn)addiu t1,6f - 5f # 延遲槽,跳轉(zhuǎn)同時(shí)把t1加上6f -5f.2: lw t8, 28(t0) # argument #8from usp 3: lw t7, 24(t0) # argument #7from usp 4: lw t6, 20(t0) # argument #6from usp 5: jr t1sw t5,16(sp) # argument #5 to ksp 延遲槽,跳轉(zhuǎn)同時(shí)存入#5sw t8, 28(sp) # argument #8 tokspsw t7, 24(sp) # argument #7 tokspsw t6, 20(sp) # argument #6 toksp 6: j stack_done # 跳回和小于等于4個(gè)參數(shù)相同的處理流程nop.set pop

    準(zhǔn)備返回到用戶態(tài)

    系統(tǒng)調(diào)用的處理程序執(zhí)行完成后,就要準(zhǔn)備返回用戶空間了。

    # # 準(zhǔn)備系統(tǒng)調(diào)用的返回值。 #li t0, -EMAXERRNO - 1 # error?sltu t0, t0, v0 # if t0< v0, t0 =1, else t0 = 0.sw t0, PT_R7(sp) #把t0的值存到a3里去。beqz t0, 1f # if t0 == 0,goto 1:negu v0 # error, v0 = -v0sw v0, PT_R0(sp) # set flagfor syscall# restarting 1: sw v0, PT_R2(sp) # result, v0存到pt_regs[2]中o32_syscall_exit:local_irq_disable # make sure need_resched and# signalsdont change between# samplingand return# 下面的內(nèi)容還是和trace syscall相關(guān)的,在系統(tǒng)調(diào)用完成后,通知父進(jìn)程。lw a2, TI_FLAGS($28) #current->workli t0, _TIF_ALLWORK_MASKand t0, a2bnez t0, o32_syscall_exit_workj restore_partial /* 恢復(fù)寄存器,并返回 */

    可以看到,系統(tǒng)調(diào)用將a3和v0返回給用戶態(tài),經(jīng)過(guò)上面的代碼處理,這兩個(gè)寄存器中的值的含義如下:

    • a3存放系統(tǒng)調(diào)用是否成功,成功就是0,失敗就是1。
    • v0存放系統(tǒng)調(diào)用的返回值,如果是負(fù)數(shù)且位于[-EMAXERRNO,-1]之間,v0就是錯(cuò)誤碼。否則,v0是該系統(tǒng)調(diào)用本來(lái)想返回的東西。注意,有效錯(cuò)誤碼的范圍在1~ EMAXERRNO之間。
    • 如果v0是錯(cuò)誤碼,就先轉(zhuǎn)換成正數(shù),再返回,這樣用戶態(tài)可直接識(shí)別。

    返回到C庫(kù)后,會(huì)根據(jù)a3判斷是成功還是失敗,如果成功就給用戶程序返回v0。如果失敗,就將v0寫到errno中,然后根據(jù)該系統(tǒng)調(diào)用的規(guī)定,給用戶程序返回失敗時(shí)的返回值。

    代碼的最后跳轉(zhuǎn)到restore_partial中去,它的定義很簡(jiǎn)單:

    FEXPORT(restore_partial) #restore partial frameRESTORE_SOMERESTORE_SP_AND_RET

    其中RESTORE_SOME對(duì)應(yīng)最開(kāi)頭的SAVE_SOME。而RESTORE_SP_AND_RET做了兩件事情:

    1.???將進(jìn)程棧中保存的sp的值恢復(fù),賦值給sp寄存器。

    2.???將進(jìn)程棧中保存的epc的值恢復(fù),并跳轉(zhuǎn)到epc指向的地址。而開(kāi)頭講到過(guò),這時(shí)epc指向syscall指令的下一條指令,即繼續(xù)執(zhí)行C庫(kù)中調(diào)用syscall指令之后的代碼。

    .macro RESTORE_SP_AND_RET.set push.set noreorderLONG_L k0, PT_EPC(sp)LONG_L sp, PT_R29(sp)jr k0rfe #在異常返回前恢復(fù)CPU狀態(tài).set pop .endm

    ?

    總結(jié)

    以上是生活随笔為你收集整理的MIPS中的异常处理和系统调用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。