高级字符设备驱动--中断下半部机制之tasklet

 

综述 

Linux把中断处理例程分两部分

上半分:实际响应中断的例程。
下半分:被顶部分调用,通过开中断的方式进行。

两种机制实现:
Tasklet
工作队列work queue

上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。因此,上半部执行的 速度就会很快,可以服务更多的中断请求。但是,仅有"登记中断"是远远不够的,因为中断的事件可能很复杂。因此,Linux引入了一个下半部,来完成中断 事件的绝大多数使命。

下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。

 

Tasklet(小任务机制)

 -- 内核在BH机制的基础上进行了扩展, 实现“软中断请求”(softirq)机制。利用软中断代替 bottom half handler 的处理。

 -- tasklet 机制正是利用软中断来完成对驱动 bottom half 的处理。

 -- tasklet会让内核选择某个合适的时间来执行给定的小任务。

 

小任务tasklet的实现

其数据结构为struct tasklet_struct,每一个结构体代表一个独立的小任务,在<linux/interrupt.h>定义如下
 
struct tasklet_struct
{
    struct tasklet_struct *next;/*指向下一个链表结构*/
    unsigned long state;/*小任务状态*/
    atomic_t count;/*引用计数器*/
    void (*func)(unsigned long);/*小任务的处理函数*/
    unsigned long data;/*传递小任务函数的参数*/
};

state的取值参照下边的枚举型:

enum
{
    TASKLET_STATE_SCHED,    /* 小任务已被调用执行*/
    TASKLET_STATE_RUN   /*仅在多处理器上使用*/
};

count域是小任务的引用计数器。只有当它的值为0的时候才能被激活,并其被设置为挂起状态时,才能够被执行,否则为禁止状态。

 

Tasklet

 -- ksoftirqd()是一个后台运行的内核线程,它会周期的遍历软中断的向量列表,如果发现哪个软中断向量被挂起了( pend ),就执行对应的处理函数。

 -- tasklet 所对应的处理函数就是tasklet_action,这个处理函数在系统启动时初始化软中断时,就在软中断向量表中注册。
 
   -- tasklet_action() 遍历一个全局的 tasklet_vec 链表。链表中的元素为 tasklet_struct结构体。


一、声明和使用小任务tasklet

静态的创建一个小任务的宏有一下两个:

#define DECLARE_TASKLET(name, func, data)  \
       struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

 name 是 tasklet 的名字,
 Func  是执行 tasklet 的函数;
 data 是 unsigned long 类型的 function 参数。

#define DECLARE_TASKLET_DISABLED(name, func, data) \
       struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

这两个宏的区别在于计数器设置的初始值不同,前者为0,后者为1。
为0的表示激活状态,为1的表示禁止状态。

其中ATOMIC_INIT宏为:
#define ATOMIC_INIT(i)   { (i) }

此宏在include/asm-generic/atomic.h中定义。这样就创建了一个名为name的小任务,其处理函数为func。当该函数被调用的时候,data参数就被传递给它。

 

二、小任务处理函数程序

处理函数的的形式为:void my_tasklet_func(unsigned long data)。这样DECLARE_TASKLET(my_tasklet, my_tasklet_func, data)实现了小任务名和处理函数的绑定,而data就是函数参数。

三、调度编写的tasklet

调度小任务时引用tasklet_schedule(&my_tasklet)函数就能使系统在合适的时候进行调度。函数原型为:

static inline void tasklet_schedule(struct tasklet_struct *t)
{
    if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
       __tasklet_schedule(t);
}
调度执行指定的tasklet。
将定义后的 tasklet 挂接到 cpu 的 tasklet_vec 链表。而且会引起一个软 tasklet 的软中断 , 既把 tasklet 对应的中断向量挂起 (pend) 。

这个调度函数放在中断处理的上半部处理函数中,这样中断申请的时候调用处理函数(即irq_handler_t handler)后,转去执行下半部的小任务。

tasklet 的接口

 void tasklet_disable(struct tasklet_struct *t);
这个函数禁止给定的 tasklet. tasklet ,但仍然可以被 tasklet_schedule 调度, 但是它的执行被延后直到这个 tasklet 被再次激活。
 void tasklet_enable(struct tasklet_struct *t);
