linux裝置驅動(十六)–中斷(一)

來源:互聯網
上載者:User

中斷處理

可以讓裝置在產生某個事件時通知處理器的方法就是中斷。一個“中斷”僅是一個訊號,當硬體需要獲得處理器對它的關注時,就可以發送這個訊號。 Linux 處理中斷的方式非常類似在使用者空間處理訊號的方式。
大多數情況下,一個驅動只需要為它的裝置的中斷註冊一個處理常式,併當中斷到來時進行正確的處理。本質上來講,中斷處理常式和其他的代碼並行運行。因此,它們不可避免地引起並發問題,並競爭資料結構和硬體。
透徹地理解並發控制技術對中斷來講非常重要。

 

安裝中斷處理常式

核心維護了一個中斷訊號線的註冊表,該註冊表類似於I/O連接埠的註冊表。模組在使用前要先請求一個中斷通道(或則插斷要求IRQ),然後在使用後釋放該通道。在許多場合下,模組也希望可以和其他的驅動程式共用中斷訊號線。

在<linux/sched.h>標頭檔中定義了以下兩個介面。

int request_irq(unsigned  int irq,irqreturn_t  (*handler)(int,void *,struct pt_regs *),unsigned long flags,

                          const char *dev_name,void *dev_id);

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

通常從request_irq函數返回給請求函數的值為0時表示申請成功,為負時表示錯誤碼。函數返回-EBUSY表示已經有另外一個驅動程式佔用了該中斷號訊號線。

unsigned int irq:是要申請的中斷號。

irqreturn_t (*handler)(int,void *,struct pt_regs *):這是要安裝的中斷處理函數指標。

unsigned long flags:與中斷管理有關的位元遮罩。

const char *dev_name:傳遞給request_irq的字串,用來在/proc/interrupts中顯示中斷的擁有者。

void *dev_id:這個指標用於共用的中斷訊號線,它是唯一的標識符,在中斷訊號線空閑時可以使用它,驅動程式也可以使用他指向驅動程式自己的私人資料區(用來識別哪個裝置產生了中斷)。在沒有強制使用共用方式時,dev_id可以被設定為NULL,總之用它來指向裝置的資料結構是一個比較好的思路。

 

flag中設定的位如下所示:

SA_INTERRUPT:當該位被設定時,表明這是一個“快速”的中斷處理常式,快速處理常式運行在中斷禁用的狀態下。

SA_SHIRQ:該位表示中斷可以在裝置之間共用。

SA_SAMPLE_RANDOM:該位表示產生的中斷能對 /dev/random 和 /dev/urandom 使用的熵池(entropy pool)有貢獻。
讀取這些裝置會返回真正的隨機數,從而有助於應用程式軟體選擇用於加密的安全密鑰。
若裝置以真正隨機的周期產生中斷,就應當設定這個標誌。若裝置中斷是可預測的,這個標誌不值得設定。可能被攻擊者影響的裝置不應當設定這個標誌。更多資訊看
drivers/char/random.c 的注釋。

中斷處理常式可在驅動程式初始化時或者裝置第一次開啟時安裝。因為中斷訊號的數量是非常有限的,所以推薦在裝置開啟時安裝中斷而不是在初始化時安裝,這樣可以共用這些有限的資源。

調用request_irq的正確位置應該是在裝置第一次開啟、硬體被告知產生中斷之前。調用free_irq的位置是最後一次關閉裝置、硬體被告知不再用中斷處理器之後。這樣我們必須為每個裝置維護一個開啟計數。

if(short_irq>=0){        //short_irq是我們要申請的中斷號

    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);

          short_irq = -1;  //中斷號致負,表明申請失敗

    }

}

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 a irq %i/n",short_irq);

        short_irq = -1;

  }else{

        outb(0x10,short_base +2);

  }

}

在i386和x86體繫結構中定義如下函數:

int can_request_irq(unsigned int irq,unsigned long flags);

如果能夠成功分配給定的中斷,則該函數返回非零值,但要注意在request_irq和can_request_irq之間,可能會發生一些事情來改變現狀。

 

 /porc介面

當硬體的中斷到達處理器時,一個內部計數遞增,這位檢查裝置是否按預期工作提供了一種方法,顯示的中斷報告在檔案/proc/interrupts中。另一個有用的檔案時/proc/stat。關於檔案中個欄位的意義,可以參與資料,兩者的區別在於interrupts檔案不用來於體繫結構,而stat檔案則是依賴的:欄位的數量依賴於核心之下的硬體

