【linux kernel】 中斷處理-中斷下半部

歡迎轉載,轉載時需保留做者信息,謝謝。html

郵箱:tangzhongp@163.comlinux

博客園地址:http://www.cnblogs.com/embedded-tzp數據結構

Csdn博客地址:http://blog.csdn.net/xiayulewa併發

 

 

1.   概述

Linux內核中斷機制:爲了在中斷執行時間儘量短和中斷處理須要完成大量工做之間找到一個平衡點,Linux將中斷處理程序分解爲兩個半部,頂半部和底半部。函數

      頂半部完成儘量少的比較緊急的任務,它每每只是簡單地讀取寄存器中的中斷狀態並清除中斷標誌位就進行登記工做,將底半部處理程序掛到該設備的底半部執行隊列中去。this

      那上半部和下半部是分界線是什麼? request_irq註冊的中斷函數爲分界線。atom

      上半部:spa

        當執行完request_irq註冊的中斷函數後,上半部結束。在註冊的中斷函數中,登記下半部要作的工做。.net

       上半部已經討論了:http://www.cnblogs.com/embedded-tzp/p/4451354.html線程

      底半部實現方式有:

            軟中斷

            tasklet

            工做隊列

     

2.   軟中斷

http://www.cnblogs.com/embedded-tzp/p/4452041.html中已討論。

3.   Tasklet

3.1. 數據結構

struct tasklet_struct

{

    struct tasklet_struct *next;

    unsigned long state;

    atomic_t count;

    void (*func)(unsigned long);

    unsigned long data;

};

該結構體由tasklet_init初始化。

 

Softirq.c (src\kernel)中:

struct tasklet_head

{

    struct tasklet_struct *head;

    struct tasklet_struct **tail;

};

struct tasklet_head  tasklet_vec;

3.2. 註冊

softirq_init中有:

open_softirq(TASKLET_SOFTIRQ, tasklet_action);

可見其實現方式爲軟件中斷。其處理函數爲tasklet_action

 

tasklet_schedule__tasklet_schedule

    t->next = NULL;

    *__this_cpu_read(tasklet_vec.tail) = t;

    __this_cpu_write(tasklet_vec.tail, &(t->next));

tasklet對象組織成一張鏈表。

 

3.3. 執行流程

       tasklet_schedule__tasklet_scheduleraise_softirq_irqoff(TASKLET_SOFTIRQ);

    觸發軟中斷執行,軟中斷執行流程見http://www.cnblogs.com/embedded-tzp/p/4452041.html

    最終軟中斷會執行到tasklet的處理函數tasklet_action

tasklet_action中是個while,循環處理tasklet_vec鏈表中的struct   tasklet_struct

 

 

3.4. 其它:

* 不容許訪問用戶空間;?????
*
不容許訪問current指針;
*
不能執行休眠或調度。

特徵:

* 一個tasklet可被禁用或啓用;只用啓用的次數和禁用的次數相同時,tasklet纔會被執行。
*
和定時器相似,tasklet能夠註冊本身;
* tasklet
可被調度在通常優先級或者高優先級上執行,高優先級總會首先執行;
*
若是系統負荷不重,則tasklet會當即獲得執行,且始終不會晚於下一個定時器滴答;
*
一個tasklet能夠和其餘tasklet併發,但對自身來說必須嚴格串行處理,即一個tasklet不會在多個處理器上執行。

 

3.5. 函數集合

 

DECLARE_TASKLET(name,func,data); /*定義及初始化tasklet*/

DECLARE_TASKLET_DISABLED(name,func,data);      /*定義及初始化後禁止該tasklet*/

 

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); /* 初始化tasklet,func指向要執行的函數,data爲傳遞給函數func的參數 */

 

void tasklet_disable(struct tasklet_struct *t) /*禁用指定tasklet */

void tasklet_disable_nosync(struct tasklet_struct *t) /*禁用指定tasklet,但不會等待任何正在運行的tasklet退出*/

void tasklet_enable(struct tasklet_struct *t)      /*啓用先前被禁用的tasklet*/

 

void tasklet_schedule(struct tasklet_struct *t)    /*註冊並調度執行指定的tasklet*/

void tasklet_hi_schedule(struct tasklet_struct *t) /*調度指定的tasklet以高優先級執行*/

 

void tasklet_kill(struct tasklet_struct *t)   /*移除指定tasklet*/

 

3.6.    實例

#include <linux/init.h>

#include <linux/module.h>

#include <linux/irq.h>

#include <linux/interrupt.h>

#include <asm/gpio.h>

#include <plat/gpio-cfg.h>

 