激活一个之前被禁止的 tasklet. 如果这个 tasklet 已经被调度, 它会很快运行.
 一个对tasklet_enable 的调用必须匹配每个对 tasklet_disable 的调用, 因为内核跟踪每个 tasklet 的"禁止次数".
 void tasklet_hi_schedule(struct tasklet_struct *t);
调度 tasklet 在更高优先级执行. 当软中断处理运行时, 它在其他软中断之前处理高优先级 tasklet。
 void tasklet_kill(struct tasklet_struct *t);
这个函数确保了这个 tasklet 没被再次调度来运行; 它常常被调用当一个设备正被关闭或者模块卸载时. 如果这个 tasklet 被调度来运行, 这个函数等待直到它已执行.

模板

使用tasklet作为下半部的处理中断的设备驱动程序模板如下:

/*定义tasklet和下半部函数并关联*/
void my_do_tasklet(unsigned long);
DECLARE_TASKLET(my_tasklet, my_do_tasklet, 0);

/*中断处理下半部*/
void my_do_tasklet(unsigned long)
{
  ……/*编写自己的处理事件内容*/
}

/*中断处理上半部*/
irpreturn_t my_interrupt(unsigned int irq,void *dev_id)
{
 ……
/*调度my_tasklet函数,根据声明将去执行my_tasklet_func函数*/
 tasklet_schedule(&my_tasklet)
 ……
}

/*设备驱动的加载函数*/
int __init xxx_init(void)
{
 ……
 /*申请中断, 转去执行my_interrupt函数并传入参数*/
result=request_irq(my_irq,my_interrupt,IRQF_DISABLED,"xxx",NULL);
 ……
}


/*设备驱动模块的卸载函数*/
void __exit xxx_exit(void)
{
……
/*释放中断*/
free_irq(my_irq,my_interrupt);
……
}

 

中断下半部机制之workqueue(二)

 

工作队列workqueue

工作队列(work queue)是另外一种将中断的部分工作推后的一种方式,它可以实现一些tasklet不能实现的工作,比如工作队列机制可以睡眠。这种差异的本质原因 是,在工作队列机制中,将推后的工作交给一个称之为工作者线程(worker thread)的内核线程去完成(单核下一般会交给默认的线程events/0)。因此,在该机制中,当内核在执行中断的剩余工作时就处在进程上下文 (process context)中。也就是说由工作队列所执行的中断代码会表现出进程的一些特性,最典型的就是可以重新调度甚至睡眠。

对于tasklet机制(中断处理程序也是如此),内核在执行时处于中断上下文(interrupt context)中。而中断上下文与进程毫无瓜葛,所以在中断上下文中就不能睡眠。

因此,当推后的那部分中断程序需要睡眠时,工作队列毫无疑问是最佳选择;否则用tasklet。

 

工作队列的实现

工作队列work_struct结构体,位于/include/linux/workqueue.h
 
typedef void (*work_func_t)(struct work_struct *work);

struct work_struct {
      atomic_long_t data; /*传递给处理函数的参数*/
#define WORK_STRUCT_PENDING 0/*工作是否正在等待处理标志*/             
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
      struct list_head entry;  /* 连接所有工作的链表*/
      work_func_t func; /* 要执行的函数*/
#ifdef CONFIG_LOCKDEP
      struct lockdep_map lockdep_map;
#endif
};

这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表 上移去。当链表上不再有对象的时候,它就会继续休眠。可以通过DECLARE_WORK在编译时静态地创建该结构,以完成推后的工作。

工作的创建(静态方法)

#define DECLARE_WORK(n, f)                                 \
            struct work_struct n = __WORK_INITIALIZER(n, f)

而后边这个宏为一下内容:
#define __WORK_INITIALIZER(n, f) {                      \
      .data = WORK_DATA_INIT(),                            \
      .entry      = { &(n).entry, &(n).entry },                    \
      .func = (f),                                        \
      __WORK_INIT_LOCKDEP_MAP(#n, &(n))                   \
      }

其为参数data赋值的宏定义为:

