<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > ARM Linux 中斷向量表建立流程

          ARM Linux 中斷向量表建立流程

          作者: 時間:2016-11-09 來源:網(wǎng)絡 收藏
          一般編寫arm的裸機程序的時候,創(chuàng)建中斷向量表就把它放在0x00000000~0x0000001c中,一般都放在這個位置上。但是中斷向量表也可以放在0xffff0000~0xffff001c中,知道這是怎么設置的么?開始看到的時候真的有點奇怪,因為在學習arm的時候,根本沒去看arm中的協(xié)處理器CP15中的c1控制寄存器中的v位來控制,我們一般都使用默認的值0,則必須將中斷向量表放在0x00000000~0x0000001c中。

          在看Linux內(nèi)核對arm中的中斷的初始化的時候,就一直對0xffff0000的地址有點懷疑,果然在網(wǎng)上發(fā)現(xiàn)這個地址不是隨便寫的,當我看到arm的協(xié)處理器進行控制,中斷向量表的地址的時候,真的是哭笑不得?。?!
          有人肯定會問?v位是什么時候設置的呢?其實仔細的朋友就知道在head.S中,在創(chuàng)建完頁表的時候,如add pc,r10,#PROCINFO_INITFUNC
          別急,r10保存在前面設置的procinfo的地址,但是很多人就覺得PROCINFO_INITFUNC的宏定義就不知道在哪找了,在include/asm/asm-offset.h中有定義。
          這些搞懂了,首先必須將中斷向量表拷貝到0xffff0000的地址上去,把中斷處理函數(shù)也拷貝到0xffff0200的地址上去,那么在中斷向量表進行跳轉的時候,如b vector_irq+stubs_offset,但是stubs_offset的偏移怎么設置呢?如果用b vector_irq的話,它就會跳轉到原先的中斷處理函數(shù)中去,因為它也拷貝到了0xffff0200的地址上去,所以將__vector_start-_stubs_start+0x200的話就轉移到拷貝后的地址上去執(zhí)行了。
          很多人應該會有點疑問吧,vector_irq好像找不到,別急,細心點,就在宏定義.macro vector_stubs,name,mode,correction中對各種處理函數(shù)有定義,所以很快就將中斷向量表創(chuàng)建好了。

          本文引用地址:http://www.ex-cimer.com/article/201611/317933.htm

          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是一個帶參數(shù)的宏,第一個是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內(nèi)容與(mode^SVC_mode)異或,即r0里為SVC_MODE 
          22:        msr    spsr_cxsf, r0  @把r0的值寫入整個spsr寄存器(cxsf表示要往哪個字節(jié)寫入)
          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的整套外部中斷的處理過程,在網(wǎng)上匯總了一些資料,整個過程差不多都了解到了。如果沒有這些資料我真是沒信心從匯編開始讀代碼,感謝 奔騰年代的jimmy.lee和 linux論壇的bx_bird。
          在下面的的注釋中有一些我讀代碼時遇到的問題,要是大家知道是怎么回事,希望多多回復。
          =============================================
          一.ARM linux的中斷向量表初始化分析
          ARM linux內(nèi)核啟動時,通過start_kernel()->trap_init()的調(diào)用關系,初始化內(nèi)核的中斷異常向量表.
          /* 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異常向量表的地址受協(xié)處理器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是非壓縮內(nèi)核的入口:
          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行繼續(xù)運行18行,對于我所分析的pxa270平臺,它將是跳轉到arch/arm/mm/proc-xscale.S中執(zhí)行__xscale_setup函數(shù),(在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個指令,執(zhí)行b _arm920_setup
          在arm920_setup設置完協(xié)處理器和返回寄存器r0之后,跳回到__ret:(31行)。
          在__xscale_setup中會讀取CP15的control register(c1)的值到r1寄存器,并在r1寄存器中設置相應的標志位(其中包括設置V位=1),但在__xscale_setup中,r1寄存器并不立即寫回到Cp15的control register中,而是在返回后的某個地方,接下來會慢慢分析到。__xscale_setup調(diào)用move pc, lr指令返回跳轉到31行。
            31行,在lr寄存器中放置__switch_data中的數(shù)據(jù)__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()函數(shù),經(jīng)過上面的分析,我們知道vectors_base返回0xffff0000。函數(shù)__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} /* 壓棧,保存數(shù)據(jù)*/
              /* 復制異常向量表(.LCvectors起始的8個地址)到r0指向的地址(異常向量地址),r0就是__trap_init(base)函數(shù)調(diào)用時傳遞的參數(shù),不明白的請參考ATPCS*/(傳遞參數(shù)順次利用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}) /*出棧,恢復數(shù)據(jù),函數(shù)__trap_init返回*/
          __trap_init函數(shù)填充后的向量表如下:
          虛擬地址 異常 處理代碼
          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 數(shù)據(jù)異常 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)
             當有異常發(fā)生時,處理器會跳轉到對應的0xffff0000起始的向量處取指令,然后,通過b指令散轉到異常處理代碼.因為ARM中b指令是相對跳轉,而且只有+/-32MB的尋址范圍,所以把__stubs_start~__stubs_end之間的異常處理代碼復制到了0xffff0200起始處.這里可直接用b指令跳轉過去,這樣比使用絕對跳轉(ldr)效率高。
          二.ARM Linux中斷處理過程分析(1)
          在我的上一篇文章(ARM linux的中斷向量表初始化分析)中已經(jīng)分析了ARM Linux中斷向量表是如何建立的,在這篇文章中,我將分析一下Linux內(nèi)核的ARM體系下,中斷處理是如何響應的一個過程。
          在ARM體系架構下,定義了7種異常,每一種異常都有自己的入口地址,即異常向量表,當異常發(fā)生時,處理器會自動跳轉到相應的入口處執(zhí)行。對于ARMv4及其以上的版本,異常向量表的起始位置由協(xié)處理器15(cp15)的控制寄存器(c1)里的V位(bit13)有關,當V=0時,異常向量表的起始位置在0x00000000,而當V=1時,異常向量表就起始于0xffff0000位置。在上一篇文章中,我們已經(jīng)分析知道異常向量表放置于0xffff0000起始位置,而IRQ中斷處理入口地址為:0xffff0018,所以當發(fā)生一IRQ中斷異常時,處理器會自動跳轉到0xffff0018這個虛擬地址上。
          0xffff0018這個虛擬地址上是一條跳轉指令:
          b __real_stubs_start + (vector_IRQ - __stubs_start)
          所以對于IRQ的處理就是從vector_IRQ標號處開始的。在linux2.4.19內(nèi)核中相應代碼如下:
          __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狀態(tài)
          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,根據(jù)進入IRQ模式之前的psr(因為在行7,lr已經(jīng)被置以spsr_irq),獲取之前的處理器模式(psr &0b1111)。
          行18,根據(jù)獲取的進入IRQ之前的處理器模式,查找相應的跳轉入口(__irq_usr 對應于之前是USR模式,__irq_svc對于之前是SVC模式,對于其它模式均跳轉到__irq_invalid,在linux系統(tǒng)中處理器進入IRQ之前只有usr和svc兩種模式,其它模式均不允許開啟IRQ)。此行實際上是:lr = pc+lr<<2,pc指向當前指令地址值加8個字節(jié)的地址,即pc指向當前指令的下兩條指令的地址,所以pc在此時指向的是.LCtab_irq地址。
          (這里有點疑惑要進入__irq_usr,則18行l(wèi)r應該為pc+4那么向回推算第7行的mrs lr, spsr中spsr[3:0]應該為0b0001;如果要進入__irq_svc,則18行l(wèi)r應該為pc+16,那么spsr[3:0]應該為0b0100;
          而cprs[4:0]=
          10000 User 模式
          10011 SVC 模式
          請達人指點迷津。。。。)
          行19,跳轉到相應入口,并且ARM寄存器r13和r14則切換到了SVC模式下的寄存器

          三.ARM Linux中斷處理過程分析(2)
          續(xù)前文,讓我們先分析進入IRQ之前的處理器模式為SVC時的情況,程序會跳轉到__irq_svc繼續(xù)運行,其相應代碼如下:
          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執(zhí)行的結果是:
          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位,如果確實找到了發(fā)生的中斷號,則Z位被清除,否則Z位被置位。get_irqnr_and_base這個宏定義的實現(xiàn)是依賴具體的硬件的,對于pxa270 cpu,其實現(xiàn)如下:
          .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:開始所作的事情是循環(huán)查intpnd哪一位置為了1。有點疑惑的是tst 指令:
          tst 類似于 CMP,不產(chǎn)生放置到目的寄存器中的結果。而是在給出的兩個操作數(shù)上進行操作并把結果反映到狀態(tài)標志上。使用 tst 來檢查是否設置了特定的位。操作數(shù) 1 是要測試的數(shù)據(jù)字而操作數(shù) 2 是一個位掩碼。經(jīng)過測試后,如果匹配則設置 Zero 標志,否則清除它。
          那么這里的tst irqstat, #1,當zero置1了表示有中斷位,為什么下面是bne 1002f而不是beq?請教請教。。。。。。。)
          asm_do_IRQ是用C語言編碼的函數(shù),它在arch/arm/kernel/irq.c中被定義,其原型為:
          asmlinkage void asm_do_IRQ(int irq, struct pt_regs *regs);
          這里牽扯到一個問題就是,在匯編中如何調(diào)用C語言的函數(shù),參數(shù)是如何傳遞的?為了讓ARM的匯編代碼可與C代碼一起連接,在編寫ARM匯編時,應遵循一套標準,這就是ATPCS(The ARM-Thumb Procedure Call Standard)。ATPCS定義{r0~r3}為參數(shù)傳遞和結果返回寄存器;若參數(shù)超過4個字型(32bit),則使用堆棧進行傳遞;頭4個參數(shù)依次存于r0...r3,大于4個的后續(xù)字型參數(shù)通過棧傳送。關于棧的使用,是使用滿遞減的堆棧標準,也就是棧是從高地址向低地址方向增長的(遞減堆棧),棧指針寄存器指向的數(shù)據(jù)是最后壓入堆棧內(nèi)的有效數(shù)據(jù)(滿堆棧)。
          所以在跳轉到asm_do_IRQ函數(shù)之前,r0就必須設置為中斷號(行28get_irqnr_and_base把中斷號放置于r0),r1就必須是指向pt_regs這樣結構(定義于include/asm-arm/proc-armv/ptrace.h)的指針,而行29把sp指針賦予r1,就完成了這樣的一個調(diào)用準備。
          行35~37:恢復寄存器,返回到發(fā)生中斷之前的代碼中繼續(xù)執(zhí)行。
          這就是整個ARM linux中斷處理的過程。以后有時間,再繼續(xù)展開asm_do_IRQ繼續(xù)分析。對于進入中斷前處理器模式是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隊列)來實現(xiàn)中斷的復用。也就是說幾個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(傳遞參數(shù));
          */
          __sti();//清除cpsr的I_bit,開中斷。
          /*如果在上面的nomask處判斷后,沒有執(zhí)行unmask動作,那么這里的__sti只是允許不同中斷通道(即icip上不同的位)上的嵌套*/
          do {
          status |= action->flags;
          action->handler(irq, action->dev_id, regs);
          action = action->next;
          } while (action);
          /*值得注意的是:整個action隊列都會被調(diào)用,所以在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;
          }
          這個函數(shù)一看就知道是找子中斷號的,
          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); /*這個函數(shù)的作用是利用中斷的隨機性來產(chǎn)生隨機數(shù)列*/
          }
          /*
          * 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內(nèi)核進程調(diào)度的朋友都知道,進程的調(diào)度可以自愿的方式隨時進行(內(nèi)核里:schedule、schedule_timeout;用戶空間:pause、nanosleep),還可以非自愿的發(fā)生,即強制地發(fā)生在每次系統(tǒng)調(diào)用返回的前夕,以及每次從中斷或異常處理返回到用戶空間的前夕(只有在用戶空間發(fā)生的中斷或異常才會引起調(diào)度)??蓞㈤喢虏俚摹禠inux內(nèi)核源代碼情景分析》上冊的第4章關于進程調(diào)度的相關地方。
          那我們就來看一下,__irq_usr在返回到usr模式(用戶空間)前夕是如何強制進行進程調(diào)度的。
          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位,把結果返回給參數(shù),其實也就是只保留sp值的高19位,這代表著把堆棧指針的地址round到8K地址邊界上,這樣它認為就得到了當前進程的task_struct數(shù)據(jù)結構了。它是因為內(nèi)核在為每個進程分配一個task_struct結構時,實際上是分配兩個連續(xù)的物理頁面的(共8K),這兩個頁面的底部是用作進程的task_struct結構,而在結構的上面就用作進程的系統(tǒng)空間堆棧;數(shù)據(jù)結構task_struct的大小約為1K,進程系統(tǒng)空間堆棧大小就約為7K。當進程在系統(tǒng)空間運行時,常常需要訪問當前進程自身的task_struct數(shù)據(jù)結構,為此內(nèi)核中定義了一個宏操作current,提供指向當前進程task_struct結構的指針,它的實現(xiàn)實際上也與這里的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的參數(shù)是tsk,它實際上是r9寄存器,它也是定義于arch/arm/kernel/entry-header.S中的:
          tsk .req r9 @ current task
          這樣r9寄存器就保存了當前進程的task_struct結構的指針了。
          Line861,程序跳轉到ret_to_user,以完成從中斷處理到返回用戶空間的過程,前面提到的進程重新調(diào)度將在那里得以體現(xiàn)。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處去調(diào)用schedule,那么,誰來設置這個字段呢?當然是內(nèi)核,從用戶空間是訪問不到進程的task_struct結構的,那么,內(nèi)核又是在什么情況下設置這個字段的呢?除當前進程通過系統(tǒng)調(diào)用自愿讓出運行以及在系統(tǒng)調(diào)用中因某種原因受阻以外,主要就是當因某種原因喚醒一個進程的時候,以及在時鐘中斷服務程序發(fā)現(xiàn)當前進程已經(jīng)連續(xù)運行太久的時候。(此段摘抄于Linux內(nèi)核源代碼情景分析》)
          Line65~66,如果當前進程的task_struct結構中的sigpedding字段為非0時才會轉到__do_signal處去調(diào)用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行l(wèi)r應該為pc+4那么向回推算第7行的mrs lr, spsr中spsr[3:0]應該為0b0001;如果要進入__irq_svc,則18行l(wèi)r應該為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 ,如果是在內(nèi)核空間,svc模式10011,移位后為0xc=0b1100,及pc+0xc,正好到了.word __irq_svc,一點都沒錯(當然不可能錯,系統(tǒng)不是跑得好好的嗎)
          注意,pc值是當前指令地址+8
          關于get_irqnr_and_base宏中:
          bics irqstat, irqstat, irqnr 對照intmsk將intpnd中禁止的中斷清0。因為intpnd在某一時刻只可以有一位為1,所以有一位被bics清0了,就會影響標志位從而beq跳轉,return r0=0;從1001:開始所作的事情是循環(huán)查intpnd哪一位置為了1。有點疑惑的是tst 指令:
          tst 類似于 CMP,不產(chǎn)生放置到目的寄存器中的結果。而是在給出的兩個操作數(shù)上進行操作并把結果反映到狀態(tài)標志上。使用 tst 來檢查是否設置了特定的位。操作數(shù) 1 是要測試的數(shù)據(jù)字而操作數(shù) 2 是一個位掩碼。經(jīng)過測試后,如果匹配則設置 Zero 標志,否則清除它。
          那么這里的tst irqstat, #1,當zero置1了表示有中斷位,為什么下面是bne 1002f而不是beq?請教請教。。。。。。。)
          沒找到你看的內(nèi)核版本中該宏的詳細定義,我在我的2.6.12中pxa體系中的此宏中沒找到tst指令,但想你的問題估計還是對tst的誤解
          pc值是當前指令地址+8
          是因為armv5是三級流水線么?
          pxa的宏里面好像是沒用tst,這里我引申到s3c2410的宏里面。
          tst的定義我翻的是網(wǎng)上搜的arm指令集,里面是這么說的:
          TST : 測試位
          (Test bits)
          TST{條件}{P} ,
          Status = op_1 AND op_2
          TST 類似于 CMP,不產(chǎn)生放置到目的寄存器中的結果。而是在給出的兩個操作數(shù)上進行操作并把結果反映到狀態(tài)標志上。使用 TST 來檢查是否設置了特定的位。操作數(shù) 1 是要測試的數(shù)據(jù)字而操作數(shù) 2 是一個位掩碼。經(jīng)過測試后,如果匹配則設置 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內(nèi)核啟動時,通過start_kernel()->trap_init()的調(diào)用關系,初始化內(nèi)核的中斷異常向量表.

          /* 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異常向量表的地址受協(xié)處理器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是非壓縮內(nèi)核的入口:

          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行繼續(xù)運行;
            18行,對于我所分析的pxa270平臺,它將是跳轉到arch/arm/mm/proc-xscale.S中執(zhí)行__xscale_setup函數(shù),在__xscale_setup中會讀取CP15的control register(c1)的值到r1寄存器,并在r1寄存器中設置相應的標志位(其中包括設置V位=1),但在__xscale_setup中,r1寄存器并不立即寫回到Cp15的control register中,而是在返回后的某個地方,接下來會慢慢分析到。__xscale_setup調(diào)用move pc, lr指令返回跳轉到31行。
            31行,在lr寄存器中放置__switch_data中的數(shù)據(jù)__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()函數(shù),經(jīng)過上面的分析,我們知道vectors_base返回0xffff0000。函數(shù)__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}/* 壓棧,保存數(shù)據(jù)*/

              /* 復制異常向量表(.LCvectors起始的8個地址)到r0指向的地址(異常向量地址),r0就是__trap_init(base)函數(shù)調(diào)用時傳遞的參數(shù),不明白的請參考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})/*出棧,恢復數(shù)據(jù),函數(shù)__trap_init返回*/

          __trap_init函數(shù)填充后的向量表如下:
          虛擬地址異常 處理代碼
          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 數(shù)據(jù)異常 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)

             當有異常發(fā)生時,處理器會跳轉到對應的0xffff0000起始的向量處取指令,然后,通過b指令散轉到異常處理代碼.因為ARM中b指令是相對跳轉,而且只有+/-32MB的尋址范圍,所以把__stubs_start~__stubs_end之間的異常處理代碼復制到了0xffff0200起始處.這里可直接用b指令跳轉過去,這樣比使用絕對跳轉(ldr)效率高。

          -------------------------參考資料--------------------
          1, 劉淼,嵌入式系統(tǒng)接口設計與Linux驅動程序開發(fā),北京航天航空大學出版社,2006.
          2, ARM Architecture Reference Manual, ARM limited,2000.
          http://jimmy-lee.blog.hexun.com/cate.aspx?cateid=7311&cate=ARM%26Linux




          關鍵詞: ARMLinux中斷向量

          評論


          技術專區(qū)

          關閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();