/*硬件相關的數據結構*/

struct btn_resource {

    int irq;  //中斷號

    char *name; //中斷名稱

};

 

//初始化板卡按鍵信息

static struct btn_resource btn_info[] = {

    [0] = {

        .irq = IRQ_EINT(0),

        .name = "KEY_UP"

    },

    [1] = {

        .irq = IRQ_EINT(1),

        .name = "KEY_DOWN"

    }

};

 

//tasklet的處理函數,在tasklet_action函數中被處理

static void btn_tasklet_func(unsigned long data)

{

    struct btn_resource *pdata = (struct btn_resource *)data;

    printk("%s: irq = %d, name = %s\n",

            __func__, pdata->irq, pdata->name);

}

 

//定義tasklet對象

static DECLARE_TASKLET(btn_tasklet,

                btn_tasklet_func, (unsigned long)&btn_info[0]);

 

//中斷處理函數

//irq:中斷號,dev_id:保存註冊中斷時傳遞的參數信息

static irqreturn_t button_isr(int irq, void *dev_id)

{

    //登記底半部信息

    tasklet_schedule(&btn_tasklet); //註冊並調度taskletCPU會在空閒時執行tasklet的處理函數

    printk("%s\n", __func__);

    return IRQ_HANDLED; //成功,失敗:IRQ_NONE

}

 

static int btn_init(void)

{

    //申請中斷和註冊中斷處理程序

    /*

     * IRQ_EINT(0):中斷號

     * button_isr:中斷處理程序,中斷髮生之後,內核執行此函數

     * IRQF*:外部中斷的觸發方式,內部中斷此參數爲0

     * KEY_UP:中斷名稱,出如今 cat /proc/interrupts

     * &mydata:給中斷處理程序傳遞的參數信息,不傳遞參數指定爲NULL

     */

    int i;

 

    for (i = 0; i < ARRAY_SIZE(btn_info); i++)

        request_irq(btn_info[i].irq, button_isr,

                IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,

                btn_info[i].name, &btn_info[i]);

    printk("%s\n", __func__);

    return 0;

}

 

static void btn_exit(void)

{

    int i;

 

    for (i = 0; i < ARRAY_SIZE(btn_info); i++)

        free_irq(btn_info[i].irq, &btn_info[i]);

    printk("%s\n", __func__);

}

module_init(btn_init);

module_exit(btn_exit);

MODULE_LICENSE("GPL");

 

 

4.   工做隊列

       工做隊列涉及到linux內核進程調度策略,但調度策略不是此處應該討論的。

4.1. 內核默認隊列

4.1.1.   數據結構

struct work_struct

struct delayed_work

 

初始化隊列對象

    INIT_WORK(&mywork, my_work_func);

    INIT_DELAYED_WORK(&mydwork, my_dwork_func);

 

4.1.2.   函數集合

schedule_work(&mywork)

schedule_delayed_work(&mydwork, 5*HZ)

 

4.1.3.   爲何是內核默認隊列

Workqueue.c (src\kernel) 中:

    init_workqueues

       system_wq = alloc_workqueue("events", 0, 0);

 

schedule_work

    queue_work(system_wq, work);

 

可見struct work_struct對象是默認放在缺省的內核線程線程events中。當缺省工做隊列負載過重,執行效率會很低,須要咱們自建隊列

 

4.1.4.   實例

#include <linux/init.h>

#include <linux/module.h>

#include <linux/irq.h>

#include <linux/interrupt.h>

#include <asm/gpio.h>

#include <plat/gpio-cfg.h>

 

/*硬件相關的數據結構*/

struct btn_resource {

    int irq;  //中斷號

    char *name; //中斷名稱

};

 

//初始化板卡按鍵信息

static struct btn_resource btn_info[] = {

    [0] = {

        .irq = IRQ_EINT(0),

        .name = "KEY_UP"

    },

    [1] = {

        .irq = IRQ_EINT(1),

        .name = "KEY_DOWN"

    }

};

 

//分配工做和延時工做

static struct work_struct mywork;

 

//工做隊列處理函數

static void my_work_func(struct work_struct *work)

{

    printk("%s\n", __func__);

}

 

//中斷處理函數

//irq:中斷號,dev_id:保存註冊中斷時傳遞的參數信息

static irqreturn_t button_isr(int irq, void *dev_id)

{

    //登記工做底半部信息

    /*

     * schedule_work:登記工做,將工做交給內核默認的工做隊列和

     * 內核線程去管理和調度執行,cpu會在適當的時候會執行工做的處理函數

     * */

    schedule_work(&mywork);

 

    printk("%s\n", __func__);

    return IRQ_HANDLED; //成功,失敗:IRQ_NONE

}

 

