一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

ARM linux的中斷處理過程

 風之library 2014-08-13

作者:linuxer 發(fā)布于:2014-8-4 19:26  

一、前言

本文主要以ARM體系結構下的中斷處理為例,講述整個中斷處理過程中的硬件行為和軟件動作。具體整個處理過程分成三個步驟來描述:

1、第二章描述了中斷處理的準備過程

2、第三章描述了當發(fā)生中的時候,ARM硬件的行為

3、第四章描述了ARM的中斷進入過程

4、第五章描述了ARM的中斷退出過程

本文涉及的代碼來自3.14內核。另外,本文注意描述ARM指令集的內容,有些source code為了簡短一些,刪除了THUMB相關的代碼,除此之外,有些debug相關的內容也會刪除。

 

二、中斷處理的準備過程

1、中斷模式的stack準備

ARM處理器有多種process mode,例如user mode(用戶空間的AP所處于的模式)、supervisor mode(即SVC mode,大部分的內核態(tài)代碼都處于這種mode)、IRQ mode(發(fā)生中斷后,處理器會切入到該mode)等。對于linux kernel,其中斷處理處理過程中,ARM 處理器大部分都是處于SVC mode。但是,實際上產生中斷的時候,ARM處理器實際上是進入IRQ mode,因此在進入真正的IRQ異常處理之前會有一小段IRQ mode的操作,之后會進入SVC mode進行真正的IRQ異常處理。由于IRQ mode只是一個過度,因此IRQ mode的棧很小,只有12個字節(jié),具體如下:

struct stack {
    u32 irq[3];
    u32 abt[3];
    u32 und[3];
} ____cacheline_aligned;


static struct stack stacks[NR_CPUS];

除了irq mode,linux kernel在處理abt mode(當發(fā)生data abort exception或者prefetch abort exception的時候進入的模式)和und mode(處理器遇到一個未定義的指令的時候進入的異常模式)的時候也是采用了相同的策略。也就是經過一個簡短的abt或者und mode之后,stack切換到svc mode的棧上,這個棧就是發(fā)生異常那個時間點current thread的內核棧。anyway,在irq mode和svc mode之間總是需要一個stack保存數(shù)據,這就是中斷模式的stack,系統(tǒng)初始化的時候,cpu_init函數(shù)中會進行中斷模式stack的設定:

void notrace cpu_init(void)
{

    unsigned int cpu = smp_processor_id();------獲取CPU ID
    struct stack *stk = &stacks[cpu];---------獲取該CPU對于的irq abt和und的stack指針

……

#ifdef CONFIG_THUMB2_KERNEL
#define PLC    "r"------Thumb-2下,msr指令不允許使用立即數(shù),只能使用寄存器。
#else
#define PLC    "I"
#endif


    __asm__ (
    "msr    cpsr_c, %1\n\t"------讓CPU進入IRQ mode
    "add    r14, %0, %2\n\t"------r14寄存器保存stk->irq
    "mov    sp, r14\n\t"--------設定IRQ mode的stack為stk->irq
    "msr    cpsr_c, %3\n\t"
    "add    r14, %0, %4\n\t"
    "mov    sp, r14\n\t"--------設定abt mode的stack為stk->abt
    "msr    cpsr_c, %5\n\t"
    "add    r14, %0, %6\n\t"
    "mov    sp, r14\n\t"--------設定und mode的stack為stk->und
    "msr    cpsr_c, %7"--------回到SVC mode
        :--------------------上面是code,下面的output部分是空的
        : "r" (stk),----------------------對應上面代碼中的%0
          PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),------對應上面代碼中的%1
          "I" (offsetof(struct stack, irq[0])),------------對應上面代碼中的%2
          PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),------以此類推,下面不贅述
          "I" (offsetof(struct stack, abt[0])),
          PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),
          "I" (offsetof(struct stack, und[0])),
          PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)
        : "r14");--------上面是input操作數(shù)列表,r14是要clobbered register列表
}

嵌入式匯編的語法格式是:asm(code : output operand list : input operand list : clobber list);大家對著上面的code就可以分開各段內容了。在input operand list中,有兩種限制符(constraint),"r"或者"I","I"表示立即數(shù)(Immediate operands),"r"表示用通用寄存器傳遞參數(shù)。clobber list中有一個r14,表示在匯編代碼中修改了r14的值,這些信息是編譯器需要的內容。

