天天看點

中斷和中斷處理(一)

版權聲明:您好,轉載請留下本人部落格的位址,謝謝 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.

繼續閱讀