<asm/irq.h>中。ARM的定義為:

#define NR_IRQS    128

 

 

自動檢測IRQ號

驅動初始化時最迫切的問題之一是決定裝置要使用的IRQ
線,驅動需要資訊來正確安裝處理常式。自動檢測中斷號對驅動的可用性來說是一個基本需求。有時自動探測依賴一些裝置具有的預設特性,以下是典型的並口中斷探測程式:

if(short_irq < 0){

     switch(short_base){

         case 0x378: 

                short_irq = 7;

                break;

         case 0x278:

                short_irq = 2;

                break;

         case 0x3bc:

                short_irq = 5;

                break;

    }

}


當目標裝置有能力告知驅動它要使用的中斷號時,自動探測中斷號只是意味著探測裝置,無需做額外的工作探測中斷。

但不是每個裝置都對程式員友好,對於他們還是需要一些探測工作。這個工作技術上非常簡單:
驅動告知裝置產生中斷並且觀察發生了什麼。如果一切順利,則只有一個中斷訊號線被啟用。儘管探測在理論上簡單,但實現可能不簡單。有 2 種方法來進行探測中斷:
調用核心定義的輔助函數DIY探測

 

核心輔助下的探測

linux核心提供了一個底層設施來探測中斷號,它只能在非共用中斷的模式下工作,但是大多數硬體有能力工作在共用中斷的模式下,並提供更好的找到配置中斷號的方法。核心的這一設施由兩個函數組成,在標頭檔<linux/iterrupt.h>中聲明。

unsigned long probe_irq_on(void);

該函數返回一個未分配中斷的位元遮罩。驅動程式必須儲存返回的為掩碼,並且將它傳遞給後面的probe_irq_off函數,調用該函數之後,驅動程式要安排裝置至少產生一次中斷。

unsigned long probe_irq_off(unsigned long);

在請求裝置產生中斷之後,驅動程式調用這個函數,並將前面probe_irq_on返回的位元遮罩作為參數傳遞給它。probe_irq_off返回"probe_irq_on"之後發生的中斷編號,如果沒有中斷髮生,就返回0(因此,IRQ 0不能被探測到,但在任何已支援的體繫結構上,沒有任何裝置能夠使用IRQ 0)。如果產生多次中斷,probe_irq_off就返回一個負值(出現二意性)。

要注意在調用probe_irq_on之後啟用裝置上的中斷,並在調用probe_irq_off之前禁用中斷。在probe_irq_off之後,需要處理裝置上代處理的中斷。

int  count = 0 ;

do{

    unsigned long mask;  定義儲存probe_irq_on返回掩碼的變數

    maske = probe_irq_on(); 

    outb(0x10,short_base+2);    啟用中斷,short_base+2的第四個引腳

    outb(0x00,short_base);        清除short_base的位

    outb(0xFF,short_base);        設定該位,進行中斷,寫資料到資料連接埠

    outb(0x00,short_base+2);    調用probe_irq_off前禁用中斷

    udelay(5);        留給中斷探測一段時間

    short_irq = probe_irq_off(mask);  

 

    if(short_irq == 0){        這裡對probe_irq_off的傳回值進行判斷,為零沒有產生中斷

          printk(KERN_INFO "short_;no irq reported by probe/n");

          short_irq = -1;

    }

 

     /*如果已經產生多個中斷,則結果為負值。

      *我們應該服務該中斷並多次再試。

      *最多重試5次,然後放棄

      */

}while(short_irq < 0 &&count++ <5)

if(short_irq < 0){

      printk("short:probe failed %i times,giving up/n",count);

}

注意在調用prpbe_irq_off 之前調用udelay的使用。探測是一個耗時的任務。因此,最好的方法是在模組初始化的時候探測中斷訊號線一次,這與時候裝置開啟時(推薦)或則在初始化函數內(不推薦)安排中斷處理函數無關。

 

DIY探測

啟用所有未被佔用的中斷,然後觀察會發生什麼。但是,我們要充分發揮對有關裝置的瞭解。通常,裝置可以使用3或4個IRQ號中的一個來進行配置,探測這些IRQ號,使我們能夠不必測試所有可能的IRQ就檢測到正確的IRQ號。

