閱讀166 返回首頁    go 技術社區[雲棲]


中斷子係統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

  上一篇:go nginx 域名跳轉
  下一篇:go 【NET】--基本常識