基于ARM Linux 中斷、異常的處理分析 本文是基于ARM S3C2410X系統(tǒng)的Linux 2.6中斷、異常和系統(tǒng)調(diào)用的處理分析。主要有以下幾個部分:
1. ARM的硬件中斷機制
2. Linux 2.6對ARM中斷向量表的初始化
3. Linux 2.6對ARM中斷、異常的處理(從匯編-->C語言函數(shù);asm_do_IRQ)
一、 ARM的硬件中斷機制 1、中斷的基本概念 在嵌入式系統(tǒng)中外部設(shè)備的功能實現(xiàn)主要是依靠中斷機制來實現(xiàn)的,即將設(shè)備功能程序的實現(xiàn)以中斷服務(wù)子程序的形式進行組織。
中斷(interrupt)通常被定義為一個事件,該事件改變處理器執(zhí)行的指令順序。這樣的事件與CPU芯片外部硬件電路產(chǎn)生的電信號相對應(yīng)。
中斷的產(chǎn)生 每個能夠發(fā)出中斷請求的硬件設(shè)備控制器都有一條稱為IRQ(Interrupt ReQuest)的輸出線。所有的IRQ線都與一個中斷控制器的輸入引腳相連,中斷控制器與CPU的INTR引腳相連。
設(shè)備
設(shè)備
控制器
中斷
控制器
IRQ
CPU
INTR
中斷向量 每個中斷和異常由0~255之間的一個數(shù)(8位)來標識,Intel稱其為中斷向量。
中斷描述符表(Interrupt Descriptor Table ,IDT)是一個系統(tǒng)表,它與每一個中斷或異常向量相聯(lián)系,每一個向量在表中有相應(yīng)的中斷或異常處理程序的入口地址。內(nèi)核在允許中斷發(fā)生前,必須適當?shù)爻跏蓟疘DT。表中的每一項對應(yīng)一個中斷或異常向量,每個向量由8個字節(jié)組成。因此,最多需要256*8=2048字節(jié)來存放IDT。CPU的idtr寄存器指向IDT表的物理基地址。
2、中斷和異常的硬件處理 在內(nèi)核被Init進程初始化后,CPU運行在保護模式下。當執(zhí)行了一條指令后,cs和eip這對寄存器包含了下一條將要執(zhí)行的指令的邏輯地址。在執(zhí)行這條指令之前,CPU控制單元會檢查在運行前一條指令時是否發(fā)生了一個中斷或者異常。如果發(fā)生了一個中斷或異常,那么CPU控制單元執(zhí)行下列操作:
(1) 確定與中斷或者異常關(guān)聯(lián)的向量i(0~255)。
(2) 讀由idtr寄存器指向的IDT表中的第i項。
(3) 從gdtr寄存器獲得GDT的基地址,并在GDT中查找,以讀取IDT表項中的選擇符所標識的段描述符,這個描述符指定中斷或異常處理程序所在段的基地址。
(4) 確定中斷是由授權(quán)的發(fā)生源發(fā)出的。
中斷:中斷處理程序的特權(quán)不能低于引起中斷的程序的特權(quán)(當前特權(quán)級CPL—對應(yīng)CS寄存器中的低兩位 其值應(yīng)該小于段描述符—對應(yīng)GDT表項中的描述符特權(quán)級DPL,特權(quán)級高于DPL,即當前代碼是能夠訪問相應(yīng)的段的,產(chǎn)生一個“”異常);
編程異常:還需進一步比較CPL與對應(yīng)IDT表項中的門描述符的DPL。
即當CPL的特權(quán)級高于GDT表項中的描述符特權(quán)級DPL,但低于IDT表項中的門描述符的DPL,就是異常。
(5) 檢查是否發(fā)生了特權(quán)級的變化,一般指是否由用戶態(tài)陷入了內(nèi)核態(tài)。也就是說CPL是否不同于所選擇的段描述符的DPL,如果是,控制單元必須開始使用與新的特權(quán)級相關(guān)的堆棧,通過以下操作來做到這點:
A、讀tr寄存器,訪問運行進程的TSS段;
B、用與新特權(quán)級相關(guān)的棧段和棧指針裝載ss和esp寄存器。這些值可以在進程的TSS段中找到;
C、在新的棧中保存ss和esp以前的值,這些值指明了與舊特權(quán)級相關(guān)的棧的邏輯地址。
(6) 若發(fā)生的是故障,用引起異常的指令地址修改cs和eip寄存器的值,以使得這條指令在異常處理結(jié)束后能被再次執(zhí)行。
(7) 在棧中保存eflags、cs和eip的內(nèi)容。
(8) 如果異常產(chǎn)生一個硬件出錯碼,則將它保存在棧中。
(9) 裝載cs和eip寄存器,其值分別是IDT表中第i項門描述符的段選擇符和偏移量字段。這對寄存器的值指出中斷或者異常處理程序的第一條指令的邏輯地址。
控制單元所執(zhí)行的最后一步就是跳轉(zhuǎn)到中斷或異常處理程序,換句話說,處理完中斷信號后,控制單元所執(zhí)行的指令就是被選中處理程序的第一條指令。
中斷/異常處理完后,相應(yīng)的處理程序會執(zhí)行一條iret匯編指令,把控制權(quán)轉(zhuǎn)交給被中斷的進程,這條匯編指令讓CPU控制單元做如下事情:
(1) 用保存在棧中的值裝載cs、eip和eflags寄存器。如果一個硬件出錯碼曾被壓入棧中,并且在eip內(nèi)容的上面,那么執(zhí)行iret指令前必先彈出這個硬件出錯碼。
(2) 檢查處理程序的特權(quán)級CPL是否等于cs中最低兩位的值(這意味著進程在被中斷的時候是運行在內(nèi)核態(tài)還是用戶態(tài),即被中斷的進程與處理程序是否運行在同一特權(quán)級)。若是,iret終止執(zhí)行;否則,轉(zhuǎn)入下一步。
(3) 從棧中裝載ss和esp寄存器。這步意味著返回到與舊特權(quán)級相關(guān)的棧。
(4) 檢查ds、es、fs和gs段寄存器的內(nèi)容,如果其中一個寄存器包含的選擇符是一個段描述符,并且特權(quán)級比當前特權(quán)級高(DPL的值小于CPL的值),則清除相應(yīng)的寄存器??刂茊卧@么做是防止懷有惡意的用戶程序利用內(nèi)核以前所用的寄存器訪問內(nèi)核空間。
二、Linux對ARM中斷向量表的初始化 1、中斷向量表的作用 如上在中斷的硬件處理的分析中可知:中斷首先是一個硬件行為, 而處理中斷呢, 顯然又是一個軟件行為,那么當硬件觸發(fā)中斷的時候,怎么調(diào)用到中斷處理函數(shù)的呢?要搞清楚這個問題,就要搞清楚發(fā)生中斷的時候 CPU做了什么。以S3C2410X系統(tǒng)的異常中斷為例:
S3C2410X是基于ARM920T內(nèi)核處理器。該系統(tǒng)提供的FIQ和IRQ異常中斷用于外部設(shè)備向CPU請求服務(wù),一般情況下都是采用IRQ。S3C2410X系統(tǒng)中通常在存儲區(qū)的低端固化了一個32字節(jié)的硬件中斷向量表,用來指定各異常中斷與其處理程序的對應(yīng)關(guān)系。CPU知道一個source觸發(fā)了中斷,怎么調(diào)用執(zhí)行一些函數(shù)(匯編,或者c語言),就是靠異常向量表(事實上,exception vector table 也是由匯編組成的)
異常
模式
向量表偏移
復(fù)位(reset)
SVC
+0x00
未定義指令
UND
+0x04
軟件中斷(SWI)
SVC
+0x08
預(yù)取指終止
ABT
+0x0c
數(shù)據(jù)終止
ABT
+0x10
未分配
--
+0x14
IRQ
IRQ
+0x18
FIQ
FIQ
+0x1c
arm異常表,對應(yīng)模式及向量表偏移 (摘自arm體系結(jié)構(gòu)與編程一書)
當一個異常/中斷出現(xiàn)后, S3C2410X系統(tǒng)中ARM處理器對其的響應(yīng)過程如下:
(1) 保存處理器當前狀態(tài)、中斷屏蔽位以及各條件標志位。將當前程序狀態(tài)寄存器CPSR的內(nèi)容保存到將要執(zhí)行的異常中斷對應(yīng)的SPSR寄存器中。
(2) 設(shè)置當前程序狀態(tài)寄存器CPSR中相應(yīng)的位。包括設(shè)置CPSR中的位,使處理器進入相應(yīng)的執(zhí)行模式;設(shè)置CPSR中的位,禁止IRQ中斷,當進入FIQ模式時,禁止FIQ中斷。
(3) 將寄存器lr_mode設(shè)置成返回地址。
(4) 將程序計數(shù)器值(PC),設(shè)置成該異常中斷的中斷向量地址,從而跳轉(zhuǎn)到相應(yīng)的異常中斷處理程序執(zhí)行。即處理器跳轉(zhuǎn)到異常向量表中相應(yīng)的入口(對于IRQ , 顯然pc=0x18) 。
所以當觸發(fā)IRQ后,CPU會最后跳入0x18 這個入口,定制kernel時只需在這個入口填入自己的指令(當然是匯編語句) ,即可調(diào)用中斷處理函數(shù),可能這樣:
觸發(fā)IRQ—CPU jump 到0x18,同時要把irqno傳入相應(yīng)的寄存器調(diào)用一個中斷通用處理函數(shù)如:asm_do_IRQ(unsigned int irqno) asm_do_IRQ() 這個函數(shù)根據(jù)irqno 就可以找到對應(yīng)的中斷描述符,然后調(diào)用中斷描述符里面的handler()了。
2、中斷向量表的初始化 v arch/arm/kernel/entry-armv.S ——中斷向量表放在這個文件里:
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
ARM linux內(nèi)核啟動時,通過start_kernel()->trap_init()的調(diào)用關(guān)系,初始化內(nèi)核的中斷異常向量表。
v linux/init/main.c Start_kernel中的中斷向量表初始化
asmlinkage void __init start_kernel(void)
{
.....
trap_init();
init_IRQ();
....
中斷的初始化主要和這兩個函數(shù)相關(guān)
(1) trap_init()
v linux/arch/arm/kernel/traps.c
void __init trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end - __kuser_helper_start;
/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
/*
* Copy signal return handlers into the vector page, and
* set sigreturn to be a pointer to these.
*/
memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
sizeof(sigreturn_codes));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
CONFIG_VECTORS_BASE是一個宏,用來獲取ARM異常向量的地址,該宏在include/arch/asm-arm/system.h中定義:
v include/arch/asm-arm/system.h
#define CPU_ARCH_ARMv5 4
#define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */
extern unsigned long cr_no_alignment; /* defined in entry-armv.S */
extern unsigned long cr_alignment; /* defined in entry-armv.S */
#if __LINUX_ARM_ARCH__ >= 4
#define vectors_high() (cr_alignment & CR_V)
#else
#define vectors_high() (0)
#endif
對于ARMv4以下的版本,這個地址固定為0;ARMv4及其以上的版本,ARM異常向量表的地址受協(xié)處理器CP15的c1寄存器(control register)中V位(bit[13])的控制,如果V=1,則異常向量表的地址為0x00000000~0x0000001C;如果V=0,則為:0xffff0000~0xffff001C。(詳情請參考ARM Architecture Reference Manual)
v arch/arm/kernel/entry-armv.S—找到cr_alignment的定義:
.globl cr_alignment
.globl cr_no_alignment
cr_alignment:
.space 4
cr_no_alignment:
.space 4
v linux/arch/arm/kernel/head.S—當內(nèi)核啟動時,進入head.S文件:
/*
* Kernel startup entry point.
* ---------------------------
*
* This is normally called from the decompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags pointer.
*
…… …… …… …… ……
*/
.section ".text.head", "ax"
.type stext, %function
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
bl __vet_atags
bl __create_page_tables //創(chuàng)建arm啟動臨時使用的前4M頁表
/*
* The following calls CPU specific code in a position independent
* manner. See arch/arm/mm/proc-*.S for details. r10 = base of
* xxx_proc_info structure selected by __lookup_machine_type
* above. On return, the CPU will be ready for the MMU to be
* turned on, and r0 will hold the CPU control register value.
*/
ldr r13, __switch_data @ address to jump to after //99行
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC //102行
…… …… …… …… …… …… ……
__turn_mmu_on:
mov r0, r0 //填充armv4中的三級流水線:mov r0,r0 對//應(yīng)一個nop,所以對應(yīng)2個nop和一個mov pc,lr剛好三個"無用"操作
mcr p15, 0, r0, c1, c0, 0 @ write control reg //193行
mrc p15, 0, r3, c0, c0, 0 @ read id reg
…… …… …… …… …… …… ……
mov pc, lr //327行
在s3c2410平臺中,它將跳轉(zhuǎn)到arch/arm/mm/proc-arm920.S中執(zhí)行__arm920 _setup函數(shù)。即第102行“add pc, r10, #PROCINFO_INITFUNC”: 執(zhí)行b _arm920_setup
v linux/arch/arm/mm/proc-arm920.S: MMU functions for ARM920
.section ".proc.info.init", #alloc, #execinstr
.type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
.long PMD_TYPE_SECT | /
PMD_SECT_BUFFERABLE | /
PMD_SECT_CACHEABLE | /
PMD_BIT4 | /
PMD_SECT_AP_WRITE | /
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | /
PMD_BIT4 | /
PMD_SECT_AP_WRITE | /
PMD_SECT_AP_READ
b __arm920_setup
// add pc, r10, #PROCINFO_INITFUNC”:將執(zhí)行b _arm920_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
.long cpu_arm920_name
.long arm920_processor_functions
…… …… …… …… …… …… ……
.size __arm920_proc_info, . - __arm920_proc_info
…… …… …… …… …… …… …… …… …… …… …… …… …… …… ……
.type __arm920_setup, #function
__arm920_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
#endif
adr r5, arm920_crval
ldmia r5, {r5, r6}
mrc p15, 0, r0, c1, c0 @ get control register v4
bic r0, r0, r5
orr r0, r0, r6
mov pc, lr
.size __arm920_setup, . - __arm920_setup
當在arm920_setup設(shè)置完協(xié)處理器和返回寄存器r0之后,跳回到linux/arch/arm/kernel/head.S文件中的第99行,在lr寄存器中放置__switch_data中的數(shù)據(jù)__mmap_switched,第327行程序會跳轉(zhuǎn)到__mmap_switched處。在__turn_mmu_on:后,第193,194行,把r0寄存器中的值寫回到cp15的control register(c1)中,再讀出來放在r0中。 接下來再來看一下跳轉(zhuǎn)到__mmap_switched處的代碼:
v linux/arch/arm/kernel/head-common.S
.type __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
.long cr_alignment @ r7
.long init_thread_union + THREAD_START_SP @ sp
/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is not position independent.
*
* r0 = cp#15 control register
* r1 = machine ID
* r2 = atags pointer
* r9 = processor ID
*/
.type __mmap_switched, %function
__mmap_switched: //40行
adr r3, __switch_data + 4 //41行
ldmia r3!, {r4, r5, r6, r7} //43行
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b //53行
ldmia r3, {r4, r5, r6, r7, sp} // sp ~ (init_task_union)+8192
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r7, {r0, r4} @ Save control register values //60行
b start_kernel // 進入內(nèi)核C程序
41~43行的結(jié)果是:r6=__bss_start,r7=__end,...,r7=cr_alignment,..,這里r7保存的是cr_alignment變量的地址。
到了60行,由于之前r0保存的是cp15的control register(c1)的值,這里把r0的值寫入r7指向的地址,即cr_alignment=r0.到此為止,我們就看清楚了cr_alignment的賦值過程。
讓我們回到trap_init()函數(shù),經(jīng)過上面的分析,我們知道vectors_base返回0xffff0000。函數(shù)__trap_init由匯編代碼編寫,在arch/arm/kernel/entry-arm.S:
v linux/arch/arm/kernel/entry-armv.S
/*============================================================
* Address exception handler
*-----------------------------------------------------------------------------
* These aren't too critical.
* (they're not supposed to happen, and won't happen in 32-bit data mode).
*/
vector_addrexcptn:
b vector_addrexcptn
/*
* We group all the following data together to optimise
* for CPUs with separate I & D caches.
*/
.align 5
.LCvswi:
.word vector_swi
.globl __stubs_end
__stubs_end:
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
.globl __vectors_start
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
.data
.globl cr_alignment
.globl cr_no_alignment
cr_alignment:
.space 4
cr_no_alignment:
.space 4
當有異常發(fā)生時,處理器會跳轉(zhuǎn)到對應(yīng)的0xffff0000起始的向量處取指令,然后,通過b指令散轉(zhuǎn)到異常處理代碼.因為ARM中b指令是相對跳轉(zhuǎn),而且只有+/-32MB的尋址范圍,所以把__stubs_start~__stubs_end之間的異常處理代碼復(fù)制到了0xffff0200起始處.這里可直接用b指令跳轉(zhuǎn)過去,這樣比使用絕對跳轉(zhuǎn)(ldr)效率高。
(2) init_IRQ()
v linux/arch/arm/kernel/irq.c
void __init init_IRQ(void)
{
int irq;
for (irq = 0; irq < NR_IRQS; irq++) // NR_IRQS代表中斷數(shù)目
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
// irq_desc數(shù)組是用來描述IRQ的請求隊列,每一個中斷號分配一個
//irq_desc結(jié)構(gòu),組成了一個數(shù)組。
#ifdef CONFIG_SMP
bad_irq_desc.affinity = CPU_MASK_ALL;
bad_irq_desc.cpu = smp_processor_id();
#endif
init_arch_irq();
}
v include/linux/irq.h
/**
* struct irq_desc - interrupt descriptor
*
* @handle_irq: highlevel irq-events handler [if NULL, __do_IRQ()]
* @chip: low level interrupt hardware access
* @msi_desc: MSI descriptor
* @handler_data: per-IRQ data for the irq_chip methods
* @chip_data: platform-specific per-chip private data for the chip
* methods, to allow shared chip implementations
* @action: the irq action chain
* @status: status information
* @depth: disable-depth, for nested irq_disable() calls
* @wake_depth: enable depth, for multiple set_irq_wake() callers
* @irq_count: stats field to detect stalled irqs
* @irqs_unhandled: stats field for spurious unhandled interrupts
* @last_unhandled: aging timer for unhandled count
* @lock: locking for SMP
* @affinity: IRQ affinity on SMP
* @cpu: cpu index useful for balancing
* @pending_mask: pending rebalanced interrupts
* @dir: /proc/irq/ procfs entry
* @affinity_entry: /proc/irq/smp_affinity procfs entry on SMP
* @name: flow handler name for /proc/interrupts output
*/
struct irq_desc {
irq_flow_handler_t handle_irq;
struct irq_chip *chip;
struct msi_desc *msi_desc;
void *handler_data;
void *chip_data;
struct irqaction *action; /* IRQ action list */
unsigned int status; /* IRQ status */
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned int irqs_unhandled;
unsigned long last_unhandled; /* Aging timer for unhandled count */
spinlock_t lock;
#ifdef CONFIG_SMP
cpumask_t affinity;
unsigned int cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
cpumask_t pending_mask;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name;
} ____cacheline_internodealigned_in_smp;
三、Linux對ARM中斷、異常的處理 1、硬件發(fā)生中斷后,找到中斷入口函數(shù) 從entry-armv.S中的中斷向量表定義可以看到,發(fā)生中斷時系統(tǒng)會跳到 vector_irq + stubs_offset處運行,這個位置實際上就是中斷入口函數(shù)。
vector_irq已經(jīng)是中斷的入口函數(shù)了,為什么又要加上 stubs_offset?是因為b指令實際上是相對當前PC的跳轉(zhuǎn),也就是說當匯編器看到B指令后會把要跳轉(zhuǎn)的標簽轉(zhuǎn)化為相對于當前PC的偏移量寫入指令碼。從上面的代碼可以看到中斷向量表和stubs都發(fā)生了代碼搬移,所以如果中斷向量表中仍然寫成b vector_irq,那么實際執(zhí)行的時候就無法跳轉(zhuǎn)到搬移后的vector_irq處,因為指令碼里寫的是原來的偏移量,所以需要把指令碼中的偏移量寫成搬移后的。
我們把搬移前的中斷向量表中的irq入口地址記irq_PC,它在中斷向量表的偏移量就是irq_PC-vectors_start, vector_irq在stubs中的偏移量是vector_irq-stubs_start,這兩個偏移量在搬移前后是不變的。搬移后 vectors_start在0xffff0000處,而stubs_start在0xffff0200處,所以搬移后的vector_irq相對于中斷向量中的中斷入口地址的偏移量就是:200+vector_irq在stubs中的偏移量再減去中斷入口在向量表中的偏移量,即200+ vector_irq-stubs_start-irq_PC +vectors_start = (vector_irq-irq_PC) + vectors_start+200-stubs_start,對于括號內(nèi)的值實際上就是中斷向量表中寫的vector_irq,減去irq_PC是由匯編器完成的,而后面的 vectors_start+200-stubs_start就應(yīng)該是stubs_offset,實際上在entry-armv.S中也是這樣定義的
2、根據(jù)原來發(fā)生中斷時CPU所處的工作模式找到相應(yīng)的入口函數(shù) v arch/arm/kernel/entry-armv.S—vector_irq是通過宏來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
.align 5
vector_/name:
.if /correction
sub lr, lr, #/correction
.endif
@
@ Save r0, lr_ (parent PC) and spsr_
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(/mode ^ SVC_MODE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
.endm
.globl __stubs_start
__stubs_start:
/*
* 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
......
從上面這段代碼可以看出,vector_irq把發(fā)生中斷時的r0,PC-4以及CPSR壓棧(注意,壓入的的irq模式的堆棧),把中斷棧的棧指針賦給 r0,最后根據(jù)原來發(fā)生中斷時CPU所處的工作模式(CPSR的低4位)找到相應(yīng)的入口函數(shù),在進入svc模式后進一步處理中斷。
3、__irq_usr完成的工作 它主要通過調(diào)用宏usr_entry進一步保存現(xiàn)場,然后調(diào)用irq_handler進行中斷處理,保存的棧結(jié)構(gòu)如下:
-1
CPSR
PC-4
LR
SP
R12
...
R2
R1
R0
其中的LR,SP是USR模式下的,R0,CPSR,PC-4是從中斷棧中拷貝的
4、irq_handler的作用 它首先通過宏 get_irqnr_and_base獲得中斷號,存入r0,然后把上面建立的pt_regs結(jié)構(gòu)的指針,也就是sp值賦給r1,把調(diào)用宏 get_irqnr_and_base的位置作為返回地址(為了處理下一個中斷),然后調(diào)用 asm_do_IRQ進一步處理中斷,以上這些操作都在建立在獲得中斷號的前提下,也就是有中斷發(fā)生,
即當某個外部硬件觸發(fā)中斷的時候,kernel最終會調(diào)用到:asm_do_IRQ
v linux/arch/arm/kernel/entry-armv.S
/*
* Interrupt handling. Preserves r7, r8, r9
*/
.macro irq_handler
get_irqnr_preamble r5, lr
1: get_irqnr_and_base r0, r6, r5, lr //獲得中斷號并存入r0
movne r1, sp //將sp的值賦給r1,即pt_regs結(jié)構(gòu)的指針
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, 1b //把調(diào)用宏 get_irqnr_and_base的位置作為返回地址
bne asm_do_IRQ
v linux/arch/arm/kernel/irq.c
/*
* do_IRQ handles all hardware IRQ's. Decoded IRQs should not
* come via this function. Instead, they should provide their
* own 'handler'
*/
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc *desc = irq_desc + irq;
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS)
desc = &bad_irq_desc;
irq_enter();
desc_handle_irq(irq, desc);
/* AT91 specific workaround */
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
v linux/include/asm-generic/irq_regs.h
static inline struct pt_regs *set_irq_regs(struct pt_regs *new_regs)
{
struct pt_regs *old_regs, **pp_regs = &__get_cpu_var(__irq_regs);
old_regs = *pp_regs;
*pp_regs = new_regs;
return old_regs;
}
v linux/kernel/softirq.c
/*
* Enter an interrupt context.
*/
void irq_enter(void)
{
#ifdef CONFIG_NO_HZ
int cpu = smp_processor_id();
if (idle_cpu(cpu) && !in_interrupt())
tick_nohz_stop_idle(cpu);
#endif
__irq_enter();
#ifdef CONFIG_NO_HZ
if (idle_cpu(cpu))
tick_nohz_update_jiffies();
#endif
}
v linux/include/asm-arm/mach/irq.h
/*
* Obsolete inline function for calling irq descriptor handlers.
*/
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}
v include/asm-arm/arch-at91/irqs.h
/*
* Acknowledge interrupt with AIC after interrupt has been handled.
* (by kernel/irq.c)
*/
#define irq_finish(irq) do { at91_sys_write(AT91_AIC_EOICR, 0); } while (0)
v linux/kernel/softirq.c
/*
* Exit an interrupt context. Process softirqs if needed and possible:
*/
void irq_exit(void)
{
account_system_vtime(current);
trace_hardirq_exit();
sub_preempt_count(IRQ_EXIT_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
#ifdef CONFIG_NO_HZ
/* Make sure that timer wheel updates are propagated */
if (!in_interrupt() && idle_cpu(smp_processor_id()) && !need_resched())
tick_nohz_stop_sched_tick();
rcu_irq_exit();
#endif
preempt_enable_no_resched();
}