linux異常處理體系結構


1、異常的作用

異常,就是可以打斷CPU正在運行流程的一些事情,比如外部中斷、未定義指令、試圖修改只讀數據、執行swi指令(中斷指令)等。當這些事情發生時,CPU暫停當前的程序,先處理異常事件,然后再繼續執行被中斷的程序。

  • 未定義指令異常: CPU在執行一些未定義的機器指令時,觸發“未定義指令異常”,操作系統可以利用這個特點使用一些自定義指令。
  • 數據訪問終止異常: 將一塊數據設為只讀的,提供給多個進程共用, 這樣可以節省內存。當某個進程試圖修改其中的數據時,將觸發"數據訪問終止異常", 在異常處理函數中將這塊數據復制出一份可寫的副本,提供給這個進程使用。
  • 當用戶程序試圖讀寫的數據或執行的指令不在內存中時,也會出發一個“數據訪問中止異常”或“指令預取中止異常”,在異常處理函數中將這些數據或指令讀入內存(內存不足時還可以將不用的數據、指令換出內存),然后重新執行被中斷的程序。這樣可以節省內存, 還使得操作系統可以運行這類程序:它們使用的內存遠大於實際的物理內存。
  • 當程序使用不對齊的地址訪問時,也會觸發"數據訪問終止異常", 在異常處理程序中先使用多個對齊的地址讀出數據。對於讀操作,從中選取數據組合好后返回給被中斷的程序;對於寫操作,修改其中的部分數據后再寫入內存。這使得程序(特別是應用程序)不用考慮地址的對齊的問題。
  • 用戶程序可以 "swi" 指令觸發 "swi異常" ,操作系統在swi異常處理函數中實現各種系統調用。

2、arm9的異常向量表

異常類型 處理器模式 異常向量 高地址向量
復位異常 (reset) 特權模式 0x00000000 0xFFFF0000
未定義指令異常(undefined interrupt) 未定義指令終止模式 0x00000004 0xFFFF0004
軟件中斷異常(software abort) 特權模式 0x00000008 0xFFFF0008
預取中止異常(prefetch) 數據訪問終止模式 0x0000000C 0xFFFF000C
數據中止異常(data abort) 數據訪問終止模式 0x00000010 0xFFFF0010
外部中斷請求(IRQ) 外部中斷模數 0x00000018 0xFFFF0018
快速中斷請求(FIQ) 快速中斷模式 0x0000001C 0xFFFF001C

3、linux內核對異常的設置

內核在start_kernel()函數(源碼在init/main.c中)調用了setup_arch(&command_line)->early_trap_init()函數和init_IRQ()函數設置了異常(說明:在之前的版本是在trap_init()init_IRQ()設置)。

1)early_trap_init()函數分析(\arch\arm\kernel\traps.c):

early_trap_init 函數被用來設置各種異常向量,包括中斷向量。ARM架構的CPU的異常向量基地址可以是0x00000000,也可以是0xffff0000,Linux 內核使用0xffff0000,early_trap_init 函數將異常向量復制0xffff0000處; 部分代碼如下:

void __init early_trap_init(void)
{
    unsigned long vectors = CONFIG_VECTORS_BASE;    // CONFIG_VECTORS_BASE 這個宏是一個內核配置項.在 .config 里面。  CONFIG_VECTORS_BASE  = 0xffff0000
    extern char __stubs_start[], __stubs_end[];
    extern char __vectors_start[], __vectors_end[];
    ...
    /*vectors  等於 0xffff0000;  __vectors_start  和  __vectors_end 之間的代碼就是異常向量*/
    memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
    /* __stubs_start和__stubs_end 之間的代碼從異常向量跳轉去執行跟復雜的代碼 */
    memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
    ...
}

__vectors_start和__vectors_end之間的代碼就是異常向量! 異常向量的代碼只是一些跳轉指令。發生異常時,CPU自動執行這些指令,跳轉去執行跟復雜的代碼;比如保存被中斷程序的執行環境,調用異常處理函數,恢復被中斷程序的執行環境並重新運行。

        .....
        .equ	stubs_offset, __vectors_start + 0x200 - __stubs_start        /*  */
        .....
	.globl	__vectors_start
__vectors_start:
	swi	SYS_ERROR0                       /* 復位時,CPU將執行這條指令 */
	b	vector_und + stubs_offset      /* 未定義指令 */
	ldr	pc, .LCvswi + stubs_offset      /* swi異常 */
	b	vector_pabt + stubs_offset     /* 指令預取中止 */
	b	vector_dabt + stubs_offset     /* 數據訪問中止 */
	b	vector_addrexcptn + stubs_offset    /* 沒有用到 */
	b	vector_irq + stubs_offset        /* irq異常 */
	b	vector_fiq + stubs_offset       /* fiq異常 */
       /* 上面這些表示發生異常時,要跳轉去執行的代碼。 */

	.globl	__vectors_end
__vectors_end:
    
    .....

vector_und 為例。當發生異常時,跳轉到異常向量,執行 b vector_und + stubs_offset,然后跳轉到下面的代碼:

        .....
