您的位置 首页 IC

ARM Linux 中断向量表树立流程

一般编写arm的裸机程序的时候,创建中断向量表就把它放在0x00000000~0x0000001c中,一般都放在这个位置上。但是中断向量表也可以放在0xff…

一般编写arm的裸机程序的时分,创立中止向量表就把它放在0x00000000~0x0000001c中,一般都放在这个方位上。可是中止向量表也能够放在0xffff0000~0xffff001c中,知道这是怎样设置的么?开端看到的时分真的有点古怪,由于在学习arm的时分,底子没去看arm中的协处理器CP15中的c1操控寄存器中的v位来操控,咱们一般都运用默许的值0,则必须将中止向量表放在0x00000000~0x0000001c中。

在看Linux内核对arm中的中止的初始化的时分,就一向对0xffff0000的地址有点置疑,果然在网上发现这个地址不是随意写的,当我看到arm的协处理器进行操控,中止向量表的地址的时分,真的是哭笑不得啊!!
有人肯定会问?v位是什么时分设置的呢?其实仔细的朋友就知道在head.S中,在创立完页表的时分,如add pc,r10,#PROCINFO_INITFUNC
别急,r10保存在前面设置的procinfo的地址,可是很多人就觉得PROCINFO_INITFUNC的宏界说就不知道在哪找了,在include/asm/asm-offset.h中有界说。
这些搞懂了,首要必须将中止向量表仿制到0xffff0000的地址上去,把中止处理函数也仿制到0xffff0200的地址上去,那么在中止向量表进行跳转的时分,如b vector_irq+stubs_offset,可是stubs_offset的偏移怎样设置呢?假如用b vector_irq的话,它就会跳转到原先的中止处理函数中去,由于它也仿制到了0xffff0200的地址上去,所以将__vector_start-_stubs_start+0x200的话就转移到仿制后的地址上去履行了。
很多人应该会有点疑问吧,vector_irq如同找不到,别急,仔细点,就在宏界说.macro vector_stubs,name,mode,correction中对各种处理函数有界说,所以很快就将中止向量表创立好了。

Linux Version : 2.6.29

1. start_kernel–>setup_arch–>early_trap_init

