5.分析內核中斷運行過程,以及中斷3大結構體:irq_desc、irq_chip、irqaction(詳解)

本節目標:html

   分析在linux中的中斷是如何運行的,以及中斷3大結構體:irq_desc、irq_chip、irqactionlinux

在裸板程序中(參考stmdb和ldmia詳解):數組

1.按鍵按下,框架

2.cpu發生中斷,dom

3.強制跳到異常向量入口執行(0x18中斷地址處)函數

3.1使用stmdb將寄存器值保存在棧頂(保護現場)this

stmdb sp!, { r0-r12,lr }

3.2執行中斷服務函數spa

3.3 使用ldmia將棧頂處數據讀出到寄存器中,並使pc=lr(恢復現場)debug

ldmia  sp!, { r0-r12,pc }^

//^表示將spsr的值複製到cpsr,由於異常返回後須要恢復異常發生前的工做狀態

 

在linux中:指針

須要先設置異常向量地址(參考linux應用手冊P412):

在ARM裸板中異常向量基地址是0x00000000,以下圖:

 

而linux內核中異常向量基地址是0xffff0000(虛擬地址),

位於代碼arch/cam/kernel/traps.c,代碼以下:

void __init trap_init(void)
{         
/* CONFIG_VECTORS_BASE :內核配置項,在.config文件中,設置的是0Xffff0000*/
/* vectors =0xffff0000*/
unsigned long vectors = CONFIG_VECTORS_BASE;
... ...
 
/*將異常向量地址複製到0xffff0000處*/ memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start); memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz); ... ... }

上面代碼中主要是將__vectors_end - __vectors_start之間的代碼複製到vectors (0xffff0000)處,

__vectors_start爲何是異常向量基地址?

經過搜索,找到它在arch/arm/kernel/entry_armv.S中定義:

__vectors_start:
         swi    SYS_ERROR0                      //復位異常,復位時會執行
         b       vector_und + stubs_offset              //undefine未定義指令異常
         ldr     pc, .LCvswi + stubs_offset             //swi軟件中斷異常 
         b       vector_pabt + stubs_offset             //指令預取停止abort
         b       vector_dabt + stubs_offset             //數據訪問停止abort
         b       vector_addrexcptn + stubs_offset       //沒有用到
         b       vector_irq + stubs_offset             //irq異常
         b       vector_fiq + stubs_offset            //fig異常

其中stubs_offset是連接地址的偏移地址, vector_und、vector_pabt等表示要跳轉去執行的代碼

1.以vector_irq中斷爲例, vector_irq是個宏,它在哪裏定義呢?

它仍是在arch/arm/kernel/entry_armv.S中定義,以下所示:

vector_stub  irq, IRQ_MODE, 4//irq:名字  IRQ_MODE:0X12    4:偏移量

上面的vector_stub  根據參數irq, IRQ_MODE, 4來定義」 vector_ irq」這個宏(其它宏也是這樣定義的)

2.vector_stub又是怎麼實現出來的定義不一樣的宏呢?

咱們找到vector_stub這個定義:

.macro    vector_stub, name, mode, correction=0  //定義vector_stub有3個參數
.align      5
vector_\name:                        //定義不一樣的宏,好比vector_ irq
         .if \correction                //判斷correction參數是否爲0
         sub    lr, lr, #\correction         //計算返回地址
         .endif
@ @ Save r0, lr_
<exception> (parent PC) and spsr_<exception> @ (parent CPSR) @ stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr //讀出spsr str lr, [sp, #8] @ save spsr @ @ Prepare for SVC32 mode. IRQs remain disabled. @ 進入管理模式 mrs r0, cpsr //讀出cpsr eor r0, r0, #(\mode ^ SVC_MODE) msr spsr_cxsf, r0 @ @ the branch table must immediately follow this code @ and lr, lr, #0x0f //lr等於進入模式以前的spsr,&0X0F就等於模式位 mov r0, sp ldr lr, [pc, lr, lsl #2] movs pc, lr @ branch to handler in SVC mode

3.所以咱們將上面__vectors_start裏的b  vector_irq + stubs_offset 中斷展開以下:

.macro    vector_stub, name, mode, correction=0  //定義vector_stub有3個參數
.align      5
  vector_stub  irq, IRQ_MODE, 4   //這三個參數值代入 vector_stub中

