ARM平臺上Linux異常處理程式碼簡要分析
Linux version 3.10.40
ARM處理器支援多種異常模式,如reset,irq,fiq等,發生異常後處理器根據配置跳到指定的地址執行,可以配置成從0地址開始,也可以配置成從0xFFFF0000地址開始。我們從兩個角度分析Linux上的實現,第一是負責異常處理的程式碼是如何安裝到該地址的,第二是這些程式碼的處理流程是什麼樣的。
一 放置異常處理程式碼流程
依次涉及:
init/main.c中的start_kernel函式
arch/arm/kernel/setup.c中的setup_arch函式
arch/arm/mm/mmu.c中的paging_init和devicemaps_init函式
arch/arm/kernel/traps.c中的early_trap_init函式
呼叫過程:
start_kernel -> setup_arch -> paging_init -> devicemaps_init -> early_trap_init
devicemaps_init:
devicemaps_init函式中程式碼片段如下,先分配儲存異常處理程式碼的空間,然後呼叫early_trap_init
/*
* Allocate the vector page early.
*/
vectors = early_alloc(PAGE_SIZE * 2);
early_trap_init(vectors);
devicemaps_init函式呼叫完early_trap_init會通過以下程式碼將異常處理的程式碼對映到異常向量地址處。
/*
* Create a mapping for the machine vectors at the high-vectors
* location (0xffff0000). If we aren't using high-vectors, also
* create a mapping at the low-vectors virtual address.
*/
map.pfn = __phys_to_pfn(virt_to_phys(vectors));
map.virtual = 0xffff0000;
map.length = PAGE_SIZE;
#ifdef CONFIG_KUSER_HELPERS
map.type = MT_HIGH_VECTORS;
#else
map.type = MT_LOW_VECTORS;
#endif
create_mapping(&map, false);
early_trap_init:
最主要的功能就是將異常處理的程式碼拷貝到前邊分配的空間中,片段如下:
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);
二 異常處理程式碼
前邊講過early_trap_init負責拷貝程式碼,其中用到的地址__vectors_start,__vectors_end,__stubs_start,__stubs_end都定義在"arch/arm/kernel/vmlinux.lds.S
/*
* The vectors and stubs are relocatable code, and the
* only thing that matters is their relative offsets
*/
__vectors_start = .;
.vectors 0 : AT(__vectors_start) {
*(.vectors)
}
. = __vectors_start + SIZEOF(.vectors);
__vectors_end = .;
__stubs_start = .;
.stubs 0x1000 : AT(__stubs_start) {
*(.stubs)
}
. = __stubs_start + SIZEOF(.stubs);
__stubs_end = .;
以上鍊接指令碼指定了__vectors_start和__stubs_start處的程式碼,原始碼位於arch/arm/kernel/entry-armv.S中,程式碼片段如下,都是跳轉指令。
.section .vectors, "ax", %progbits
__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, __vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq
W(b) vector_fiq
vector_rst實現如下:
vector_rst:
ARM( swi SYS_ERROR0 )
THUMB( svc #0 )
THUMB( nop )
b vector_und
vector_irq,vector_dabt,vector_pabt,vector_und,vector_fiq都是通過如下的vector_stub巨集實現的。
.macro vector_stub, name, mode, correction=0
stubs段定義如下,根據vmlinux.lds.S中的定義,該段位於相對偏移0x1000處。
.section .stubs, "ax", %progbits
__stubs_start:
從以下的System.map檔案也可以看出__vectors_start處於0地址,__stubs_start處於0x1000處。
00000000 t __vectors_start
00000020 A cpu_v7_suspend_size
00001000 t __stubs_start
00001004 t vector_rst
00001020 t vector_irq
000010a0 t vector_dabt
00001120 t vector_pabt
000011a0 t vector_und
00001220 t vector_fiq
00001220 T vector_fiq_offset
000012a0 t vector_addrexcptn
c0004000 A swapper_pg_dir
以中斷處理為例,vector_irq如下,vector_stub的實現非常討巧,以下程式碼實現了在user模式發生IRQ下進入__irq_usr,在supervisor模式發生IRQ進入__irq_svc的,其他模式發生IRQ進入__irq_invalid。
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
需要注意的是__irq_invalid,除了在user和supervisor模式外其他模式發生IRQ都是認為非法的,呼叫流程為
__irq_invalid -> common_invalid -> bad_mode
在bad_mode中會打印出日誌並且呼叫die和panic。
asmlinkage void bad_mode(struct pt_regs *regs, int reason)
{
console_verbose();
printk(KERN_CRIT "Bad mode in %s handler detected\n", handler[reason]);
die("Oops - bad mode", regs, 0);
local_irq_disable();
panic("bad mode");
}
遺留問題:
1. 各種異常的詳細處理過程還需分析;
2. die和panic的區別是什麼;