第7章中斷和中斷處理

7.5 編寫中斷處理程序

以下是一箇中斷處理程序聲明:

static irqreturn_t intr_handler(int irq, void *dev);

注意,它的類型與request_irq()參數中handler所要求的參數類型相匹配。第一個參數irq是這個處理程序要響應的中斷的中斷號。在2.0版本以前的Linux內核中,由於沒有dev這個參數,必須通過irq才能區分使用相同驅動程序,因而也使用相同的中斷處理程序的多個設備。例如,具有多個相同類型硬盤驅動控制器的計算機。

       第二個參數dev是一個通用指針,它在與中斷處理程序註冊時傳遞給request_irq()的參數dev必須一致。如果該值有唯一確定性,那麼它就相當於一個cookie,可以用來區分共享同一中斷處理程序的多個設備。另外dev也可能指向中斷處理程序使用的一個數據結構。因爲對每個設備而言,設備結構都是唯一的,而且可能在中斷處理程序中也用得到,因此,它也通常被看做dev。

中斷處理程序的返回值是一個特殊類型:irqreturn_t。中斷處理程序可能返回兩個特殊的值:IRQ_NONE和IRQ_HANDLED。當中斷處理程序檢測到一箇中斷,但該中斷對應的設備並不是在註冊處理函數期間指定的產生源時,返回IRQ_NONE;當中斷處理程序被正確調用,且確實是它所對應的設備產生了中斷時,返回IRQ_HANDLED。另外,也可以使用宏IRQ_RETVAL(val)。如果val爲非0值,那麼該宏返回IRQ_HANDLED;否則,返回IRQ_NONE。利用這些特殊的值,內核可以知道設備發出的是否是一種虛假的中斷。如果給定中斷線上所有中斷處理程序返回的都是IRQ_NONE,那麼,內核就可以檢測到出了問題。注意,irqreturn_t這個返回類型實際上是一個int型。之所以使用這些特殊值是爲了與早期的內核保持兼容——2.6版本之前的內核並不支持這種特性,中斷處理程序只需返回void就行了。如果要在2.4或更早的內核上使用這樣的驅動程序,只需將typedef irqreturn_t 改爲void,屏蔽掉此特性,並給no-ops定義不同的返回值,其他用不着做什麼大的修改。中斷處理程序通常會標記爲static,因爲它從來不會被別的文件中的代碼直接調用。

中斷處理程序扮演什麼樣的角色要取決於產生中斷的設備和該設備爲什麼要發送中斷。即使其他什麼工作也不做,絕大部分的中斷處理程序至少需要知道產生中斷的設備,告訴它已經收到中斷了。對於複雜一些的設備,可能還需要在中斷處理程序中發送和接收數據,以及執行一些擴充的工作。如前所述,應儘可能將擴充的工作推給下半部處理程序。

重入和中斷處理程序

Linux中的中斷處理程序是無須重入的。當一個給定的中斷處理程序正在執行時,相應的中斷線在所有處理器上都會被屏蔽掉,以防止在同一中斷線上接收另外一個新的中斷。通常情況下,所有其他的中斷都是打開的,所以這些不同中斷線上的其他中斷都能被處理,但當前中斷線總是被禁止的。可以看出,同一個中斷處理程序絕對不會被同時調用以處理嵌套的中斷。這簡化了中斷處理程序的編寫。

7.5.1 共享的中斷處理程序

共享的處理程序與非共享的處理程序在註冊和運行方式上比較相似,但差異主要有以下三處:

request_irq()的參數flags必須設置IRQF_SHARED標誌。

對於每個註冊的中斷處理程序來說,dev參數必須唯一。指向任一設備結構的指針就可以滿足這一要求;通常會選擇設備結構,因爲它是惟一的,而且中斷處理程序可能會用到它。不能給共享的處理程序傳遞NULL值。

中斷處理程序必須能夠區分它的設備是否真的產生了中斷。這既需要硬件的支持,也需要處理程序中有相關的處理邏輯。如果硬件不支持這一功能,那中斷處理程序肯定會束手無策,它根本沒法知道到底是與它對應的設備發出了這個中斷,還是共享這條中斷線的其他設備發出了中斷。

所有共享中斷線的驅動程序都必須滿足以上要求。只要有任何一個設備沒有按規則進行共享,那麼中斷線就無法共享了。指定IRQF_SHARED以調用request_irq()時,只有在以下兩種情況下才可能成功:中斷線當前未被註冊,或者在該線上的所有已註冊處理程序都指定了IRQF_SHARED。注意,在這一點上2.6版與以前的內核是不同的,共享的處理程序可以混用IRQF_DISABLED。

內核接收一箇中斷後,它將依次調用在該中斷線上註冊的每一個處理程序。因此,一個處理程序必須知道它是否應該爲這個中斷負責。如果與它相關的設備並沒有產生中斷,那麼處理程序應該立即退出。這需要硬件設備提供狀態寄存器,以便中斷處理程序進行檢查。

7.5.2 中斷處理程序實例

考察一個實際的中斷處理程序,它來自RTC驅動程序,可以在drivers/char/rtc.c中找到。很多機器包括PC都可以找到RTC。它是一個從系統定時器中獨立出來的設備,用於設置系統時鐘,提供報警器或週期性的定時器。對大多數體系結構而言,系統時鐘的設置,通常只需要向某個特定的寄存器或IO地址寫入想要的時間就可以了。然而報警器或週期性定時器通常就得靠中斷來實現。這種中斷與生活中的鬧鈴差不多:中斷髮出時,報警器或定時器就會啓動。

RTC驅動程序裝載時,rtc_init()函數會被調用,對這個驅動程序進行初始化。它的職責之一就是註冊中斷處理程序:

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\n", rtc_irq);
        return -EIO;
    }

從中看到,中斷號由rtc_irq指定。這個變量用於爲給定體系結構指定RTC中斷。例如,在PC上,RTC位於IRQ 8。第二個參數是中斷處理程序rtc_interrupt——它將與其他中斷處理程序共享中斷線,因爲它設置了IRQF_SHARED標誌。由第四個參數看出,驅動程序的名稱爲"rtc"。

最後是中斷處理程序本身:

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.
     */

    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.
         */
        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;
}

只要計算機一接收到RTC中斷,就會調用這個函數。首先要注意的是使用了自旋鎖——第一次調用時爲了保證rtc_irq_data不被SMP機器上的其他處理器同時訪問,第二次調用避免rtc_callback出現相同的情況。

rtc_irq_data變量是unsigned long類型,存放有關RTC的信息,每次中斷時都會更新以反映中斷的狀態。

接下來,如果設置了RTC週期性定時器,就要通過函數mod_timer()對其更新。

代碼的最後一部分——處於註釋現在執行其餘的操作下,會執行一個可能被預先設置好的回調函數。RTC驅動程序允許註冊一個回調函數,並在每個RTC中斷到來時執行。

最後,這個函數會返回IRQ_HANDLED,標明已經正確地完成了對此設備的操作。因爲這個中斷處理程序不支持共享,而且RTC也沒有什麼用來測試虛假中斷的機制,所以該處理程序總是返回IRQ_HANDLED。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章