vector_ irq:                   //定義 vector_ irq
  /*計算返回地址(在arm流水線中,lr=pc+8,可是pc+4只譯碼沒有執行,因此lr=lr-4) */
         sub    lr, lr, #4             
        
         @
         @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
         @ (parent CPSR)
         @保存r0和lr和spsr
         stmia sp, {r0, lr}               //存入sp棧裏  
         mrs   lr, spsr                       //讀出spsr
         str     lr, [sp, #8]           @ save spsr

 

         @
         @ Prepare for SVC32 mode.  IRQs remain disabled.
         @ 進入管理模式
         mrs   r0, cpsr                    //讀出cpsr
         eor    r0, r0, #(\mode ^ SVC_MODE)  
         msr   spsr_cxsf, r0

         @
         @ the branch table must immediately follow this code
         @
         and    lr, lr, #0x0f    //lr等於進入模式以前的spsr,&0X0F就等於模式位
         mov  r0, sp
         ldr     lr, [pc, lr, lsl #2]   //若是進入中斷前是usr,則取出PC+4*0的內容,即__irq_usr @若是進入中斷前是svc,則取出PC+4*3的內容,即__irq_svc

         movs pc, lr                    //跳轉到下面某處,且目標寄存器是pc,指令S結尾,最後會恢復cpsr.
     
         .long __irq_usr                              @  0  (USR_26 / USR_32)
         .long __irq_invalid                          @  1  (FIQ_26 / FIQ_32)
         .long __irq_invalid                          @  2  (IRQ_26 / IRQ_32)
         .long __irq_svc                              @  3  (SVC_26 / SVC_32)
         .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

從上面代碼中的註釋能夠看出:

  • 1).將發生異常前的各個寄存器值保存在SP棧裏,如果中斷異常,則PC=PC-4,也就是CPU下個要運行的位置處
  • 2).而後根據進入中斷前的工做模式不一樣,程序下一步將跳轉到_irq_usr 、或__irq_svc等位置。

4.咱們先選擇__irq_usr做爲下一步跟蹤的目標:

4.1其中__irq_usr的實現以下(arch\arm\kernel\entry-armv.S):

__irq_usr:   usr_entry //保存數據到棧裏   get_thread_info tsk   irq_handler //調用irq_handler   b ret_to_user

4.2.irq_handler的實現過程,arch\arm\kernel\entry-armv.S

.macro  irq_handler
  get_irqnr_preamble r5, lr                
  get_irqnr_and_base r0, r6, r5, lr         // get_irqnr_and_base:獲取中斷號,r0=中斷號
  movne        r1, sp                 //r1等於sp  (發生中斷以前的各個寄存器的基地址)            
  adrne lr, 1b
  bne    asm_do_IRQ                   //調用asm_do_IRQ, irq=r0   regs=r1

irq_handler最終調用asm_do_IRQ

4.3 asm_do_IRQ實現過程,arch/arm/kernel/irq.c

該函數和裸板中斷處理同樣的,完成3件事情:

1).分辨是哪一個中斷;

2).經過desc_handle_irq(irq, desc)調用對應的中斷處理函數;

3).清中斷

 asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)  //irq:中斷號   *regs:發生中斷前的各個寄存器基地址
{
struct pt_regs *old_regs = set_irq_regs(regs);
/*根據irq中斷號,找到哪一個中斷, *desc =irq_desc[irq]*/
struct irq_desc *desc = irq_desc + irq; // irq_desc是個數組(位於kernel/irq/handle.c)
 

if (irq >= NR_IRQS) 
desc = &bad_irq_desc;
 

irq_enter();    
desc_handle_irq(irq, desc);     // desc_handle_irq根據中斷號和desc,調用函數指針,進入中斷處理,

 
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);

}

上面主要是執行desc_handle_irq函數進入中斷處理

其中desc_handle_irq代碼以下:

desc->handle_irq(irq, desc);//至關於執行irq_desc[irq]-> handle_irq(irq, irq_desc[irq]);

它會執行handle_irq成員函數,這個成員handle_irq又是在哪裏被賦值的?

搜索handle_irq,找到它位於kernel/irq/chip.c,__set_irq_handler函數下:

void  __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,const char *name)
{
  ... ...
  desc = irq_desc + irq;      //在irq_desc結構體數組中找到對應的中斷
  ... ...
  desc->handle_irq = handle;  //使handle_irq成員指向handle參數函數
}

繼續搜索__set_irq_handler函數,它被set_irq_handler函數調用:

static inline void set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
{
         __set_irq_handler(irq, handle, 0, NULL);
}

繼續搜索set_irq_handler函數,以下圖

發現它在s3c24xx_init_irq(void)函數中被屢次使用,顯然在中斷初始化時,屢次進入__set_irq_handler函數,並在irq_desc數組中構造了不少項 handle_irq函數

咱們來看看irq_desc中斷描述結構體到底有什麼內容:

