LDD3學習-第十章-中斷處理

         LDD3-第十章-中斷處理<一>        

         中斷是指,CPU在執行程序的過程中,出現了某些突發事件時,CPU必須暫停執行當前的程序,轉去處理其它突然事件,處理完畢後CPU又返回原來的程序被中斷的位置並繼續執行。中斷分爲兩種:內部中斷,來源於CPU內部(溢出,中斷指令,用戶態到內核態切換等);外部中斷,來源於CPU外部,由外設提出請求(一般爲中斷引腳的信號)。

        中斷信號線是非常珍貴且有限的資源(x86體系, 244個),內核維護了一箇中斷信號線的註冊表。中斷信號線的使用遵循註冊-撤銷的使用模式,並且有時可以與其他設備共享信號線。

中斷信號線申請函數:
#include <linux/sched.h>    /* <kernel/irq/Manage.h>  */
int request_irq(unsigned int irq,     /* 中斷信號線值 */
                irqreturn_t (*handler)(int, void *, struct pt_regs *), /* 中斷處理程序*/
                unsigned long  flags,  /* 與中斷管理相關的位掩碼 */
                const char *dev_name,  /* 傳遞給rquest_irq函數的字符串,用以在/proc/interrupts 顯示,一般爲設備名稱 */
                void *dev_id  /* 用於標明此終端號共享。一般指向自己實現的 struct xxx_device結構 */
               );
返回值: 0 成功; -INVAL 中斷號無效或者中斷處理程序handler爲NULL; -EBUSY 中斷被其他設備使用且不能共享

中斷管理先關的位掩碼flags:
    SA_INTERRUPT: 表示handler是一個快速中斷的處理例程。快速中斷運行在中斷被禁用的情況下,即在中斷函數運行的過程中會屏蔽所有中斷。
    SA_SHIRQ: shared irq, 表示該終端號(線)能夠被多個設備共享
    SA_SAMPLE_RANDOM:  表示中斷信號的產生時間是真正的無規律的,可以對/dev/random和/dev/urandom設備使用的熵池做出共享。不建議有規律的、可以被控制的設備設置此標誌  

取消中斷號的使用
void free_irq(unsigned int irq, void *dev_id);

         中斷處理例程(handler)可以在驅動程序本身初始化或者設備第一次打開的時候安裝。但是,在驅動程序本身初始化時安裝中斷不是好主意,因爲一個驅動模塊(module)可能已經插入了內核,但是對應的設備可能很長時間或者從來不使用,那麼該驅動始終佔據一箇中斷號但是從來不真正使用,特別是當該驅動獨佔(即沒有設置爲SA_SHIRQ)申請到的終端號。因此,在設備第一次打開時申請並設置,最後一次關閉設備時釋放中斷號,是最佳的方式。缺點是,這樣必須記錄設備被打開和關閉的引用計數(打開時增加,關閉時減少)用以讓驅動知道哪次打開是第一次,哪次關閉是最後一次。


