版權聲明:您好,轉載請留下本人部落格的位址,謝謝 https://blog.csdn.net/hongbochen1223/article/details/46857879
(一):中斷
中斷本質上是一種特殊的電信号,由硬體裝置發向處理器。處理器在接收到中斷後,會馬上向作業系統反映此信号的到來,然後就u由作業系統來處理這些新到來的資料。不同的裝置對應的中斷不同,而每個中斷都通過一個唯一的數字标志。這些中斷值被稱為中斷請求線(IRQ)。中斷是随時随地發生的,也就是說中斷并不考慮與處理器的時鐘同步。
異常:異常的産生必須與處理器時鐘同步,異常也被成為同步中斷。在處理器執行到由于程式設計失誤而導緻的錯誤指令的時候,或者是在執行期間出現特殊情況,必須靠核心來處理的時候,處理器就會産生一個異常。中斷是由硬體引起的,異常是由于軟體引起的。
(二):中斷處理程式
在響應一個特定中斷的時候,核心會執行一個函數,該函數叫做中斷處理程式或中斷服務例程。産生中斷的每個裝置都有一個相應的中斷處理程式。中斷處理程式與其他核心函數的真正差別在于,中斷處理程式是被核心調用來響應中端的,而他們運作在我們稱之為中斷上下文的特殊上下文中,中斷上下文也被成為原子上下文,該上下文執行的代碼不可阻塞。
(三):上半部與下半部的對比
由于中斷處理程式既需要運作的快,又需要完成的工作量多,是以将終端處理切分為兩部分。中斷處理程式是上半部,一旦接收到一個中斷,他就立即開始執行,但隻做有嚴格時限的工作。能夠被允許稍後執行的工作會推遲到下半部去。
(四):注冊中斷處理程式
中斷處理程式是管理硬體的驅動程式的組成部分。每一個裝置都有相關的驅動程式,如果裝置使用中斷,那麼相應的驅動程式就注冊一個中斷處理程式。
驅動程式可以通過request_irp()函數注冊一個中斷處理程式,該函數被定義在linux/interrupt.h檔案中,并且激活給定的中斷線,以進行中斷。
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
第一個參數表示要配置設定的中斷号。對于大多數其他裝置來說,中斷号要麼可以通過探測擷取,要麼可以通過程式設計動态确定。
第二個參數handler是一個指針,指向處理這個中斷的實際中斷處理程式。隻要作業系統一接收到中斷,該函數就被調用。
handler函數的原型。
typedef irqreturn_t (*irq_handler_t)(int, void *);
1:中斷處理程式标志
第三個參數flags可以為0,也可能是下列一個或多個标志的位掩碼。定義在linux/interrupt.h檔案中。下面列舉一下幾個比較重要的标志:
/*
* These flags used only by the kernel as part of the
* irq handling routines.
*
* IRQF_DISABLED - keep irqs disabled when calling the action handler
* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
* Used by threaded interrupts which need to keep the
* irq line disabled until the threaded handler has been run.
*/
#define IRQF_DISABLED 0x00000020
#define IRQF_SAMPLE_RANDOM 0x00000040
#define IRQF_SHARED 0x00000080
#define IRQF_PROBE_SHARED 0x00000100
#define IRQF_TIMER 0x00000200
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000
IRQF_DISABLE - 該标志被設定後,意味着核心在進行中斷處理程式本身期間,要禁止所有的其他中斷。如果不設定,中斷處理程式可以與除本身以外的其他任何中斷同時運作。
IRQF_SAMPLE_RANDOM - 此标志表明這個裝置産生的中斷對核心熵池(entroy pool)有貢獻。核心熵池負責提供從各種随機事件導出真正的随機數。如果指定了該标志,那麼來自該裝置的中斷間隔時間就會作為熵填充到熵池。
IRQF_TIMER - 該标志是特别為系統定時器的中斷處理準備的。
IRQF_SHARED - 該标志表明可以在多個中斷處理程式之間共享中斷線。在同一個中斷線上注冊的每個處理程式必須指定這個标志,否則,在每條線上隻能有一個處理程式。
第四個參數name是與中斷相關的裝置的ASCII文本表示
第五個參數dev用于共享中斷線。當一個中斷處理程式需要釋放的時候,dev将提供唯一的标志資訊(cookie),以便從共享中斷線的諸多中斷處理程式中删除指定的那一個。
request_irq()成功執行會傳回0,如果傳回非0值,表示有錯誤發生。其中最常見的錯誤是-EBUSY,他表示給定的中斷線已經在使用。
注意request_irq()函數可能會睡眠,是以,不能在中斷上下文或其他不允許阻塞的代碼調用該函數。
2:一個中斷的例子
在一個驅動程式中請求一個中斷線,并在通過request_irq()安裝中斷處理程式:
request_irq();
if(request_irq(irqn,my_interrupt,IRQF_SHARED,"my_device",my_dev)){
printk(KERN_ERR "my_device: cannot register IRQ %d
",irqn);
return -EIO;
}
在編寫中斷處理函數的時候,初始化硬體和注冊中斷處理程式的順序必須正确,以防止中斷處理程式在設别初始化之前就開始執行。
3:釋放中斷處理程式
解除安裝驅動程式的時候,需要登出中斷處理程式,并釋放中斷線,上述動作需要調用:
void free_irq(unsigned int irq,void *dev)
如果指定的中斷線不是共享的,那麼該函數删除處理程式的同時,禁用這條中斷線.如果中斷線是共享的,則僅僅删除dev所對應的中斷處理程式,而中斷線本身隻有在删除了最後一個處理程式時才會被禁用.
(五):編寫中斷處理程式
一下是一個中斷處理程式的聲明:
static irqreturn_t intr_handler(int irq,void *dev);
注意,他的類型與request_irq()參數中handler所要求的參數類型相比對.第一個參數irq就是處理程式要相應的中斷的中斷号.
第二個參數dev是一個通用指針,他與在中斷處理程式注冊時傳遞給request_irq()的參數dev必須一緻.
中斷處理程式的傳回值是一個特殊類型:irqreturn_t.中斷處理程式可能傳回兩個特殊的值:IRQ_NONE和IRQ_HANDLED.當中斷處理程式檢測到一個中斷,但該中斷對應的裝置并不是在注冊處理函數期間指定的産生源時,傳回IRQ_NONE.當中斷處理程式被正确調用,并且确實是他對應的裝置産生了中斷的時候,傳回IRQ_HANDLED
注意:
Linux中的中斷處理程式是無須重入的.當一個給定的中斷處理程式正在執行時,相應的中斷線在所有的處理器上都會被屏蔽掉.以防止在同一個中斷線上接收另一個新的中斷.由此可見,同一個中斷處理程式絕對不會被同時調用以處理嵌套中斷.
1:共享的中斷處理程式
共享的中斷處理程式和非共享的中斷處理程式有一下幾個差異:
1):request_irq()的參數flags必須設定為IRQF_SHARED标志
2):對于每一個注冊的中斷處理程式來說,dev參數必須唯一.指向任一裝置結構的指針就可以滿足這一要求.不能給中斷處理程式傳遞 NULL值.
3):中斷處理程式必須能夠區分他的裝置是否真正的産生了中斷.這既需要硬體的支援,也需要處理程式中有相關的處理邏輯.
還有,指定IRQF_SHARED标志以調用 request_irq()的時候,隻有在一下兩種情況下才可能成功:中斷線目前未被注冊,或者在該線上的所有以注冊處理程式都指定了IRQF_SHARED.
2:中斷處理程式執行個體
下面我們看一下RTC(real-time clock)的中斷處理程式.該程式位于drivers/char/rtc.c 檔案中.該裝置用于系統時鐘,提供報警器或周期性的定時器.
首先,在rtc初始化的時候注冊中斷處理程式.我們來看一下.
函數rtc_init(void):
/* 對rtc_irq 注冊 rtc_interrupt */
if (request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc",
(void *)&rtc_port)) {
rtc_has_irq = 0;
printk(KERN_ERR "rtc: cannot register IRQ %d
", rtc_irq);
return -EIO;
}
從中我們看出,中斷号由rtc_irq提供.這個變量用于為給定體系結構指定RTC中斷.第二個參數是我們的中斷處理程式rtc_interrupt—他将于其他中斷處理程式共享中斷線,因為他設定了IRQF_SHARED标志.第四個參數,可以得出驅動程式的名稱為”rtc”,因為這個裝置允許共享中斷線,是以他給dev型參傳遞了一個面向每個裝置的實參值.
下面我們看一下具體的中斷處理函數:
#ifdef RTC_IRQ
/*
* A very tiny interrupt handler. It runs with IRQF_DISABLED set,
* but there is possibility of conflicting with the set_rtc_mmss()
* call (the rtc irq and the timer irq can easily run at the same
* time in two different CPUs). So we need to serialize
* accesses to the chip with the rtc_lock spinlock that each
* architecture should implement in the timer code.
* (See ./arch/XXXX/kernel/time.c for the set_rtc_mmss() function.)
*
* 一個非常輕型的中斷處理函數.他是和IRQF_DISABLED集一起運作的,
* 但是很有可能和set_rtc_mmss()調用發生沖突(rtc 中斷和timer中斷很容易
* 同時在兩個不同的CPU上運作).是以我們需要使用rtc_lock自旋鎖來序列化
* 對晶片的通路,使得每一個架構都應該在timer代碼中實作.
*
*/
static irqreturn_t rtc_interrupt(int irq, void *dev_id)
{
/*
* Can be an alarm interrupt, update complete interrupt,
* or a periodic interrupt. We store the status in the
* low byte and the number of interrupts received since
* the last read in the remainder of rtc_irq_data.
*
* 可以是alarm報警中斷,更新完成的中斷,或者是周期性中斷.
* 我們把這些狀态儲存在rtc_irq_data的低位元組中,而把最後一次讀取的中斷
* 号儲存到rtc_irq_data的其他位元組中.
*/
//自旋鎖
spin_lock(&rtc_lock);
rtc_irq_data += 0x100;
rtc_irq_data &= ~0xff;
if (is_hpet_enabled()) {
/*
* In this case it is HPET RTC interrupt handler
* calling us, with the interrupt information
* passed as arg1, instead of irq.
*
* 在這種情況下,是HPET RTC中斷處理函數調用我們,
* 伴随着是中斷資訊作為參數1而不是irq
*/
rtc_irq_data |= (unsigned long)irq & 0xF0;
} else {
rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
}
if (rtc_status & RTC_TIMER_ON)
mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
spin_unlock(&rtc_lock);
/* Now do the rest of the actions */
/* 現在執行其他的操作 */
spin_lock(&rtc_task_lock);
if (rtc_callback)
rtc_callback->func(rtc_callback->private_data);
spin_unlock(&rtc_task_lock);
wake_up_interruptible(&rtc_wait);
kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
return IRQ_HANDLED;
}
#endif
隻要計算機一接收到RTC中斷,就會調用這個函數.首先要注意的是使用了自旋鎖–第一次調用是為了保證rtc_irq_data不會被SMP機器上的其他處理器同時通路,第二次調用是為了避免rtc_callback出現相同的情況.
程式後面會執行一個回調函數,RTC驅動程式允許注冊一個回調函數,并在每個RTC中斷到來時執行.
最後傳回IRQ_HANDLED.