#define WORK_DATA_INIT()       ATOMIC_LONG_INIT(0)

这样就会静态地创建一个名为n,待执行函数为f,参数为data的work_struct结构。

 

工作的创建(动态方法)

在运行时通过指针创建一个工作:

INIT_WORK(struct work_struct *work, void(*func) (void *));

这会动态地初始化一个由work指向的工作队列,并将其与处理函数绑定。宏原型为:


#define INIT_WORK(_work, _func)                                        \
      do {                                                        \
             static struct lock_class_key __key;                 \
                                                              \
             (_work)->data = (atomic_long_t) WORK_DATA_INIT();  \
             lockdep_init_map(&(_work)->lockdep_map, #_work, &__key, 0);\
             INIT_LIST_HEAD(&(_work)->entry);                 \
             PREPARE_WORK((_work), (_func));                         \
      } while (0)

在需要调度的时候引用类似tasklet_schedule()函数的相应调度工作队列执行的函数schedule_work(),如:

schedule_work(&work);/*调度工作队列执行*/

如果有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。在这种情况下,可以调度指定的时间后执行函数:

schedule_delayed_work(&work,delay);函数原型为:

int schedule_delayed_work(struct delayed_work *work, unsigned long delay);

其中是以delayed_work为结构体的指针,而这个结构体的定义是在work_struct结构体的基础上增加了一项timer_list结构体。

struct delayed_work {
    struct work_struct work;
    struct timer_list timer; /* 延迟的工作队列所用到的定时器,当不需要延迟时初始化为NULL*/
};

这样,便使预设的工作队列直到delay指定的时钟节拍用完以后才会执行。

 

工作队列workqueue
 
 建立 work_struct 结构并初始化, 使用下面宏:
      INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);

 一个工作队列必须明确的在使用前创建,宏为:
 struct workqueue_struct *create_workqueue(const char *name);

当用完一个工作队列,可以去掉它,使用:
     void destroy_workqueue(struct workqueue_struct *queue);

把任务(work_struct)加入到工作队列中
    
     int queue_work(struct workqueue_struct *queue, struct work_struct *work);
     int queue_delayed_work(struct workqueue_struct *queue,
                                                 struct work_struct *work, unsigned long delay);

    //delay是为了保证至少在经过一段给定的最小延迟时间以后,工作队列中的任务才可以真正执行

1. 在工作队列中等待了长时间也没有运行的任务可以用下面的方法取消:
    int cancel_delayed_work(struct work_struct *work);
2.清空工作队列中的所有任务使用:
    void flush_workqueue(struct workqueue_struct *queue);
3.销毁工作队列使用:
    void destroy_workqueue(struct workqueue_struct *queue);


4.向内核缺省工作队列中加入任务

int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct work_struct *work, unsigned long delay);


 模板

使用工作队列处理中断下半部的设备驱动程序模板如下:

/*定义工作队列和下半部函数并关联*/
struct work_struct my_wq;

void my_do_work(unsigned long);
/*中断处理下半部*/
void my_do_work(unsigned long)
{
  ……/*编写自己的处理事件内容*/
}

/*中断处理上半部*/
irpreturn_t my_interrupt(unsigned int irq,void *dev_id)
{
 ……
 schedule_work(&my_wq)/*调度my_wq函数,根据工作队列初始化函数将去执行my_do_work函数*/
 ……
}

/*设备驱动的加载函数*/
int __init xxx_init(void)
{
 ……
 /*申请中断,转去执行my_interrupt函数并传入参数*/
 result=request_irq(my_irq,my_interrupt,IRQF_DISABLED,"xxx",NULL);
 ……
 /*初始化工作队列函数,并与自定义处理函数关联*/
 INIT_WORK(&my_irq,(void (*)(void *))my_do_work);
 ……
}

/*设备驱动模块的卸载函数*/
void __exit xxx_exit(void)
{
……
/*释放中断*/
free_irq(my_irq,my_interrupt);
……
}


