中斷分類
在中斷的多種分類方法中,我們根據(jù)中斷的來源來分類:
內(nèi)部中斷:例如軟件中斷指令, 溢出,除法錯(cuò)誤等。操作系統(tǒng)從用戶態(tài)切換到內(nèi)核態(tài)。都會(huì)在一條指令執(zhí)行完后才會(huì)觸發(fā),因?yàn)闉橥健?/p>
外部中斷:由外設(shè)提供。由于不知道何時(shí)中斷會(huì)到來,因此為異步過程。
外部中斷的整體框架如下圖:
首先我們先了解幾個(gè)名詞:
軟件中斷irq:它是發(fā)生設(shè)備中斷時(shí)處理器從PIC中讀到的中斷號(hào)碼,在內(nèi)核建立的中斷處理框架內(nèi),會(huì)使用這個(gè)irq號(hào)來表示一個(gè)外設(shè)的中斷,并調(diào)用對(duì)應(yīng)的中斷處理例程。
中斷向量表(Vector table):除了外部中斷還有異常,陷阱等異常事件,中斷向量表里面的每一項(xiàng)都是一個(gè)中斷或異常處理函數(shù)的入口地址。外部設(shè)備的中斷常常對(duì)應(yīng)向量表中的某一項(xiàng),這是個(gè)通用的外部中斷處理函數(shù)入口,因此進(jìn)入通用的中斷處理函數(shù)之后,系統(tǒng)必須要知道正在處理的中斷是哪一個(gè)設(shè)備產(chǎn)生的,而這正是由irq決定的。中斷向量表的內(nèi)容由操作系統(tǒng)在初始化階段來填寫。對(duì)于外部中斷,操作系統(tǒng)負(fù)責(zé)實(shí)現(xiàn)一個(gè)通用的外部中斷處理函數(shù),然后把這個(gè)函數(shù)的入口地址翻到中斷向量表中的對(duì)應(yīng)位置。
可編程中斷控制器:PIC(Programmable Interrupt Controller) ,一般可通過處理器進(jìn)行編程配置。
下面是從ARM開發(fā)手冊(cè)當(dāng)中GIC的中斷控制器框架圖:
ARM GIC支持3種類型的中斷:
SGI(Software Generated Interrupt): 軟件產(chǎn)生的中斷,可以用于多核間通信。一個(gè)CPU可以通過寫GIC寄存器給另一個(gè)CPU產(chǎn)生中斷。多核間調(diào)度用的IPI_WAKEUP, IPI_TIMER,.....等都是由SGI產(chǎn)生
PPI(Private Peripheral Interrupt): 某個(gè)CPU私有外設(shè)的中斷,這類外設(shè)的中斷只能發(fā)送給綁定的那個(gè)CPU
SPI(Share Peripheral Interrupt):共享外設(shè)中斷,這類外設(shè)的中斷可以路由到任何一個(gè)CPU
Linux外部中斷處理程序框架
外部中斷處理程序架構(gòu)如下:
內(nèi)核中斷向量表初始化過程(下文在不做特別說明的狀況下均以Linux5.0內(nèi)核為版本進(jìn)行分析)
//arch/arm64/kernel/entry.S
/**Exception vectors*/
.pushsection ".entry.text", "ax"
.align 11
ENTRY(vectors)
kernel_ventry 1, sync_invalid // Synchronous EL1t
kernel_ventry 1, irq_invalid // IRQ EL1t
kernel_ventry 1, fiq_invalid // FIQ EL1t
kernel_ventry 1, error_invalid // Error EL1t
kernel_ventry 1, sync // Synchronous EL1h
kernel_ventry 1, irq // IRQ EL1h
kernel_ventry 1, fiq_invalid // FIQ EL1h
kernel_ventry 1, error // Error EL1h
kernel_ventry 0, sync // Synchronous 64-bit EL0
kernel_ventry 0, irq // IRQ 64-bit EL0
kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0
kernel_ventry 0, error // Error 64-bit EL0
#ifdef CONFIG_COMPAT
kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0
kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0
kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0
kernel_ventry 0, error_compat, 32 // Error 32-bit EL0
#else
kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0
kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0
kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0
kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0
#endifEND(vectors)
對(duì)kernel_ventry宏做解讀:.align=7,說明該段代碼是以2^7=128字節(jié)對(duì)齊的,這和向量表中每一個(gè)offset的大小是一致的
代碼看似非常復(fù)雜,其實(shí)最終跳轉(zhuǎn)到了b el()\\el()_\\label, 翻譯一下,其實(shí)就是跳轉(zhuǎn)到了如下這樣的函數(shù)中
el1_sync_invalid
el1_irq_invalid
el1_fiq_invalid
el1_error_invalid
el1_sync:
...
el1_irq:
kernel_entry 1
enable_da_f
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
irq_handler
#ifdef CONFIG_PREEMPT
ldr x24, [tsk, #TSK_TI_PREEMPT] // get preempt count
cbnz x24, 1f // preempt count != 0
bl el1_preempt
.macro irq_handler
ldr_l x1, handle_arch_irq
mov x0, sp
irq_stack_entry
blr x1
irq_stack_exit
...
el1_fiq:
...
el1_error:
...
el0_sync:
...
el0_irq:
...
el0_fiq:
...
el0_error:
...
ARM64中,當(dāng)外部中斷發(fā)生時(shí), 會(huì)調(diào)用el1_irq, 在上述20行中,會(huì)call handle_arch_irq. 此為一個(gè)函數(shù)指針, 在GIC中斷控制器在做初始化時(shí)設(shè)置的. 而中斷來臨時(shí),handle_domain_irq會(huì)被call 到.
//kernel/irq/handle.c
int __init set_handle_irq(void(*handle_irq)(stuct pt_regs*)){
....
handle_arch_irq = handle_irq;
....
}
//drivers/irqchip/irq-gic.c
static void _exception_irq_entry gic_handle_irq(struct pt_regs* regs){
.....
//調(diào)用對(duì)應(yīng)的通用中斷處理函數(shù).
handle_domain_irq(gic- >domain,irqnr,regs);
.....
}
//drivers/irqchip/irq-gic.c
static int __init __gic_init_bases(struct gic_chip_data *gic,int irq_start, struct fwnode_handle *handle){
....
set_handle_irq(gic_handle_irq);//中斷向量指向的irq, 匯編語言會(huì)調(diào)用到此函數(shù)
....
}
//drivers/irqchip/irq-gic.c
static int gic_init_bases(struct gic_chip_data *gic, int irq_start,struct fwnode_handle *handle){
....
//分配irq_desc, 里面會(huì)call gic_irq_domain_map函數(shù), 設(shè)置一吃handle的電信號(hào)處理函數(shù)
irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,numa_node_id());
....
}
//driver/irqchip/irq-gic.c
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,irq_hw_number_t hw){
struct gic_chip_data *gic = d- >host_data;
//根據(jù)中斷號(hào)的不同,處理不同的handle, driver 的實(shí)現(xiàn)依賴于硬件的GIC 硬件spec, 參考規(guī)范
if (hw < 32) {
irq_set_percpu_devid(irq);
//設(shè)置真正的PIC的一級(jí)handle,處理PIC的電信號(hào)
irq_domain_set_info(d, irq, hw, &gic- >chip, d- >host_data,handle_percpu_devid_irq, NULL, NULL);
irq_set_status_flags(irq, IRQ_NOAUTOEN);
} else {
//設(shè)置真正的PIC的一級(jí)handle,處理PIC的電信號(hào)
irq_domain_set_info(d, irq, hw, &gic- >chip, d- >host_data,handle_fasteoi_irq, NULL, NULL);
irq_set_probe(irq);
irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
}
return 0;
}
//kernel/irq/chip.c
//irq > 32的電信號(hào)處理函數(shù), 下面幾個(gè)是函數(shù)的層級(jí),一步一步call到最終bsp注冊(cè)處理的函數(shù)當(dāng)中.
void handle_fasteio_iq(struct irq_desc *desc){
...
handle_irq_event(desc);
...
}
//kernel/irq/handle.c
irqreturn_t handle_irq_event(struct irq_desc *desc)){
...
handle_irq_event_percpu(desc);
...
}
//kernel/irq/handle.c
irqreturn_t handle_irq_event_percpu(struct irq_desc *desc){
...
retval = __handle_irq_event_percpu(desc, &flags);
add_interrupt_randomness(desc- >irq_data.irq, flags);
...
}
//kernel/irq/handle.c
//電信號(hào)上半部最終處理的函數(shù),這里面會(huì)去call bsp 工程師注冊(cè)的上半部的函數(shù)
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags){
...
for_each_action_of_desc(desc, action) {
...
res = action- >handler(irq, action- >dev_id);//設(shè)備driver的上半部callback函數(shù)
....
switch (res) {
//上半部的返回值決定了下半部的觸發(fā)方式,下節(jié)從BSP角度去看會(huì)用到.
case IRQ_WAKE_THREAD:
/**Catch drivers which return WAKE_THREAD but did not set up a thread function*/
if (unlikely(!action- >thread_fn)) {
warn_no_thread(irq, action);
break;
}
__irq_wake_thread(desc, action);//喚醒線程化傳輸?shù)南掳氩縯hread_fn
/* Fall through to add to randomness */
case IRQ_HANDLED:
*flags |= action- >flags;
break;
....
}
}
...
}
//kernel/irq/handle.c
//喚醒線程化傳輸?shù)南掳氩縯hread_fn
void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action){
...
wake_up_process(action- >thread);
}