【原創】Linux中斷子系統(二)-通用框架處理
阿新 • • 發佈:2020-06-06
# 背景
- `Read the fucking source code!` --By 魯迅
- `A picture is worth a thousand words.` --By 高爾基
說明:
1. Kernel版本:4.14
2. ARM64處理器,Contex-A53,雙核
3. 使用工具:Source Insight 3.5, Visio
# 1. 概述
[【原創】Linux中斷子系統(一)-中斷控制器及驅動分析](https://www.cnblogs.com/LoyenWang/p/12996812.html)講到了底層硬體GIC驅動,以及Arch-Specific的中斷程式碼,本文將研究下通用的中斷處理的過程,屬於硬體無關層。當然,我還是建議你看一下上篇文章。
這篇文章會解答兩個問題:
1. 使用者是怎麼使用中斷的(`中斷註冊`)?
2. 外設觸發中斷訊號時,最終是怎麼呼叫到中斷handler的(`中斷處理`)?
# 2. 資料結構分析
先來看一下總的資料結構,核心是圍繞著`struct irq_desc`來展開:
![](https://img2020.cnblogs.com/blog/1771657/202006/1771657-20200605223624401-1747243848.png)
- Linux核心的中斷處理,圍繞著中斷描述符結構`struct irq_desc`展開,核心提供了兩種中斷描述符組織形式:
1. 開啟`CONFIG_SPARSE_IRQ`巨集(中斷編號不連續),中斷描述符以`radix-tree`來組織,使用者在初始化時進行動態分配,然後再插入`radix-tree`中;
2. 關閉`CONFIG_SPARSE_IRQ`巨集(中斷編號連續),中斷描述符以陣列的形式組織,並且已經分配好;
3. 不管哪種形式,最終都可以通過`linux irq`號來找到對應的中斷描述符;
- 圖的左側灰色部分,主要在中斷控制器驅動中進行初始化設定,包括各個結構中函式指標的指向等,其中`struct irq_chip`用於對中斷控制器的硬體操作,`struct irq_domain`與中斷控制器對應,完成的工作是硬體中斷號到`Linux irq`的對映;
- 圖的上側灰色部分,中斷描述符的建立(這裡指`CONFIG_SPARSE_IRQ`),主要在獲取裝置中斷資訊的過程中完成的,從而讓裝置樹中的中斷能與具體的中斷描述符`irq_desc`匹配;
- 圖中剩餘部分,在裝置申請註冊中斷的過程中進行設定,比如`struct irqaction`中`handler`的設定,這個用於指向我們裝置驅動程式中的中斷處理函數了;
中斷的處理主要有以下幾個功能模組:
1. 硬體中斷號到`Linux irq`中斷號的對映,並建立好`irq_desc`中斷描述符;
2. 中斷註冊時,先獲取裝置的中斷號,根據中斷號找到對應的`irq_desc`,並將裝置的中斷處理函式新增到`irq_desc`中;
3. 裝置觸發中斷訊號時,根據硬體中斷號得到`Linux irq`中斷號,找到對應的`irq_desc`,最終呼叫到裝置的中斷處理函式;
上述的描述比較簡單,更詳細的過程,往下看吧。
# 3. 流程分析
## 3.1 中斷註冊
這一次,讓我們以問題的方式來展開:
先來讓我們回答第一個問題:使用者是怎麼使用中斷的?
1. 熟悉裝置驅動的同學應該都清楚,經常會在驅動程式中呼叫`request_irq()`介面或者`request_threaded_irq()`介面來註冊裝置的中斷處理函式;
2. `request_irq()/request_threaded_irq`介面中,都需要用到`irq`,也就是中斷號,那麼這個中斷號是從哪裡來的呢?它是`Linux irq`,它又是如何對映到具體的硬體裝置的中斷號的呢?
> 先來看第二個問題:裝置硬體中斷號到`Linux irq`中斷號的對映
![](https://img2020.cnblogs.com/blog/1771657/202006/1771657-20200605222653356-1874117507.png)
- 硬體裝置的中斷資訊都在裝置樹`device tree`中進行了描述,在系統啟動過程中,這些資訊都已經載入到記憶體中並得到了解析;
- 驅動中通常會使用`platform_get_irq`或`irq_of_parse_and_map`介面,去根據裝置樹的資訊去建立對映關係(硬體中斷號到`linux irq`中斷號對映);
- [【原創】Linux中斷子系統(一)-中斷控制器及驅動分析](https://www.cnblogs.com/LoyenWang/p/12996812.html)提到過`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`完成兩個工作:
1. 針對`linux irq`中斷號建立一個`irq_desc`中斷描述符;
2. 呼叫`domain->ops->alloc`函式來完成對映,在`ARM GICv2`驅動中對應`gic_irq_domain_alloc`函式,這個函式很關鍵,所以下文介紹一下;
`gic_irq_domain_alloc`函式如下:
![](https://img2020.cnblogs.com/blog/1771657/202006/1771657-20200605222839660-1799351554.png)
- `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`;
上述函式執行完成後,完成了兩大工作:
1. 硬體中斷號與Linux中斷號完成對映,併為Linux中斷號建立了`irq_desc`中斷描述符;
2. 資料結構的繫結及初始化,關鍵的地方是設定了中斷處理往上執行的入口;
> 再看第一個問題:中斷是怎麼來註冊的?
裝置驅動中,獲取到了`irq`中斷號後,通常就會採用`request_irq/request_threaded_irq`來註冊中斷,其中`request_irq`用於註冊普通處理的中斷,`request_threaded_irq`用於註冊執行緒化處理的中斷;
在講具體的註冊流程前,先看一下主要的中斷標誌位:
```c
#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的使用者共享中斷時,執行本裝置的中斷處理函式
```
![](https://img2020.cnblogs.com/blog/1771657/202006/1771657-20200605223042609-247616444.png)
- `request_irq`也是呼叫`request_threaded_irq`,只是在傳參的時候,執行緒處理函式`thread_fn`函式設定成NULL;
- 由於在硬體中斷號和Linux中斷號完成對映後,`irq_desc`已經建立好,可以通過`irq_to_desc`介面去獲取對應的`irq_desc`;
- 建立`irqaction`,並初始化該結構體中的各個欄位,其中包括傳入的中斷處理函式賦值給對應的欄位;
- `__setup_irq`用於完成中斷的相關設定,包括中斷執行緒化的處理:
1. 中斷執行緒化用於減少系統關中斷的時間,增強系統的實時性;
2. ARM64預設開啟了`CONFIG_IRQ_FORCED_THREADING`,引導引數傳入`threadirqs`時,則除了`IRQF_NO_THREAD`外的中斷,其他的都將強制執行緒化處理;
3. 中斷執行緒化會為每個中斷都建立一個核心執行緒,如果中斷進行共享,對應`irqaction`將連線成連結串列,每個`irqaction`都有`thread_mask`點陣圖欄位,當所有共享中斷都處理完成後才能`unmask`中斷,解除中斷遮蔽;
## 3.2 中斷處理
當完成中斷的註冊後,所有結構的組織關係都已經建立好,剩下的工作就是當訊號來臨時,進行中斷的處理工作。
來回顧一下[【原創】Linux中斷子系統(一)-中斷控制器及驅動分析](https://www.cnblogs.com/LoyenWang/p/12996812.html)中的Arch-specific處理流程:
![](https://img2020.cnblogs.com/blog/1771657/202006/1771657-20200605223246669-1755658498.png)
- 中斷收到之後,首先會跳轉到異常向量表的入口處,進而逐級進行回撥處理,最終呼叫到`generic_handle_irq`來進行中斷處理。
`generic_handle_irq`處理如下圖:
![](https://img2020.cnblogs.com/blog/1771657/202006/1771657-20200605223306409-238446169.png)
- `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`:
![](https://img2020.cnblogs.com/blog/1771657/202006/1771657-20200605223341892-243579938.png)
- `__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 總結
中斷的處理,總體來說可以分為兩部分來看:
1. 從上到下:圍繞`irq_desc`中斷描述符建立好連線關係,這個過程就包括:中斷源資訊的解析(裝置樹),硬體中斷號到Linux中斷號的對映關係、`irq_desc`結構的分配及初始化(內部各個結構的組織關係)、中斷的註冊(填充`irq_desc`結構,包括handler處理函式)等,總而言之,就是完成靜態關係建立,為中斷處理做好準備;
2. 從下到上,當外設觸發中斷訊號時,中斷控制器接收到訊號併發送到處理器,此時處理器進行異常模式切換,並逐步從處理器架構相關程式碼逐級回撥。如果涉及到中斷執行緒化,則還需要進行中斷核心執行緒的喚醒操作,最終完成中斷處理函式的執行。
歡迎關注個人公眾號,不定期分享Linux核心機制文章
![](https://img2020.cnblogs.com/blog/1771657/202006/1771657-20200605223526643-13623665