天天看點

Linux核心設計與實作(8)---中斷和中斷處理

之前寫過中斷相關的文章了,詳細分析過ARM底層中斷實作過程,這篇文章着重從作業系統設計層面了解中斷系統。

1.中斷

中斷:是外圍裝置産生的異步事件,不同裝置,中斷号不同;比如在經典PC上,IRQ 0是時鐘中斷,IRQ 1是鍵盤中斷;對于連接配接于PCI總線上的裝置,中斷還可以是動态配置設定的。

異常:與中斷不同,異常的産生必須考慮與處理器時鐘同步,比如除零,缺頁,軟中斷等,都是由處理器自身産生的同步事件,異常也可稱為同步中斷;

本文對異步中斷的讨論,大部分也适合于異常(同步中斷)。

​2.中斷處理每個中斷都對應有一個中斷處理程式;

ISR是被核心調用來響應中斷的,它運作在中斷上下文中(也稱原子上下文),是以代碼是不可阻塞的。

ISR應該快速執行。

​3.上半部與下半部又想ISR運作的快,又想ISR完成的工作量多,這是一個此消彼長的沖突關系;為了平衡這個沖突,Linux核心把中斷分為兩部分:

上半部:接收一個中斷,立即開始執行,但隻做嚴格時限的工作;例如中斷應答,硬體複位之類,這些工作是在所有中斷被禁止的情況下完成的。

下半部:上半部執行完後,在合适的時間,排程執行下半部,下半部可以處理一些耗時的工作,可以休眠。

​4.注冊中斷處理程式中斷處理程式通過request_irq()注冊(在linux/interrupt.h定義)

此處)折疊或打開

1. int request_irq(
2. unsigned int irq,
3. irq_handler_t handler,
4. unsigned long flags,
5. const char *name,
6. void *dev)      

(1)第一個參數irq:

表示要配置設定的中斷号,這個值通常是預先确定的,但對有的裝置來也可以探測擷取,或通過程式設計動态确定。

(2) handler:函數指針,指向這個中斷的ISR。

該函數原型如下,接受2個參數,并有irqreturn_t類型的傳回值:

typedef irqreturn_t (*irq_handler_t)(int, void *);

(3) flags:中斷處理标志,可以用0,也可以用以下一個或多個标志的位掩碼

①IRQF_DISABLED:  keep irqs disabled when calling the action handler

如果不設定該位,中斷處理程式可以與除本身外的其他任何中斷同時運作,一般不會設定此位,這個用法是給希望快速執行的輕量級中斷,在過去的中斷中用以區分“快速”和“慢速”中斷。

②IRQF_SAMPLE_RANDOM

此标志表明這個裝置的中斷對核心熵池有貢獻,用以産生随機數。

③IRQF_TIMER:

該标志是特别為系統定時器的中斷處理程式而準備的。

④IRQF_SHARED:allow sharing the irq among several devices

必須在給定中斷線的所有處理程式中都指定這個标志,否則,一個中斷線上隻能有一個處理程式;

(4)name:

中斷裝置的名字,比如PC上鍵盤中斷對應的值是“keyboard”,這個名字會被/proc/irq和/proc/interrupts檔案使用。

(5)dev :用于共享中斷線

①中斷線被共享:dev必須傳遞唯一資訊,當一個中斷處理程式需要釋放時,dev将提供唯一的标志資訊,以便确定要在共享中斷線上要删除哪一個處理程式;

②不是共享中斷線,可以設為NULL,也可以用來傳遞裝置私有資訊。

request_irq()成功執行會傳回0,若非0,就表示發生錯誤,這時指定中斷處理程式不會被注冊。最常見的錯誤是-EBUSY,它表示中斷線已經在使用,或者注冊共享中斷時沒有指定IRQF_SHARED。

初始化硬體和注冊中斷處理程式的順序必須正确,以防止ISR在裝置初始化完成之前就開始執行。初始化硬體,包括set_irq_type();

int set_irq_type(unsigned int irq, unsigned int type);

irq:中斷号

type:中斷類型,可以設為下列标志

IRQT_RISING,IRQT_FALLING,IRQT_BOTHEDGE,IRQT_LOW ,IRQT_HIGH

​釋放中斷處理程式:

解除安裝驅動程式的時候,要登出相應的中斷處理程式,釋放中斷線

void free_irq(unsigned int irq, void *dev);

如果中斷線非共享,該函數删除處理程式,禁止相應中斷線。

若為共享中斷,free_irq()删除dev相應的處理程式,而這個中斷線隻有在所有處理程式都被删除後,才會被禁用。

必須在程序上下文中調用free_irq()。

5.編寫中斷處理程式

(1) 中斷處理程式原型:typedef irqreturn_t (*irq_handler_t)(int, irq void * dev);

irq:中斷号

dev:通用指針,就是request_irq()注冊時的dev。

在共享中斷線時,用來區分不同裝置;

非共享中斷中,一般傳入硬體裝置私有資訊

irqreturn_t:傳回值,一般由兩個值

IRQ_NONE: 當ISR檢測到一個中斷,但該中斷對應的裝置并不是注冊ISR時指定的産生源時,傳回IRQ_NONE;

IRQ_HANDLED: ISR被正确調用且确實是它所對應的裝置産生了中斷,傳回IRQ_HANDLED.

也可以用宏IRQ_RETVAL(val),val非零時傳回IRQ_HANDLED,val為0時傳回IRQ_NONE.

利用特殊傳回值,讷河可以知道裝置發出的是否是一種虛假的中斷(為請求)。