1:        memcpy((void   *)vectors, __vectors_start, __vectors_end - __vectors_start);
2:        memcpy((void   *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
3:        memcpy((void   *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

关于榜首行:

__vectors_start 和 __vectors_end 界说在 arch/arm/kernel/entry-armv.S , 它们之间保存了中止向量表。

1:        .globl    __vectors_start
2:    __vectors_start:
3:        swi    SYS_ERROR0 
4:        b    vector_und + stubs_offset
5:        ldr    pc, .LCvswi + stubs_offset
6:        b    vector_pabt + stubs_offset
7:        b    vector_dabt + stubs_offset
8:        b    vector_addrexcptn + stubs_offset
9:        b    vector_irq + stubs_offset
10:        b    vector_fiq + stubs_offset
11:    
12:        .globl    __vectors_end
13:    __vectors_end:

vectors 的地址为CONFIG_VECTORS_BASE , 在.config中界说为0xffff0000

所以 第1行便是把中止向量表仿制到0xffff0000

关于第二行:

vector_stub是一个带参数的宏,榜首个是name,第二个是arm excepiton mode,第三个是为了得到回来地址,lr需求减去的偏移

1:        .macro    vector_stub, name, mode, correction=0
2:        .align    5
3:    
4:    vector_/name:
5:        .if   /correction
6:        sub    lr, lr, #/correction          @得到正确的回来地址
7:        .endif
8:    
9:        @
10:        @ Save r0, lr_ (parent PC) and spsr_
11:        @ (parent CPSR)
12:        @
13:        stmia    sp, {r0, lr}        @ save r0, lr
14:        mrs    lr, spsr
15:        str    lr, [sp, #8]        @ save spsr
16:    
17:        @
18:        @ Prepare for   SVC32 mode.  IRQs remain disabled.
19:        @ 
20:        mrs    r0, cpsr
21:        eor    r0, r0, #(/mode ^ SVC_MODE) @把cpsr内容与(mode^SVC_mode)异或,即r0里为SVC_MODE 
22:        msr    spsr_cxsf, r0  @把r0的值写入整个spsr寄存器(cxsf表明要往哪个字节写入)
23:    
24:        @
25:        @ the branch table must immediately follow this   code
26:        @
27:        and    lr, lr, #0x0f @lr为spsr_的值,此句子取到进入反常前的mode
28:        mov    r0, sp         @ 
29:        ldr    lr, [pc, lr, lsl #2] @lr=pc+mode*4,其间pc为紧接着30的指令,即vector_stub后的榜首条指令
30:        movs    pc, lr            @ movs会把spsr的值赋给cpsr,所以branch to handler in   SVC mode
31:    ENDPROC(vector_/name)
32:        .endm

再来看下vector 跳转表

1:        .long      __irq_usr            @  0  (USR_26 / USR_32)
2:        .long      __irq_invalid            @  1  (FIQ_26 / FIQ_32)
3:        .long      __irq_invalid            @  2  (IRQ_26 / IRQ_32)
4:        .long      __irq_svc            @  3  (SVC_26 / SVC_32)
5:        .long      __irq_invalid            @  4
6:        .long      __irq_invalid            @  5
7:        .long      __irq_invalid            @  6
8:        .long      __irq_invalid            @  7
9:        .long      __irq_invalid            @  8
10:        .long      __irq_invalid            @  9
11:        .long      __irq_invalid            @  a
12:        .long      __irq_invalid            @  b
13:        .long      __irq_invalid            @  c
14:        .long      __irq_invalid            @  d
15:        .long      __irq_invalid            @  e
16:        .long      __irq_invalid            @  f

这儿只要usr 和svc 有进口,而其他都是invalid ,是由于linux只会从usr(application) 和svc(kernel)两种mode跳转到exception来

__stubs_start 和 __stubs_end 之间的代码简化后为:

1:    __stubs_start:
2:       vector_irq:    @vector_stub    irq, IRQ_MODE, 4
3:       vector_dabt: @vector_stub    dabt, ABT_MODE, 8
4:       vector_pabt:   @vector_stub    pabt, ABT_MODE, 4
5:       vector_und: @vector_stub    und, UND_MODE
6:       vector_fiq:
7:       vector_addrexcptn:
8:       .LCvswi:
9:    __stubs_end:

由此能够知道 __stubs_start 和 __stubs_end 之间界说了各种反常的进口

咱们再来看为什么反常进口是“b vector_und + stubs_offset”, 一同为什么stubs_offset 的界说如下

.equ    stubs_offset, __vectors_start + 0x200 - __stubs_start

arm 的跳转指令b 是跳转到相关于PC的一个偏移地址( offset ),汇编器在编译时会对label 减去PC 得到offset,一同vector 仿制后是如下摆放的

__vectors_start

B vector_

__vectors_end

+0x200

__stubs_start

vector_

__stubs_end

因而,”b vector_” 的label –PC = offset, 而offset 为 b 指令与vector的offset,即

vector_-__stubs_start + ( 0x200 – ( PC_old – __vectors_start ) )

= vector_ + __vectors_start + 0x200 – __stubs_start – PC_old

所以反常进口为“b vector_und + stubs_offset”, 一同stubs_offset= __vectors_start + 0x200 – __stubs_start

咱们能够经过objdump反汇编来验证:

00000060 :
.globl __stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
60: e24ee004 sub lr, lr, #4 ; 0x4
64: e88d4001 stm sp, {r0, lr}

1d4: e1a00000 .word 0xe1a00000
1d8: e1a00000 .word 0xe1a00000
1dc: e1a00000 .word 0xe1a00000

000001e0 :

/*
* Undef instr entry dispatcher
* Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*/

__vectors_start:
swi SYS_ERROR0
284: ef9f0000 svc 0x009f0000
b vector_und + stubs_offset
288: ea0000dd b 604
ldr pc, .LCvswi + stubs_offset
28c: e59ff410 ldr pc, [pc, #1040] ; 6a4
b vector_pabt + stubs_offset
290: ea0000bb b 584
b vector_dabt + stubs_offset
294: ea00009a b 504
b vector_addrexcptn + stubs_offset
298: ea0000fa b 688
b vector_irq + stubs_offset
29c: ea000078 b 484
b vector_fiq + stubs_offset
2a0: ea0000f7 b 684

0x1e0 – 0x60 + 0x200 – ( 0x288 + 8 ) – 0x284 = 0xdd*4

ARM Linux外部中止处理进程

最近在学习arm linux的整套外部中止的处理进程,在网上汇总了一些材料,整个进程差不多都了解到了。假如没有这些材料我真是没决心从汇编开端读代码,感谢 飞跃时代的jimmy.lee和 linux论坛的bx_bird。
鄙人面的的注释中有一些我读代码时遇到的问题,要是咱们知道是怎样回事,期望多多回复。
=============================================
一.ARM linux的中止向量表初始化剖析
ARM linux内核启动时,经过start_kernel()->trap_init()的调用联系,初始化内核的中止反常向量表.
/* arch/arm/kernel/traps.c */
void __init trap_init(void)
{
extern void __trap_init(unsigned long);
unsigned long base = vectors_base();
__trap_init(base);
if (base != 0)
oopsprintk(KERN_DEBUG “Relocating machine vectors to 0x%08lxn”, base);
#ifdef CONFIG_CPU_32
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
#endif
}
vectors_base是一个宏,它的作用是获取ARM反常向量的地址,该宏在include/arch/asm-arm/proc-armv/system.h中界说:
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_base() ((cr_alignment & CR_V) ? 0xffff0000 : 0)
#else
#define vectors_base() (0)
#endif
  关于ARMv4以下的版别,这个地址固定为0;ARMv4及其以上的版别,ARM反常向量表的地址受协处理器CP15的c1寄存器(control register)中V位(bit[13])的操控,假如V=1,则反常向量表的地址为0x00000000~0x0000001C;假如V=0,则为:0xffff0000~0xffff001C。(详情请参看ARM Architecture Reference Manual)
  下面剖析一下cr_alginment的值是在哪确认的,咱们在arch/arm/kernel/entry-armv.S找到cr_alignment的界说:
.globl SYMBOL_NAME(cr_alignment)
.globl SYMBOL_NAME(cr_no_alignment)
SYMBOL_NAME(cr_alignment):
.space 4
SYMBOL_NAME(cr_no_alignment):
.space 4
  剖析过head-armv.S文件的朋友都会知道,head-armv.S对错紧缩内核的进口:
1 .section “.text.init”,#alloc,#execinstr
2 .type stext, #function
3ENTRY(stext)
4 mov r12, r0

6 mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode
7 msr cpsr_c, r0 @ and all irqs disabled
8 bl __lookup_processor_type
9 teq r10, #0 @ invalid processor?
10 moveq r0, #p @ yes, error p
11 beq __error
12 bl __lookup_architecture_type
13 teq r7, #0 @ invalid architecture?
14 moveq r0, #a @ yes, error a
15 beq __error
16 bl __create_page_tables
17 adr lr, __ret @ return address
18 add pc, r10, #12 @ initialise processor
19 @ (return control reg)
20
21 .type __switch_data, %object
22__switch_data: .long __mmap_switched
23 .long SYMBOL_NAME(__bss_start)
24 .long SYMBOL_NAME(_end)
25 .long SYMBOL_NAME(processor_id)
26 .long SYMBOL_NAME(__machine_arch_type)
27 .long SYMBOL_NAME(cr_alignment)
28 .long SYMBOL_NAME(init_task_union)+8192
29
30 .type __ret, %function
31__ret: ldr lr, __switch_data
32 mcr p15, 0, r0, c1, c0
33 mrc p15, 0, r0, c1, c0, 0 @ read it back.
34 mov r0, r0
35 mov r0, r0
36 mov pc, lr
这儿咱们关怀的是从17行开端,17行code处将lr放置为__ret标号处的相对地址,以便将来某处回来时跳转到31行持续运转18行,关于我所剖析的pxa270渠道,它将是跳转到arch/arm/mm/proc-xscale.S中履行__xscale_setup函数,(在s3c2410渠道中,它跳转到arch/arm/mm/proc-arm920.S,在
type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
.long 0x00000c1e @ mmuflags
b __arm920_setup
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
.long cpu_arm920_info
.long arm920_processor_functions
能够知道add pc, r10, #12 的#12意思是越过3个指令,履行b _arm920_setup
在arm920_setup设置完协处理器和回来寄存器r0之后,跳回到__ret:(31行)。
在__xscale_setup中会读取CP15的control register(c1)的值到r1寄存器,并在r1寄存器中设置相应的标志位(其间包含设置V位=1),但在__xscale_setup中,r1寄存器并不当即写回到Cp15的control register中,而是在回来后的某个当地,接下来会渐渐剖析到。__xscale_setup调用move pc, lr指令回来跳转到31行。
  31行,在lr寄存器中放置__switch_data中的数据__mmap_switched,在36行程序会跳转到__mmap_switched处。
  32,33行,把r0寄存器中的值写回到cp15的control register(c1)中,再读出来放在r0中。
  
  接下来再来看一下跳转到__mmap_switched处的代码:
40 _mmap_switched:
41 adr r3, __switch_data + 4
42 ldmia r3, {r4, r5, r6, r7, r8, sp}@ r2 = compat
43 @ sp = stack pointer
44
45 mov fp, #0 @ Clear BSS (and zero fp)
46 1: cmp r4, r5
47 strcc fp, [r4],#4
48 bcc 1b
49
50 str r9, [r6] @ Save processor ID
51 str r1, [r7] @ Save machine type
52 bic r2, r0, #2 @ Clear A bit
53 stmia r8, {r0, r2} @ Save control register values
54 b SYMBOL_NAME(start_kernel)

41~42行的成果是:r4=__bss_start,r5=__end,…,r8=cr_alignment,..,这儿r8保存的是cr_alignment变量的地址.
  到了53行,由于之前r0保存的是cp15的control register(c1)的值,这儿把r0的值写入r8指向的地址,即cr_alignment=r0.到此为止,咱们就看清楚了cr_alignment的赋值进程。
  
  让咱们回到trap_init()函数,经过上面的剖析,咱们知道vectors_base回来0xffff0000。函数__trap_init由汇编代码编写,在arch/arm/kernel/entry-arm.S:
    .align 5
__stubs_start:
vector_IRQ:
     …
vector_data:
    ….
vector_prefetch:
     …
vector_undefinstr:
     …
vector_FIQ: disable_fiq
     subs pc, lr, #4
vector_addrexcptn:
     b vector_addrexcptn
    …
__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 sp!, {r4 – r6, lr} /* 压栈,保存数据*/
    /* 仿制反常向量表(.LCvectors开端的8个地址)到r0指向的地址(反常向量地址),r0便是__trap_init(base)函数调用时传递的参数,不明白的请参看ATPCS*/(传递参数顺次运用r0,r1,r2,r3)
    adr r1, .LCvectors @ set up the vectors
    ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}
     stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}
/* 在反常向量地址后的0x200偏移处,放置散转代码,即__stubs_start~__stubs_end之间的各个反常处理代码*/
     add r2, r0, #0x200
     adr r0, __stubs_start @ copy stubs to 0x200
     adr r1, __stubs_end
1: ldr r3, [r0], #4
     str r3, [r2], #4
     cmp r0, r1
blt 1b
LOADREGS(fd, sp!, {r4 – r6, pc}) /*出栈,康复数据,函数__trap_init回来*/
__trap_init函数填充后的向量表如下:
虚拟地址 反常 处理代码
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)
   当有反常产生时,处理器会跳转到对应的0xffff0000开端的向量处取指令,然后,经过b指令散转到反常处理代码.由于ARM中b指令是相对跳转,并且只要+/-32MB的寻址规模,所以把__stubs_start~__stubs_end之间的反常处理代码仿制到了0xffff0200开端处.这儿可直接用b指令跳转曩昔,这样比运用肯定跳转(ldr)效率高。
二.ARM Linux中止处理进程剖析(1)
在我的上一篇文章(ARM linux的中止向量表初始化剖析)中现已剖析了ARM Linux中止向量表是怎样树立的,在这篇文章中,我将剖析一下Linux内核的ARM体系下,中止处理是怎样呼应的一个进程。
在ARM体系架构下,界说了7种反常,每一种反常都有自己的进口地址,即反常向量表,当反常产生时,处理器会主动跳转到相应的进口处履行。关于ARMv4及其以上的版别,反常向量表的开端方位由协处理器15(cp15)的操控寄存器(c1)里的V位(bit13)有关,当V=0时,反常向量表的开端方位在0x00000000,而当V=1时,反常向量表就开端于0xffff0000方位。在上一篇文章中,咱们现已剖析知道反常向量表放置于0xffff0000开端方位,而IRQ中止处理进口地址为:0xffff0018,所以当产生一IRQ中止反常时,处理器会主动跳转到0xffff0018这个虚拟地址上。
0xffff0018这个虚拟地址上是一条跳转指令:
b __real_stubs_start + (vector_IRQ – __stubs_start)
所以关于IRQ的处理便是从vector_IRQ标号处开端的。在linux2.4.19内核中相应代码如下:
__stubs_start:
/*
* Interrupt dispatcher
* Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*/阐明其实linux只用到了arm的svc和usr形式,其他的几个形式都没怎样用。
1 vector_IRQ: @
2 @ save mode specific registers
3 @
4 ldr r13, .LCsirq
5 sub lr, lr, #4
6 str lr, [r13] @ save lr_IRQ
7 mrs lr, spsr
8 str lr, [r13, #4] @ save spsr_IRQ
9 @
10 @ now branch to the relevent MODE handling routine
11 @
12 mrs r13, cpsr
13 bic r13, r13, #MODE_MASK
14 orr r13, r13, #I_BIT | MODE_SVC
15 msr spsr_c, r13 @ switch to SVC_32 mode
16
17 and lr, lr, #15
18 ldr lr, [pc, lr, lsl #2]
19 movs pc, lr @ Changes mode and branches
20
21.LCtab_irq: .word __irq_usr @ 0 (USR_26 / USR_32)
22 .word __irq_invalid @ 1 (FIQ_26 / FIQ_32)
23 .word __irq_invalid @ 2 (IRQ_26 / IRQ_32)
24 .word __irq_svc @ 3 (SVC_26 / SVC_32)
25 .word __irq_invalid @ 4
26 .word __irq_invalid @ 5
27 .word __irq_invalid @ 6
28 .word __irq_invalid @ 7
29 .word __irq_invalid @ 8
30 .word __irq_invalid @ 9
31 .word __irq_invalid @ a
32 .word __irq_invalid @ b
33 .word __irq_invalid @ c
34 .word __irq_invalid @ d
35 .word __irq_invalid @ e
36 .word __irq_invalid @ f
首要,行4~8是保存进入IRQ形式之前的pc指针(在lr_IRQ)和CPSR(在SPSR_IRQ)到.LCsirq所指向的地址中。.LCsirq相关代码也是坐落entry-armv.S中:
.LCsirq: .word __temp_irq

__temp_irq: .word 0 @ saved lr_irq
.word 0 @ saved spsr_irq
.word -1 @ old_r0
在这儿弥补一下ARM关于反常的处理进程,能够用下面的一段伪码来表明:
r14_<反常形式> = return link
SPSR_<反常形式> = CPSR
CPSR[4:0] = 反常形式编码
CPSR[5] = 0 ;运转于ARM状况
If<反常形式> == Reset or FIQ then{
;当复位或呼应FIQ反常时,制止新的fiq和irq反常
CPSR[6] = 1;
CPSR[7] = 1;
}else if<反常形式> == IRQ then{
;当呼应IRQ反常时,制止新的IRQ反常
CPSR[7] = 1;
}
PC = 反常向量地址
所以在运转到行4~8之前时,lr为进入IRQ之前的pc指针,spsr为进入IRQ之前的cpsr指针。
接着,行12~15更新spsr寄存器为SVR形式,并封闭IRQ,为从IRQ形式切换到SVR形式做预备。
行17,依据进入IRQ形式之前的psr(由于内行7,lr现已被置以spsr_irq),获取之前的处理器形式(psr &0b1111)。
行18,依据获取的进入IRQ之前的处理器形式,查找相应的跳转进口(__irq_usr 对应于之前是USR形式,__irq_svc关于之前是SVC形式,关于其它形式均跳转到__irq_invalid,在linux体系中处理器进入IRQ之前只要usr和svc两种形式,其它形式均不答应敞开IRQ)。此行实际上是:lr = pc+lr<<2,pc指向当时指令地址值加8个字节的地址,即pc指向当时指令的下两条指令的地址,所以pc在此刻指向的是.LCtab_irq地址。
(这儿有点疑问要进入__irq_usr,则18行lr应该为pc+4那么向回计算第7行的mrs lr, spsr中spsr[3:0]应该为0b0001;假如要进入__irq_svc,则18行lr应该为pc+16,那么spsr[3:0]应该为0b0100;
而cprs[4:0]=
10000 User 形式
10011 SVC 形式
请达人指点迷津。。。。)
行19,跳转到相应进口,并且ARM寄存器r13和r14则切换到了SVC形式下的寄存器

三.ARM Linux中止处理进程剖析(2)
续前文,让咱们先剖析进入IRQ之前的处理器形式为SVC时的状况,程序会跳转到__irq_svc持续运转,其相应代码如下:
20__irq_svc: sub sp, sp, #S_FRAME_SIZE
21 stmia sp, {r0 – r12} @ save r0 – r12
22 ldr r7, .LCirq
23 add r5, sp, #S_FRAME_SIZE
24 ldmia r7, {r7 – r9}
25 add r4, sp, #S_SP
26 mov r6, lr
27 stmia r4, {r5, r6, r7, r8, r9} @ save sp_SVC, lr_SVC, pc, cpsr, old_ro
28 1: get_irqnr_and_base r0, r6, r5, lr
29 movne r1, sp
30 @
31 @ routine called with r0 = irq number, r1 = struct pt_regs *
32 @
33 adrsvc ne, lr, 1b
34 bne asm_do_IRQ
35 ldr r0, [sp, #S_PSR] @ irqs are already disabled
36 msr spsr, r0
37 ldmia sp, {r0 – pc}^ @ load r0 – pc, cpsr
行20~27:保存进入中止之前的寄存器,把它们放在仓库中。其间#S_FRAME_SIZE和#S_SP的界说在arch/arm/kernel/entry-header.S中:
#ifdef CONFIG_CPU_32
#define S_FRAME_SIZE 72
#define S_OLD_R0 68
#define S_PSR 64
#else
#define S_FRAME_SIZE 68
#define S_OLD_R0 64
#define S_PSR 60
#endif
#define S_PC 60
#define S_LR 56
#define S_SP 52
#define S_IP 48
#define S_FP 44
#define S_R10 40
#define S_R9 36
#define S_R8 32
#define S_R7 28
#define S_R6 24
#define S_R5 20
#define S_R4 16
#define S_R3 12
#define S_R2 8
#define S_R1 4
#define S_R0 0
#define S_OFF 8
.LCirq在entry-armv.S中是这样界说的:
.LCirq: .word __temp_irq
这与行4处的.LCsirq界说是相同的,可见整个进程运用__temp_irq作为中转,把进入中止之前的CPSR和PC(中止处理完毕后要回来的地址)放入仓库,以便中止回来时直接康复。
行20~27履行的成果是:
r5-> old_r0
cpsr
pc
lr_svc
r4-> sp_svc
r12
r11

r1
sp-> r0
行28的get_irqnr_and_base,它是一个宏界说,作用是获取中止号(irq number),它将被保存在r0中。别的,get_irqnr_and_base还会改动cpsr寄存器中的Z位,假如的确找到了产生的中止号,则Z位被铲除,不然Z位被置位。get_irqnr_and_base这个宏界说的完结是依靠具体的硬件的,关于pxa270 cpu,其完结如下:
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
mov base, #io_p2v(0x40000000) @ IIR Ctl = 0x40d00000
add base, base, #0x00d00000
ldr irqstat, [base, #0] @ ICIP
ldr irqnr, [base, #4] @ ICMR
ands irqstat, irqstat, irqnr
beq 1001f /* 没找到中止,跳转*/
rsb irqnr, irqstat, #0
and irqstat, irqstat, irqnr
clz irqnr, irqstat
rsb irqnr, irqnr, #(31 – PXA_IRQ_SKIP)
#ifdef CONFIG_CPU_BULVERDE
b 1002f
#endif
1001:
1002:
.endm
.macro irq_prio_table
.endm
bics irqstat, irqstat, irqnr 对照intmsk将intpnd中制止的中止清0。由于intpnd在某一时间只能够有一位为1,所以有一位被bics清0了,就会影响标志位然后beq跳转,return r0=0;从1001:开端所作的工作是循环查intpnd哪一方位为了1。有点疑问的是tst 指令:
tst 类似于 CMP,不产生放置到意图寄存器中的成果。而是在给出的两个操作数上进行操作并把成果反映到状况标志上。运用 tst 来查看是否设置了特定的位。操作数 1 是要测验的数据字而操作数 2 是一个位掩码。经过测验后,假如匹配则设置 Zero 标志,不然铲除它。
那么这儿的tst irqstat, #1,当zero置1了表明有中止位,为什么下面是bne 1002f而不是beq?讨教讨教。。。。。。。)
asm_do_IRQ是用C言语编码的函数,它在arch/arm/kernel/irq.c中被界说,其原型为:
asmlinkage void asm_do_IRQ(int irq, struct pt_regs *regs);
这儿牵扯到一个问题便是,在汇编中怎样调用C言语的函数,参数是怎样传递的?为了让ARM的汇编代码可与C代码一同衔接,在编写ARM汇编时,应遵从一套规范,这便是ATPCS(The ARM-Thumb Procedure Call Standard)。ATPCS界说{r0~r3}为参数传递和成果回来寄存器;若参数超越4个字型(32bit),则运用仓库进行传递;头4个参数顺次存于r0…r3,大于4个的后续字型参数经过栈传送。关于栈的运用,是运用满递减的仓库规范,也便是栈是从高地址向低地址方向增加的(递减仓库),栈指针寄存器指向的数据是最终压入仓库内的有用数据(满仓库)。
所以在跳转到asm_do_IRQ函数之前,r0就必须设置为中止号(行28get_irqnr_and_base把中止号放置于r0),r1就必须是指向pt_regs这样结构(界说于include/asm-arm/proc-armv/ptrace.h)的指针,而行29把sp指针赋予r1,就完结了这样的一个调用预备。
行35~37:康复寄存器,回来到产生中止之前的代码中持续履行。
这便是整个ARM linux中止处理的进程。今后有时间,再持续打开asm_do_IRQ持续剖析。关于进入中止前处理器形式是USR的中止处理进程(__irq_usr),这儿就不再做剖析,这与__irq_svc根本相同
asmlinkage void do_IRQ(int irq, struct pt_regs * regs)
{
struct irqdesc * desc;
struct irqaction * action;
int cpu;
irq = fixup_irq(irq);// 查找子中止号,如无子中止return 原irq
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS)
goto bad_irq;
desc = irq_desc + irq;
spin_lock(&irq_controller_lock);
desc->mask_ack(irq);
/*———————————-
void __init init_IRQ(void)
{
extern void init_dma(void);
int irq;
for (irq = 0; irq < NR_IRQS; irq++) {
irq_desc[irq].probe_ok = 0;
irq_desc[irq].valid = 0;
irq_desc[irq].noautoenable = 0;
irq_desc[irq].mask_ack = dummy_mask_unmask_irq;
irq_desc[irq].mask = dummy_mask_unmask_irq;
irq_desc[irq].unmask = dummy_mask_unmask_irq;
}
init_arch_irq();
init_dma();
}
init_arch_irq(); init_dma();最终被指向/mach-s3c2410中的s3c2410_init_irq(void)和s3c2410_init_dma(void), desc->mask_ack(irq);将在那里被填充。
——————————–*/
spin_unlock(&irq_controller_lock);
cpu = smp_processor_id(); //#define smp_processor_id() 0
irq_enter(cpu, irq);
kstat.irqs[cpu][irq]++;
desc->triggered = 1;
/* Return with this interrupt masked if no action */
action = desc->action;
/* 这个结构由driver经过request_irq()挂入,包含了具体的中止处理程序进口和flags.一个中止的irq_desc下面可能会挂几个action(一个action行列)来完结中止的复用。也便是说几个driver能够共用一个中止号。*/
if (action) {
int status = 0;
if (desc->nomask) {
spin_lock(&irq_controller_lock);
desc->unmask(irq);
spin_unlock(&irq_controller_lock);
}
if (!(action->flags & SA_INTERRUPT))
/* SA_INTERRUPT Disable local interrupts while processing
SA_SHIRQ is shared
这个flag能够一向追到request irq的action->flags = irq_flags(传递参数);
*/
__sti();//铲除cpsr的I_bit,开中止。
/*假如在上面的nomask处判断后,没有履行unmask动作,那么这儿的__sti仅仅答应不同中止通道(即icip上不同的位)上的嵌套*/
do {
status |= action->flags;
action->handler(irq, action->dev_id, regs);
action = action->next;
} while (action);
/*值得留意的是:整个action行列都会被调用,所以在driver里要断定是否是归于自己的中止*/
if (status & SA_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
__cli();
if (!desc->nomask && desc->enabled) {
spin_lock(&irq_controller_lock);
desc->unmask(irq);
spin_unlock(&irq_controller_lock);
}
}
unsigned int fixup_irq(int irq) {
unsigned int ret;
unsigned long sub_mask, ext_mask;
if (irq == OS_TIMER)
return irq;
switch (irq) {
case IRQ_UART0:
sub_mask = SUBSRCPND & ~INTSUBMSK;
ret = get_subIRQ(sub_mask, 0, 2, irq);
break;
case IRQ_UART1:
sub_mask = SUBSRCPND & ~INTSUBMSK;
ret = get_subIRQ(sub_mask, 3, 5, irq);
break;
case IRQ_UART2:
sub_mask = SUBSRCPND & ~INTSUBMSK;
ret = get_subIRQ(sub_mask, 6, 8, irq);
break;
case IRQ_ADCTC:
sub_mask = SUBSRCPND & ~INTSUBMSK;
ret = get_subIRQ(sub_mask, 9, 10, irq);
break;
case IRQ_EINT4_7:
ext_mask = EINTPEND & ~EINTMASK;
ret = get_extIRQ(ext_mask, 4, 7, irq);
break;
case IRQ_EINT8_23:
ext_mask = EINTPEND & ~EINTMASK;
ret = get_extIRQ(ext_mask, 8, 23, irq);
break;
default:
ret = irq;
}
这个函数一看就知道是找子中止号的,
inline unsigned int get_subIRQ(int irq, int begin, int end, int fail_irq) {
int i;
for(i=begin; i <= end; i++) {
if (irq & (1 << i))
return (EXT_IRQ_OFFSET + i);
}
return fail_irq;
}
inline unsigned int get_extIRQ(int irq, int begin, int end, int fail_irq) {
int i;
for(i=begin; i <= end; i++) {
if (irq & (1 << i))
return (NORMAL_IRQ_OFFSET – 4 + i);
}
return fail_irq;
}
#define NORMAL_IRQ_OFFSET 32
#define EXT_IRQ_OFFSET (20 +NORMAL_IRQ_OFFSET)
=========================================
请求中止:
int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *),
unsigned long irq_flags, const char * devname, void *dev_id)
{
unsigned long retval;
struct irqaction *action;
if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler ||
(irq_flags & SA_SHIRQ && !dev_id))
return -EINVAL;
action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler;
action->flags = irq_flags;
action->mask = 0;
action->name = devname;
action->next = NULL;
action->dev_id = dev_id;
retval = setup_arm_irq(irq, action); /* 把这个action挂到对应irq的action链表中*/
if (retval)
kfree(action);
return retval;
}
int setup_arm_irq(int irq, struct irqaction * new)
{
int shared = 0;
struct irqaction *old, **p; /*这儿的**p 用的太妙了*/
unsigned long flags;
struct irqdesc *desc;
/*
* Some drivers like serial.c use request_irq() heavily,
* so we have to be careful not to interfere with a
* running system.
*/
if (new->flags & SA_SAMPLE_RANDOM) {
/*
* This function might sleep, we want to call it first,
* outside of the atomic block.
* Yes, this might clear the entropy pool if the wrong
* driver is attempted to be loaded, without actually
* installing a new handler, but is this really a problem,
* only the sysadmin is able to do this.
*/
rand_initialize_irq(irq); /*这个函数的作用是运用中止的随机性来产生随机数列*/
}
/*
* The following block of code has to be executed atomically
*/
desc = irq_desc + irq;
spin_lock_irqsave(&irq_controller_lock, flags);
p = &desc->action;
if ((old = *p) != NULL) {
留意/* Cant share interrupts unless both agree to */
if (!(old->flags & new->flags & SA_SHIRQ)) {
spin_unlock_irqrestore(&irq_controller_lock, flags);
return -EBUSY;
}
/* add new interrupt at end of irq queue */
do {
p = &old->next;
old = *p;
} while (old);/*当没有下一个irqaction链表元素时,next就位null*/
shared = 1;
}
*p = new;
if (!shared) {
desc->nomask = (new->flags & SA_IRQNOMASK) ? 1 : 0;
desc->probing = 0;
if (!desc->noautoenable) {
desc->enabled = 1;
desc->unmask(irq);
}
}
spin_unlock_irqrestore(&irq_controller_lock, flags);
return 0;
}
四.ARM Linux中止处理进程剖析(3)
在之前的文章中,我剖析了进入IRQ之前处理器形式为SVC的状况,在本篇文章中,即将评论的是进入IRQ之前处理器形式为USR的景象。
843 __irq_usr: sub sp, sp, #S_FRAME_SIZE
844 stmia sp, {r0 – r12} @ save r0 – r12
845 ldr r4, .LCirq
846 add r8, sp, #S_PC
847 ldmia r4, {r5 – r7} @ get saved PC, SPSR
848 stmia r8, {r5 – r7} @ save pc, psr, old_r0
849 stmdb r8, {sp, lr}^
850 alignment_trap r4, r7, __temp_irq
851 zero_fp
852 1: get_irqnr_and_base r0, r6, r5, lr
853 movne r1, sp
854 adrsvc ne, lr, 1b
855 @
856 @ routine called with r0 = irq number, r1 = struct pt_regs *
857 @
858 bne asm_do_IRQ
859 mov why, #0
860 get_current_task tsk
861 b ret_to_user
__irq_usr关于中止处理的进程大体与__irq_svc是相同的,这儿咱们要点要剖析中止处理回来时的不同。
研读过linux内核进程调度的朋友都知道,进程的调度能够自愿的方法随时进行(内核里:schedule、schedule_timeout;用户空间:pause、nanosleep),还能够非自愿的产生,即强制地产生在每次体系调用回来的前夕,以及每次从中止或反常处理回来到用户空间的前夕(只要在用户空间产生的中止或反常才会引起调度)。可参看毛德操的《Linux内核源代码情形剖析》上册的第4章关于进程调度的相关当地。
那咱们就来看一下,__irq_usr在回来到usr形式(用户空间)前夕是怎样强制进行进程调度的。
Line860,这是中止处理回来后,获取当时进程的task_struct指针,get_current_task是一个宏,它界说于arch/arm/kernel/entry-header.S中:
.macro get_current_task, rd
mov rd, sp, lsr #13
mov rd, rd, lsl #13
.endm
该宏是先将sp的值右移13位,再左移13位,把成果回来给参数,其实也便是只保存sp值的高19位,这代表着把仓库指针的地址round到8K地址鸿沟上,这样它以为就得到了当时进程的task_struct数据结构了。它是由于内核在为每个进程分配一个task_struct结构时,实际上是分配两个接连的物理页面的(共8K),这两个页面的底部是用作进程的task_struct结构,而在结构的上面就用作进程的体系空间仓库;数据结构task_struct的巨细约为1K,进程体系空间仓库巨细就约为7K。当进程在体系空间运转时,常常需求拜访当时进程本身的task_struct数据结构,为此内核中界说了一个宏操作current,供给指向当时进程task_struct结构的指针,它的完结实际上也与这儿的get_current_task宏是差不多的。
/* include/asm-arm/current.h */
static inline struct task_struct *get_current(void)
{
register unsigned long sp asm (“sp”);
return (struct task_struct *)(sp & ~0x1fff);
}
#define current (get_current())
再回到lin860,get_current_task的参数是tsk,它实际上是r9寄存器,它也是界说于arch/arm/kernel/entry-header.S中的:
tsk .req r9 @ current task
这样r9寄存器就保存了当时进程的task_struct结构的指针了。
Line861,程序跳转到ret_to_user,以完结从中止处理到回来用户空间的进程,前面说到的进程从头调度将在那里得以表现。ret_to_user界说于arch/arm/entry-common.S中:
55 reschedule:
56 bl SYMBOL_NAME(schedule)
57 ret_disable_irq:
58 disable_irq r1 @ ensure IRQs are disabled
59 ENTRY(ret_to_user)
60 ret_slow_syscall:
61 ldr r1, [tsk, #TSK_NEED_RESCHED]
62 ldr r2, [tsk, #TSK_SIGPENDING]
63 teq r1, #0 @ need_resched => schedule()
64 bne reschedule
65 1: teq r2, #0 @ sigpending => do_signal()
66 bne __do_signal
67 restore:
68 restore_user_regs
69
70 __do_signal:
71 enable_irq r1
72 mov r0, #0 @ NULL oldset
73 mov r1, sp @ regs
74 mov r2, why @ syscall
75 bl SYMBOL_NAME(do_signal) @ note the bl above sets lr
76 disable_irq r1 @ ensure IRQs are disabled
77 b restore
Line61,TSK_NEED_RESCHED值为20,它是task_struct结构中其成员变量need_resched相关于结构首地址的偏移量,所以此刻r1的值便是当时进程task_struct结构里need_resched变量的值。同理在line62,r2存储便是task_struct->sigpenging的值。
从line63~64可见,只要在当时进程的task_struct结构中的need_resched字段为非0时才会转到reschedule处去调用schedule,那么,谁来设置这个字段呢?当然是内核,从用户空间是拜访不到进程的task_struct结构的,那么,内核又是在什么状况下设置这个字段的呢?除当时进程经过体系调用自愿让出运转以及在体系调用中因某种原因受阻以外,首要便是当因某种原因唤醒一个进程的时分,以及在时钟中止服务程序发现当时进程现已接连运转太久的时分。(此段摘录于Linux内核源代码情形剖析》)
Line65~66,假如当时进程的task_struct结构中的sigpedding字段为非0时才会转到__do_signal处去调用do_signal处理信号。
Line68, restore_user_regs,它是一个宏界说于arch/arm/kernel/head-header.S中:
102 /*
103 * Must be called with IRQs already disabled.
104 */
105 .macro restore_user_regs
106 ldr r1, [sp, #S_PSR] @ Get calling cpsr
107 ldr lr, [sp, #S_PC]! @ Get PC
108 msr spsr, r1 @ save in spsr_svc
109 ldmdb sp, {r0 – lr}^ @ Get calling r0 – lr
110 mov r0, r0
111 add sp, sp, #S_FRAME_SIZE – S_PC
112 movs pc, lr @ return & move spsr_svc into cpsr
113 .endm
17 and lr, lr, #15
18 ldr lr, [pc, lr, lsl #2]
19 movs pc, lr @ Changes mode and branches
20
21.LCtab_irq: .word __irq_usr @ 0 (USR_26 / USR_32)
22 .word __irq_invalid @ 1 (FIQ_26 / FIQ_32)
23 .word __irq_invalid @ 2 (IRQ_26 / IRQ_32)
24 .word __irq_svc @ 3 (SVC_26 / SVC_32)
这儿有点疑问要进入__irq_usr,则18行lr应该为pc+4那么向回计算第7行的mrs lr, spsr中spsr[3:0]应该为0b0001;假如要进入__irq_svc,则18行lr应该为pc+16,那么spsr[3:0]应该为0b0100;
而cprs[4:0]=
10000 User 形式
10011 SVC 形式
请达人指点迷津。。。。)
行19,跳转到相应进口,并且ARM寄存器r13和r14则切换到了SVC形式下的寄存器
这儿第18行中的pc值正好是21行的.LCtab_irq,假如是在用户空间,User形式10000,逻辑左移两位为0x0=0b0000,即pc+0x0,刚好到了.word __irq_usr ,假如是在内核空间,svc形式10011,移位后为0xc=0b1100,及pc+0xc,正好到了.word __irq_svc,一点都没错(当然不可能错,体系不是跑得好好的吗)
留意,pc值是当时指令地址+8
关于get_irqnr_and_base宏中:
bics irqstat, irqstat, irqnr 对照intmsk将intpnd中制止的中止清0。由于intpnd在某一时间只能够有一位为1,所以有一位被bics清0了,就会影响标志位然后beq跳转,return r0=0;从1001:开端所作的工作是循环查intpnd哪一方位为了1。有点疑问的是tst 指令:
tst 类似于 CMP,不产生放置到意图寄存器中的成果。而是在给出的两个操作数上进行操作并把成果反映到状况标志上。运用 tst 来查看是否设置了特定的位。操作数 1 是要测验的数据字而操作数 2 是一个位掩码。经过测验后,假如匹配则设置 Zero 标志,不然铲除它。
那么这儿的tst irqstat, #1,当zero置1了表明有中止位,为什么下面是bne 1002f而不是beq?讨教讨教。。。。。。。)
没找到你看的内核版别中该宏的具体界说,我在我的2.6.12中pxa体系中的此宏中没找到tst指令,但想你的问题估量仍是对tst的误解
pc值是当时指令地址+8
是由于armv5是三级流水线么?
pxa的宏里边如同是没用tst,这儿我引申到s3c2410的宏里边。
tst的界说我翻的是网上搜的arm指令集,里边是这么说的:
TST : 测验位
(Test bits)
TST{条件}{P} ,
Status = op_1 AND op_2
TST 类似于 CMP,不产生放置到意图寄存器中的成果。而是在给出的两个操作数上进行操作并把成果反映到状况标志上。运用 TST 来查看是否设置了特定的位。操作数 1 是要测验的数据字而操作数 2 是一个位掩码。经过测验后,假如匹配则设置 Zero 标志,不然铲除它。象 CMP 那样,你不需求指定 S 后缀。
TST R0, #%1 ; 测验在 R0 中是否设置了位 0。
我觉得在这儿是有点转不过弯来了,,,
ARM linux的中止向量表初始化剖析
Author: jimmy.li
Time: 2007-06-09

  本文剖析根据linux2.4.19 source,pxa 270 cpu.

  ARM linux内核启动时,经过start_kernel()->trap_init()的调用联系,初始化内核的中止反常向量表.

/* arch/arm/kernel/traps.c */
void __init trap_init(void)
{
extern void __trap_init(unsigned long);
unsigned long base = vectors_base();

__trap_init(base);
if (base != 0)
oopsprintk(KERN_DEBUG “Relocating machine vectors to 0x%08lxn”, base);
#ifdef CONFIG_CPU_32
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
#endif
}

vectors_base是一个宏,它的作用是获取ARM反常向量的地址,该宏在include/arch/asm-arm/proc-armv/system.h中界说:
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_base()((cr_alignment & CR_V) ? 0xffff0000 : 0)
#else
#define vectors_base()(0)
#endif

  关于ARMv4以下的版别,这个地址固定为0;ARMv4及其以上的版别,ARM反常向量表的地址受协处理器CP15的c1寄存器(control register)中V位(bit[13])的操控,假如V=1,则反常向量表的地址为0x00000000~0x0000001C;假如V=0,则为:0xffff0000~0xffff001C。(详情请参看ARM Architecture Reference Manual)
  下面剖析一下cr_alginment的值是在哪确认的,咱们在arch/arm/kernel/entry-armv.S找到cr_alignment的界说:
.globl SYMBOL_NAME(cr_alignment)
.globl SYMBOL_NAME(cr_no_alignment)
SYMBOL_NAME(cr_alignment):
.space 4
SYMBOL_NAME(cr_no_alignment):
.space 4
  剖析过head-armv.S文件的朋友都会知道,head-armv.S对错紧缩内核的进口:

1 .section “.text.init”,#alloc,#execinstr
2 .type stext, #function
3ENTRY(stext)
4 mov r12, r0

6 mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode
7 msr cpsr_c, r0 @ and all irqs disabled
8 bl __lookup_processor_type
9 teq r10, #0 @ invalid processor?
10 moveq r0, #p @ yes, error p
11 beq __error
12 bl __lookup_architecture_type
13 teq r7, #0 @ invalid architecture?
14 moveq r0, #a @ yes, error a
15 beq __error
16 bl __create_page_tables
17 adr lr, __ret @ return address
18 add pc, r10, #12 @ initialise processor
19 @ (return control reg)
20
21 .type __switch_data, %object
22__switch_data: .long __mmap_switched
23 .long SYMBOL_NAME(__bss_start)
24 .long SYMBOL_NAME(_end)
25 .long SYMBOL_NAME(processor_id)
26 .long SYMBOL_NAME(__machine_arch_type)
27 .long SYMBOL_NAME(cr_alignment)
28 .long SYMBOL_NAME(init_task_union)+8192
29
30 .type __ret, %function
31__ret: ldr lr, __switch_data
32 mcr p15, 0, r0, c1, c0
33 mrc p15, 0, r0, c1, c0, 0 @ read it back.
34 mov r0, r0
35 mov r0, r0
36 mov pc, lr

  这儿咱们关怀的是从17行开端,17行code处将lr放置为__ret标号处的相对地址,以便将来某处回来时跳转到31行持续运转;
  18行,关于我所剖析的pxa270渠道,它将是跳转到arch/arm/mm/proc-xscale.S中履行__xscale_setup函数,在__xscale_setup中会读取CP15的control register(c1)的值到r1寄存器,并在r1寄存器中设置相应的标志位(其间包含设置V位=1),但在__xscale_setup中,r1寄存器并不当即写回到Cp15的control register中,而是在回来后的某个当地,接下来会渐渐剖析到。__xscale_setup调用move pc, lr指令回来跳转到31行。
  31行,在lr寄存器中放置__switch_data中的数据__mmap_switched,在36行程序会跳转到__mmap_switched处。
  32,33行,把r0寄存器中的值写回到cp15的control register(c1)中,再读出来放在r0中。
  
  接下来再来看一下跳转到__mmap_switched处的代码:
40 _mmap_switched:
41 adr r3, __switch_data + 4
42 ldmia r3, {r4, r5, r6, r7, r8, sp}@ r2 = compat
43 @ sp = stack pointer
44
45 mov fp, #0 @ Clear BSS (and zero fp)
46 1: cmp r4, r5
47 strcc fp, [r4],#4
48 bcc 1b
49
50 str r9, [r6] @ Save processor ID
51 str r1, [r7] @ Save machine type
52 bic r2, r0, #2 @ Clear A bit
53 stmia r8, {r0, r2} @ Save control register values
54 b SYMBOL_NAME(start_kernel)

  41~42行的成果是:r4=__bss_start,r5=__end,…,r8=cr_alignment,..,这儿r8保存的是cr_alignment变量的地址.
  到了53行,由于之前r0保存的是cp15的control register(c1)的值,这儿把r0的值写入r8指向的地址,即cr_alignment=r0.到此为止,咱们就看清楚了cr_alignment的赋值进程。
  

  让咱们回到trap_init()函数,经过上面的剖析,咱们知道vectors_base回来0xffff0000。函数__trap_init由汇编代码编写,在arch/arm/kernel/entry-arm.S:
    .align5
__stubs_start:
vector_IRQ:
    …
vector_data:
    ….
vector_prefetch:
    …
vector_undefinstr:
    …
vector_FIQ:disable_fiq
    subspc, lr, #4
vector_addrexcptn:
    bvector_addrexcptn
    …
__stubs_end:
    .equ__real_stubs_start, .LCvectors + 0x200

.LCvectors:swiSYS_ERROR0
    b__real_stubs_start + (vector_undefinstr – __stubs_start)
    ldrpc, __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)
    stmfdsp!, {r4 – r6, lr}/* 压栈,保存数据*/

    /* 仿制反常向量表(.LCvectors开端的8个地址)到r0指向的地址(反常向量地址),r0便是__trap_init(base)函数调用时传递的参数,不明白的请参看ATPCS*/
    adrr1, .LCvectors@ set up the vectors
    ldmiar1, {r1, r2, r3, r4, r5, r6, ip, lr}
    stmiar0, {r1, r2, r3, r4, r5, r6, ip, lr}

    /* 在反常向量地址后的0x200偏移处,放置散转代码,即__stubs_start~__stubs_end之间的各个反常处理代码*/
    addr2, r0, #0x200
    adrr0, __stubs_start@ copy stubs to 0x200
    adrr1, __stubs_end
1: ldrr3, [r0], #4
    strr3, [r2], #4
    cmpr0, r1
blt1b
LOADREGS(fd, sp!, {r4 – r6, pc})/*出栈,康复数据,函数__trap_init回来*/

__trap_init函数填充后的向量表如下:
虚拟地址反常 处理代码
0xffff0000reset swi SYS_ERROR0
0xffff0004 undefined b__real_stubs_start + (vector_undefinstr – __stubs_start)
0xffff0008 软件中止 ldrpc, __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)

   当有反常产生时,处理器会跳转到对应的0xffff0000开端的向量处取指令,然后,经过b指令散转到反常处理代码.由于ARM中b指令是相对跳转,并且只要+/-32MB的寻址规模,所以把__stubs_start~__stubs_end之间的反常处理代码仿制到了0xffff0200开端处.这儿可直接用b指令跳转曩昔,这样比运用肯定跳转(ldr)效率高。

————————-参看材料——————–
1, 刘淼,嵌入式体系接口规划与Linux驱动程序开发,北京航天航空大学出版社,2006.
2, ARM Architecture Reference Manual, ARM limited,2000.
http://jimmy-lee.blog.hexun.com/cate.aspx?cateid=7311&cate=ARM%26Linux

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/bandaoti/277210.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部