2、SVC模式的stack準備

我們經常說進程的用戶空間和內核空間,對于一個應用程序而言,可以運行在用戶空間,也可以通過系統(tǒng)調用進入內核空間。在用戶空間,使用的是用戶棧,也就是我們軟件工程師編寫用戶空間程序的時候,保存局部變量的stack。陷入內核后,當然不能用用戶棧了,這時候就需要使用到內核棧。所謂內核棧其實就是處于SVC mode時候使用的棧。

Linux kernel在創(chuàng)建進程(包括用戶進程和內核線程)的時候都會分配一個(或者兩個,和配置相關)page frame,底部是struct thread_info數(shù)據結構,頂部(高地址)就是該進程的內核棧。當進程切換的時候,整個硬件和軟件的上下文都會進行切換,這里就包括了svc mode的sp寄存器的值被切換到調度算法選定的新的進程的內核棧上來。

 

3、異常向量表的準備

對于ARM處理器而言,當發(fā)生異常的時候,處理器會暫停當前指令的執(zhí)行,保存現(xiàn)場,轉而去執(zhí)行對應的異常向量處的指令,當處理完該異常的時候,恢復現(xiàn)場,回到原來的那點去繼續(xù)執(zhí)行程序。系統(tǒng)所有的異常向量(共計8個)組成了異常向量表。向量表(vector table)的代碼如下:

.section .vectors, "ax", %progbits
__vectors_start:
    W(b)    vector_rst
    W(b)    vector_und
    W(ldr)    pc, __vectors_start + 0x1000
    W(b)    vector_pabt
    W(b)    vector_dabt
    W(b)    vector_addrexcptn
    W(b)    vector_irq ---------------------------IRQ Vector
    W(b)    vector_fiq

對于本文而言,我們重點關注vector_irq這個exception vector。異常向量表可能被安放在兩個位置上:

(1)異常向量表位于0x0的地址。這種設置叫做Normal vectors或者Low vectors。

(2)異常向量表位于0xffff0000的地址。這種設置叫做high vectors

具體是low vectors還是high vectors是由ARM的一個叫做的SCTLR寄存器的第13個bit (vector bit)控制的。對于啟用MMU的ARM Linux而言,系統(tǒng)使用了high vectors。為什么不用low vector呢?對于linux而言,0~3G的空間是用戶空間,如果使用low vector,那么異常向量表在0地址,那么則是用戶空間的位置,因此linux選用high vector。當然,使用Low vector也可以,這樣Low vector所在的空間則屬于kernel space了(也就是說,3G~4G的空間加上Low vector所占的空間屬于kernel space),不過這時候要注意一點,因為所有的進程共享kernel space,而用戶空間的程序經常會發(fā)生空指針訪問,這時候,內存保護機制應該可以捕獲這種錯誤(大部分的MMU都可以做到,例如:禁止userspace訪問kernel space的地址空間),防止vector table被訪問到。對于內核中由于程序錯誤導致的空指針訪問,內存保護機制也需要控制vector table被修改,因此vector table所在的空間被設置成read only的。在使用了MMU之后,具體異常向量表放在那個物理地址已經不重要了,重要的是把它映射到0xffff0000的虛擬地址就OK了,具體代碼如下:

static void __init devicemaps_init(const struct machine_desc *mdesc)
{
    ……
    vectors = early_alloc(PAGE_SIZE * 2); -----分配兩個page的物理頁幀

    early_trap_init(vectors); -------copy向量表以及相關help function到該區(qū)域

    ……
    map.pfn = __phys_to_pfn(virt_to_phys(vectors));
    map.virtual = 0xffff0000;
    map.length = PAGE_SIZE;
#ifdef CONFIG_KUSER_HELPERS
    map.type = MT_HIGH_VECTORS;
#else
    map.type = MT_LOW_VECTORS;
#endif
    create_mapping(&map); ----------映射0xffff0000的那個page frame

    if (!vectors_high()) {---如果SCTLR.V的值設定為low vectors,那么還要映射0地址開始的memory
        map.virtual = 0;
        map.length = PAGE_SIZE * 2;
        map.type = MT_LOW_VECTORS;
        create_mapping(&map);
    }


    map.pfn += 1;
    map.virtual = 0xffff0000 + PAGE_SIZE;
    map.length = PAGE_SIZE;
    map.type = MT_LOW_VECTORS;
    create_mapping(&map); ----------映射high vecotr開始的第二個page frame

……
}

