工作队列——workqueue 与 tasklet

本文深入介绍了Linux内核中的工作队列机制,包括其数据结构、创建、调度、销毁等过程,并通过实例展示了如何在实际应用中使用工作队列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

workqueue与tasklet功能相似,都是让某个函数在将来被调用。但是两者件也有一些区别:

  • tasklet在软件中断上下文中运行,tasklet代码必须是原子的。工作队列在一个特殊的内核线程的上下文中运行,工作队列允许休眠;
  • tasklet始终运行在被初始提交的同一处理器上,工作队列不是一定的;
  • tasklet函数通常立即得到执行,但始终不会晚于下一个定时器滴答;工作队列函数延迟执行的时间间隔可以设定;

工作队列

工作队列(workqueue)是一种将工作推后执行的形式。工作队列可以把工作推后,交由一个内核线程去执行。

数据结构:
把推后执行的任务叫做工作(work),描述它的数据结构为work_struct:

struct work_struct {  
    atomic_long_t data;       /*工作处理函数func的参数*/  
#define WORK_STRUCT_PENDING 0        /* T if work item pending execution */  
#define WORK_STRUCT_STATIC 1        /* static initializer (debugobjects) */  
#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)连接成队列称为工作队列(workqueue),其数据结构为workqueue_struct:
头文件<linux/workqueue.h>

struct workqueue_struct {  
 struct cpu_workqueue_struct *cpu_wq;  
 struct list_head list;  
 const char *name;   /*workqueue name*/  
 int singlethread;   /*是不是单线程 - 单线程我们首选第一个CPU -0表示采用默认的工作者线程event*/  
 int freezeable;  /* Freeze threads during suspend */  
 int rt;  
};  

1. 创建工作队列


struct workqueue_struct *create_workqueue(const char *name)  //多处理下 创建多个内核线程
struct workqueue_struct *create_singlethread_workqueue(const char *name)  // 仅对应一个内核线程
函数传参是内核中工作队列的名称,返回值是workqueue_struct结构体的指针,该结构体用来维护一个等待队列。

 例如:
 struct workqueue_struct * test_wq; //定义工作队列
 test_wq = create_workqueue(" testwq");

2. 创建工作(work)

  1. 静态创建
    DECLARE_WORK(n, f);
    DECLARE_DELAYED_WORK(n, f);
头文件#include <linux/workqueue.h>

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

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

  1. 动态创建
/*初始化之前需要定义一个work_struct 结构*/
INIT_WORK(struct work_struct work, work_func_t func);   /*初始化work_struct 结构*/
PREPARE_WORK(struct work_struct work, work_func_t func);  /*一般用于更新work_struct 结构*/

INIT_DELAYED_WORK(struct delayed_work work, work_func_t func);  /*初始化一个延时任务*/
PREPARE_DELAYED_WORK(struct delayed_work work, work_func_t func); 
/*linux/workqueue.h*/

 #define INIT_WORK(_work, _func) \

 do { \

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

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

 PREPARE_WORK((_work), (_func)); \

} while (0)

#endif

3. 将work添加到queue
工作队列和worl结构体都已经实现了,接下来就可以调度了,使用一下函数:

/*kernel/workqueue.c*/
 int queue_work(struct workqueue_struct *wq, struct work_struct *work);
 int queue_delayed_work(struct workqueue_struct *wq, struct work_struct *work, unsigned long delay);
将指定的任务(work_struct),添加到指定的工作队列中。

调度并不代表处理函数能够马上执行,这由内核进程调度决定。

4. 注销工作队列

刷新等待队列函数:
/*kernel/workqueue.c*/
void flush_workqueue(struct workqueue_struct *wq)
该函数会一直等待,知道指定的等待队列中所有的任务都执行完毕并从等待队列中移除。

注销等待队列:
/*kernel/workqueue.c*/
void destroy_workqueue(struct workqueue_struct *wq)
该函数是是创建等待队列的反操作,注销掉指定的等待队列。

普通任务示例:

struct workqueue_struct *wq;
struct work_struct work;
wq = create_workqueue("workqueue");
INIT_WORK(&work, func);
queue_work(wq, &work);
destroy_workqueue(wq);

延迟任务示例:

struct workqueue_struct *wq;
struct delayed_work work;
wq = create_workqueue("workqueue");
INIT_DELAYED_WORK(&work, func);
queue_delayed_work(wq, &work, timeout * HZ);
cancel_delayed_work(&work);
destroy_workqueue(wq);

工作队列应用示例:

#include <linux/module.h>
#include <linux/init.h>

#include <linux/interrupt.h>
#include <linux/workqueue.h>

#define DEBUG_SWITCH 1
#if DEBUG_SWITCH
    #define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args)
#else
    #define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args)
#endif

