1. 程式人生 > >ARM平臺上Linux異常處理程式碼簡要分析

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的區別是什麼;