為什么要分配兩個page frame呢?這里vectors table和kuser helper函數(shù)(內核空間提供的函數(shù),但是用戶空間使用)占用了一個page frame,另外異常處理的stub函數(shù)占用了另外一個page frame。為什么會有stub函數(shù)呢?稍后會講到。

在early_trap_init函數(shù)中會初始化異常向量表,具體代碼如下:

void __init early_trap_init(void *vectors_base)
{
    unsigned long vectors = (unsigned long)vectors_base;
    extern char __stubs_start[], __stubs_end[];
    extern char __vectors_start[], __vectors_end[];
    unsigned i;

    vectors_page = vectors_base;

    將整個vector table那個page frame填充成未定義的指令。起始vector table加上kuser helper函數(shù)并不能完全的充滿這個page,有些縫隙。如果不這么處理,當極端情況下(程序錯誤或者HW的issue),CPU可能從這些縫隙中取指執(zhí)行,從而導致不可知的后果。如果將這些縫隙填充未定義指令,那么CPU可以捕獲這種異常。
    for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
        ((u32 *)vectors_base)[i] = 0xe7fddef1;

  拷貝vector table,拷貝stub function
    memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
    memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);

    kuser_init(vectors_base); ----copy kuser helper function

    flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
    modify_domain(DOMAIN_USER, DOMAIN_CLIENT);

}