 struct workqueue_struct *ZP1015_wq; //1.定义工作队列
 struct work_struct ZP1015_work;    //2定义work结构体

 void ZP1015_func(struct work_struct *work) //2实现处理函数
 {
    printk("hello ZP1015!\n");
 }

irqreturn_t irq_handler(int irqno, void *dev_id)
 {
     printk("key down\n");
     queue_work(ZP1015_wq ,&ZP1015_work); //3调度任务
     return IRQ_HANDLED;
 }
 static int __init test_init(void) //模块初始化函数
 {
     int ret;

     /*work*/
     ZP1015_wq = create_workqueue("ZP1015"); //1初始化工作对列
     INIT_WORK(&ZP1015_work, ZP1015_func);  //2初始化work结构体

     ret = request_irq(IRQ_EINT1, irq_handler,
     IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL);

     if(ret){
        P_DEBUG("request irq failed!\n");
        return ret;
    }

     printk("hello irq\n");
     return 0;
 }

 static void __exit test_exit(void) //模块卸载函数
 {
     flush_workqueue(ZP1015_wq);    //4刷新工作队列
     destroy_workqueue(ZP1015_wq); //4注销工作队列
     free_irq(IRQ_EINT1, NULL);
     printk("good bye irq\n");
 }

 module_init(test_init);
 module_exit(test_exit);

 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("ZP1015");
 MODULE_VERSION("v0.1");

共享队列

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

共享队列使用分为两步:
1. 定义并初始化work_struct结构体;定义 func 函数;(这里和上面的初始化完全相同)
2. 任务调度;

任务调度:

/*kernel/workqueue.c*/