static int btn_init(void)

{

    //申請中斷和註冊中斷處理程序

    /*

     * IRQ_EINT(0):中斷號

     * button_isr:中斷處理程序,中斷髮生之後,內核執行此函數

     * IRQF*:外部中斷的觸發方式,內部中斷此參數爲0

     * KEY_UP:中斷名稱,出如今 cat /proc/interrupts

     * &mydata:給中斷處理程序傳遞的參數信息,不傳遞參數指定爲NULL

     */

    int i;

 

    for (i = 0; i < ARRAY_SIZE(btn_info); i++)

        request_irq(btn_info[i].irq, button_isr,

                IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,

                btn_info[i].name, &btn_info[i]);

   

    //初始化工做和延時工做

    INIT_WORK(&mywork, my_work_func);

 

    printk("%s\n", __func__);

    return 0;

}

 

static void btn_exit(void)

{

    int i;

 

    for (i = 0; i < ARRAY_SIZE(btn_info); i++)

        free_irq(btn_info[i].irq, &btn_info[i]);

    printk("%s\n", __func__);

}

module_init(btn_init);

module_exit(btn_exit);

MODULE_LICENSE("GPL");

 

4.2. 自建隊列

如上討論,當使用work_struct,默認放在缺省的內核線程線程events中。當缺省工做隊列負載過重,執行效率會很低,須要咱們自建隊列

4.2.1.   數據結構

struct workqueue_struct

 

4.2.2.   函數集合

wq = create_workqueue("tzp");

 

queue_work(wq, &mywork);

queue_delayed_work(wq, &mydwork, 3*HZ); // 3秒後執行

 

destroy_workqueue

 

 

4.2.3.   實例

#include <linux/init.h>

#include <linux/module.h>

#include <linux/irq.h>

#include <linux/interrupt.h>

#include <asm/gpio.h>

#include <plat/gpio-cfg.h>

 

/*硬件相關的數據結構*/

struct btn_resource {

    int irq;  //中斷號

    char *name; //中斷名稱

};

 

//初始化板卡按鍵信息

static struct btn_resource btn_info[] = {

    [0] = {

        .irq = IRQ_EINT(0),

        .name = "KEY_UP"

    },

    [1] = {

        .irq = IRQ_EINT(1),

        .name = "KEY_DOWN"

    }

};

//定義工做隊列的指針

static struct workqueue_struct *wq;

 

//分配延時工做

static struct delayed_work mydwork;

 

//延時工做的處理函數

static void my_dwork_func(struct work_struct *work)

{

    printk("%s\n", __func__);

}

 

 

//中斷處理函數

//irq:中斷號,dev_id:保存註冊中斷時傳遞的參數信息

static irqreturn_t button_isr(int irq, void *dev_id)

{

    //登記關聯本身的工做隊列和工做

    queue_delayed_work(wq, &mydwork, 3*HZ);

   

    printk("%s\n", __func__);

    return IRQ_HANDLED; //成功,失敗:IRQ_NONE

}

 

static int btn_init(void)

{

    //申請中斷和註冊中斷處理程序

    /*

     * IRQ_EINT(0):中斷號

     * button_isr:中斷處理程序,中斷髮生之後,內核執行此函數

     * IRQF*:外部中斷的觸發方式,內部中斷此參數爲0

     * KEY_UP:中斷名稱,出如今 cat /proc/interrupts

     * &mydata:給中斷處理程序傳遞的參數信息,不傳遞參數指定爲NULL

     */

    int i;

 

    for (i = 0; i < ARRAY_SIZE(btn_info); i++)

        request_irq(btn_info[i].irq, button_isr,

                IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,

                btn_info[i].name, &btn_info[i]);

   

    //初始化延時工做

    INIT_DELAYED_WORK(&mydwork, my_dwork_func);

 

    //建立本身的工做隊列和內核線程

    wq = create_workqueue("tzp"); //線程名叫tzp

 

    printk("%s\n", __func__);

    return 0;

}

 

static void btn_exit(void)

{

    int i;

 

    //銷燬本身的工做隊列和線程

    destroy_workqueue(wq);

   

    for (i = 0; i < ARRAY_SIZE(btn_info); i++)

        free_irq(btn_info[i].irq, &btn_info[i]);

    printk("%s\n", __func__);

}

module_init(btn_init);

module_exit(btn_exit);

MODULE_LICENSE("GPL");

相關文章
相關標籤/搜索