vector_stub	und, UND_MODE         /* "vector_stub" 是一個宏,根據 "und, UND_MODE"  定義一段代碼*/
         /* 下面這些代碼是跳轉去執行更復雜的代碼 */
	.long	__und_usr			@  0 (USR_26 / USR_32)     /* 用戶模式下執行了未定義指令 */
	.long	__und_invalid			@  1 (FIQ_26 / FIQ_32)
	.long	__und_invalid			@  2 (IRQ_26 / IRQ_32)
	.long	__und_svc			@  3 (SVC_26 / SVC_32)    /* 在管理模式下執行了未定義指令 */
	.long	__und_invalid			@  4
	.long	__und_invalid			@  5
	.long	__und_invalid			@  6
	.long	__und_invalid			@  7
	.long	__und_invalid			@  8
	.long	__und_invalid			@  9
	.long	__und_invalid			@  a
	.long	__und_invalid			@  b
	.long	__und_invalid			@  c
	.long	__und_invalid			@  d
	.long	__und_invalid			@  e
	.long	__und_invalid			@  f
        .....

vector_stub 宏的功能, 計算處理完異常后的返回地址,保存一些寄存器,然后進入管理模式,最后根據異常的工作模式跳轉到 上面 代碼 的某個分支 。 這個宏的代碼如下:

/*
 * Vector stubs.
 *
 * This code is copied to 0xffff0200 so we can use branches in the
 * vectors, rather than ldr's.  Note that this code must not
 * exceed 0x300 bytes.
 *
 * Common stub entry macro:
 *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 *
 * SP points to a minimal amount of processor-private memory, the address
 * of which is copied into r0 for the mode specific abort handler.
 */
	.macro	vector_stub, name, mode, correction=0       /* “.macro” 這個偽匯編 是定義一個宏 ,使用方法可以參考這個 https://www.cnblogs.com/Widesky/p/9006954.html    */
	.align	5      /* 4字節對齊 linux下交叉 編譯器的對齊方式 2^5bit對齊  */
vector_\name:
	.if \correction
	sub	lr, lr, #\correction    /* 根據不同的異常,計算返回地址 */
	.endif

	@
	@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
	@ (parent CPSR)
	@
	stmia	sp, {r0, lr}		@ save r0, lr     /* 將r0,lr 壓入到各自異常的堆棧中 */
	mrs	lr, spsr                                              /* 將spsr賦給lr */
	str	lr, [sp, #8]		@ save spsr    /* 將lr入棧,即spsr入棧 */

	@
	@ Prepare for SVC32 mode.  IRQs remain disabled.
	@
	mrs	r0, cpsr                         
	eor	r0, r0, #(\mode ^ SVC_MODE)
	msr	spsr_cxsf, r0                           /* 將r0的值賦給spsr_cxsf,此時的狀態還是處於und模式 */

	@
	@ the branch table must immediately follow this code
	@
	and	lr, lr, #0x0f                            /* lr=lr&0x0f,lr起始就是spsr的值,它保存了進入IRQ模式前的CPU模式,其實是5位控制的,這里只用到4位,用來跳轉到不同的處理函數 */
	mov	r0, sp                                   /* 將管理模式的sp的值給r0 */
	ldr	lr, [pc, lr, lsl #2]                    /* lr = *(pc+lr<<2)。如果在進入IRQ之前是用戶模式即是從應用層進入的,那么lr = pc = __und_usr.否則是管理模式也就是處於內核層時發生了IRQ異常 lr = pc+12=__und_svc */
	movs	pc, lr			@ branch to handler in SVC mode          /* 將lr的值給pc,同時將spsr的值賦給cpsr,此時才是進入了管理模式 */
	.endm

__und_usr__und_svc 兩個不同的分支,只是在它們的入口處(比如保存被中斷程序的寄存器)稍有差別,后續的處理大體,相同,都是調用相應的C函數。未定義指令異常最會調用do_undefinstr函數來處理,跳轉的代碼如下。

用戶模式下發生未定義指令異常

/* __und_usr  用戶模式下發生未定義指令異常的處理代碼 */
__und_usr:
	usr_entry

	tst	r3, #PSR_T_BIT			@ Thumb mode?
	bne	__und_usr_unknown		@ ignore FP    //跳轉到__und_usr_unknown
	sub	r4, r2, #4
        ........     //下面還有一些匯編指令
__und_usr_unknown:
	mov	r0, sp               /* 將棧頂地址,作為參數傳入 */
	adr	lr, ret_from_exception    /* 將返回地址寫入到 r0, 處理完C函數后將返回到這里 */
	b	do_undefinstr    /*C函數入口,處理未定義指令異常*/

管理模式下發生未定義指令異常

__und_svc:
	svc_entry

	@
	@ call emulation code, which returns using r9 if it has emulated
	@ the instruction, or the more conventional lr if we are to treat
	@ this as a real undefined instruction
	@
	@  r0 - instruction
	@
	ldr	r0, [r2, #-4]
	adr	r9, 1f
	bl	call_fpe

	mov	r0, sp				@ struct pt_regs *regs
	bl	do_undefinstr             /*C函數入口,處理未定義指令異常*/

	@
	@ IRQs off again before pulling preserved data off the stack
	@
1:	disable_irq

	@
	@ restore SPSR and restart the instruction
	@
	ldr	lr, [sp, #S_PSR]		@ Get SVC cpsr
	msr	spsr_cxsf, lr
	ldmia	sp, {r0 - pc}^			@ Restore SVC registers     /* 將之前保存到堆棧的寄存器,出棧,並且恢復相應的cpsr寄存器,然后返回斷點處繼續執行 */

4、小結

未定義指令異常簡單的分析了一下linux 下的異常處理體系。
linux 下地異常處理 和 裸機地差不多,只不過在linux下使用了高地址
異常向量和處理異常的代碼,搬移前和搬以后的地址如下圖:

中斷也是異常的一種,因為中斷的處理一些中斷的處理函數,必須由驅動開發者提供,下一章會單獨分析中斷管理的框架。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM