背景
Read the fucking source code!--By 魯迅A picture is worth a thousand words.--By 高爾基
說明:
- Kernel版本:4.14
- ARM64處理器,Contex-A53,雙核
- 使用工具:Source Insight 3.5, Visio
1. 概述
【原創】Linux中斷子系統(一)-中斷控制器及驅動分析講到了底層硬體GIC驅動,以及Arch-Specific的中斷代碼,本文將研究下通用的中斷處理的程序,屬于硬體無關層,當然,我還是建議你看一下上篇文章,
這篇文章會解答兩個問題:
- 用戶是怎么使用中斷的(
中斷注冊)? - 外設觸發中斷信號時,最終是怎么呼叫到中斷handler的(
中斷處理)?
2. 資料結構分析
先來看一下總的資料結構,核心是圍繞著struct irq_desc來展開:

-
Linux內核的中斷處理,圍繞著中斷描述符結構
struct irq_desc展開,內核提供了兩種中斷描述符組織形式:- 打開
CONFIG_SPARSE_IRQ宏(中斷編號不連續),中斷描述符以radix-tree來組織,用戶在初始化時進行動態分配,然后再插入radix-tree中; - 關閉
CONFIG_SPARSE_IRQ宏(中斷編號連續),中斷描述符以陣列的形式組織,并且已經分配好; - 不管哪種形式,最終都可以通過
linux irq號來找到對應的中斷描述符;
- 打開
-
圖的左側灰色部分,主要在中斷控制器驅動中進行初始化設定,包括各個結構中函式指標的指向等,其中
struct irq_chip用于對中斷控制器的硬體操作,struct irq_domain與中斷控制器對應,完成的作業是硬體中斷號到Linux irq的映射; -
圖的上側灰色部分,中斷描述符的創建(這里指
CONFIG_SPARSE_IRQ),主要在獲取設備中斷資訊的程序中完成的,從而讓設備樹中的中斷能與具體的中斷描述符irq_desc匹配; -
圖中剩余部分,在設備申請注冊中斷的程序中進行設定,比如
struct irqaction中handler的設定,這個用于指向我們設備驅動程式中的中斷處理函式了;
中斷的處理主要有以下幾個功能模塊:
- 硬體中斷號到
Linux irq中斷號的映射,并創建好irq_desc中斷描述符; - 中斷注冊時,先獲取設備的中斷號,根據中斷號找到對應的
irq_desc,并將設備的中斷處理函式添加到irq_desc中; - 設備觸發中斷信號時,根據硬體中斷號得到
Linux irq中斷號,找到對應的irq_desc,最終呼叫到設備的中斷處理函式;
上述的描述比較簡單,更詳細的程序,往下看吧,
3. 流程分析
3.1 中斷注冊
這一次,讓我們以問題的方式來展開:
先來讓我們回答第一個問題:用戶是怎么使用中斷的?
- 熟悉設備驅動的同學應該都清楚,經常會在驅動程式中呼叫
request_irq()介面或者request_threaded_irq()介面來注冊設備的中斷處理函式; request_irq()/request_threaded_irq介面中,都需要用到irq,也就是中斷號,那么這個中斷號是從哪里來的呢?它是Linux irq,它又是如何映射到具體的硬體設備的中斷號的呢?
先來看第二個問題:設備硬體中斷號到
Linux irq中斷號的映射

- 硬體設備的中斷資訊都在設備樹
device tree中進行了描述,在系統啟動程序中,這些資訊都已經加載到記憶體中并得到了決議; - 驅動中通常會使用
platform_get_irq或irq_of_parse_and_map介面,去根據設備樹的資訊去創建映射關系(硬體中斷號到linux irq中斷號映射); - 【原創】Linux中斷子系統(一)-中斷控制器及驅動分析提到過
struct irq_domain用于完成映射作業,因此在irq_create_fwspec_mapping介面中,會先去找到匹配的irq domain,再去回呼該irq domain中的函式集,通常irq domain都是在中斷控制器驅動中初始化的,以ARM GICv2為例,最侄訓呼到gic_irq_domain_hierarchy_ops中的函式; - 如果已經創建好了映射,那么可以直接進行回傳
linux irq中斷號了,否則的話需要irq_domain_alloc_irqs來創建映射關系; irq_domain_alloc_irqs完成兩個作業:- 針對
linux irq中斷號創建一個irq_desc中斷描述符; - 呼叫
domain->ops->alloc函式來完成映射,在ARM GICv2驅動中對應gic_irq_domain_alloc函式,這個函式很關鍵,所以下文介紹一下;
- 針對
gic_irq_domain_alloc函式如下:

gic_irq_domain_translate:負責決議出設備樹中描述的中斷號和中斷觸發型別(邊緣觸發、電平觸發等);gic_irq_domain_map:將硬體中斷號和linux中斷號系結到一個結構中,也就完成了映射,此外還系結了irq_desc結構中的其他欄位,最重要的是設定了irq_desc->handle_irq的函式指標,這個最終是中斷回應時往上執行的入口,這個是關鍵,下文講述中斷處理程序時還會提到;- 根據硬體中斷號的范圍設定
irq_desc->handle_irq的指標,共享中斷入口為handle_fasteoi_irq,私有中斷入口為handle_percpu_devid_irq;
上述函式執行完成后,完成了兩大作業:
- 硬體中斷號與Linux中斷號完成映射,并為Linux中斷號創建了
irq_desc中斷描述符; - 資料結構的系結及初始化,關鍵的地方是設定了中斷處理往上執行的入口;
再看第一個問題:中斷是怎么來注冊的?
設備驅動中,獲取到了irq中斷號后,通常就會采用request_irq/request_threaded_irq來注冊中斷,其中request_irq用于注冊普通處理的中斷,request_threaded_irq用于注冊執行緒化處理的中斷;
在講具體的注冊流程前,先看一下主要的中斷標志位:
#define IRQF_SHARED 0x00000080 //多個設備共享一個中斷號,需要外設硬體支持
#define IRQF_PROBE_SHARED 0x00000100 //中斷處理程式允許sharing mismatch發生
#define __IRQF_TIMER 0x00000200 //時鐘中斷
#define IRQF_PERCPU 0x00000400 //屬于特定CPU的中斷
#define IRQF_NOBALANCING 0x00000800 //禁止在CPU之間進行中斷均衡處理
#define IRQF_IRQPOLL 0x00001000 //中斷被用作輪訓
#define IRQF_ONESHOT 0x00002000 //一次性觸發的中斷,不能嵌套,1)在硬體中斷處理完成后才能打開中斷;2)在中斷執行緒化中保持關閉狀態,直到該中斷源上的所有thread_fn函式都執行完
#define IRQF_NO_SUSPEND 0x00004000 //系統休眠喚醒操作中,不關閉該中斷
#define IRQF_FORCE_RESUME 0x00008000 //系統喚醒程序中必須強制打開該中斷
#define IRQF_NO_THREAD 0x00010000 //禁止中斷執行緒化
#define IRQF_EARLY_RESUME 0x00020000 //系統喚醒程序中在syscore階段resume,而不用等到設備resume階段
#define IRQF_COND_SUSPEND 0x00040000 //與NO_SUSPEND的用戶共享中斷時,執行本設備的中斷處理函式

request_irq也是呼叫request_threaded_irq,只是在傳參的時候,執行緒處理函式thread_fn函式設定成NULL;- 由于在硬體中斷號和Linux中斷號完成映射后,
irq_desc已經創建好,可以通過irq_to_desc介面去獲取對應的irq_desc; - 創建
irqaction,并初始化該結構體中的各個欄位,其中包括傳入的中斷處理函式賦值給對應的欄位; __setup_irq用于完成中斷的相關設定,包括中斷執行緒化的處理:- 中斷執行緒化用于減少系統關中斷的時間,增強系統的實時性;
- ARM64默認開啟了
CONFIG_IRQ_FORCED_THREADING,引導引數傳入threadirqs時,則除了IRQF_NO_THREAD外的中斷,其他的都將強制執行緒化處理; - 中斷執行緒化會為每個中斷都創建一個內核執行緒,如果中斷進行共享,對應
irqaction將連接成鏈表,每個irqaction都有thread_mask位圖欄位,當所有共享中斷都處理完成后才能unmask中斷,解除中斷屏蔽;
3.2 中斷處理
當完成中斷的注冊后,所有結構的組織關系都已經建立好,剩下的作業就是當信號來臨時,進行中斷的處理作業,
來回顧一下【原創】Linux中斷子系統(一)-中斷控制器及驅動分析中的Arch-specific處理流程:

- 中斷收到之后,首先會跳轉到例外向量表的入口處,進而逐級進行回呼處理,最終呼叫到
generic_handle_irq來進行中斷處理,
generic_handle_irq處理如下圖:

generic_handle_irq函式最侄訓呼叫到desc->handle_irq(),這個也就是對應到上文中在建立映射關系的程序中,呼叫irq_domain_set_info函式,設定好了函式指標,也就是handle_fasteoi_irq和handle_percpu_devid_irq;handle_fasteoi_irq:處理共享中斷,并且遍歷irqaction鏈表,逐個呼叫action->handler()函式,這個函式正是設備驅動程式呼叫request_irq/request_threaded_irq介面注冊的中斷處理函式,此外如果中斷執行緒化處理的話,還會呼叫__irq_wake_thread()喚醒內核執行緒;handle_percpu_devid_irq:處理per-CPU中斷處理,在這個程序中會分別呼叫中斷控制器的處理函式進行硬體操作,該函式呼叫action->handler()來進行中斷處理;
來看看中斷執行緒化處理后的喚醒流程吧__handle_irq_event_percpu->__irq_wake_thread:

__handle_irq_event_percpu->__irq_wake_thread將喚醒irq_thread中斷內核執行緒;irq_thread內核執行緒,將根據是否為強制中斷執行緒化對函式指標handler_fn進行初始化,以便后續進行呼叫;irq_thread內核執行緒將while(!irq_wait_for_interrupt)回圈進行中斷的處理,當滿足條件時,執行handler_fn,在該函式中最終呼叫action->thread_fn,也就是完成了中斷的處理;irq_wait_for_interrupt函式,將會判斷中斷執行緒的喚醒條件,如果滿足了,則將當前任務設定成TASK_RUNNING狀態,并回傳0,這樣就能執行中斷的處理,否則就呼叫schedule()進行調度,讓出CPU,并將任務設定成TASK_INTERRUPTIBLE可中斷睡眠狀態;
3.3 總結
中斷的處理,總體來說可以分為兩部分來看:
- 從上到下:圍繞
irq_desc中斷描述符建立好連接關系,這個程序就包括:中斷源資訊的決議(設備樹),硬體中斷號到Linux中斷號的映射關系、irq_desc結構的分配及初始化(內部各個結構的組織關系)、中斷的注冊(填充irq_desc結構,包括handler處理函式)等,總而言之,就是完成靜態關系創建,為中斷處理做好準備; - 從下到上,當外設觸發中斷信號時,中斷控制器接收到信號并發送到處理器,此時處理器進行例外模式切換,并逐步從處理器架構相關代碼逐級回呼,如果涉及到中斷執行緒化,則還需要進行中斷內核執行緒的喚醒操作,最終完成中斷處理函式的執行,
歡迎關注個人公眾號,不定期分享Linux內核機制文章

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/61702.html
標籤:Linux
下一篇:在已經編譯安裝好php7場景下,install gd庫 with free-type (解決Call to undefined function imagettftext())