中斷處理期望完成的工作,擴充的耗時的工作,應該配置設定給下半部完成。

(2) 中斷處理程式特點:

①不能向使用者空間發送或接收資料,因為它不在任何程序上下文運作;

②處理程式不能做任何可能休眠的操作;

③不能調用schedule()函數;

④Linux的ISR無須是重入的,當一個給定ISR在執行時,相應的中斷線在所有處理器上都會被屏蔽。

⑤通常情況下,其他中斷是打開的,是以這些不同中斷線上的ISR都可以被處理。

中斷的嵌套,還要看CPU硬體提供的支援,Linux隻能适應硬體,而不能改變硬體特性。

(3) 共享的中斷處理程式:注意點主要有三

①request_irq()的參數flags必須設定為IRQF_SHARED标志;

②對于每個注冊的中斷處理程式,dev參數必須唯一,且不能為空;

③中斷處理程式必須能夠區分裝置是否真産生了中斷,一般由狀态寄存器或類似機制實作;

核心收到一個中斷後,它将依次調用在該中斷線上注冊的每一個處理程式,是以每個處理程式都應該能為中斷負責,若發現相關裝置并未産生中斷,應該立即退出。

6.中斷上下文程序上下文,是一種核心所處的操作模式,此時核心代表程序執行,程序以程序上下文的形式連接配接到核心中,是以程序上下文可以睡眠,也可以調用排程程式;

中斷上下文,與程序不相幹,它無法被排程,是以不能睡眠(一旦睡眠,則無法傳回);

中斷上下文具有較為嚴格的時間限制,中斷上下文的代碼應當迅速、簡潔,盡量不要去處理繁重的工作。中斷打斷了其他代碼的執行(甚至可能是打斷了在其他中斷線上的另一個中斷處理程式),是以ISR應該盡量快速的退出,盡量把工作從ISR中分離出來,放到下半部去執行。

中斷處理在32位機上隻有4K的中斷棧,是以應該盡量節約核心棧空間。

7.中斷處理機制的實作

中斷處理系統在Linux中的實作,是非常依賴于體系結構的。

當一個中斷産生,處理器會跳到一個預先定義好的入口點,每條中斷線處理器都會跳轉到一個唯一的對應位置。在棧中儲存IRQ号,調用do_IRQ()函數。

取出棧頂的參數,計算出中斷号後,do_IRQ()對所接收的中斷進行應答,禁止這條線上的中斷傳遞。然後調用handle_IRQ_event()來運作中斷處理程式。

handle_IRQ_event()函數中,若為共享中斷,所有該中斷線上的處理程式都被執行一遍。

執行完中斷處理程式,傳回入口點跳到ret_from_intr()。

這個例程中檢查重新排程程式是否正在挂起(need_resched被設定),如果重新排程程式正在挂起,如果核心正傳回使用者空間,那麼schedule()會被調用;如果核心正要傳回核心空間(中斷了核心本身),隻有在preempt_count為0時(不持有鎖),schedule()才會被調用,否則搶占核心不安全。

8./proc/interrupts

procfs是一個虛拟檔案系統,隻存在于核心記憶體中。對procfs的讀寫都要通過核心函數實作,/proc/interrupts檔案,存放的是系統中與中斷相關的統計資訊。分别顯示中斷線,中斷發生次數,中斷控制器,發生中斷名字等。

9.中斷控制

Linux核心提供了一組接口,能夠禁止目前處理器的中斷系統,或屏蔽掉整個機器的一條中斷線的能力。

控制中斷系統的原因歸根結底,是需要提提供同步,通過禁止中斷,可以確定某個中斷處理程式不會搶占目前的代碼,禁止中斷還可以禁止核心搶占。

Linux支援多處理器,是以核心代碼一般都需要擷取某種鎖,來防止其他處理器對共享資料的并發通路。鎖提供保護機制,防止其他處理器的并發通路;禁止中斷提供保護機制,防止來自其他中斷處理程式的并發通路;

(1)禁止和激活中斷

禁止/激活目前處理器上的本地中斷,

此處)折疊或打開

1. local_irq_disable();
2. local_irq_enable()      

禁止中斷之前儲存中斷系統狀态會更安全

禁止本地中斷,并儲存中斷狀态

此處)折疊或打開

1. unsigned long flags;
2. local_irq_save(flags);//禁止中斷
3. …
4. local_irq_restore(flags);//中斷被恢複到它們原來的狀态      

這一對函數,應當在同一個函數内調用。

(2)禁止指定中斷線

此處)折疊或打開

一般來說禁止共享中斷線是不合适的,這就禁止了這條線上的所有中斷傳遞。

1. void disable_irq(unsigned int irq); //目前正在執行的所有處理程式完成後,才傳回
2. void disable_irq_nosync(unsigned int irq); //不等待ISR執行完畢
3. void enable_irq(unsigned int irq);
4. void synchronize_irq(unsigned int irq); //等待指定中斷處理程式退出,才傳回
5. disable_irq()/enable_irq();//必須成對使用,如果enable_irq()被調用多次,隻有最有一次調用後,才會真正激活中斷線;      

(3)中斷系統狀态

此處)折疊或打開

1. irqs_disabled();//本地處理器上的中斷系統被禁止,則它傳回非0,否則傳回0.
2. in_interrupt();//核心處于任何類型的中斷中,傳回非0,說明核心正在執行isr,或下半部處理程式
3. in_irq();// 當核心确實正在執行中斷處理程式時,傳回非0      

繼續閱讀