Linux混入了mmu內存管理之後,ARM的中斷是怎麼樣的呢?和我們在裸板上的中斷有沒有區別?讓我們從源代碼入手,做一個粗略的分析:
init/main.c->start_kernel()->trap_init()
//-----------------------------------------------
1.trap_init()
//gliethttp函數位於arch/arm/kernel/traps.c
void __init trap_init(void)
{
extern void __trap_init(unsigned long);
unsigned long base = vectors_base(); //返回中斷base基址0xffff0000
__trap_init(base); //以base為vector基址,初始化中斷向量表
if (base != 0)
printk(KERN_DEBUG "Relocating machine vectors to 0x%08lx\n",
base);
#ifdef CONFIG_CPU_32
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
#endif
}
//--------------------------------------
2.vectors_base()
//gliethttp include/arch/asm-arm/proc-armv/system.h
extern unsigned long cr_alignment;
#if __LINUX_ARM_ARCH__ >= 4 //at91rm9200是ARMV4結構
#define vectors_base() ((cr_alignment & CR_V) ? 0xffff0000 : 0)
#else
#define vectors_base() (0)
#endif
可以看到ARMv4以下的版本,該地址固定為0;ARMv4及以上版本,ARM中斷向量表的地址由CP15協處理器c1寄存器中V位(bit[13])控制,V和中斷向量表的對應關系如下:
V=0 ~ 0x00000000~0x0000001C
V=1 ~ 0xffff0000~0xffff001C
//------------------------------------------
2.1 cr_alignment
//gliethttp arch/arm/kernel/entry-armv.S
ENTRY(stext)
mov r12, r0
mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode
msr cpsr_c, r0 @ and all irqs disabled
//__lookup_processor_type 查詢處理器類型,[gliethttp 以後補上<淺析head-armv.S>]返回值
//2007-07-04
//r9 = processor ID //讀取cp15的c0寄存器
//r10 = pointer to processor structure //下面會add pc, r10, #12,跳轉到__arm920_setup
//gliethttp 在vmlinux-armv.lds.in中
//__proc_info_begin = .;
// *(.proc.info)
// __proc_info_end = .;
//見2.2
bl __lookup_processor_type
teq r10, #0 @ invalid processor?
moveq r0, #'p' @ yes, error 'p'
beq __error
bl __lookup_architecture_type
teq r7, #0 @ invalid architecture?
moveq r0, #'a' @ yes, error 'a'
beq __error
//__create_page_tables 創建arm啟動臨時使用的前4M頁表
bl __create_page_tables
adr lr, __ret @ return address
add pc, r10, #12 @ initialise processor
.type __switch_data, %object
__switch_data: .long __mmap_switched
.long SYMBOL_NAME(__bss_start)
.long SYMBOL_NAME(_end)
.long SYMBOL_NAME(processor_id)
.long SYMBOL_NAME(__machine_arch_type)
.long SYMBOL_NAME(cr_alignment)
.long SYMBOL_NAME(init_task_union)+8192
/*
* Enable the MMU. This completely changes the structure of the visible
* memory space. You will not be able to trace execution through this.
* If you have an enquiry about this, *please* check the linux-arm-kernel
* mailing list archives BEFORE sending another post to the list.
*/
.type __ret, %function
__ret: ldr lr, __switch_data
mcr p15, 0, r0, c1, c0
//將__arm920_setup中設置的r0值,置入cp15協處理器c1寄存器中
mrc p15, 0, r0, c1, c0, 0 @ read it back.
mov r0, r0
//填充armv4中的三級流水線:mov r0,
r0 對應一個nop,所以對應2個nop和一個mov pc,lr剛好三個"無用"操作
mov r0, r0
mov pc, lr
//跳轉到__mmap_switched函數 gliethtttp
/*
* The following fragment of code is executed with the MMU on, and uses
* absolute addresses; this is not position independent.
*
* r0 = processor control register
* r1 = machine ID
* r9 = processor ID
*/
.align 5
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3, {r4, r5, r6, r7, r8, sp}@ r2 = compat
//2007-07-04 gliethttp
//r4 ~ __bss_start
//r5 ~ _end
//r6 ~ processor_id
//r7 ~ __machine_arch_type
//r8 ~ cr_alignment
//sp ~ (init_task_union)+8192
//以下幾步操作對processor_id,__machine_arch_type,cr_alignment賦值gliethttp
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r4, r5 //bss區清0
strcc fp, [r4],#4
bcc 1b
str r9, [r6] @ Save processor ID
str r1, [r7] @ Save machine type
#ifdef CONFIG_ALIGNMENT_TRAP
orr r0, r0, #2 @ ...........A.
#endif
bic r2, r0, #2 @ Clear 'A' bit
//r2存放 禁用TRAP隊列故障 後的r0值
//r8->cr_alignment,cr_no_alignment
//所以stmia r8, {r0, r2}後,cr_alignment = r0,cr_no_alignment = r2
stmia r8, {r0, r2} @ Save control register values
b SYMBOL_NAME(start_kernel) //進入內核C程序
//--------------------------------------
2.2 __arm920_proc_info
//gliethttp arch/arm/mm/proc-arm920.S
.section ".proc.info", #alloc, #execinstr
.type __arm920_proc_info,#object
__arm920_proc_info:
//該地址存儲到r10中
.long 0x41009200
.long 0xff00fff0
.long 0x00000c1e
@ mmuflags
小結:通過以上的源碼分析,我們可以清楚的看到vectors_base()返回的中斷向量基址值為0xFFFF0000,接下來我們繼續分析下面的源碼:
3.__trap_ini()
//gliethttp函數位於arch/arm/kernel/entry-armv.S
.align 5
__stubs_start:
/*
* Interrupt dispatcher
* Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*/
vector_IRQ: @
@ save mode specific registers
@
ldr r13, .LCsirq
sub lr, lr, #4
str lr, [r13] @ save lr_IRQ
mrs lr, spsr
str lr, [r13, #4] @ save spsr_IRQ
...略...
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
.LCsirq: .word __temp_irq
.LCsund: .word __temp_und
.LCsabt: .word __temp_abt
__stubs_end:
.equ __real_stubs_start, .LCvectors + 0x200
.LCvectors: swi SYS_ERROR0
b __real_stubs_start + (vector_undefinstr - __stubs_start)
ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
b __real_stubs_start + (vector_prefetch - __stubs_start)
b __real_stubs_start + (vector_data - __stubs_start)
b __real_stubs_start + (vector_addrexcptn - __stubs_start)
b __real_stubs_start + (vector_IRQ - __stubs_start)
b __real_stubs_start + (vector_FIQ - __stubs_start)
ENTRY(__trap_init)
stmfd {r4 - r6, lr}
adr r1, .LCvectors @ set up the vectors
//通過adr指令獲得與pc地址為偏移地址數據,最後r1=pc-0x2c
ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}
//將中斷向量表跳轉數據分別轉存到r1,r2,r3,r4,r5,r6,ip,lr寄存器
stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}
//根據編譯器規則r0存放了函數__trap_init(base)傳入的參數值base,其值為
//0xFFFF0000
//將r1,r2,r3,r4,r5,r6,ip,lr數據順序轉儲到以虛擬地址0xFFFF0000為起始地址的空間
add r2, r0, #0x200
//r2=0xFFFF0000+0x200=0xFFFF2000
adr r0, __stubs_start@ copy stubs to 0x200
//r0=pc相對地址=pc-0x26c
adr r1, __stubs_end
//r1=pc相對地址=pc-0x40
1: ldr r3, [r0], #4
str r3, [r2], #4
//將__stubs_start
和__stubs_end之間的中斷處理代碼拷貝到以虛擬地址0xFFFF2000為起始地址的順序空間
cmp r0, r1
blt 1b
LOADREGS(fd, {r4 - r6, pc})
這樣我們來看看空間分布圖:
虛擬地址 異常 處理代碼
0xffff0000 reset swi SYS_ERROR0
0xffff0004 undefined b __real_stubs_start + (vector_undefinstr - __stubs_start)
0xffff0008 軟件中斷 ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
0xffff000c 取指令異常 b __real_stubs_start + (vector_prefetch - __stubs_start)
0xffff0010 數據異常 b __real_stubs_start + (vector_data - __stubs_start)
0xffff0014 reserved b __real_stubs_start + (vector_addrexcptn - __stubs_start)
0xffff0018 irq b __real_stubs_start + (vector_IRQ - __stubs_start)
0xffff001c fiq b __real_stubs_start + (vector_FIQ - __stubs_start)
...
0xffff2000 __stubs_start: ldr r13, .LCsirq
0xffff2004 sub lr, lr, #4
0xffff2008 str lr, [r13] @ save lr_IRQ
...
0xffff21a4 &nb
sp; .LCsirq: .word __temp_irq
如果你現在有這樣一種疑惑?程序為什麼編譯地址是0xc0008000,將其直接拷貝到0xffff0000和0xffff2000為什麼還能順利執行,請參考我的另一篇blog《arm相對跳轉到底是怎麼回事》,主要原因是b指令是相對跳轉指令,adr也是基於pc的前後偏移指令,當然對於ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)是絕對地址賦值,所以最後pc會跳轉到0xc000xxxx空間執行代碼,其他的跳轉如:b __real_stubs_start + (vector_IRQ - __stubs_start)都會到0xffff2xxxx相應的vector_IRQ處執行向量中斷處理函數,還有一個要分析的問題是:.equ __real_stubs_start,
.LCvectors + 0x200,語句b __real_stubs_start
+ (vector_undefinstr - __stubs_start)就是跳轉到LCvectors+0x200空間執行。舉一個例子:
org 0x8000
reset b InitRest
...
InitRest:
...
那麼reset標號的地址為0x8000,他的意思是在0x8000處向前跳轉到InitRest, 我們也可以這樣來構造跳轉:
org 0x8000
reset b (0x8000+(.InitRest - .reset))
...
InitRest:
...
以上的構造語句同樣實現了相對0x8000地址的跳轉