深入分析Linux中斷子系統(tǒng)之通用框架處理
說明:
Kernel版本:4.14
ARM64處理器,Contex-A53,雙核
使用工具:Source Insight 3.5, Visio
1. 概述
《Linux中斷子系統(tǒng)(一)-中斷控制器及驅(qū)動分析》
講到了底層硬件GIC驅(qū)動,以及Arch-Specific的中斷代碼,本文將研究下通用的中斷處理的過程,屬于硬件無關(guān)層。當(dāng)然,我還是建議你看一下上篇文章。
這篇文章會解答兩個問題:
用戶是怎么使用中斷的(
中斷注冊
)?外設(shè)觸發(fā)中斷信號時,最終是怎么調(diào)用到中斷handler的(
中斷處理
)?
2. 數(shù)據(jù)結(jié)構(gòu)分析
先來看一下總的數(shù)據(jù)結(jié)構(gòu),核心是圍繞著struct irq_desc
來展開:

Linux內(nèi)核的中斷處理,圍繞著中斷描述符結(jié)構(gòu)
struct irq_desc
展開,內(nèi)核提供了兩種中斷描述符組織形式:打開
CONFIG_SPARSE_IRQ
宏(中斷編號不連續(xù)),中斷描述符以radix-tree
來組織,用戶在初始化時進行動態(tài)分配,然后再插入radix-tree
中;關(guān)閉
CONFIG_SPARSE_IRQ
宏(中斷編號連續(xù)),中斷描述符以數(shù)組的形式組織,并且已經(jīng)分配好;不管哪種形式,都可以通過
linux irq
號來找到對應(yīng)的中斷描述符;圖的左側(cè)灰色部分,主要在中斷控制器驅(qū)動中進行初始化設(shè)置,包括各個結(jié)構(gòu)中函數(shù)指針的指向等,其中
struct irq_chip
用于對中斷控制器的硬件操作,struct irq_domain
與中斷控制器對應(yīng),完成的工作是硬件中斷號到Linux irq
的映射;圖的上側(cè)灰色部分,中斷描述符的創(chuàng)建(這里指
CONFIG_SPARSE_IRQ
),主要在獲取設(shè)備中斷信息的過程中完成的,從而讓設(shè)備樹中的中斷能與具體的中斷描述符irq_desc
匹配;圖中剩余部分,在設(shè)備申請注冊中斷的過程中進行設(shè)置,比如
struct irqaction
中handler
的設(shè)置,這個用于指向我們設(shè)備驅(qū)動程序中的中斷處理函數(shù)了;
中斷的處理主要有以下幾個功能模塊:
硬件中斷號到
Linux irq
中斷號的映射,并創(chuàng)建好irq_desc
中斷描述符;中斷注冊時,先獲取設(shè)備的中斷號,根據(jù)中斷號找到對應(yīng)的
irq_desc
,并將設(shè)備的中斷處理函數(shù)添加到irq_desc
中;設(shè)備觸發(fā)中斷信號時,根據(jù)硬件中斷號得到
Linux irq
中斷號,找到對應(yīng)的irq_desc
,最終調(diào)用到設(shè)備的中斷處理函數(shù);
上述的描述比較簡單,更詳細的過程,往下看吧。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ??


3. 流程分析
3.1 中斷注冊
這一次,讓我們以問題的方式來展開:先來讓我們回答第一個問題:用戶是怎么使用中斷的?
熟悉設(shè)備驅(qū)動的同學(xué)應(yīng)該都清楚,經(jīng)常會在驅(qū)動程序中調(diào)用
request_irq()
接口或者request_threaded_irq()
接口來注冊設(shè)備的中斷處理函數(shù);request_irq()/request_threaded_irq
接口中,都需要用到irq
,也就是中斷號,那么這個中斷號是從哪里來的呢?它是Linux irq
,它又是如何映射到具體的硬件設(shè)備的中斷號的呢?
先來看第二個問題:設(shè)備硬件中斷號到
Linux irq
中斷號的映射

硬件設(shè)備的中斷信息都在設(shè)備樹
device tree
中進行了描述,在系統(tǒng)啟動過程中,這些信息都已經(jīng)加載到內(nèi)存中并得到了解析;驅(qū)動中通常會使用
platform_get_irq
或irq_of_parse_and_map
接口,去根據(jù)設(shè)備樹的信息去創(chuàng)建映射關(guān)系(硬件中斷號到linux irq
中斷號映射);《Linux中斷子系統(tǒng)(一)-中斷控制器及驅(qū)動分析》
提到過struct irq_domain
用于完成映射工作,因此在irq_create_fwspec_mapping
接口中,會先去找到匹配的irq domain
,再去回調(diào)該irq domain
中的函數(shù)集,通常irq domain
都是在中斷控制器驅(qū)動中初始化的,以ARM GICV2
為例,最終回調(diào)到gic_irq_domain_hierarchy_ops
中的函數(shù);如果已經(jīng)創(chuàng)建好了映射,那么可以直接進行返回
linux irq
中斷號了,否則的話需要irq_domain_alloc_irqs
來創(chuàng)建映射關(guān)系;irq_domain_alloc_irqs
完成兩個工作:針對
linux irq
中斷號創(chuàng)建一個irq_desc
中斷描述符;調(diào)用
domain->ops->alloc
函數(shù)來完成映射,在ARM GICV2
驅(qū)動中對應(yīng)gic_irq_domain_alloc
函數(shù),這個函數(shù)很關(guān)鍵,所以下文介紹一下;
gic_irq_domain_alloc
函數(shù)如下:

gic_irq_domain_translate
:負責(zé)解析出設(shè)備樹中描述的中斷號和中斷觸發(fā)類型(邊緣觸發(fā)、電平觸發(fā)等);gic_irq_domain_map
:將硬件中斷號和linux中斷號綁定到一個結(jié)構(gòu)中,也就完成了映射,此外還綁定了irq_desc
結(jié)構(gòu)中的其他字段,最重要的是設(shè)置了irq_desc->handle_irq
的函數(shù)指針,這個最終是中斷響應(yīng)時往上執(zhí)行的入口,這個是關(guān)鍵,下文講述中斷處理過程時還會提到;根據(jù)硬件中斷號的范圍設(shè)置
irq_desc->handle_irq
的指針,共享中斷入口為handle_fasteoi_irq
,私有中斷入口為handle_percpu_devid_irq
;
上述函數(shù)執(zhí)行完成后,完成了兩大工作:
硬件中斷號與Linux中斷號完成映射,并為Linux中斷號創(chuàng)建了
irq_desc
中斷描述符;數(shù)據(jù)結(jié)構(gòu)的綁定及初始化,關(guān)鍵的地方是設(shè)置了中斷處理往上執(zhí)行的入口;
再看第一個問題:中斷是怎么來注冊的?
設(shè)備驅(qū)動中,獲取到了irq
中斷號后,通常就會采用request_irq/request_threaded_irq
來注冊中斷,其中request_irq
用于注冊普通處理的中斷,request_threaded_irq
用于注冊線程化處理的中斷;
在講具體的注冊流程前,先看一下主要的中斷標志位:

request_irq
也是調(diào)用request_threaded_irq
,只是在傳參的時候,線程處理函數(shù)thread_fn
函數(shù)設(shè)置成NULL;由于在硬件中斷號和Linux中斷號完成映射后,
irq_desc
已經(jīng)創(chuàng)建好,可以通過irq_to_desc
接口去獲取對應(yīng)的irq_desc
;創(chuàng)建
irqaction
,并初始化該結(jié)構(gòu)體中的各個字段,其中包括傳入的中斷處理函數(shù)賦值給對應(yīng)的字段;__setup_irq
用于完成中斷的相關(guān)設(shè)置,包括中斷線程化的處理:中斷線程化用于減少系統(tǒng)關(guān)中斷的時間,增強系統(tǒng)的實時性;
ARM64默認開啟了
CONFIG_IRQ_FORCED_THREADING
,引導(dǎo)參數(shù)傳入threadirqs
時,則除了IRQF_NO_THREAD
外的中斷,其他的都將強制線程化處理;中斷線程化會為每個中斷都創(chuàng)建一個內(nèi)核線程,如果中斷進行共享,對應(yīng)
irqaction
將連接成鏈表,每個irqaction
都有thread_mask
位圖字段,當(dāng)所有共享中斷都處理完成后才能unmask
中斷,解除中斷屏蔽;
3.2 中斷處理
當(dāng)完成中斷的注冊后,所有結(jié)構(gòu)的組織關(guān)系都已經(jīng)建立好,剩下的工作就是當(dāng)信號來臨時,進行中斷的處理工作。
來回顧一下《Linux中斷子系統(tǒng)(一)-中斷控制器及驅(qū)動分析》
中的Arch-specific
處理流程:

