工作队列(系统工作队列和自定义工作队列使用区别) tasklet

本文详细介绍了Linux内核中的工作队列机制,包括工作队列的两种使用方式及其应用场景。此外,还深入探讨了利用系统共享工作队列和自创工作队列的具体步骤,并附带相关代码实例。

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

中断底半部机制有三种:1.工作队列 2.tasklet 3.软中断

注:软中断和tasklet运行于软中断上下文,仍然属于原子上下文的一种,而工作队列则运行于进程上下文,因此,软中断和taeklet处理函数中不能睡眠

而工作队列处理函数中允许睡眠。


工作队列: (work queue) Linux kernel 中将 工作推后执行的一种机制 。这种机制和 BH Tasklets 不同之处在于工作队列是把推后的工作交由一个 内核线程去执行 ,因此工作队列的优势就在于它允许重新调度甚至睡眠。


总结:

工作队列的使用又分两种情况 一种是利用系统共享的工作队列来添加自己的工作,这种情况处理函数不能消耗太多时间,这样会影响共享队列中其他任务的处理,调用使用(schedule_work or schedule_delayed_work);
另外一种是创建自己的工作队列并添加工作,调用使用(queue_work  or  queue_delayed_work)。


第一种:利用系统共享的工作队列,自己创建节点并添加到系统共享工作队列

(对于多cpu而言,会在所有cpu上创建线程,执行挂在工作队列的工作节点上的函数。)

        

        第一步:声明或编写一个工作处理函数

  void my_func();

       第二 步:创建一个工作结构体变量,并将处理函数和参数的入口地址赋给这个工作结构体变量

          法一:DECLARE_WORK(my_work,my_func,&data); //编译时创建名为my_work的结构体变量并把函数入口地址和参数地址赋给它;   如果不想要在编译时就用DECLARE_WORK()创建并初始化工作结构体变量,也可以在程序运行时再用INIT_WORK()创建

               法二:struct work_struct my_work; //创建一个名为my_work的结构体变量,创建后才能使用INIT_WORK()

                        或struct  delayed_work  my_work;//创建后用INIT_DELAYED_WORK()

  INIT_WORK(&my_work,my_func,&data); //初始化已经创建的my_work,其实就是往这个结构体变量中添加处理函数的入口地址和data的地址,或INIT_DELAYED_WORK();

       第三步 将工作结构体变量添加入系统的共享工作队列

schedule_work(&my_work); //添加入队列的工作完成后会自动从队列中删除

  或schedule_delayed_work(&my_work,tick); //延时tick个滴答后再提交工作

第二种:自己创建工作队列,自己创建工作节点并添加到自己创建的工作队列当中

对于多cpu而言,会在调用执行挂在工作队列的工作节点上的函数上的那个cpu上,创建线程,函数如下:

    需要对自己创建的工作队列开个线程:workqueue = create_singlethread_workqueue("dsx_exp_workqueue");

    

    第一步:声明工作处理函数和一个指向工作队列的指针

  void my_func();

  struct workqueue_struct *p_queue;

 第二步:创建自己的工作队列和工作节点

  (1).p_queue=create_workqueue("my_queue"); //创建一个名为my_queue的工作队列并把工作队列的入口地址赋给声明的指针

或:p_queue = create_singlethread_workqueue("dsx_exp_workqueue");//哪个cpu调用工作处理函数,为该cpu创建处理线程


  (2).struct work_struct my_work;//创建工作队列上的一个工作节点

                或struct  delayed_work  my_work;//创建后用INIT_DELAYED_WORK()初始化


  (3).INIT_WORK(&my_work, my_func, &data); //创建一个工作结构体变量并初始化,或INIT_DELAYED_WORK();

  第三步:将工作节点添加入自己创建的工作队列等待执行

  queue_work(p_queue, &my_work); //将自定义工作节点加入自定义工作队列,并调用线程执行。

  //作用与schedule_work()类似,不同的是将工作添加入p_queue指针指向的工作队列而不是系统共享的工作队列

 第四步:删除自己的工作队列

  destroy_workqueue(p_queue); //一般是在close函数中删除

示例:



于是就让我们来仔细看看INIT_WORKINIT_DELAYED_WORK.其实前者是后者的一个特例,它们涉及到的就是传说中的工作队列