实验代码:

    1. #include <linux/init.h>  
    2. #include <linux/module.h>  
    3. #include <linux/kernel.h>  
    4.   
    5. #include <linux/fs.h>  
    6. #include <asm/uaccess.h>  
    7.   
    8. #include <asm/io.h>  
    9. #include <mach/regs-gpio.h>  
    10. #include <linux/ioport.h>  
    11.   
    12. #include <linux/interrupt.h>  
    13.   
    14. #define MAJOR   251  
    15. static char drv_name[] ="interrupt_dev";  
    16. static char kernel_buf[1024];  
    17. char var;  
    18. #define key_irq1 IRQ_EINT7  
    19.   
    20. struct work_struct my_wq;  
    21. void my_do_tasklet(unsigned long);  
    22. DECLARE_TASKLET(my_tasklet,my_do_tasklet,0);  
    23. void my_do_tasklet(unsigned long data)  
    24. {  
    25.     printk("-----tasklet-----\n");  
    26. }  
    27. void my_do_work(unsigned long data)  
    28. {  
    29.     printk("----work_queue-----\n");  
    30. }  
    31.   
    32. static void led_init()  
    33. {  
    34.     printk("led_init!\n");  
    35.     __raw_writel(((__raw_readl(S3C2410_GPBCON) & (~(0xff<<10))) | (0x55<<10)), S3C2410_GPBCON);  
    36.     __raw_writel(__raw_readl(S3C2410_GPBUP) | (0xf<<5), S3C2410_GPBUP);      
    37.     __raw_writel(__raw_readl(S3C2410_GPBDAT) | (0xf<<5), S3C2410_GPBDAT);  
    38. }  
    39. static void Key_init()  
    40. {  
    41.     printk("key_init\n");  
    42.   
    43. //  __raw_writel(((__raw_readl(S3C2410_GPFCON) & (~(0xff<<0))) | (0x02<<14)), S3C2410_GPFCON);   
    44.   
    45. }  
    46.   
    47. static irqreturn_t irq_handle_key(void)  
    48. {  
    49.     printk("this is for a test!\n");  
    50.     schedule_work(&my_wq);  
    51.     tasklet_schedule(&my_tasklet);  
    52.     return IRQ_RETVAL(IRQ_HANDLED);  
    53.       
    54. }  
    55.   
    56.   
    57. static int char_dev_open(struct inode *inode,struct file *file)  
    58. {  
    59.   
    60.     led_init();  
    61.     Key_init();  
    62.   
    63.     INIT_WORK(&my_wq,my_do_work);  
    64.     if(request_irq(key_irq1,irq_handle_key,IRQF_TRIGGER_FALLING,drv_name,NULL) == 0)  
    65.     {  
    66.         printk("request_irq success!\n");  
    67.     }else{  
    68.         printk("request_irq fail!\n");  
    69.         return -1;  
    70.     }  
    71.         return 0;  
    72. }  
    73.   
    74. static int char_dev_release(struct inode *inode,struct file *file)  
    75. {  
    76.         free_irq(key_irq1,NULL);  
    77.         printk("\n\nchar_dev_release success!");  
    78.         return 0;  
    79. }  
    80.   
    81. static struct file_operations char_dev_fops = {  
    82.         .owner = THIS_MODULE,  
    83.         .open  = char_dev_open,  
    84.         .release = char_dev_release,  
    85. };  
    86.   
    87. static int __init char_dev_init(void)  
    88. {  
    89.     printk("module init\n");  
    90.     if(register_chrdev(MAJOR,drv_name,&char_dev_fops)<0)   
    91.     {  
    92.             printk("fail to register!\n");  
    93.             return -1;  
    94.         }  
    95.         else  
    96.         printk("success to register!\n");  
    97.   
    98.     return 0;  
    99. }  
    100.   
    101. static void __exit char_dev_exit(void)  
    102. {  
    103.     unregister_chrdev(MAJOR,drv_name);  
    104.     printk("module exit\n");  
    105. }  
    106.   
    107. module_init(char_dev_init);  
    108. module_exit(char_dev_exit);  
    109.   
    110. MODULE_LICENSE("GPL");  
    111. MODULE_AUTHOR("jianchi88"); 


 

posted on 2013-02-18 10:04  爱哎唉  阅读(186)  评论(0)    收藏  举报