返回目錄:《ARM-Linux中斷系統》。
總結:
一從作為一名驅動工程師角度看,用好中斷需要正確認識request_threaded_irq/request_irq關系、中斷臨界區保護、中斷上下半部使用。
二介紹了參與終端三種器件:外設->中斷控制器->CPU,以及中斷控制器和CPU之間拓撲關系:中斷控制器級聯、多核CPU中斷分發、設置中斷和CPU的親和性。
三將中斷子系統軟件架構划分為四塊:通用中斷處理模塊、體系架構相關處理、中斷控制器驅動、外設驅動。
四是一個系列文檔導讀。
原文地址:《Linux kernel的中斷子系統之(一):綜述》
一、前言
一個合格的linux驅動工程師需要對kernel中的中斷子系統有深刻的理解,只有這樣,在寫具體driver的時候才能:
1、正確的使用linux kernel提供的的API,例如最著名的request_threaded_irq(request_irq)接口
Notes:那么request_threaded_irq和request_irq有什么區別呢?《 linux中斷申請之request_threaded_irq 》進行了對比,在lwn.net也有相關文檔《Moving interrupts to threads》。
request_threaded_irq相比request_irq的優點: 1 減少 kernel 延遲時間 2 避免處理中斷時要分辨是在硬體中斷或軟體中斷? 3 更容易為kernel 中斷處理除錯,可能可完全取代tasklet 原本的中斷處理分上半部(硬體中斷處理,必須關閉中斷無法處理新的中斷)跟下半部(軟體中斷處理),因此上半部的硬體中斷處理必須盡可能簡短,讓系統反應速度更快。 request_threaded_irq 是在將上半部的硬件中斷處理縮短為只確定硬體中斷來自我們要處理的裝置,喚醒kernel thread 執行后續中斷任務。 跟傳統top/bottom havles 的差異是threaded_irq 受Linux kernel system的 process scheduling 控制,不會因為寫錯的bottom half 代碼造成整個系統延遲的問題。
2、正確使用同步機制保護驅動代碼中的臨界區
3、正確的使用kernel提供的softirq、tasklet、workqueue等機制來完成具體的中斷處理
Notes:中斷的使用主要關注臨界區保護、上半部避免睡眠和調度、下半部合適處理。
基於上面的原因,我希望能夠通過一系列的文檔來描述清楚linux kernel中的中斷子系統方方面面的知識。一方面是整理自己的思緒,另外一方面,希望能夠對其他的驅動工程師(或者想從事linux驅動工作的工程師)有所幫助。
二、中斷系統相關硬件描述
中斷硬件系統主要有三種器件參與,各個外設、中斷控制器和CPU。各個外設提供irq request line,在發生中斷事件的時候,通過irq request line上的電氣信號向CPU系統請求處理。外設的irq request line太多,CPU需要一個小伙伴幫他,這就是Interrupt controller。Interrupt Controller是連接外設中斷系統和CPU系統的橋梁。根據外設irq request line的多少,Interrupt Controller可以級聯。CPU的主要功能是運算,因此CPU並不處理中斷優先級,那是Interrupt controller的事情。對於CPU而言,一般有兩種中斷請求,例如:對於ARM,是IRQ和FIQ信號線,分別讓ARM進入IRQ mode和FIQ mode。對於X86,有可屏蔽中斷和不可屏蔽中斷。
本章節不是描述具體的硬件,而是使用了HW block這樣的概念。例如CPU HW block是只ARM core或者X86這樣的實際硬件block的一個邏輯描述,實際中,可能是任何可能的CPU block。
1、HW中斷系統的邏輯block圖
我對HW中斷系統之邏輯block diagram的理解如下圖所示:
系統中有若干個CPU block用來接收中斷事件並進行處理,若干個Interrupt controller形成樹狀的結構,匯集系統中所有外設的irq request line,並將中斷事件分發給某一個CPU block進行處理。從接口層面看,主要有兩類接口,一種是中斷接口。有的實現中,具體中斷接口的形態就是一個硬件的信號線,通過電平信號傳遞中斷事件(ARM以及GIC組成的中斷系統就是這么設計的)。有些系統采用了其他的方法來傳遞中斷事件,比如x86+APIC(Advanced Programmable Interrupt Controller)組成的系統,每個x86的核有一個Local APIC,這些Local APIC們通過ICC(Interrupt Controller Communication)bus連接到IO APIC上。IO APIC收集各個外設的中斷,並翻譯成總線上的message,傳遞給某個CPU上的Local APIC。因此,上面的紅色線條也是邏輯層面的中斷信號,可能是實際的PCB上的銅線(或者SOC內部的銅線),也可能是一個message而已。除了中斷接口,CPU和Interrupt Controller之間還需要有控制信息的交流。Interrupt Controller會開放一些寄存器讓CPU訪問、控制。
2、多個Interrupt controller和多個cpu之間的拓撲結構
Interrupt controller有的是支持多個CPU core的(例如GIC、APIC等),有的不支持(例如S3C2410的中斷控制器,X86平台的PIC等)。如果硬件平台中只有一個GIC的話,那么通過控制該GIC的寄存器可以將所有的外設中斷,分發給連接在該interrupt controller上的CPU。如果有多個GIC呢(或者級聯的interrupt controller都支持multi cpu core)?假設我們要設計一個非常復雜的系統,系統中有8個CPU,有2000個外設中斷要處理,這時候你如何設計系統中的interrupt controller?如果使用GIC的話,我們需要兩個GIC(一個GIC最多支持1024個中斷源),一個是root GIC,另外一個是secondary GIC。這時候,你有兩種方案:
Notes:大部分方案采用的架構。
(1)把8個cpu都連接到root GIC上,secondary GIC不接CPU。這時候原本掛接在secondary GIC的外設中斷會輸出到某個cpu,現在,只能是(通過某個cpu interface的irq signal)輸到root GIC的某個SPI上。對於軟件而言,這是一個比較簡單的設計,secondary GIC的cpu interface的設定是固定不變的,永遠是從一個固定的CPU interface輸出到root GIC。這種方案的壞處是:這時候secondary GIC的PPI和SGI都是沒有用的了。此外,在這種設定下,所有連接在secondary GIC上的外設中斷要送達的target CPU是統一處理的,要么送去cpu0,要么cpu 5,不能單獨控制。
(2)當然,你也可以讓每個GIC分別連接4個CPU core,root GIC連接CPU0~CPU3,secondary GIC連接CPU4~CPU7。這種狀態下,連接在root GIC的中斷可以由CPU0~CPU3分擔處理,連接在secondary GIC的中斷可以由CPU4~CPU7分擔處理。但這樣,在中斷處理方面看起來就體現不出8核的威力了。
注:上一節中的邏輯block示意圖采用的就是方案一。
3、Interrupt controller把中斷事件送給哪個CPU?
毫無疑問,只有支持multi cpu core的中斷控制器才有這種幸福的煩惱。一般而言,中斷控制器可以把中斷事件上報給一個CPU或者一組CPU(包括廣播到所有的CPU上去)。對於外設類型的中斷,當然是送到一個cpu上就OK了,我看不出來要把這樣的中斷送給多個CPU進行處理的必要性。如果送達了多個cpu,實際上,也應該只有一個handler實際和外設進行交互,另外一個cpu上的handler的動作應該是這樣的:發現該irq number對應的中斷已經被另外一個cpu處理了,直接退出handler,返回中斷現場。IPI的中斷不存在這個限制,IPI更像一個CPU之間通信的機制,對這種中斷廣播應該是毫無壓力。
實際上,從用戶的角度看,其需求是相當復雜的,我們的目標可能包括:
(1)讓某個IRQ number的中斷由某個特定的CPU處理
(2)讓某個特定的中斷由幾個CPU輪流處理
……
Notes:中斷和CPU親和性。
當然,具體的需求可能更加復雜,但是如何區分軟件和硬件的分工呢?讓硬件處理那么復雜的策略其實是不合理的,復雜的邏輯如果由硬件實現,那么就意味着更多的晶體管,更多的功耗。因此,最普通的做法就是為Interrupt Controller支持的每一個中斷設定一個target cpu的控制接口(當然應該是以寄存器形式出現,對於GIC,這個寄存器就是Interrupt processor target register)。系統有多個cpu,這個控制接口就有多少個bit,每個bit代表一個CPU。如果該bit設定為1,那么該interrupt就上報給該CPU,如果為0,則不上報給該CPU。這樣的硬件邏輯比較簡單,剩余的控制內容就交給軟件好了。例如如果系統有兩個cpu core,某中斷想輪流由兩個CPU處理。那么當CPU0相應該中斷進入interrupt handler的時候,可以將Interrupt processor target register中本CPU對應的bit設定為0,另外一個CPU的bit設定為1。這樣,在下次中斷發生的時候,interupt controller就把中斷送給了CPU1。對於CPU1而言,在執行該中斷的handler的時候,將Interrupt processor target register中CPU0的bit為設置為1,disable本CPU的比特位,這樣在下次中斷發生的時候,interupt controller就把中斷送給了CPU0。這樣軟件控制的結果就是實現了特定中斷由2個CPU輪流處理的算法。
4、更多的思考
面對這個HW中斷系統之邏輯block diagram,我們其實可以提出更多的問題:
(1)中斷控制器發送給CPU的中斷是否可以收回?重新分發給另外一個CPU?
(2)系統中的中斷如何分發才能獲得更好的性能呢?
(3)中斷分發的策略需要考慮哪些因素呢?
……
很多問題其實我也沒有答案,慢慢思考,慢慢逼近真相吧。
三、中斷子系統相關的軟件框架
linux kernel的中斷子系統相關的軟件框架圖如下所示:
由上面的block圖,我們可知linux kernel的中斷子系統分成4個部分:
(1)硬件無關的代碼,我們稱之Linux kernel通用中斷處理模塊。無論是哪種CPU,哪種controller,其中斷處理的過程都有一些相同的內容,這些相同的內容被抽象出來,和HW無關。此外,各個外設的驅動代碼中,也希望能用一個統一的接口實現irq相關的管理(不和具體的中斷硬件系統以及CPU體系結構相關)這些“通用”的代碼組成了linux kernel interrupt subsystem的核心部分。
(2)CPU architecture相關的中斷處理。 和系統使用的具體的CPU architecture相關。
(3)Interrupt controller驅動代碼 。和系統使用的Interrupt controller相關。
(4)普通外設的驅動。這些驅動將使用Linux kernel通用中斷處理模塊的API來實現自己的驅動邏輯。
四、中斷子系統文檔規划
中斷相關的文檔規划如下:
1、linux kernel的中斷子系統之(一),也就是本文,其實是一個導論,沒有實際的內容,主要是給讀者一個大概的軟硬件框架。
2、linux kernel的中斷子系統之(二):irq domain介紹。主要描述如何將一個HW interrupt ID轉換成IRQ number。
3、linux kernel的中斷子系統之(三):IRQ number和中斷描述符。主要描述中斷描述符相關的數據結構和接口API。
4、linux kernel的中斷子系統之(四):high level irq event handler。
5、linux kernel的中斷子系統之(五):driver API。主要以一個普通的驅動程序為視角,看待linux interrupt subsystem提供的API,如何利用這些API,分配資源,是否資源,如何處理中斷相關的同步問題等等。
6、linux kernel的中斷子系統之(六):ARM中斷處理過程,這份文檔以ARM CPU為例,描述ARM相關的中斷處理過程
7、linux kernel的中斷子系統之(七):GIC代碼分析,這份文檔是以一個具體的interrupt controller為例,描述irq chip driver的代碼構成情況。