如果我们将来某个时刻希望能够调用led_work()这么一个我们自己写的函数,那么我们所要做的就是利用工作队列.如何利用呢?第一步就是使用INIT_WORK()或者INIT_DELAYED_WORK()来初始化这么一个工作,或者叫任务,初始化了之后,将来如果咱们希望调用这个led_work()函数,那么咱们只要用一句schedule_work()或者schedule_delayed_work()就可以了,特别的,咱们这里使用的是INIT_DELAYED_WORK(),那么之后我们就会调用schedule_delayed_work(),这俩是一对.它表示,您希望经过一段延时然后再执行某个函数,所以,咱们今后会见到schedule_delayed_work()这个函数的,而它所需要的参数,一个就是咱们这里的&hub->leds,另一个就是具体自己需要的延时

INIT_WORK () 
INIT_DELAYED_WORK()

INIT_DELAYED_WORK()schedule_delayed_work(),INIT_WORK()schedule_work().



,它们是cancel_delayed_work(struct delayed_work *work)flush_scheduled_work().其中cancel_delayed_work()的意思不言自明,对一个延迟执行的工作来说,这个函数的作用是在这个工作还未执行的时候就把它给取消掉.flush_scheduled_work()的作用,是为了防止有竞争条件的出现,虽说哥们儿也不是很清楚如何防止竞争,可是好歹大二那年学过一门专业课,数字电子线路,尽管没学到什么有用的东西,怎么说也还是记住了两个专业名词,竞争与冒险.您要是对竞争条件不是很明白,那也不要紧,反正基本上每次cancel_delayed_work之后您都得调用flush_scheduled_work()这个函数,特别是对于内核模块,


中断上下文:

1.新建等待队列头和flag标志 

2.在进程中执行:wait_event_interruptible(waiter, tpd_flag != 0);

目的:将本进程置为可中断的挂起状态条件满足后,即把本进程状态置为运行态(此时如果不执行下面的函数 wake_up_interruptible,wait_event_interruptible还会使进程继续休眠)。

3.在中断中执行:tpd_flag = 1;

    wake_up_interruptible(&waiter);
目的:中断产生后,标志满足,wake_up唤醒执行waiter里的进程,


4解释案列 :


Linux使用wake_up_interruptible()唤醒注册到等待队列上的进程

(2013-02-18 22:24:53)
标签:

linux

嵌入式

it


功能:唤醒注册到等待队列上的进程

原型
    #include
    void wake_up_interruptible (wait_queue_head_t  *q);

说明
       唤醒 q 指定的注册在等待队列上的进程。该函数不能直接的立即唤醒进程,而是由调度程序转换上下文,调整为可运行状态。

变量
q :   等待队列变量指针。

 
       最近在学习驱动时有一个问题始终想不明白,为什么wait_event_interruptible(button_waitq, ev_press)需要一个全局变量来记住中断的发生?
在驱动中实现读取操作时,使用了
  1. wait_event_interruptible(button_waitq, ev_press);
在中断函数中使用了如下语句唤醒:
  1. ev_press = 1;  //表示中断发生了
  2. wake_up_interruptible(&button_waitq);  //唤醒休眠的进程
这样的话,中断能正确读取到。我分别尝试了屏蔽ev_press = 1;和wake_up_interruptible(&button_waitq);代码,发现中断不能正常产生。

