Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基說明:html
【原創】Linux中斷子系統(一)-中斷控制器及驅動分析講到了底層硬件GIC驅動,以及Arch-Specific的中斷代碼,本文將研究下通用的中斷處理的過程,屬於硬件無關層。固然,我仍是建議你看一下上篇文章。linux
這篇文章會解答兩個問題:數組
中斷註冊
)?中斷處理
)?先來看一下總的數據結構,核心是圍繞着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
匹配;app
圖中剩餘部分,在設備申請註冊中斷的過程當中進行設置,好比struct irqaction
中handler
的設置,這個用於指向咱們設備驅動程序中的中斷處理函數了;dom
中斷的處理主要有如下幾個功能模塊:函數
Linux irq
中斷號的映射,並建立好irq_desc
中斷描述符;irq_desc
,並將設備的中斷處理函數添加到irq_desc
中;Linux irq
中斷號,找到對應的irq_desc
,最終調用到設備的中斷處理函數;上述的描述比較簡單,更詳細的過程,往下看吧。工具
這一次,讓咱們以問題的方式來展開:
先來讓咱們回答第一個問題:用戶是怎麼使用中斷的?
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
中斷號映射);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
;上述函數執行完成後,完成了兩大工做:
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;irq_desc
已經建立好,能夠經過irq_to_desc
接口去獲取對應的irq_desc
;irqaction
,並初始化該結構體中的各個字段,其中包括傳入的中斷處理函數賦值給對應的字段;__setup_irq
用於完成中斷的相關設置,包括中斷線程化的處理:
CONFIG_IRQ_FORCED_THREADING
,引導參數傳入threadirqs
時,則除了IRQF_NO_THREAD
外的中斷,其餘的都將強制線程化處理;irqaction
將鏈接成鏈表,每一個irqaction
都有thread_mask
位圖字段,當全部共享中斷都處理完成後才能unmask
中斷,解除中斷屏蔽;當完成中斷的註冊後,全部結構的組織關係都已經創建好,剩下的工做就是當信號來臨時,進行中斷的處理工做。
來回顧一下【原創】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
可中斷睡眠狀態;中斷的處理,整體來講能夠分爲兩部分來看:
irq_desc
中斷描述符創建好鏈接關係,這個過程就包括:中斷源信息的解析(設備樹),硬件中斷號到Linux中斷號的映射關係、irq_desc
結構的分配及初始化(內部各個結構的組織關係)、中斷的註冊(填充irq_desc
結構,包括handler處理函數)等,總而言之,就是完成靜態關係建立,爲中斷處理作好準備;歡迎關注我的公衆號,不按期分享Linux內核機制文章