1. 程式人生 > 其它 >中斷處理

中斷處理

中斷處理

註冊中斷處理

    #### 申請IRQ

int request_irq(unsigned  int   irq,
                irqreturn_t (*handler)(int, void *, struct pt_regs *),
                unsigned   long   flags,
                const char   *dev_name,
                void   *dev_id);

        - irq是要申請的硬體中斷號

        - flags是與中斷有關的位掩碼

            SA_INTERRUPT

                當該位被設定時,表明這是一個“快速”的中斷處理程式,快速處理程式是執行在中斷禁止狀態下;

            SA_SHIRQ

                該位表示中斷可以在裝置之間共享。

            SA_SAMPLE_RANDOM

                該位指出產生的中斷能對/dev/random 裝置和 /dev/urandom裝置使用的熵池有貢獻。從這些裝置讀取資料,將會返回真正的隨機數,從而有助於應用軟體選擇用於加密的安全金鑰。

        - handler是向系統註冊的中斷處理函式指標;

        - dev_name傳遞給request_irq的字串,用來在/proc/interrupts中顯示中斷的擁有者

        - dev_id用於共享的中斷訊號線

        request_irq()返回0表示成功,返回-INVAL表示中斷號無效或處理函式指標為NULL,返回-EBUSY表示中斷已經被佔用且不能共享;

    #### 釋放IRQ

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

    呼叫request_irq的正確位置應該是第一次開啟裝置時。

    呼叫free_irq的位置是在最後一次關閉裝置。

    #### 查詢

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

        如果能夠成功分配給定的中斷,則該函式返回函式值0;

        在呼叫can_request_irq()和request_irq()之間,可能被分配掉;

    ### /proc

        /proc是一種偽檔案系統(也即虛擬檔案系統),儲存的是當前核心執行狀態的一系列特殊檔案,使用者可以通過這些檔案檢視有關係統硬體及當前正在執行程序的資訊,甚至可以通過更改其中某些檔案來改變核心的執行狀態。

            /proc/interrupts 顯示已經安裝的中斷

            /proc/stat 記錄系統啟動以來收到的中斷數

    ### 檢測IRQ號

        #### 自動檢測

        #### 自定義

            呼叫核心的幫助函式

            實現自己的版本

    ### 快速&慢速處理

實現一個處理例程

    相比普通函式的一些限制

    - 不在程序上下文,不能在核心空間和使用者空間中傳遞資料

    - 不能做任何可能睡眠的事

    中斷的典型任務:喚醒睡眠在裝置上的程序

    #### 引數&返回值

        **引數: **

        - int irq 中斷號

        - void *dev_id

            一種使用者資料型別(驅動程式可用的私有資料),傳遞給 request_irq的 void* 引數,會在中斷髮生時作為引數傳給處理例程。

            我們通常傳遞一個指向裝置資料結構的指標 到 dev_id 中,這樣一個管理若干相同裝置的驅動在中斷處理例程中不需要任何額外的程式碼,就可以找出哪個裝置產生了當前的中斷事件。

        - struct pt_regs *regs

            很少用到,在進入中斷前的快照,用於監視與除錯

static irqreturn_t sample_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
   struct sample_dev *dev = dev_id;
   /* now `dev' points to the right hardware item */
   /* .... */
}

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

    ### 使能&禁止中斷

禁止單箇中斷源:
void disable_irq (int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);

禁止所有中斷源:
void local_irq_save(unsigned long flags);
void local_irq_disable(void);

與上述兩個禁止中斷對應的恢復中斷的方法如下:
void local_irq_restore(unsigned long flags);
void local_irq_enable (void);

local開頭的方法的作用範圍是本CPU內

頂半部&底半部

    響應依次裝置中斷需要完成一定數量的工作,但是中斷處理例程需要儘快結束而不能使中斷阻塞的時間過長。

    - 頂半部 是實際響應中斷的例程(request_irq註冊的那個例程)。

    - 底半部 是被頂半部排程,並在稍後更安全的時間內執行的函式。

        底半部處理例程執行時,所有中斷都是開啟的(這就是所謂的在更安全的時間內執行)。

        實現機制

        1. tasklet (首選機制),它非常快, 但是所有的tasklet程式碼必須是原子的;

        2. 工作佇列, 它可能有更高的延時,但允許休眠。

    典型的情況是:頂半部儲存裝置資料到一個裝置特定的快取並排程它的底半部,最後退出, 這個操作非常快。底半部接著進行任何其他需要的工作。這種方式的好處是在底半部工作期間,頂半部仍然可以繼續為新中斷服務。

        a)如果一個任務對時間非常敏感,將其放在中斷處理程式中執行。

        b)如果一個任務和硬體相關,將其放在中斷處理程式中執行。

        c)如果一個任務要保證不被其他中斷(特別是相同的中斷)打斷,將其放在中斷處理程式中執行。

        d)其他所有任務,考慮放在下半部去執行。

    #### Tasklets

void my_tasklet_func(unsigned long); //定義一個處理函式
DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);
//定義一個tasklet結構my_tasklet,與my_tasklet_func()函式關聯

tasklet_schedule(&my_tasklet);//排程my_tasklet系統會在適當的時候進行排程執行

/*定義tasklet和底半部函式並關聯*/
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
void xxx_do_tasklet(unsigned long)/*中斷處理函式底半部*/
{   ......  }

/*中斷處理頂半部*/
irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
  ...
  tasklet_schedule(&xxx_tasklet);//排程tasklet
  ...
} 

/*裝置驅動模組載入函式*/
int __init xxx_int(void)
{   ...
   /*申請中斷*/
    result = request_irq(xxx_irq, xxx_interrupt, SA_INTERRUPT, "xxx", NULL);
   ....
}
/*裝置驅動模組解除安裝函式*/
void __exit xxx_exit(void)
{
  ...
  /*釋放中斷*/
  free_irq(xxx_irq, xxx_interrupt);
  ...
}

    #### Workqueues

struct work_struct my_wq; /*定義一個工作佇列*/
void my_wq_func(unsigned long); /*定義一個處理函式*/

INIT_WORK(&my_wq, (void (*)(void *))my_wq_func, NULL);
//初始化工作佇列並將其與處理函式繫結

schedule_work(&my_wq); //排程工作佇列執行

struct work_struct xxx_wq;
void xxx_do_work();

/*中斷處理底半部*/
void xxx_do_work(unsigned long)
{
...
}
/*中斷處理頂半部*/
irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
...
schedule_work(&xxx_wq);
...    
}

        與tasklet處理不同,在設計驅動模組載入函式中增加了初始化工作佇列的程式碼;

int __init xxx_init(void)/*裝置驅動模組載入函式*/
{   ...
   /*申請中斷*/
result = request_irq(xxx_irq, xxx_interrupt, SA_INTERRUPT, "xxx", NULL);
...
/*初始化工作佇列*/
INIT_WORK(&my_wq, (void(*)(void *))xxx_do_work, NULL);
...
}
void __exit xxx_exit(void)/*裝置驅動模組解除安裝函式*/
   { ...
     free_irq(xxx_irq, xxx_interrupt);/*釋放中斷*/
     ... }

struct work_struct my_wq; /定義一個工作佇列 /
void my_wq_func(unsigned long); /定義一個處理函式 /

INIT_WORK(&my_wq, (void (*)(void *))my_wq_func, NULL);
//初始化工作佇列並將其與處理函式繫結

schedule_work(&my_wq); //排程工作佇列執行