中斷收到之后,首先會跳轉(zhuǎn)到異常向量表的入口處,進而逐級進行回調(diào)處理,最終調(diào)用到
generic_handle_irq
來進行中斷處理。
generic_handle_irq
處理如下圖:

generic_handle_irq
函數(shù)最終會調(diào)用到desc->handle_irq()
,這個也就是對應(yīng)到上文中在建立映射關(guān)系的過程中,調(diào)用irq_domain_set_info
函數(shù),設(shè)置好了函數(shù)指針,也就是handle_fasteoi_irq
和handle_percpu_devid_irq
;handle_fasteoi_irq
:處理共享中斷,并且遍歷irqaction
鏈表,逐個調(diào)用action->handler()
函數(shù),這個函數(shù)正是設(shè)備驅(qū)動程序調(diào)用request_irq/request_threaded_irq
接口注冊的中斷處理函數(shù),此外如果中斷線程化處理的話,還會調(diào)用__irq_wake_thread()
喚醒內(nèi)核線程;handle_percpu_devid_irq
:處理per-CPU中斷處理,在這個過程中會分別調(diào)用中斷控制器的處理函數(shù)進行硬件操作,該函數(shù)調(diào)用action->handler()
來進行中斷處理;
來看看中斷線程化處理后的喚醒流程吧__handle_irq_event_percpu->__irq_wake_thread
:

__handle_irq_event_percpu->__irq_wake_thread
將喚醒irq_thread
中斷內(nèi)核線程;irq_thread
內(nèi)核線程,將根據(jù)是否為強制中斷線程化對函數(shù)指針handler_fn
進行初始化,以便后續(xù)進行調(diào)用;irq_thread
內(nèi)核線程將while(!irq_wait_for_interrupt)
循環(huán)進行中斷的處理,當(dāng)滿足條件時,執(zhí)行handler_fn
,在該函數(shù)中最終調(diào)用action->thread_fn
,也就是完成了中斷的處理;irq_wait_for_interrupt
函數(shù),將會判斷中斷線程的喚醒條件,如果滿足了,則將當(dāng)前任務(wù)設(shè)置成TASK_RUNNING
狀態(tài),并返回0,這樣就能執(zhí)行中斷的處理,否則就調(diào)用schedule()
進行調(diào)度,讓出CPU,并將任務(wù)設(shè)置成TASK_INTERRUPTIBLE
可中斷睡眠狀態(tài);
3.3 總結(jié)
中斷的處理,總體來說可以分為兩部分來看:
從上到下:圍繞
irq_desc
中斷描述符建立好連接關(guān)系,這個過程就包括:中斷源信息的解析(設(shè)備樹),硬件中斷號到Linux中斷號的映射關(guān)系、irq_desc
結(jié)構(gòu)的分配及初始化(內(nèi)部各個結(jié)構(gòu)的組織關(guān)系)、中斷的注冊(填充irq_desc
結(jié)構(gòu),包括handler處理函數(shù))等,總而言之,就是完成靜態(tài)關(guān)系創(chuàng)建,為中斷處理做好準備;從下到上,當(dāng)外設(shè)觸發(fā)中斷信號時,中斷控制器接收到信號并發(fā)送到處理器,此時處理器進行異常模式切換,并逐步從處理器架構(gòu)相關(guān)代碼逐級回調(diào)。如果涉及到中斷線程化,則還需要進行中斷內(nèi)核線程的喚醒操作,最終完成中斷處理函數(shù)的執(zhí)行。
原文作者:LoyenWang
