166
技術社區[雲棲]
中斷子係統3_中斷入口處理
// 中斷入口 // 注:gnu 每個符號分屬global(被輸出)和local(不被輸出)兩類中的一種。 1.1 #define ENTRY(name) \ .globl name; \ ALIGN; \//之後的代碼對齊到32字節,使用NOP(0x90)補齊 name: // 代碼對齊 // .align(n) power-of-2對齊 // 4 對齊到16字節, 5 對齊到32字節 // 0x90 NOP 指令的機器碼,用於填充到指定的對齊字節 1.2 #define ALIGN .align 4,0x90 // 可屏蔽中斷入口 // 1.IRQn中斷處理程序所在的地址開始是保存在interrupt[n]之中的,之後才複製到IDT相應的表項中斷門中 // 2..text段連續存儲在一起,.data段連續存儲在一起 // 3.最終在內存中: // 3.1 所有可屏蔽中斷的入口地址依次連續存儲在.data段,interrupt保存數組起始地址 // interrupt -> // interrupt[0] // interrupt[1] // interrupt[2] // . // . // interrupt[255] // 3.2 所有可屏蔽中斷處理函數依次連續存儲在.text段,irq_entries_start保存數組起始地址 // irq_entries_start -> // pushl -256 // jmp common_interrupt // pushl -255 // jmp common_interrupt // pushl -254 // jmp common_interrupt // . // . // pushl -1 // jmp common_interrupt 1.3 .data //數據段 ENTRY(interrupt) .text //代碼段 // IRQ0~IRQ255 vector=0 ENTRY(irq_entries_start) .rept NR_IRQS //.rept,.endr之間的代碼展開255次 ALIGN 1: pushl $vector-256 //IRQ號取負 jmp common_interrupt .data //數據段,會與35行合並,所有數據段連續存儲起來 .long 1b // 標簽1的地址 .text vector=vector+1 .endr //32行重複結束 ALIGN common_interrupt: //所有可屏蔽中斷函數的公共部分 SAVE_ALL //寄存器入棧 movl %esp,%eax // eax保存棧頂指針 call do_IRQ //中斷處理函數 jmp ret_from_intr 1.4 #define SAVE_ALL \ cld; \ //清除方向標誌 pushl %es; \ pushl %ds; \ pushl %eax; \ //eax保存中斷號 pushl %ebp; \ pushl %edi; \ pushl %esi; \ pushl %edx; \ pushl %ecx; \ pushl %ebx; \ movl $(__USER_DS), %edx; \ //es,ds指向用戶數據段 movl %edx, %ds; \ movl %edx, %es; // 中斷入口函數 // 參數:regs,通過eax傳遞被中斷進程或被中斷中斷的上下文 // 函數主要任務: // 1.遞增中斷嵌套計數器 // 2.切換內核棧 // 2.1 4k核心棧,並且當前在進程上下文 // 3.__do_IRQ統一入口 // 3.1 向芯片屏蔽並確認中斷 // 3.2 執行中斷處理函數 // 3.3 解除屏蔽 // 4.退出中斷 // 4.1 遞減中斷嵌套計數器 // 4.2 執行軟中斷,或調度進程執行 1.5 fastcall unsigned int do_IRQ(struct pt_regs *regs) { int irq = regs->orig_eax & 0xff; //eax低8位保存中斷號 #ifdef CONFIG_4KSTACKS //每個線程使用4k的內核堆棧,中斷使用獨立的棧 union irq_ctx *curctx, *irqctx; u32 *isp; #endif //遞增current->preempt_count中的中斷嵌套計數 irq_enter(); #ifdef CONFIG_4KSTACKS //irq_ctx與thread_union結構相同,因此可以相互轉換 curctx = (union irq_ctx *) current_thread_info(); //本cpu的中斷棧 irqctx = hardirq_ctx[smp_processor_id()]; //當前在進程上下文,則需要切換到中斷棧 //當前在中斷上下文,則無需切換 if (curctx != irqctx) { //進程被中斷 int arg1, arg2, ebx; //中斷棧棧頂的位置 isp = (u32*) ((char*)irqctx + sizeof(*irqctx)); //保存被中斷的進程描述符 irqctx->tinfo.task = curctx->tinfo.task; //保存被中斷的進程的棧指針 irqctx->tinfo.previous_esp = current_stack_pointer; asm volatile( " xchgl %%ebx,%%esp \n" //交換esp,ebx,交換前ebx的值為被中斷進程的用戶代碼段,esp為核心棧 " call __do_IRQ \n" " movl %%ebx,%%esp \n" : "=a" (arg1), "=d" (arg2), "=b" (ebx) //返回值:arg1=eax, arg2=edx, ebx=ebx : "0" (irq), "1" (regs), "2" (isp) //形參結合:$0=irq, $1=regs, $2=isp : "memory", "cc", "ecx" ); } else #endif __do_IRQ(irq, regs); //退出中斷處理 irq_exit(); return 1; } // 中斷通用處理函數 // 函數主要任務: // 1.如果為cpu本地中斷 // 1.1 向中斷控製器ack // 1.2 調用共享該中斷號的所有中斷處理程序 // 1.3 向中斷控製器end // 2.否則 // 2.1 獲取中斷描述符鎖 // 2.2 向中斷控製器顯示確認中斷 // 2.3 設置IRQ_PENDING表示中斷還沒有被處理 // 2.4 如果當前中斷沒有被禁用,並且中斷處理程序沒有在運行中 // 2.4.1 清除IRQ_PENDING,設置IRQ_INPROGESS, 表示中斷處理開始 // 2.4.2 釋放irq描述符鎖,執行中斷處理函數 // 2.4.3 檢查在中斷處理函數執行過程中,此中斷是否再次發生 // 2.4.3.1 如果pending設置,表示在中斷函數處理過程中,此中斷再次發生,再次執行處理函數 // 2.4.3.2 否則退出 // 2.5 向中斷控製器end // 注: // 1.互斥使用中斷控製器進行ack,mask,end。 // 2.中斷處理函數irq_desc_t->action在cpu間可以並行 // 3.通過irq_desc_t->status = // IRQ_INPROGRESS 保證中斷處理函數同時隻有一個在執行 // IRQ_PENDING 保證在中斷處理函數在執行時,可以記錄第二次發生的中斷 // 1.6 fastcall unsigned int __do_IRQ(unsigned int irq, struct pt_regs *regs) { //irq號對應的中斷描述符 irq_desc_t *desc = irq_desc + irq; struct irqaction * action; unsigned int status; //統計信息 kstat_this_cpu.irqs[irq]++; //cpu本地中斷 if (desc->status & IRQ_PER_CPU) { irqreturn_t action_ret; //向中斷控製器顯式確認 desc->handler->ack(irq); //遍曆中斷描述符下邊所有的共享中斷號的中斷處理程序 action_ret = handle_IRQ_event(irq, regs, desc->action); desc->handler->end(irq); return 1; } //互斥操作中斷控製器 spin_lock(&desc->lock); //顯式確認 desc->handler->ack(irq); //IRQ_REPLAY IRQ已經被禁用 //IRQ_WAITING IRQ的自動檢測 status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING); //IRQ_PENDING cpu注意到中斷,但是尚未處理它 status |= IRQ_PENDING; //當前中斷沒有被禁用,當前中斷沒有在執行 action = NULL; if (likely(!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))) { action = desc->action; //清除IRQ_PENDING,設置IRQ_INPROGRESS表示馬上執行處理函數 status &= ~IRQ_PENDING; status |= IRQ_INPROGRESS; } desc->status = status; if (action == NULL) goto out; //在處理中斷的過程中,在此發生的同一irq,通過pending標識 for (;;) { irqreturn_t action_ret; //中斷處理函數可以在cpu間並行執行 spin_unlock(&desc->lock); action_ret = handle_IRQ_event(irq, regs, action); spin_lock(&desc->lock); //沒有新到來的中斷,退出 if (likely(!(desc->status & IRQ_PENDING))) break; desc->status &= ~IRQ_PENDING; } desc->status &= ~IRQ_INPROGRESS; out: //通知中斷控製器已經處理完中斷 desc->handler->end(irq); spin_unlock(&desc->lock); return 1; }
最後更新:2017-04-03 14:54:06