 int schedule_work(struct work_struct *work)

int schedule_work(struct work_struct *work)
{
    return queue_work(keventd_wq, work);
}

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

代码示例:

#include <linux/module.h>
#include <linux/init.h>

#include <linux/interrupt.h>
#include <linux/workqueue.h>

#define DEBUG_SWITCH 1
 #if DEBUG_SWITCH
    #define P_DEBUG(fmt, args...) printk("<1>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args)
 #else
    #define P_DEBUG(fmt, args...) printk("<7>" "<kernel>[%s]"fmt, __FUNCTI ON__, ##args)
 #endif

struct work_struct ZP1015_work; //定义work结构体

void ZP1015_func(struct work_struct *work)
{
    printk("hello Linux world!\n");
}

irqreturn_t irq_handler(int irqno, void *dev_id) //中断处理函数
{
    printk("key down\n");
    schedule_work(&ZP1015_work); //调度任务
    return IRQ_HANDLED;
}

static int __init test_init(void) //模块初始化函数
{
    int ret;

    /*work*/
    INIT_WORK(&ZP1015_work, ZP1015_func); //初始化work结构体

    ret = request_irq(IRQ_EINT1, irq_handler,
    IRQF_TRIGGER_FALLING, "key INT_EINT1", NULL);

    if(ret){
        P_DEBUG("request irq failed!\n");
        return ret;
    }

    printk("hello irq\n");
    return 0;
}

static void __exit test_exit(void) //模块卸载函数
{
    free_irq(IRQ_EINT1, NULL);
    printk("good bye irq\n");
}

module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZP1015");
MODULE_VERSION("v0.1");
<think>我们正在讨论Linux内核中tasklet工作队列(workqueue)的创建方法。根据提供的引用材料,我们可以总结如下:1.**tasklet**:-是一种软中断(softirq)机制,运行在中断上下文中,不能睡眠。-创建方法:使用`tasklet_init()`函数初始化,然后使用`tasklet_schedule()`调度执行。-销毁方法:在模块卸载时使用`tasklet_kill()`。2.**工作队列(workqueue)**:-将工作推迟到内核线程中执行,运行在进程上下文中,可以睡眠。-创建方法:可以使用系统默认的工作队列(如`eventd_wq`)或者自己创建(`create_workqueue`)。-调度方法:使用`schedule_work()`或`queue_work()`将工作添加到队列。下面我将详细说明两者的创建步骤示例代码。###1.Tasklet的创建使用####步骤:1.**定义tasklet结构体**:`structtasklet_struct`2.**初始化tasklet**:使用`tasklet_init()`函数,指定处理函数参数。3.**调度tasklet**:使用`tasklet_schedule()`将tasklet加入软中断队列等待执行。4.**销毁tasklet**:在模块退出时使用`tasklet_kill()`。####示例代码(基于引用[2]):```c#include<linux/init.h>#include<linux/module.h>#include<linux/interrupt.h>staticstructtasklet_structmy_tasklet;//Tasklet处理函数staticvoidtasklet_handler(unsignedlongdata){printk(KERN_INFO"Taskletisrunning\n");}staticint__initmy_module_init(void){//初始化tasklet,关联处理函数tasklet_init(&my_tasklet,tasklet_handler,0);//调度tasklettasklet_schedule(&my_tasklet);return0;}staticvoid__exitmy_module_exit(void){//销毁tasklettasklet_kill(&my_tasklet);printk(KERN_INFO"Moduleexit\n");}module_init(my_module_init);module_exit(my_module_exit);MODULE_LICENSE("GPL");```###2.Workqueue的创建使用####两种方式:-**使用系统默认工作队列**:无需自己创建队列,直接定义`work_struct`并使用`schedule_work()`调度。-**创建自定义工作队列**:使用`create_workqueue()`创建,然后使用`queue_work()`调度。####步骤(自定义工作队列):1.**创建工作队列**:`create_workqueue(name)`2.**定义工作结构体**:`structwork_struct`3.**初始化工作**:`INIT_WORK(work,work_func)`4.**调度工作**:`queue_work(workqueue,work)`5.**销毁工作队列**:使用`destroy_workqueue()`####示例代码(自定义工作队列):```c#include<linux/init.h>#include<linux/module.h>#include<linux/workqueue.h>staticstructworkqueue_struct*my_wq;staticstructwork_structmy_work;//工作处理函数staticvoidwork_handler(structwork_struct*work){printk(KERN_INFO"Workqueuehandlerisrunning\n");}staticint__initmy_module_init(void){//创建工作队列my_wq=create_workqueue("my_workqueue");if(!my_wq)return-ENOMEM;//初始化工作,关联处理函数INIT_WORK(&my_work,work_handler);//调度工作queue_work(my_wq,&my_work);return0;}staticvoid__exitmy_module_exit(void){//销毁工作队列destroy_workqueue(my_wq);printk(KERN_INFO"Moduleexit\n");}module_init(my_module_init);module_exit(my_module_exit);MODULE_LICENSE("GPL");```###关键区别总结|特性|Tasklet|Workqueue||---------------------|----------------------------------|----------------------------||执行上下文|中断上下文(不可睡眠)|进程上下文(可睡眠)||并发性|同类型tasklet不能并发执行|可并发执行(多线程)||创建方式|`tasklet_init`+`tasklet_schedule`|`create_workqueue`+`INIT_WORK`+`queue_work`||适用场景|短时间、不可睡眠的任务|可能睡眠或长时间的任务|###注意事项1.**Tasklet安全性**:同一tasklet不会同时在多个CPU上运行,但不同tasklet可以并行[^1]。2.**Workqueue资源**:自定义工作队列会创建独立的内核线程,消耗更多资源[^3]。3.**默认工作队列**:使用`schedule_work(work)`会使用系统共享的工作队列(如`system_wq`),避免资源浪费[^4]。§§相关问题§§1.Linux内核中软中断(softirq)tasklet有什么关系?2.工作队列(workqueue)内核线程(kthread)有何区别联系?3.如何选择使用tasklet还是workqueue?4.工作队列中的工作是如何被调度的?</think>### Linux内核中TaskletWorkqueue创建方法详解 #### 一、Tasklet创建方法 Tasklet是Linux内核中用于中断下半部处理的轻量级机制,运行在软中断上下文。 **创建步骤:** 1. **声明tasklet结构体** ```c static struct tasklet_struct my_tasklet; ``` 2. **定义处理函数** ```c static void tasklet_handler(unsigned long data) { printk(KERN_INFO "Tasklet executed\n"); // 处理逻辑 } ``` 3. **初始化tasklet** ```c tasklet_init(&my_tasklet, tasklet_handler, 0); // 0为传入参数 ``` 4. **调度tasklet** ```c tasklet_schedule(&my_tasklet); // 加入执行队列 ``` 5. **销毁tasklet**(模块退出时) ```c tasklet_kill(&my_tasklet); // 确保安全终止[^2] ``` **特性:** - 原子性:同类型tasklet不会并发执行 - 不可睡眠:运行在中断上下文 - 低延迟:适合实时性要求高的场景[^1] #### 二、Workqueue创建方法 Workqueue将工作推迟到内核线程执行,运行在进程上下文,可处理复杂操作。 **两种创建方式:** 1. **使用系统默认工作队列**(推荐) ```c static struct work_struct my_work; // 定义工作处理函数 void work_handler(struct work_struct *work) { printk(KERN_INFO "Work executed in kernel thread\n"); } // 初始化调度 INIT_WORK(&my_work, work_handler); schedule_work(&my_work); // 加入系统队列[^4] ``` 2. **创建自定义工作队列** ```c struct workqueue_struct *custom_wq = create_workqueue("my_wq"); static struct work_struct custom_work; INIT_WORK(&custom_work, work_handler); // 提交到自定义队列 queue_work(custom_wq, &custom_work); // 销毁队列(模块退出时) destroy_workqueue(custom_wq);[^3] ``` **关键特性对比:** | 特性 | Tasklet | Workqueue | |---------------------|-----------------------------|----------------------------| | 执行上下文 | 中断上下文(不可睡眠) | 进程上下文(可睡眠) | | 并发性 | 同类型串行执行 | 可并行执行 | | 适用场景 | 简单快速操作 | 复杂/可能阻塞的操作 | | 资源消耗 | 低 | 较高(需内核线程) | | 超时处理 | 不支持 | 支持延迟工作(delayed_work) | #### 三、使用场景选择 1. **选择Tasklet当:** - 需要极低延迟(如网络包处理) - 操作简单且不可阻塞 - 避免线程调度开销[^1] 2. **选择Workqueue当:** - 需要执行可能阻塞的操作(如I/O) - 需要睡眠或等待资源 - 复杂处理需要进程上下文[^3] #### 四、最佳实践 1. **内存管理:** ```c // Tasklet共享数据需原子操作 atomic_t shared_data = ATOMIC_INIT(0); // Workqueue可使用标准锁机制 DEFINE_SPINLOCK(my_lock); ``` 2. **错误处理:** ```c // Workqueue创建需检查 if (!custom_wq) { printk(KERN_ERR "Workqueue creation failed\n"); return -ENOMEM; } ``` 3. **性能优化:** - 高频任务用`tasklet_hi_schedule()`提高优先级 - CPU密集型工作用`create_singlethread_workqueue()`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值