一旦涉及代碼的拷貝,我們就需要關心其編譯連接時地址(link-time address)和運行時地址(run-time address)。在kernel完成鏈接后,__vectors_start有了其link-time address,如果link-time address和run-time address一致,那么這段代碼運行時毫無壓力。但是,目前對于vector table而言,其被copy到其他的地址上(對于High vector,這是地址就是0xffff00000),也就是說,link-time address和run-time address不一樣了,如果仍然想要這些代碼可以正確運行,那么需要這些代碼是位置無關的代碼。對于vector table而言,必須要位置無關。B這個branch instruction本身就是位置無關的,它可以跳轉到一個當前位置的offset。不過并非所有的vector都是使用了branch instruction,對于軟中斷,其vector地址上指令是“W(ldr)    pc, __vectors_start + 0x1000 ”,這條指令被編譯器編譯成ldr     pc, [pc, #4080],這種情況下,該指令也是位置無關的,但是有個限制,offset必須在4K的范圍內,這也是為何存在stub section的原因了。

4、中斷控制器的初始化

具體可以參考GIC代碼分析。

 

三、ARM HW對中斷事件的處理

當一切準備好之后,一旦打開處理器的全局中斷就可以處理來自外設的各種中斷事件了。

當外設(SOC內部或者外部都可以)檢測到了中斷事件,就會通過interrupt requestion line上的電平或者邊沿(上升沿或者下降沿或者both)通知到該外設連接到的那個中斷控制器,而中斷控制器就會在多個處理器中選擇一個,并把該中斷通過IRQ(或者FIQ,本文不討論FIQ的情況)分發(fā)給process。ARM處理器感知到了中斷事件后,會進行下面一系列的動作:

1、修改CPSR(Current Program Status Register)寄存器中的M[4:0]。M[4:0]表示了ARM處理器當前處于的模式( processor modes)。ARM定義的mode包括:

處理器模式 縮寫 對應的M[4:0]編碼 Privilege level
User usr 10000 PL0
FIQ fiq 10001 PL1
IRQ irq 10010 PL1
Supervisor svc 10011 PL1
Monitor mon 10110 PL1
Abort abt 10111 PL1
Hyp hyp 11010 PL2
Undefined und 11011 PL1
System sys 11111 PL1

一旦設定了CPSR.M,ARM處理器就會將processor mode切換到IRQ mode。

2、保存發(fā)生中斷那一點的CPSR值(step 1之前的狀態(tài))和PC值

ARM處理器支持9種processor mode,每種mode看到的ARM core register(R0~R15,共計16個)都是不同的。每種mode都是從一個包括所有的Banked ARM core register中選取。全部Banked ARM core register包括:

Usr System Hyp Supervisor abort undefined Monitor IRQ FIQ
R0_usr                
R1_usr                
R2_usr                
R3_usr                
R4_usr                
R5_usr                
R6_usr                
R7_usr                
R8_usr               R8_fiq
R9_usr               R9_fiq
R10_usr               R10_fiq
R11_usr               R11_fiq
R12_usr               R12_fiq
SP_usr   SP_hyp SP_svc SP_abt SP_und SP_mon SP_irq SP_fiq
LR_usr     LR_svc LR_abt LR_und LR_mon LR_irq LR_fiq
PC                
CPSR                
    SPSR_hyp SPSR_svc SPSR_abt SPSR_und SPSR_mon SPSR_irq SPSR_fiq
    ELR_hyp            

在IRQ mode下,CPU看到的R0~R12寄存器、PC以及CPSR是和usr mode(userspace)或者svc mode(kernel space)是一樣的。不同的是IRQ mode下,有自己的R13(SP,stack pointer)、R14(LR,link register)和SPSR(Saved Program Status Register)。

CPSR是共用的,雖然中斷可能發(fā)生在usr mode(用戶空間),也可能是svc mode(內核空間),不過這些信息都是體現(xiàn)在CPSR寄存器中。硬件會將發(fā)生中斷那一刻的CPSR保存在SPSR寄存器中(由于不同的mode下有不同的SPSR寄存器,因此更準確的說應該是SPSR-irq,也就是IRQ mode中的SPSR寄存器)。

PC也是共用的,由于后續(xù)PC會被修改為irq exception vector,因此有必要保存PC值。當然,與其說保存PC值,不如說是保存返回執(zhí)行的地址。對于IRQ而言,我們期望返回地址是發(fā)生中斷那一點執(zhí)行指令的下一條指令。具體的返回地址保存在lr寄存器中(注意:這個lr寄存器是IRQ mode的lr寄存器,可以表示為lr_irq):

(1)對于thumb state,lr_irq = PC

(2)對于ARM state,lr_irq = PC - 4

為何要減去4?我的理解是這樣的(不一定對)。由于ARM采用流水線結構,當CPU正在執(zhí)行某一條指令的時候,其實取指的動作早就執(zhí)行了,這時候PC值=正在執(zhí)行的指令地址 + 8,如下所示:

----> 發(fā)生中斷的指令

               發(fā)生中斷的指令+4

-PC-->發(fā)生中斷的指令+8

               發(fā)生中斷的指令+12

一旦發(fā)生了中斷,當前正在執(zhí)行的指令當然要執(zhí)行完畢,但是已經完成取指、譯碼的指令則終止執(zhí)行。當發(fā)生中斷的指令執(zhí)行完畢之后,原來指向(發(fā)生中斷的指令+8)的PC會繼續(xù)增加4,因此發(fā)生中斷后,ARM core的硬件著手處理該中斷的時候,硬件現(xiàn)場如下圖所示:


----> 發(fā)生中斷的指令

               發(fā)生中斷的指令+4 <-------中斷返回的指令是這條指令

              發(fā)生中斷的指令+8

-PC-->發(fā)生中斷的指令+12


這時候的PC值其實是比發(fā)生中斷時候的指令超前12。減去4之后,lr_irq中保存了(發(fā)生中斷的指令+8)的地址。為什么HW不幫忙直接減去8呢?這樣,后續(xù)軟件不就不用再減去4了。這里我們不能孤立的看待問題,實際上ARM的異常處理的硬件邏輯不僅僅處理IRQ的exception,還要處理各種exception,很遺憾,不同的exception期望的返回地址不統(tǒng)一,因此,硬件只是幫忙減去4,剩下的交給軟件去調整。

3、mask IRQ exception。也就是設定CPSR.I = 1

4、設定PC值為IRQ exception vector?;旧?,ARM處理器的硬件就只能幫你幫到這里了,一旦設定PC值,ARM處理器就會跳轉到IRQ的exception vector地址了,后續(xù)的動作都是軟件行為了。

 

四、如何進入ARM中斷處理

1、IRQ mode中的處理

IRQ mode的處理都在vector_irq中,vector_stub是一個宏,定義如下:

.macro    vector_stub, name, mode, correction=0
    .align    5

vector_\name:
    .if \correction
    sub    lr, lr, #\correction-------------(1)
    .endif

    @
    @ Save r0, lr_ (parent PC) and spsr_
    @ (parent CPSR)
    @
    stmia    sp, {r0, lr}        @ save r0, lr--------(2)
    mrs    lr, spsr
    str    lr, [sp, #8]        @ save spsr

    @
    @ Prepare for SVC32 mode.  IRQs remain disabled.
    @
    mrs    r0, cpsr-----------------------(3)
    eor    r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
    msr    spsr_cxsf, r0

    @
    @ the branch table must immediately follow this code
    @
    and    lr, lr, #0x0f---lr保存了發(fā)生IRQ時候的CPSR,通過and操作,可以獲取CPSR.M[3:0]的值

                            這時候,如果中斷發(fā)生在用戶空間,lr=0,如果是內核空間,lr=3
THUMB( adr    r0, 1f            )----根據當前PC值,獲取lable 1的地址
THUMB( ldr    lr, [r0, lr, lsl #2]  )-lr根據當前mode,要么是__irq_usr的地址 ,要么是__irq_svc的地址
    mov    r0, sp------將irq mode的stack point通過r0傳遞給即將跳轉的函數(shù)
ARM(    ldr    lr, [pc, lr, lsl #2]    )---根據mode,給lr賦值,__irq_usr或者__irq_svc
    movs    pc, lr            @ branch to handler in SVC mode-----(4)
ENDPROC(vector_\name)

    .align    2
    @ handler addresses follow this label
1:
    .endm

(1)我們期望在棧上保存發(fā)生中斷時候的硬件現(xiàn)場(HW context),這里就包括ARM的core register。上一章我們已經了解到,當發(fā)生IRQ中斷的時候,lr中保存了發(fā)生中斷的PC+4,如果減去4的話,得到的就是發(fā)生中斷那一點的PC值。

(2)當前是IRQ mode,SP_irq在初始化的時候已經設定(12個字節(jié))。在irq mode的stack上,依次保存了發(fā)生中斷那一點的r0值、PC值以及CPSR值(具體操作是通過spsr進行的,其實硬件已經幫我們保存了CPSR到SPSR中了)。為何要保存r0值?因為隨后的代碼要使用r0寄存器,因此我們要把r0放到棧上,只有這樣才能完完全全恢復硬件現(xiàn)場。

(3)可憐的IRQ mode稍縱即逝,這段代碼就是準備將ARM推送到SVC mode。如何準備?其實就是修改SPSR的值,SPSR不是CPSR,不會引起processor mode的切換(畢竟這一步只是準備而已)。

(4)很多異常處理的代碼返回的時候都是使用了stack相關的操作,這里沒有。“movs    pc, lr ”指令除了字面上意思(把lr的值付給pc),還有一個隱含的操作(movs中‘s’的含義):把SPSR copy到CPSR,從而實現(xiàn)了模式的切換。

2、當發(fā)生中斷的時候,代碼運行在用戶空間

Interrupt dispatcher的代碼如下:

vector_stub    irq, IRQ_MODE, 4 -----減去4,確保返回發(fā)生中斷之后的那條指令

.long    __irq_usr            @  0  (USR_26 / USR_32)   <---------------------> base address + 0
.long    __irq_invalid            @  1  (FIQ_26 / FIQ_32)
.long    __irq_invalid            @  2  (IRQ_26 / IRQ_32)
.long    __irq_svc            @  3  (SVC_26 / SVC_32)<---------------------> base address + 12
.long    __irq_invalid            @  4
.long    __irq_invalid            @  5
.long    __irq_invalid            @  6
.long    __irq_invalid            @  7
.long    __irq_invalid            @  8
.long    __irq_invalid            @  9
.long    __irq_invalid            @  a
.long    __irq_invalid            @  b
.long    __irq_invalid            @  c
.long    __irq_invalid            @  d
.long    __irq_invalid            @  e
.long    __irq_invalid            @  f

這其實就是一個lookup table,根據CPSR.M[3:0]的值進行跳轉(參考上一節(jié)的代碼:and    lr, lr, #0x0f)。因此,該lookup table共設定了16個入口,當然只有兩項有效,分別對應user mode和svc mode的跳轉地址。其他入口的__irq_invalid也是非常關鍵的,這保證了在其模式下發(fā)生了中斷,系統(tǒng)可以捕獲到這樣的錯誤,為debug提供有用的信息。

    .align    5
__irq_usr:
    usr_entry---------請參考本章第一節(jié)(1)保存用戶現(xiàn)場的描述
    kuser_cmpxchg_check---和本文描述的內容無關,這些不就介紹了
    irq_handler----------核心處理內容,請參考本章第二節(jié)的描述
    get_thread_info tsk------tsk是r9,指向當前的thread info數(shù)據結構
    mov    why, #0--------why是r8
    b    ret_to_user_from_irq----中斷返回,下一章會詳細描述

(1)保存發(fā)生中斷時候的現(xiàn)場。所謂保存現(xiàn)場其實就是把發(fā)生中斷那一刻的硬件上下文(各個寄存器)保存在了SVC mode的stack上。

    .macro    usr_entry
    sub    sp, sp, #S_FRAME_SIZE--------------A
    stmib    sp, {r1 - r12} -------------------B

    ldmia    r0, {r3 - r5}--------------------C
    add    r0, sp, #S_PC-------------------D
    mov    r6, #-1----orig_r0的值

    str    r3, [sp] ----保存中斷那一刻的r0


    stmia    r0, {r4 - r6}--------------------E
    stmdb    r0, {sp, lr}^-------------------F
    .endm

A:代碼執(zhí)行到這里的時候,ARM處理已經切換到了SVC mode。一旦進入SVC mode,ARM處理器看到的寄存器已經發(fā)生變化,這里的sp已經變成了sp_svc了。因此,后續(xù)的壓棧操作都是壓入了發(fā)生中斷那一刻的進程的(或者內核線程)內核棧(svc mode棧)。具體保存多少個寄存器值?S_FRAME_SIZE已經給出了答案,這個值是18個寄存器。r0~r15再加上CPSR也只有17個而已。先保留這個疑問,我們稍后回答。

B:壓棧首先壓入了r1~r12,這里為何不處理r0?因為r0在irq mode切到svc mode的時候被污染了,不過,原始的r0被保存的irq mode的stack上了。r13(sp)和r14(lr)需要保存嗎,當然需要,稍后再保存。執(zhí)行到這里,內核棧的布局如下圖所示:

ir1

stmib中的ib表示increment before,因此,在壓入R1的時候,stack pointer會先增加4,重要是預留r0的位置。stmib    sp, {r1 - r12}指令中的sp沒有“!”的修飾符,表示壓棧完成后并不會真正更新stack pointer,因此sp保持原來的值。

C:注意,這里r0指向了irq stack,因此,r3是中斷時候的r0值,r4是中斷現(xiàn)場的PC值,r5是中斷現(xiàn)場的CPSR值。

D:把r0賦值為S_PC的值。根據struct pt_regs的定義(這個數(shù)據結構反應了內核棧上的保存的寄存器的排列信息),從低地址到高地址依次為:

ARM_r0
ARM_r1
ARM_r2
ARM_r3
ARM_r4
ARM_r5
ARM_r6
ARM_r7
ARM_r8
ARM_r9
ARM_r10
ARM_fp
ARM_ip
ARM_sp 
ARM_lr
ARM_pc<---------add    r0, sp, #S_PC指令使得r0指向了這個位置
ARM_cpsr
ARM_ORIG_r0

為什么要給r0賦值?因此kernel不想修改sp的值,保持sp指向棧頂。

E:在內核棧上保存剩余的寄存器的值,根據代碼,依次是r0,PC,CPSR和orig r0。執(zhí)行到這里,內核棧的布局如下圖所示:

ir2

R0,PC和CPSR來自IRQ mode的stack。實際上這段操作就是從irq stack就中斷現(xiàn)場搬移到內核棧上。

F:內核棧上還有兩個寄存器沒有保持,分別是發(fā)生中斷時候sp和lr這兩個寄存器。這時候,r0指向了保存PC寄存器那個地址(add    r0, sp, #S_PC),stmdb    r0, {sp, lr}^中的“db”是decrement before,因此,將sp和lr壓入stack中的剩余的兩個位置。需要注意的是,我們保存的是發(fā)生中斷那一刻(對于本節(jié),這是當時user mode的sp和lr),指令中的“^”符號表示訪問user mode的寄存器。

(2)核心處理

irq_handler的處理有兩種配置。一種是配置了CONFIG_MULTI_IRQ_HANDLER。這種情況下,linux kernel允許run time設定irq handler。如果我們需要一個linux kernel image支持多個平臺,這是就需要配置這個選項。另外一種是傳統(tǒng)的linux的做法,irq_handler實際上就是arch_irq_handler_default,具體代碼如下:

    .macro    irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
    ldr    r1, =handle_arch_irq
    mov    r0, sp--------設定傳遞給machine定義的handle_arch_irq的參數(shù)
    adr    lr, BSYM(9997f)----設定返回地址
    ldr    pc, [r1]
#else
    arch_irq_handler_default
#endif
9997:
    .endm

對于情況一,machine相關代碼需要設定handle_arch_irq函數(shù)指針,這里的匯編指令只需要調用這個machine代碼提供的irq handler即可(當然,要準備好參數(shù)傳遞和返回地址設定)。

情況二要稍微復雜一些(而且,看起來kernel中使用的越來越少),代碼如下:

    .macro    arch_irq_handler_default
    get_irqnr_preamble r6, lr
1:    get_irqnr_and_base r0, r2, r6, lr
    movne    r1, sp
    @
    @ asm_do_IRQ 需要兩個參數(shù),一個是 irq number(保存在r0)
    @                                          另一個是 struct pt_regs *(保存在r1中)
    adrne    lr, BSYM(1b)-------返回地址設定為符號1,也就是說要不斷的解析irq狀態(tài)寄存器

                                       的內容,得到IRQ number,直到所有的irq number處理完畢
    bne    asm_do_IRQ 
    .endm

這里的代碼已經是和machine相關的代碼了,我們這里只是簡短描述一下。所謂machine相關也就是說和系統(tǒng)中的中斷控制器相關了。get_irqnr_preamble是為中斷處理做準備,有些平臺根本不需要這個步驟,直接定義為空即可。get_irqnr_and_base 有四個參數(shù),分別是:r0保存了本次解析的irq number,r2是irq狀態(tài)寄存器的值,r6是irq controller的base address,lr是scratch register。

3、當發(fā)生中斷的時候,代碼運行在內核空間

如果中斷發(fā)生在內核空間,代碼會跳轉到__irq_svc處執(zhí)行:

    .align    5
__irq_svc:
    svc_entry----保存發(fā)生中斷那一刻的現(xiàn)場保存在內核棧上
    irq_handler ----具體的中斷處理,同user mode的處理。

#ifdef CONFIG_PREEMPT--------和preempt相關的處理,本文不進行描述
    get_thread_info tsk
    ldr    r8, [tsk, #TI_PREEMPT]        @ get preempt count
    ldr    r0, [tsk, #TI_FLAGS]        @ get flags
    teq    r8, #0                @ if preempt count != 0
    movne    r0, #0                @ force flags to 0
    tst    r0, #_TIF_NEED_RESCHED
    blne    svc_preempt
#endif

    svc_exit r5, irq = 1            @ return from exception

保存現(xiàn)場的代碼和user mode下的現(xiàn)場保存是類似的,因此這里不再詳細描述,只是在下面的代碼中內嵌一些注釋。

    .macro    svc_entry, stack_hole=0
    sub    sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)----sp指向struct pt_regs中r1的位置
    stmia    sp, {r1 - r12} ------寄存器入棧。

    ldmia    r0, {r3 - r5}
    add    r7, sp, #S_SP - 4 ------r7指向struct pt_regs中r12的位置
    mov    r6, #-1 ----------orig r0設為-1
    add    r2, sp, #(S_FRAME_SIZE + \stack_hole - 4)----r2是發(fā)現(xiàn)中斷那一刻stack的現(xiàn)場
    str    r3, [sp, #-4]! ----保存r0,注意有一個!,sp會加上4,這時候sp就指向棧頂?shù)膔0位置了

    mov    r3, lr ----保存svc mode的lr到r3
    stmia    r7, {r2 - r6} ---------壓棧,在棧上形成形成struct pt_regs
    .endm

五、中斷退出過程

1、中斷發(fā)生在user mode下的退出過程,代碼如下:

ENTRY(ret_to_user_from_irq)
    ldr    r1, [tsk, #TI_FLAGS]
    tst    r1, #_TIF_WORK_MASK---------------A
    bne    work_pending
no_work_pending:
    asm_trace_hardirqs_on ------和irq flag trace相關,暫且略過

    /* perform architecture specific actions before user return */
    arch_ret_to_user r1, lr----有些硬件平臺需要在中斷返回用戶空間做一些特別處理
    ct_user_enter save = 0 ----和trace context相關,暫且略過

    restore_user_regs fast = 0, offset = 0------------B
ENDPROC(ret_to_user_from_irq)
ENDPROC(ret_to_user)

A:thread_info中的flags成員中有一些low level的標識,如果這些標識設定了就需要進行一些特別的處理,這里檢測的flag主要包括:

#define _TIF_WORK_MASK   (_TIF_NEED_RESCHED | _TIF_SIGPENDING | _TIF_NOTIFY_RESUME)

這三個flag分別表示是否需要調度、是否有信號處理、返回用戶空間之前是否需要調用callback函數(shù)。只要有一個flag被設定了,程序就進入work_pending這個分支。

B:從字面的意思也可以看成,這部分的代碼就是將進入中斷的時候保存的現(xiàn)場(寄存器值)恢復到實際的ARM的各個寄存器中,從而完全返回到了中斷發(fā)生的那一點。具體的代碼如下:

    .macro    restore_user_regs, fast = 0, offset = 0
    ldr    r1, [sp, #\offset + S_PSR] ----r1保存了pt_regs中的spsr,也就是發(fā)生中斷時的CPSR
    ldr    lr, [sp, #\offset + S_PC]!    ----lr保存了PC值,同時sp移動到了pt_regs中PC的位置
    msr    spsr_cxsf, r1 ---------賦值給spsr,進行返回用戶空間的準備
    clrex                    @ clear the exclusive monitor

    .if    \fast
    ldmdb    sp, {r1 - lr}^            @ get calling r1 - lr
    .else
    ldmdb    sp, {r0 - lr}^ ------將保存在內核棧上的數(shù)據保存到用戶態(tài)的r0~r14寄存器
    .endif
    mov    r0, r0   ---------NOP操作,ARMv5T之前的需要這個操作
    add    sp, sp, #S_FRAME_SIZE - S_PC----現(xiàn)場已經恢復,移動svc mode的sp到原來的位置
    movs    pc, lr               --------返回用戶空間
    .endm

2、中斷發(fā)生在svc mode下的退出過程,代碼如下:

    .macro    svc_exit, rpsr, irq = 0
    .if    \irq != 0
    @ IRQs already off
    .else
    @ IRQs off again before pulling preserved data off the stack
    disable_irq_notrace
    .endif
    msr    spsr_cxsf, \rpsr-------將中斷現(xiàn)場的cpsr值保存到spsr中,準備返回中斷發(fā)生的現(xiàn)場

    ldmia    sp, {r0 - pc}^ -----這條指令是ldm異常返回指令,這條指令除了字面上的操作,

                                       還包括了將spsr copy到cpsr中。

    .endm

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    我想看亚洲一级黄色录像| 中文字幕日韩欧美一区| 黄色在线免费高清观看| 成人免费视频免费观看| 欧美性欧美一区二区三区| 国产三级欧美三级日韩三级| 91播色在线免费播放| 国产一区欧美一区日韩一区| 麻豆精品视频一二三区| 老司机精品线观看86| 在线免费观看黄色美女| 久久精品久久久精品久久| 亚洲熟女乱色一区二区三区| 大香蕉大香蕉手机在线视频| 国产欧美一区二区三区精品视| 日本加勒比在线观看一区| 初尝人妻少妇中文字幕在线| 欧美国产日产在线观看| 日韩三极片在线免费播放| 日韩精品一区二区三区四区| 欧美乱视频一区二区三区| 国产丝袜美女诱惑一区二区| 精品欧美日韩一二三区| 国产免费一区二区三区不卡| 中文字幕91在线观看| 国产精品亚洲欧美一区麻豆| 成人综合网视频在线观看| 亚洲综合色在线视频香蕉视频| 九九热精品视频在线观看| 玩弄人妻少妇一区二区桃花| 深夜视频成人在线观看| 激情亚洲一区国产精品久久| 97人妻精品一区二区三区免| 亚洲天堂男人在线观看| 久久99爱爱视频视频| 在线免费国产一区二区三区| 精品少妇人妻av一区二区蜜桃| 天堂av一区一区一区| 精品人妻av区波多野结依| 国产一级内片内射免费看| 丁香六月啪啪激情综合区|