Linux--硬件中斷處理

我們知道關於中斷被分成了兩種,一種是軟中斷(比如:系統調用,系統報錯等);另一種是硬件中斷。這裏我們來了解一下內核是如何識別並處理這些中斷的。

以5.6版本的內核爲例,在內核啓動階段的代碼中可以看到這麼一個函數init_IRQ(),顧名思義,該函數主要的任務是初始化中斷。因爲每個CPU的架構不同,所以其硬件中斷存在一定的差異性,因此該函數由各個CPU的架構自己來實現。這裏以mips架構爲例,其實現如下:

void __init init_IRQ(void)
{
	int i;
	unsigned int order = get_order(IRQ_STACK_SIZE);
	//這裏IRQ_STACK_SIZE被設置爲2^14,該函數的作用便是獲取到指數14。
	for (i = 0; i < NR_IRQS; i++)
		irq_set_noprobe(i);
	//這裏NR_IRQS被定義爲128,另外,在init_IRQ函數之前,有一個名爲early_irq_init的函數,該函數主要對中斷所需要的數據結構進行準備與初始化,early_irq_init函數主要利用基數樹的屬性結構來對中斷結構體struct irq_desc進行了組織。所以,在當前初始化階段,該函數通過上述的基數樹與中斷號來查找所對應的struct irq_desc結構體對象。當獲得irq_desc結構體對象後,來修改其中相關的成員參數。
	if (cpu_has_veic)
		clear_c0_status(ST0_IM);
	arch_init_irq();
	//該函數主要完成與架構相關的中斷初始化,這部分與各自CPU架構所採用的中斷控制器工作方式相關,比如:有的CPU架構中斷採用的是8259中斷控制器,因此函數調用init_i8259_irqs函數來初始化8259芯片,以及向內核中添加8259的中斷域。中斷初始化的過程主要通過struct irq_desc、struct irq_action、struct irq_chip、struct irq_domain這幾個結構體來完成。
	//另外,內核提供了一種名爲irq_chip機制的中斷管理方法,利用該機制可以在irqchip目錄下實現所需的硬件中斷處理文件,最後可以通過irq_chip_init函數將irqchip目錄下的中斷處理依次註冊到內核中,其實該機制主要使用了編譯器的特性,可以在IRQCHIP_DECLARE宏定義中看見。
	for_each_possible_cpu(i) {
		void *s = (void *)__get_free_pages(GFP_KERNEL, order);
		irq_stack[i] = s;
		pr_debug("CPU%d IRQ stack at 0x%p - 0x%p\n", i, irq_stack[i], irq_stack[i] + IRQ_STACK_SIZE);
	}
	//該循環主要爲每一箇中斷所對應的棧來分配空間,從代碼中可以知道每個棧的空間大小爲2^14. 
}

當中斷初始化完成之後,如何利用初始化形成的數據結構來處理中斷呢?

首先了解一下MIPS架構中是如何通過寄存器來識別並處理中斷的。在MIPS架構的CPU中,其內部存在的協處理器中有許多的功能不同的寄存器,其中與中斷相關的寄存器有以下幾個:

寄存器名稱 作用
Status 處理器狀態與控制寄存器,在該寄存器中與中斷向量入口地址相關的爲BEV位域
IntCtl 中斷系統狀態與控制寄存器,該寄存器中的VS位域代表了各個中斷向量入口地址的間距
Cause 存放上次例外原因(MIPS將中斷也歸類爲例外的一種),其中IV位域表示中斷例外向量入口控制位(1:使用特殊中斷向量0x200;0:使用通用例外向量0x180),ExcCode位域表示例外類型(0x00:中斷)
EBase 例外入口基址寄存器,其中Exception Base位域左移12位爲例外入口向量基址

當發生中斷時,內核會根據EBase寄存器以及偏移量來跳轉到異常處理入口地址處(因爲中斷也術語異常的一種形式),而關於EBase和偏移量的大小則是根據Status和Cause寄存器來確定的,如下:

例外向量基址

例外 Status.BEV=0 Status.BEV=1
其它例外情況 EBase 63…12 or 0x000 0xFFFF.FFFF.BFC0.0200

例外向量偏移

例外 向量偏移
中斷(Cause.IV=0) 0x180
中斷(IntCtl.VS=0 且 Cause.IV=1) 0x200
中斷(Status.BEV=1 且 Cause.IV=1) 0x200
中斷(Cause.IV=1 且 Status.BEV=0 且 IntCtl.VS!=0) 0x200 + (中斷向量號 × (IntCtl.VS or 0xb00000))

這裏的“or”代表或操作。所以當處理器接收到中斷信號後,會根據上述的寄存器跳轉到所對應的異常處理入口。

而在內核中MIPS架構提供的異常處理入口爲except_vec3_generic函數,隨後該函數通過讀取Cause寄存器中的ExCode來判斷是哪種異常類型,並通過全局變量exception_handlers數組來找到對應的入口函數,如果是中斷類型,則會去調用handle_int()函數。關於設置異常處理入口地址,以及根據類型找到對應的處理函數,都是在內核的初始化階段trap_init()函數中來完成的。

void __init trap_init(void)
{
	...
	set_handler(0x180, &except_vec3_generic, 0x80);
	//關於該函數,其內部實現如下:
	//void set_handler(unsigned long offset, void *addr, unsigned long size)
	//{
	//	...
	//	memcpy((void *)(ebase + offset), addr, size);
	//	...
	//}
	//結合上邊對寄存器的描述,可以看出內核將except_vec3_generic的入口地址複製到了ebase+offset處,即異常處理入口地址
	...
	set_except_vector(EXCCODE_INT, using_rollback_handler() ? rollback_handle_int : handle_int);
	//該函數的內部實現如下:
	//void __init *set_except_vector(int n, void *addr)
	//{
	//	...
	//	old_handler = xchg(&exception_handlers[n], handler);
	//	...
	//}
}

根據上述代碼,可知內核在trap_init中對異常的處理入口,以及異常類型的處理入口進行了設置。關於except_vec3_generic以及handle_int的實現,均在arch/mips/kernel/genex.S文件中。

handle_init函數主要完成中斷處理前期的任務,比如:關閉中斷,保護現場等。隨後調用plat_irq_dispatch函數來路由中斷。plat_irq_dispatch()通過讀取Cause、Status、SR(IM)來得知當前中斷所在的引腳(IP0~IP7)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章