下面的代碼通過測試所有“可能”的中斷並觀察將要發生的事情來進行中斷探測。trials數組列出了以0作為結束標誌的需要測試的IRQ,tried數組用來記錄哪個處理常式被驅動程式註冊了。

int trials[] = {3,5,7,9,0};

int tried[]  = {0,0,0,0,0};

int i,count = 0;

為所有可能的中斷線安裝探測處理常式。

for(i=0;trials[i];i++){

    tried[i] = request_irq(trials[i],short_probing,SA_INTERRUPTIBLE,"short probe",NULL);

}

do{

    short_irq = 0;

    outb(ox10,short_base+2);

    outb(0x00,short_base);

    outb(0xff,short_base);

    outb(0x00,short_base+2);

    udelay(5);//這裡等待中斷的發生,如果發生了中斷會在handler中重新設定short_irq的值

 

    if(short_irq == 0){

          printk(KERN_INFO "short:no irq reported by probe./n");

    }

}while(short_irq <= 0 && count++ < 5)

 

for(i=0;tried[i];i++){

    if(tried[i] == 0)

          free_irq(trials[i],NULL);

}

 

if(short_irq < 0){

      printk(KERN_INFO "short:probe failed %i times,giving up/n",count);

}

 

handler函數實現如下:

irqreturn_t short_probing(int irq,void *dev_id,struct pt_regs *regs)

{

   if(short_irq == 0)  short_irq = irq;

   if(short_irq != irq)  short_irq = -irq;

   return IRQ_HANDLED;

}

 

 有時,我們無法預知“可能的IRQ值。在這種情況下,需要探測所有的空閑裝置號,而不僅僅是那些由trials數組列出的中斷號。為了探測所有中斷,不得不從IRQ 0 探測到IRQ NR_IRQS-1NR_IRQS是在標頭檔<asm/irq.h>中定義的與平台相關的常數。

 

 

 

快速和慢速處理常式

快速中斷是那些能夠很快處理的中斷,而處理慢速中斷會花費更長的時間。在處理慢速中斷時處理器重新使能中斷,避免快速中斷被延時過長。在現代核心中,快速和慢速中斷的區別已經消失,剩下的只有一個:快速中斷(使用
SA_INTERRUPT )執行時禁止所有在當前處理器上的其他中斷。注意:其他的處理器仍然能夠處理中斷。

 除非你充足的理由在禁止其他中斷情況下來運行中斷處理常式,否則不應當使用SA_INTERRUPT.

 

 

實現中斷處理常式

中斷處理常式的一些限制:1.處理常式不能向使用者空間發送或者接受資料,因為它不是在任何進程的上下文中執行的。2.處理常式也不能做任何可能發生休眠的操作,例如調用:wait_event、使用不帶GFP_ATOMIC標誌的記憶體配置操作,或則鎖住一個訊號量等等。3.最後處理常式不能調用schedule函數。

 

中斷處理常式的功能就是將有關中斷接收的資訊反饋給裝置,並根據正在服務的中斷的不同含義對資料進行相應的讀或寫。中斷處理常式的第一步通常要清除介面卡上的一個位,大多數硬體裝置在他們的”interruptible-pending"位被清除之前不會產生其它的中斷。這也要根據硬體的工作原理決定, 這一步也可能需要在最後做而不是開始; 這裡沒有通用的規則。一些裝置不需要這步, 因為它們沒有一個"中斷掛起"位;
這樣的裝置是少數。

 

中斷處理常式的一個典型任務是:如果中斷通知進程所等待的事件已經發生,比如新的資料已經到達,就會喚醒在該裝置上休眠的進程。

例如在幀捕捉卡的例子,一個進程通過連續的讀該裝置來擷取一系列映像;在讀每一幀資料前,read調用都是阻塞的,每當新的資料幀到達時,中斷處理常式就會喚醒該進程。

不管是快速或慢速處理常式,程式員應編寫執行時間儘可能短的處理常式。 如果需要進行長時間計算, 最好的方法是使用 tasklet 或者 workqueue
在一個更安全的時間來調度計算任務(參見“頂半和底半”)。

 

irqreturn_t short_interrupt(int irq,void *dev_id,struct pt_reg *regs)

