娇小w搡bbbb搡bbb,《第一次の人妻》,中国成熟妇女毛茸茸,边啃奶头边躁狠狠躁视频免费观看

歷史上的今天

今天是:2025年01月16日(星期四)

正在發生

2019年01月16日 | ARM中斷向量表的簡單分析

發布者:清新時光 來源: eefocus關鍵字:ARM  中斷向量表 手機看文章 掃描二維碼
隨時隨地手機看文章

一般編寫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的地址上去,那么在中斷向量表進行跳轉的時候,如bvector_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_

  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_

  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_

                       = vector_


所以異常入口為“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%08lx\n", 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 

5 

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) 

1

所以對于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_

SPSR_

CPSR[4:0] = 異常模式編碼 

CPSR[5] = 0 ;運行于ARM狀態 

If

;當復位或響應FIQ異常時,禁止新的fiq和irq異常 

CPSR[6] = 1; 

CPSR[7] = 1; 

}else if

;當響應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 

1

這與行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) { 

注意/* Can't 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%08lx\n", 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

5                

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)


15

  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*/

      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  中斷向量表 引用地址:ARM中斷向量表的簡單分析

上一篇:s3c2440裸機開發環境的搭建
下一篇:arm7 LPC2103 中斷的處理方法

推薦閱讀

 一般紅外電視遙控器的輸出都是用編碼后串行數據對38~40kHz的方波進行脈沖幅度調制而產生的。  當發射器按鍵按下后,即有遙控碼發出,所按的鍵不同遙控編碼也不同。這種遙控碼具有以下特征:  采用脈寬調制的串行碼,以脈寬為0.565ms、間隔0.56ms、周期為1.125ms的組合表示二進制的“0”;以脈寬為0.565ms、間隔1.685ms、周期為2.25ms的組合表示二進...
普通時候,人們對于操作系統的feel是淡漠的,只有不尋常的時日,操作系統才會顯現出與眾不同。OS,英文全稱operating system,即操作系統。維基百科是這樣解釋的:用于管理計算機硬件和軟件資源,并提供通用服務的計算機程序。這句話太拗口,不易理解,對于普羅大眾而言,操作系統就是Windows、Macos、Android,稍微了解多的一點,也是從購買電腦時,推銷...
這次聊聊定時器A,Timer_A。F5529的定時器和F149剛能上幾乎沒有區別,會用F149的定時器就會用F5529的定時器了。可是呢~F55系列定時器的數量不一樣了,而且寄存器的名字也有所改變了。F5529一共有3個定時器A,3個!TimerA!這3個分別是Timer0_A5,Timer1_A3,Timer2_A3,前面的數字就是定時器的序數,后面的數字是這個定時所擁有的捕獲比較單元數量。這些我都...
引言當前,激光技術發展日趨完善,在基礎研究、醫療、機械、通訊等領域廣泛應用。目前多采用計算機進行可視化激光器控制,因為計算機有很高的速度和很大的存貯空間,可以控制大的系統。在控制中小型系統時,計算機的優勢并不明顯,而且軟硬件復雜、成本較高。采用簡單的控制系統來取代計算機這樣復雜的系統,可以解決以上問題。在一般控制系統中,單片機可...

史海拾趣

問答坊 | AI 解惑

跪求《單片機C語言程序設計實訓100例》,在線等?

單片機C語言程序設計實訓100例(基于8051+Proteus仿真) /謝謝各位大哥大姐~~~~~…

查看全部問答∨

DIY 個浪漫光影棒,報名請跟帖!

經過大家的集思廣益,本次活動確定制作大家熟悉的,伴有爛漫神奇色彩的魔幻搖搖棒~  搖一搖,搖出你的心意,搖一搖,搖出你的勤勞~  換種方式大膽說出:I LOVE YOU! 年輕的工程師們,你們還等什么?DO IT YOURSELF ! 參與活 ...…

查看全部問答∨

關于CE的內存結構的問題!

據我所知,CE中分32個進程槽,每個進程可以使用32M的內存。 加起來就是1GB。。 可是CE最大支持512M物理內存。 如果32個進程同時運行,,哪兒有那么多物理內存可用呀???? …

查看全部問答∨

智能設備編程為何沒有Application.StartupPath

提示Application沒有這個StartupPath屬性…

查看全部問答∨

程序裸機運行可以,加入系統里為什么不行?

一個裸機程序,溫度傳感器裸機可以測試通過,串口可以打印溫度信息,為什么把它移植到wince操作系統下,做成一個一按鍵就讀取數據的驅動程序,為什么讀取的數據不對呢?…

查看全部問答∨

WINCE消息機制,使用回調函數WndProc,能否指定某一個FORM響應操作,其他窗體無響應?

同時打開兩個FORM的情況下,發現使用回調函數WndProc,兩個FORM都會有響應,能否指定只有當前的FORM(獲得焦點的窗體)能夠執行操作,其他打開的窗體不響應?…

查看全部問答∨

連到系統上的設備沒有發揮作用

以前做好的一個VISTA的NDIS filter,現在編譯出來用osrloader總是加載不起來。錯誤原因就是“連到系統上的設備沒有發揮作用”。試了下把WDK6000自帶的filter編譯出來也是同樣的結果,應該不是代碼的問題。出現這個的原因是什么呢 …

查看全部問答∨

電路方面,如何從0開始學?

以后后想做嵌入式。 但是我就懂點C和UNIX方面的,現在沒有任何的硬件,電路基礎。 我聽別人說,看《數字電路》  我買了這本書,發現看不懂,好像需要數字邏輯方面的知識,我又買了本《數字邏輯與數字系統》,但是發現還是欠缺知識。 ...…

查看全部問答∨

點陣字體

問一下,什麼是點陣字體,它的格式是不是都一樣的,是不是點陣字體可以生成不同後綴的文件?…

查看全部問答∨

國外DIY網站大集合

多個國外DIY網站收集 http://www.powerlabs.org 高能diy,電磁槍,微波武器.   http://www.altair.org/tesla.html 高電壓diy愛好者   http://www.richieburnett.co.uk/tesla.shtml 高電壓diy愛好者   http://www.richieburn ...…

查看全部問答∨
小廣播
設計資源 培訓 開發板 精華推薦

最新單片機文章
何立民專欄 單片機及嵌入式寶典

北京航空航天大學教授,20余年來致力于單片機與嵌入式系統推廣工作。

 
EEWorld訂閱號

 
EEWorld服務號

 
汽車開發圈

 
機器人開發圈

電子工程世界版權所有 京ICP證060456號 京ICP備10001474號-1 電信業務審批[2006]字第258號函 京公網安備 11010802033920號 Copyright ? 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
主站蜘蛛池模板: 专栏| 耒阳市| 平陆县| 寿光市| 邳州市| 松溪县| 巫山县| 白河县| 壶关县| 靖西县| 平凉市| 察雅县| 买车| 盐山县| 龙海市| 务川| 盖州市| 临安市| 金阳县| 葵青区| 永和县| 大余县| 定陶县| 溆浦县| 阳东县| 青神县| 乐都县| 综艺| 尉犁县| 治多县| 皋兰县| 北海市| 宜宾县| 黑龙江省| 明水县| 安远县| 阜宁县| 进贤县| 东至县| 太湖县| 南投县|