struct irq_desc {
         irq_flow_handler_t       handle_irq;  //指向中斷函數, 中斷產生後,就會執行這個handle_irq
         struct irq_chip   *chip; //指向irq_chip結構體,用於底層的硬件訪問,下面會介紹
         struct msi_desc             *msi_desc; 
         void                     *handler_data;  
         void                     *chip_data;
         struct irqaction     *action;      /* IRQ action list */   //action鏈表,用於中斷處理函數
         unsigned int                  status;                  /* IRQ status */
         unsigned int                  depth;                  /* nested irq disables */
         unsigned int                  wake_depth;        /* nested wake enables */
         unsigned int                  irq_count;   /* For detecting broken IRQs */
         unsigned int                  irqs_unhandled;
         spinlock_t            lock;          
     ... ...
         const char            *name;              //產生中斷的硬件名字
} ;

其中的成員*chip的結構體,用於底層的硬件訪問, irq_chip類型以下:

struct irq_chip {
         const char   *name;
         unsigned int    (*startup)(unsigned int irq);       //啓動中斷 
         void            (*shutdown)(unsigned int irq);      //關閉中斷
         void            (*enable)(unsigned int irq);         //使能中斷
         void            (*disable)(unsigned int irq);        //禁止中斷
         void            (*ack)(unsigned int irq);       //響應中斷,就是清除當前中斷使得能夠再接收下箇中斷
         void            (*mask)(unsigned int irq);     //屏蔽中斷源 
         void            (*mask_ack)(unsigned int irq);  //屏蔽和響應中斷
         void            (*unmask)(unsigned int irq);   //開啓中斷源
         ... ...
     int              (*set_type)(unsigned int irq, unsigned int flow_type);  //將對應的引腳設置爲中斷類型的引腳
     ... ...
#ifdef CONFIG_IRQ_RELEASE_METHOD
         void            (*release)(unsigned int irq, void *dev_id);       //釋放中斷服務函數
#endif

};

其中的成員struct irqaction  *action,主要是用來存用戶註冊的中斷處理函數,

一箇中斷能夠有多個處理函數 ,當一箇中斷有多個處理函數,說明這個是共享中斷.

所謂共享中斷就是一箇中斷的來源有不少,這些來源共享同一個引腳。

因此在irq_desc結構體中的action成員是個鏈表,以action爲表頭,如果一個以上的鏈表就是共享中斷

 irqaction結構定義以下:

struct irqaction {
         irq_handler_t handler;      //等於用戶註冊的中斷處理函數,中斷髮生時就會運行這個中斷處理函數
         unsigned long flags;         //中斷標誌,註冊時設置,好比上升沿中斷,降低沿中斷等
         cpumask_t mask;           //中斷掩碼
         const char *name;          //中斷名稱,產生中斷的硬件的名字
         void *dev_id;              //設備id
         struct irqaction *next;        //指向下一個成員
         int irq;                    //中斷號,
         struct proc_dir_entry *dir;    //指向IRQn相關的/proc/irq/

};

上面3個結構體的關係以下圖所示:

 

咱們來看看s3c24xx_init_irq()函數是怎麼初始化中斷的,之外部中斷0爲例(位於s3c24xx_init_irq函數):

s3c24xx_init_irq()函數中部分代碼以下:

/*其中IRQ_EINT0=16, 因此irqno=16 */
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) 
{ irqdbf(
"registering irq %d (ext int)\n", irqno);
/*在set_irq_chip函數中會執行: desc = irq_desc + irq; desc->chip = chip;*/ set_irq_chip(irqno, &s3c_irq_eint0t4); //因此(irq_desc+16)->chip= &s3c_irq_eint0t4 /* set_irq_handler 會調用__set_irq_handler 函數*/ set_irq_handler(irqno, handle_edge_irq); //因此(irq_desc+16)-> handle_irq = handle_edge_irq

set_irq_flags(irqno, IRQF_VALID); }

初始化了外部中斷0後,當外部中斷0觸發,就會進入咱們以前分析的asm_do_IRQ函數中,調用(irq_desc+16)-> handle_irq也就是handle_edge_irq函數。

咱們來分析下handle_edge_irq函數是如何執行中斷服務的:

void fastcall handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
         const unsigned int cpu = smp_processor_id();
         spin_lock(&desc->lock);
         desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);


   /*判斷這個中斷是否正在運行(INPROGRESS)或者禁止(DISABLED)*/
     if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) || !desc->action)) 
    { desc
->status |= (IRQ_PENDING | IRQ_MASKED); mask_ack_irq(desc, irq); //屏蔽中斷 goto out_unlock; } kstat_cpu(cpu).irqs[irq]++; //計數中斷次數
/* Start handling the irq */ desc->chip->ack(irq); //開始處理這個中斷 /* Mark the IRQ currently in progress.*/ desc->status |= IRQ_INPROGRESS; //標記當前中斷正在運行 do { struct irqaction *action = desc->action; irqreturn_t action_ret; if (unlikely(!action)) { //判斷鏈表是否爲空 desc->chip->mask(irq); goto out_unlock; } if (unlikely((desc->status & (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) == (IRQ_PENDING | IRQ_MASKED))) { desc->chip->unmask(irq); desc->status &= ~IRQ_MASKED; } desc->status &= ~IRQ_PENDING; spin_unlock(&desc->lock); action_ret = handle_IRQ_event(irq, action); //真正的處理過程 if (!noirqdebug) note_interrupt(irq, desc, action_ret); spin_lock(&desc->lock); } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING); desc->status &= ~IRQ_INPROGRESS; out_unlock: spin_unlock(&desc->lock); }

 

上面handle_edge_irq()函數主要執行了:

1.  desc->chip->ack(irq);    //開始處理這個中斷

在s3c24xx_init_irq()函數中chip成員指向了s3c_irq_eint0t4(),

因此desc->chip->ack(irq)就是執行handle_edge_irq(irq)函數,handle_edge_irq函數以下:

s3c_irq_ack(unsigned int irqno)
{
         unsigned long bitval = 1UL << (irqno - IRQ_EINT0); 
         __raw_writel(bitval, S3C2410_SRCPND);    //向SRCPND寄存器寫入bitval ,清SRCPND中斷
         __raw_writel(bitval, S3C2410_INTPND);   //向INTPND寄存器位寫入bitval ,清INTPND中斷
}

因此desc->chip->ack(irq); 主要執行清中斷之類的

2.handle_IRQ_event(irq, action);   //真正的處理過程

handle_IRQ_event()代碼以下:

handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
         irqreturn_t ret, retval = IRQ_NONE;
         unsigned int status = 0;
         handle_dynamic_tick(action);
         if (!(action->flags & IRQF_DISABLED))
                   local_irq_enable_in_hardirq();
         do {
                   ret = action->handler(irq, action->dev_id);      //執行action->handler
                   if (ret == IRQ_HANDLED)
                            status |= action->flags;
                   retval |= ret;
                   action = action->next;    //指向下個action成員
         } while (action);          //取出action全部成員


         if (status & IRQF_SAMPLE_RANDOM)
                   add_interrupt_randomness(irq);
         local_irq_disable();
         return retval;

}

因此handle_IRQ_event()函數主要是取出action鏈表中的成員,而後執行irq_desc->action->handler(irq, action->dev_id);

action鏈表是irq_desc中斷描述符結構體的 成員

 

本節經常使用函數總結:

trap_init(): 初始化異常向量的虛擬基地址,通常爲0XFFFF0000

s3c24xx_init_irq():初始化各個中斷

set_irq_chip(irqno, &s3c_irq_eint0t4):設置irq_desc[irqno]->chip等於第二個參數

set_irq_handler(irqno, handle_edge_irq); 設置irq_desc[irqno]->handle_irq等於第二個參數

asm_do_IRQ():中斷產生後,會進入這個函數,最終執行 desc->handle_irq(irq, desc);

handle_edge_irq(irq, desc):執行中斷函數,主要是執行如下兩步驟:

(1) desc->chip->ack(irq):相應中斷,也就是清中斷,使能再次接受中斷

(2) handle_IRQ_event(irq, action):執行中斷的服務函數,desc->action->handler

 

中斷運行總結:

當產生一箇中斷異常

1.進入異常向量vector,好比中斷異常:  vector_irq + stubs_offset

2.好比中斷異常以前是用戶模式(正常工做),則進入 __irq_usr,而後最終進入asm_do_IRQ函數,

3.而後執行irq_desc [irq]->handle_irq(irq, irq_desc [irq]);        

經過剛纔的分析,外部中斷0(irq_desc[16])的handle_irq成員等於handle_edge_irq函數,

因此就是執行handle_edge_irq(irq, irq_desc [irq]);

4.之外部中斷0爲例,在handle_edge_irq函數中主要執行兩步:

  ->4.1 desc->chip->ack    //使用chip成員中的ack函數來清中斷

  ->4.2  執行action鏈表 irq_desc->action->handler

 

這4步都是系統給作好的(中斷的框架),當咱們想本身寫個中斷處理程序,去執行本身的代碼,就須要寫irq_desc->action->handler,而後經過request_irq()來向內核申請註冊中斷

中斷運行分析完畢後,接下來開始分析如何經過函數來註冊卸載中斷

相關文章
相關標籤/搜索