{

   struct timeval tv;

   int written;

 

   do_gettimeofday(tv);

   written = sprintf((char *)short_head,"%08u.%06u/n",(int)(tv.tv_sec % 100000000),(int)(tv.tv_usec));

   sprintf是一個格式化函數,原型為:int sprintf(char *string,char *format,arg_list);作用是將參數格式化的輸出到目的字串中(第一個參數為目的字串),傳回值表示有多少個字元被輸入到目標字串。

   BUG_ON(written != 16);

   short_incr_bp(&short_head,written);

   wake_up_interruptible(&short_queue);

   return IRQ_HANDLED;

}

 

static inline short_incr_bp(volatile unsigned long *index,int delta)

{

    unsigned long new = *index + delta;

    barrier();  //禁止對前後兩條語句進行最佳化。

    *index = (new > (short_buffer + PAGE_SIZE))?short_buffer:new);

}

 

處理常式的參數及傳回值

中斷處理常式中用到3個參數,int irq、void *dev_id、struct pt_reg *regs:

1.如果存在任何可以列印到日誌的訊息時,中斷號(int irq)是很有用的。

2.void *dev_id是一種客戶資料類型(即驅動程式可用的私人資料)。傳遞給request_irq函數的void *參數會在中斷髮生時作為參數被傳回處理常式(void *dev_id參數和request_irq函數中的*dev_id是相同的)。通常我們會為dev_id傳遞一個指向自己裝置的資料結構指標。這樣一個管理若干相同裝置的驅動在中斷處理常式中不需要任何額外的代碼,就可以找出哪個裝置產生了當前的中斷事件。

3.最後一個參數struct pt_reg *regs很少使用,它用來儲存處理器進入中斷代碼之前的處理器上下文快照,該寄存器可被用來監視和調試,對一般的裝置驅動程式任務來說通常不是必須的。

static irqreturn_t sample_interrupt(int irq,void *dev_id,struct pt_regs *regs)

{

   struct sample_dev *dev = dev_id;

   ...

}

處理常式對應的典型open代碼:

static void sample_open(struct inode *inode,struct file *filp)

{

   struct sample_dev *dev = hwinfo +MINOR(inode->i_rdev);

   request_irq(dev->irq,sample_interrupt,

             0/* flags */,"sample",dev/* dev_id */);

   ...

   return 0;

}

中斷處理常式應該返回一個值,用來指明是否真正處理了一個中斷。如果處理常式發現裝置確實需要處理, 應當返回 IRQ_HANDLED; 否則傳回值 IRQ_NONE。

 

 

 

啟用和禁用中斷

有時裝置驅動程式必須在一個時間段內(希望較短)阻塞中斷的發出。通常來說我們必須在擁有自旋鎖的情況下阻塞中斷,以免死結系統。在涉及自旋鎖的情況下,有多種方法可禁用中斷。但是我們應注意盡量少禁用中斷,即使在裝置驅動程式中也是如此。

 

禁用單個中斷:有時(但很少),驅動程式需要禁用某個特定中斷線的中斷產生。核心提供了三個函數<asm/irq.h>。需要注意的是我們不能禁用共用的中斷線,而在現代系統中,中斷的共用是很常見的。

 

void disable_irq(int irq);//不但會禁止給定的中斷,而且也會等待當前正在執行的中斷處理常式完成。要注意的是,如果調用disable_irq的線程擁有任何中斷處理常式所需要的資源,則系統會死結。

void disable_irq_nosync(int irq);//和disable_irq不同, disable_irq_nosync是立即返回的。因此使用後則會更快,但是很可能讓驅動程式處於竟態狀態。

void enable_irq(int irq);

 

調用任一函數可能更新在可程式化控制器(PIC)中的特定 irq 的掩碼, 從而禁止或使能所有處理器特定的 IRQ。這些函數的調用能夠嵌套,即如果
disable_irq 被連續調用 2 次,則需要 2 個 enable_irq 重新使能 IRQ
。可以在中斷處理常式中調用這些函數,但在處理某個IRQ時再開啟它是不好的做法。

 

禁用所有中斷:在<asm/system.h> 中,定義了兩個函數:

void local_irq_save(unsigned long flags);把當前中斷狀態儲存到flags中,然後禁用當前處理器上的中斷髮送。注意flags是被直接傳遞。

void local_irq_disable(void);不儲存狀態而關閉本地處理器上的中斷髮送。不推薦使用。

如果調用鏈中的多個函數需要禁用中斷,則應該使用local_irq_save。

 

開啟中斷:void local_irq_restore(unsigned long flags);

void local_irq_enable(void);

在目前的2.6核心中,沒有辦法全域禁用整個系統上的所有中斷。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.