查找资料,阅读源代码。
  1. #define wait_event_interruptible(wq, condition)          \
  2. ({                                                       \
  3.     int __ret = 0;                                       \
  4.     if (!(condition))                                    \
  5.         __wait_event_interruptible(wq, condition, __ret);\
  6.     __ret;                                               \
  7. })
  1. #define __wait_event_interruptible(wq, condition, ret)                        \
  2. do {                                                                        \
  3.         DEFINE_WAIT(__wait);                                                \
  4.                                                                         \
  5.         for (;;) {                                                        \
  6.                 prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);        \
  7.                 if (condition)                                                \
  8.                         break;                                                \
  9.                 if (!signal_pending(current)) {                                \
  10.                         schedule();                                        \
  11.                         continue;                                        \
  12.                                                                        \
  13.                 ret = -ERESTARTSYS;                                        \
  14.                 break;                                                        \
  15.                                                                        \
  16.         finish_wait(&wq, &__wait);                                        \
  17. } while (0)
__wait_event_interruptible()首先定义并初始化一个wait_queue_t变量__wait,其中数据为当前进程current,并把__wait入队。
    在无限循环中,__wait_event_interruptible()将本进程置为可中断的挂起状态,反复检查condition是否成立,如果成立则退出,如果不成立则继续休眠;条件满足后,即把本进程运行状态置为 运行态( 此时如果不执行下面的函数  wake_up_interruptible, 上面 wait_event_interruptible 还会继续休眠 ) ,并将__wait从等待队列中清除掉,从而进程能够调度运行。如果进程当前有异步信号(POSIX的),则返回-ERESTARTSYS。
  1. //唤醒 q 指定的注册在等待队列上的进程。该函数不能直接的立即唤醒进程,而是由调度程序转换上下文,调整为可运行状态。
  2. wake_up_interruptible (wait_queue_head_t *q);
我是这样考虑的:read 调用wait_event_interruptible(button_waitq),我觉得一个参数就够了。不判断什么条件,直接休眠,下面用wake_up_interruptible(&button_waitq)直接唤醒。这样看起来很简单。
不知道内核这样设计是基于什么原因?
 


<think>好的,我需要回答用户关于Linux内核中工作队列tasklet区别及应用场景的问题。首先,我应该回顾用户提供的引用资料,从中提取关键信息。 根据引用[1],tasklet在软中断上下文中运行,代码是原子的,而工作队列在内核进程上下文中运行,有更好的灵活性。这意味着tasklet适用于需要原子操作但不可阻塞的场景,而工作队列适合需要睡眠或长时间执行的任务。 引用[3]提到工作队列可以设置重入性,允许在不同CPU上处理,但通过标志可以限制这一点。而tasklet可能在同一个CPU上串行执行,避免并发问题。 引用[4]说明软中断的调度时机,比如中断处理完成后。这可能关联到tasklet的调度,因为tasklet基于软中断机制。 接下来需要整理两者的工作原理。Tasklet基于软中断,运行在中断上下文,不可睡眠;工作队列则在内核线程中运行,属于进程上下文,可以调度阻塞。 区别方面,执行上下文、调度机制、并发处理能力是关键点。应用场景部分,tasklet适合高频率的中断处理,工作队列适合需要复杂操作或阻塞的任务。 最后,生成相关问题时要覆盖原理、区别、选择依据实际案例。确保回答结构清晰,引用正确,比如引用[1]、[3][4]来支持关键点。</think>### Linux内核中工作队列tasklet区别及应用场景 #### 一、工作原理 1. **Tasklet** - 基于软中断机制实现,运行在**软中断上下文**中(中断下半部),所有代码均为原子操作[^1]。 - 同一类型的tasklet在单个CPU上**串行执行**,不同CPU上可能并行执行[^3]。 - 典型触发时机:中断处理完成后(如`irq_exit`调用时)[^4]。 2. **工作队列(Work Queue)** - 由内核创建的**工作者线程**(如`kworker/n`)执行任务,运行在**进程上下文**中[^2]。 - 支持阻塞操作(如I/O等待、互斥锁),可通过`WQ_NON_REENTRANT`标志控制跨CPU重入性。 - 任务可延迟执行或通过调度策略灵活管理。 #### 二、核心区别 | **特性** | Tasklet | 工作队列 | |-------------------|-------------------------|-----------------------| | **执行上下文** | 软中断(原子操作) | 进程上下文(可调度) | | **阻塞能力** | 不可阻塞 | 允许阻塞 | | **并发控制** | 同类型单CPU串行 | 可跨CPU并行(可配置) | | **延迟任务** | 仅支持即时调度 | 支持延迟执行 | #### 三、应用场景 1. **Tasklet适用场景** - 高频中断的轻量级处理(如网络包接收后处理)[^4]。 - 需原子操作且无需睡眠的任务(如硬件状态快速更新)。 2. **工作队列适用场景** - 涉及阻塞的操作(如文件系统缓存同步)。 - 长时间运行的任务(如设备驱动的固件加载)。 - 需要动态调整优先级或延迟执行的任务(如电池管理后台任务)。 #### 四、代码示例 ```c // Tasklet定义与调度 void tasklet_handler(unsigned long data); DECLARE_TASKLET(my_tasklet, tasklet_handler, 0); // 工作队列定义与调度 struct work_struct my_work; void work_handler(struct work_struct *work); INIT_WORK(&my_work, work_handler); schedule_work(&my_work); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值