申請中斷實例(LDD3, short模塊):
if (short_irq >= 0) { /* 已確定用哪個中斷號 */
	result = request_irq(short_irq, short_interrupt,/* 將終端號,和中斷處理例,設備名字等關聯起來 */ 
			SA_INTERRUPT, "short", NULL);
	if (result) {
		printk(KERN_INFO "short: can't get assigned irq %i\n",
				short_irq);
		hort_irq = -1;
	}
	else { /* actually enable it -- assume this *is* a parallel port */
			outb(0x10,short_base+2); /* 啓動設備的中斷能力, 設備在初始化時是默認關閉中斷能力的 */
	}
}

         可以通過/proc/interrupts查看正在使用的終端號的信息,包括終端號: cpu0  cpu1 ... 中斷控制器  處理中斷的設備(dev_id)。另外,/proc/stat也能顯示終端信息,但是是與體系相關的。

        

          檢測中斷號IRQ

         自動檢測中斷號irq。在上面的申請中斷號的時候,我們必須已經知道了驅動設備所使用的驅動號,即irq>=0。但是,實際情況是我們可能並不預先知道一個設備所使用的中斷號。這就要求我們能夠通過一定的手段探知設備將要使用的中斷號(在模塊的初始化過程中探測將要使用的中斷號,但是在打開設備的時候申請request_irq並使用此中斷號),一般分爲幾種情況,依據實際的硬件的說明使用:

          1.  通過讀取設備配置寄存器的特定位來獲知所使用的中斷號。 例如:PCI設備,0x300c。

          2. 通過內核輔助函數,探測設備的中斷號,例如short模塊:

    int count = 0;
    do {
        unsigned long mask;

        mask = probe_irq_on();   /* 啓用中斷 */
        outb_p(0x10,short_base+2); /* enable reporting */
        outb_p(0x00,short_base);   /* clear the bit */
        outb_p(0xFF,short_base);   /* set the bit: interrupt! */
        outb_p(0x00,short_base+2); /* disable reporting */
        udelay(5);  /* give it some time */
        short_irq = probe_irq_off(mask);  /* 返回0沒有找到中斷號,負值產生了多次中斷 */

        if (short_irq == 0) { /* 沒有中斷號 */
            printk(KERN_INFO "short: no irq reported by probe\n");
            short_irq = -1;
        }
        /* 在這應該實現對此次產生的中斷的處理, 將每次爲了探測中斷號而產生的中斷處理掉 
         * if more than one line has been activated, the result is
         * negative. We should service the interrupt (no need for lpt port)
         * and loop over again. Loop at most five times, then give up
         */
    } while (short_irq < 0 && count++ < 5);  /* 循環探測 5次 */
    if (short_irq < 0)  /* 最後還是沒有探測到中斷號 */
        printk("short: probe failed %i times, giving up\n", count);

          3.  通過I/O地址分配中斷號,例如short模塊:

	if (short_irq < 0) /* not yet specified: force the default on */
		switch(short_base) {
		    case 0x378: short_irq = 7; break;
		    case 0x278: short_irq = 2; break;
		    case 0x3bc: short_irq = 5; break;
		}
          4. 自己手動探測中斷號。即,將每個中斷號(0 ~NR_IRQS -1)都申請一次並且產生中斷,然後通過中斷處理程序中的第一個參數irq來獲得中斷號。因爲,中斷號申請後產生中斷,內核會判斷該中斷產生的中斷號,並且在調用中斷處理函數的過程中賦值給第一個int irq參數。(具體代碼,見LDD3)
       irqreturn_t short_probing(int irq, void *dev_id, struct pt_regs *regs)     /* 中斷處理程序 */
   

        中斷處理程序

        中斷處理函數是在中斷時間運行的,可以理解爲與其他程序並行運行,且不在任何進程的上下文中。因此,中斷處理函數不能向用戶態發送或者接受數據、不能做任何發生休眠的操作(wait_event,非GPA_ATOMIC的內存分配,鎖住信號量等),最後也不能調用內核的schdule函數

      另外,中斷處理程序沒有自己的棧,相反,它共享被中斷進程的內核棧,如果沒有正在運行的進程,它就使用idle進程的棧。因爲中斷程序共享別人的堆棧,所以它們在棧中獲取空間時必須非常節省。內核棧在32位體系結構上是8KB,在64位體系結構上是16KB.執行的進程上下文和產生的所有中斷都共享內核棧。

      #include <linux/sched.h>
      static irqreturn_t (*handler)(int irq, void *dev_id, struct pt_regs *regs)     /* 中斷處理程序 */
      
      irq: 使用的中斷號,內核自己安排並在運行中斷處理程序handler時傳遞給int irq參數。
      dev_id: 用戶數據,傳遞給request_irq函數中的void *dev_id。一般在request_irq函數中將其置爲指向用戶自己實現的xxx_device結構,這樣當共享中斷時可以通過它來判斷是哪個具體設備產生了中斷
      regs: 很少用,保存CPU進入中斷處理程序之前的寄存器狀態,用來調試。 

        中斷處理函數的返回值很重要,如果確實處理了設備的中斷(例如,給設備寫讀等),就返回IRQ_HANDLED ,如果不需要則返回IRQ_NONE。或者使用宏IRQ_RETVAL(value) 。對應的內核定義爲,0、1與 value != 0。

       irqrequest_t,可能返回兩個特殊的值:IRQ_NONE和IRQ_HANDLED.當中斷處理程序檢測到一箇中斷時,但該中斷對應的設備並不是在註冊處理函數期間指定的產生源時,返回IRQ_NONE;當中斷處理程序被正確調用,且確實是它所對應的設備產生了中斷時,返回IRQ_HANDLED.C此外,也可以使用宏IRQ_RETVAL(x),如果x非0值,那麼該宏返回IRQ_HANDLED,否則,返回IRQ_NONE.利用這個特殊的值,內核可以知道設備發出的是否是一種虛假的(未請求)中斷。如果給定中斷線上所有中斷處理程序返回的都是IRQ_NONE,那麼,內核就可以檢測到出了問題。最後,需要說明的就是那個static了,中斷處理程序通常會標記爲static,因爲它從來不會被別的文件中的代碼直接調用。另外,中斷處理程序是無需重入的,當一個給定的中斷處理程序正在執行時,相應的中斷線在所有處理器上都會被屏蔽掉,以防止在同一個中斷上接收另外一個新的中斷。通常情況下,所有其他的中斷都是打開的,所以這些不同中斷線上的其他中斷都能被處理,但當前中斷總是被禁止的。由此可見,同一個中斷處理程序絕對不會被同時調用以處理嵌套的中斷。      

        中斷的功能就是將中斷相關的信息反饋給設備,並根據情況進行相應的數據的讀寫。典型的任務是:通知進程等待的事件已經發生,例如數據到達,然後喚醒在該設備上休眠的程序。

          中斷的啓用和禁止。

           當需要在短時間內禁止中斷,或者在持有自旋鎖時不得不禁止中斷時,內核提供了一下3個函數。不過應該儘量少用此策略,且不用在驅動中的互斥機制中。(......)

#include <asm/irq.h>
void disable_irq(int irq);  /* 禁止給定的中斷,並且等待中斷處理程序的結束返回。如果調用disable_irq函數的線程持有中斷處理函數的資源則可能死鎖 */
void disbale_irq_nosync(int irq); /* 禁止給定的中斷,立即返回。可能產生競爭 */
void eable_irq(int irq);  /* 恢復中斷 */ 
           可以嵌套調用,但是也要嵌套的恢復。 關閉當前處理器(即調用這些函數時運行的CPU)的所有中斷:

#include <asm/system.h>
void local_irq_save(unsigned long flags);  /* 保存當前的中斷狀態到flags之後禁止中斷 */ /* 當前中斷狀態? */
void local_irq_disable(void);  /* 直接關閉 */ 
/* 多個調用鏈中有個多個函數需要禁止中斷,應使用local_irq_save */

void local_irq_restore(unsigned  long flags);
void local_irq__enable(void); 

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