zoukankan      html  css  js  c++  java
  • 【Linux开发】linux设备驱动归纳总结(六):3.中断的上半部和下半部——工作队列

    linux设备驱动归纳总结(六):3.中断的上半部和下半部——工作队列


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    这节介绍另外一种的下半部实现——工作队列。相对于软中断/tasklet,工作对列运行在进程上下文,允许睡眠,接下来慢慢介绍。

    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    1、工作队列的使用


    按惯例,在介绍工作队列如何实现之前,先说说如何使用工作队列实现下半部。


    步骤一、定义并初始化工作队列:


    创建工作队列函数:

    struct workqueue_struct *create_workqueue(const char *name)

    函数传参是内核中工作队列的名称,返回值是workqueue_struct结构体的指针,该结构体用来维护一个等待队列。

    我的代码如下:

    /*6th_irq_3/4th/test.c*/

    14 struct workqueue_struct *xiaobai_wq; //定义工作队列

    33 xiaobai_wq = create_workqueue("xiaobai");


    步骤二、定义并初始化work结构体:


    内核使用结构体来维护一个加入工作队列的任务:

    /*linux/workqueue.h*/

    25 struct work_struct {

    26 atomic_long_t data;

    27 #define WORK_STRUCT_PENDING 0 /* T if work item pending execution */

    28 #define WORK_STRUCT_FLAG_MASK (3UL)

    29 #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)

    30 struct list_head entry;

    31 work_func_t func; //这个是重点,下半部实现的处理函数指针就放在这

    32 #ifdef CONFIG_LOCKDEP

    33 struct lockdep_map lockdep_map;

    34 #endif

    35 };


    同样有静态和动态两种方法:

    静态定义并初始化work结构体:

    /*linux/workqueue.h*/

    72 #define DECLARE_WORK(n, f) 

    73 struct work_struct n = __WORK_INITIALIZER(n, f)

    定义并初始化一个叫nwork_struct数据结构,它对应的的处理函数是f

    对应的动态初始化方法,该函数返回work_struct指针,所以需要先定义一个work_struct结构:

    /*linux/workqueue.h*/

    107 #define INIT_WORK(_work, _func) 

    108 do {

    109 (_work)->data = (atomic_long_t) WORK_DATA_INIT();

    110 INIT_LIST_HEAD(&(_work)->entry);

    111 PREPARE_WORK((_work), (_func));

    112 } while (0)

    113 #endif


    tasklet一样,在初始化的同时,需要将处理函数实现,代码如下:

    /*6th_irq_3/4th/test.c*/

    15 struct work_struct xiaobai_work; //定义work结构体

    16

    17 void xiaobai_func(struct work_struct *work) //处理函数

    18 {

    19 printk("hello xiaobai! "); //同样什么都没干,只是打印

    20 }

    34 INIT_WORK(&xiaobai_work, xiaobai_func); //初始化work结构体


    步骤三、在中断处理函数中调度任务:


    工作队列和worl结构体都已经实现了,接下来就可以调度了,使用一下函数:

    /*kernel/workqueue.c*/

    161 int queue_work(struct workqueue_struct *wq, struct work_struct *work)

    将指定的任务(work_struct),添加到指定的工作队列中。同样的,调度并不代表处理函数能够马上执行,这由内核进程调度决定。


    步骤四、在卸载模块时,刷新并注销等待队列:


    刷新等待队列函数:

    /*kernel/workqueue.c*/

    411 void flush_workqueue(struct workqueue_struct *wq)

    该函数会一直等待,知道指定的等待队列中所有的任务都执行完毕并从等待队列中移除。

    注销等待队列:

    /*kernel/workqueue.c*/

    904 void destroy_workqueue(struct workqueue_struct *wq)

    该函数是是创建等待队列的反操作,注销掉指定的等待队列。


    四个步骤讲完,贴个代码:

    /*6th_irq_3/4th/test.c*/

    1 #include

    2 #include

    3

    4 #include

    5 #include

    6

    7 #define DEBUG_SWITCH 1

    8 #if DEBUG_SWITCH

    9 #define P_DEBUG(fmt, args...) printk("<1>" "[%s]"fmt, __FUNCTI ON__, ##args)

    10 #else

    11 #define P_DEBUG(fmt, args...) printk("<7>" "[%s]"fmt, __FUNCTI ON__, ##args)

    12 #endif

    13

    14 struct workqueue_struct *xiaobai_wq; //1.定义工作队列

    15 struct work_struct xiaobai_work; //2定义work结构体

    16

    17 void xiaobai_func(struct work_struct *work) //2实现处理函数

    18 {

    19 printk("hello xiaobai! ");

    20 }

    21

    22 irqreturn_t irq_handler(int irqno, void *dev_id)

    23 {

    24 printk("key down ");

    25 queue_work(xiaobai_wq ,&xiaobai_work); //3调度任务

    26 return IRQ_HANDLED;

    27 }

    28 static int __init test_init(void) //模块初始化函数

    29 {

    30 int ret;

    31

    32 /*work*/

    33 xiaobai_wq = create_workqueue("xiaobai"); //1初始化工作对列

    34 INIT_WORK(&xiaobai_work, xiaobai_func); //2初始化work结构体

    35

    36 ret = request_irq(IRQ_EINT1, irq_handler,

    37 IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL);

    38 if(ret){

    39 P_DEBUG("request irq failed! ");

    40 return ret;

    41 }

    42

    43 printk("hello irq ");

    44 return 0;

    45 }

    46

    47 static void __exit test_exit(void) //模块卸载函数

    48 {

    49 flush_workqueue(xiaobai_wq); //4刷新工作队列

    50 destroy_workqueue(xiaobai_wq); //4注销工作队列

    51 free_irq(IRQ_EINT1, NULL);

    52 printk("good bye irq ");

    53 }

    54

    55 module_init(test_init);

    56 module_exit(test_exit);

    57

    58 MODULE_LICENSE("GPL");

    59 MODULE_AUTHOR("xoao bai");

    60 MODULE_VERSION("v0.1");

    和以往的一样,下半部仅仅是打印,没实质操作,看效果:

    [root: 4th]# insmod test.ko

    hello irq

    [root: 4th]# key down

    hello xiaobai!

    key down

    hello xiaobai!

    [root: 4th]# rmmod test

    good bye irq


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    二、使用共享的工作队列


    在内核中有一个默认的工作队列events,使用共享的工作队列可以省去创建和注销工作队列的步骤。当然,队列是共享的,用起来当然会不爽,如果有多个不同的任务都加入到这个工作对列中,每个任务调度的速度就会比较慢,肯定不如自己创建一个爽。不过,一般默认工作队列都能满足要求,不需要创建一个新的。


    少了上面创建和注销等待队列两步,使用共享工作队列步骤相对少一点


    步骤一、实现处理函数,定义并初始化work结构体:


    这一步骤上一节介绍的步骤二完全一样,所以就不说了。


    步骤二、调度任务:


    默认工作队列的中任务的调度不需要指定工作对列,所以函数有所不同:

    /*kernel/workqueue.c*/

    620 int schedule_work(struct work_struct *work)

    该函数会把work_struct结构体加入到默认工作对列events中。


    上函数:

    /*6th_irq_3/3rd/test.c*/

    1 #include

    2 #include

    3

    4 #include

    5 #include

    6

    7 #define DEBUG_SWITCH 1

    8 #if DEBUG_SWITCH

    9 #define P_DEBUG(fmt, args...) printk("<1>" "[%s]"fmt, __FUNCTI ON__, ##args)

    10 #else

    11 #define P_DEBUG(fmt, args...) printk("<7>" "[%s]"fmt, __FUNCTI ON__, ##args)

    12 #endif

    13

    14 struct work_struct xiaobai_work; //定义work结构体

    15

    16 void xiaobai_func(struct work_struct *work)

    17 {

    18 printk("hello xiaobai! ");

    19 }

    20

    21 irqreturn_t irq_handler(int irqno, void *dev_id) //中断处理函数

    22 {

    23 printk("key down ");

    24 schedule_work(&xiaobai_work); //调度任务

    25 return IRQ_HANDLED;

    26 }

    27 static int __init test_init(void) //模块初始化函数

    28 {

    29 int ret;

    30

    31 /*work*/

    32 INIT_WORK(&xiaobai_work, xiaobai_func); //初始化worl结构体

    33

    34 ret = request_irq(IRQ_EINT1, irq_handler,

    35 IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL);

    36 if(ret){

    37 P_DEBUG("request irq failed! ");

    38 return ret;

    39 }

    40

    41 printk("hello irq ");

    42 return 0;

    43 }

    44

    45 static void __exit test_exit(void) //模块卸载函数

    46 {

    47 free_irq(IRQ_EINT1, NULL);

    48 printk("good bye irq ");

    49 }

    50

    51 module_init(test_init);

    52 module_exit(test_exit);

    53

    54 MODULE_LICENSE("GPL");

    55 MODULE_AUTHOR("xoao bai");

    56 MODULE_VERSION("v0.1");

    再看一下实现效果,效果和之前的一样:

    [root: 3rd]# insmod test.ko

    hello irq

    [root: 3rd]# key down

    hello xiaobai!

    key down

    hello xiaobai!

    [root: 3rd]# rmmod test

    good bye irq


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    三、工作队列的实现


    在介绍工作队列的实现之前,必须要介绍什么是工作者线程和三个数据结构。


    工作者线程,是指负责执行在内核队列中任务的内核线程。在工作队列中,有专门的工作者线程来处理加入到工作对列中的任务。工作对列对应的工作者线程可能不止一个,每个处理器有且仅有一个工作队列对应的工作者线程。当然,如果内核中两个工作对列,那每个处理器就分别有两个工作者线程。

    在内核中有一个默认的工作队列events,对于单处理器的ARM9,有一个对应的工作者线程。


    工作队列结构体workqueue_struct

    59 struct workqueue_struct {

    60 struct cpu_workqueue_struct *cpu_wq; //一个工作者线程对应一个该结构体

    61 struct list_head list;

    62 const char *name;

    63 int singlethread;

    64 int freezeable; /* Freeze threads during suspend */

    65 int rt;

    66 #ifdef CONFIG_LOCKDEP

    67 struct lockdep_map lockdep_map;

    68 #endif

    69 };

    工作对列workqueue_struct有一个成员cpu_workqueue_struct,每个工作者线程对应一个cpu_workqueue。所以,对于单处理器的ARM9,一个工作对列只有一个cpu_workqueue_struct


    结构体cpu_workqueue_struct

    41 struct cpu_workqueue_struct {

    42

    43 spinlock_t lock;

    44 /*这是内核链表,所有分配在这个处理器的work_struct将通过链表连在一起,等待执行*/

    45 struct list_head worklist;

    46 wait_queue_head_t more_work;

    47 struct work_struct *current_work; //指向当前执行的work_struct

    48

    49 struct workqueue_struct *wq; //指向关联自己的工作队列

    50 struct task_struct *thread; //指向对应的内核线程,即工作者线程

    51

    52 int run_depth; /* Detect run_workqueue() recursion depth */

    53 } ____cacheline_aligned;

    由上面知道,当我们调用queue_work来调度任务时,并不是把work_struct添加到等待队列中,而是会被分配到工作对列的成员cpu_workqueue_struct中。


    work结构体work_struct

    25 struct work_struct {

    26 atomic_long_t data;

    27 #define WORK_STRUCT_PENDING 0 /* T if work item pending execution */

    28 #define WORK_STRUCT_FLAG_MASK (3UL)

    29 #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)

    30 struct list_head entry; //cpu_workqueue_struct通过这个成员,将wrok_struct连在一起

    31 work_func_t func; //每个任务的处理函数

    32 #ifdef CONFIG_LOCKDEP

    33 struct lockdep_map lockdep_map;

    34 #endif

    35 };


    可能上面讲得很乱,下面将来个图来讲解一下,双处理器的情况下:


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    四、选择哪个来实现下半部


    2.6内核,提供三种实现中断下半部的方法,软中断、tasklet和工作队列,其中tasklet是基于软中断实现的,两者很相近。而工作队列完全不同,它是靠内核线程实现的。


    有这样的一张表:

    简单来说,软中断和tasklet优先级较高,性能较好,调度快,但不能睡眠。而工作队列是内核的进程调度,相对来说较慢,但能睡眠。所以,如果你的下半部需要睡眠,那只能选择动作队列。否则最好用tasklet


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    五、总结


    这节简单介绍了工作队列的使用和实现。其中,还有函数能够使指定工作队列的任务延时执行,相关的结构体和函数有:

    struct delayed_work

    DECLARE_DELAYED_WORK(n,f)

    INIT_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));

    int queue_delayed_work(struct workqueue_struct *queue, struct delayed_work *work, unsigned long delay);

    schedule_delayed_work(struct delayed_work *work, unsigned long delay)

    有兴趣自己看看,很简单。


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    源代码: 6th_irq_3(2).rar   

  • 相关阅读:
    【LeetCode】650. 只有两个键的键盘
    【LeetCode】70. 爬楼梯
    【LeetCode】746. 使用最小花费爬楼梯
    【LeetCode】198. 打家劫舍
    【LeetCode】53.最大子序和
    【LeetCode】1056-易混淆数
    【人工智能系列】python的Quepy库的学习
    【计划书】关于运营商的人工智能的想法
    【转】经典!python中使用xlrd、xlwt操作excel表格详解
    转]python 结巴分词(jieba)学习
  • 原文地址:https://www.cnblogs.com/huty/p/8518569.html
Copyright © 2011-2022 走看看