工作队列workqueue

工作队列  <linux/workqueue.h>
工作队列类似 taskets,允许内核代码请求在将来某个时间调用一个函数,不同在于:
(1)tasklet 在软件中断上下文中运行,所以 tasklet 代码必须是原子的。而工作队列函数在一个特殊内核进程上下文运行,有更多的灵活性,且能够休眠。
(2)tasklet 只能在最初被提交的处理器上运行,这只是工作队列默认工作方式。
(3)内核代码可以请求工作队列函数被延后一个给定的时间间隔。
(4)tasklet 执行的很快, 短时期, 并且在原子态, 而工作队列函数可能是长周期且不需要是原子的,两个机制有它适合的情形
工作队列有 struct workqueue_struct 类型,在 中定义。一个工作队列必须明确的在使用前创建,宏为:
struct workqueue_struct *create_workqueue(const char *name);
struct workqueue_struct *create_singlethread_workqueue(const char *name);

每个工作队列有一个或多个专用的进程("内核线程"), 这些进程运行提交给这个队列的函数。若使用 create_workqueue, 就得到一个工作队列它在系统的每个处理器上有一个专用的线程。在很多情况下,过多线程对系统性能有影响,如果单个线程就足够则使用 create_singlethread_workqueue 来创建工作队列。
提交一个任务给一个工作队列,在这里《LDD3》介绍的内核2.6.10和我用的新内核2.6.22.2已经有不同了,老接口已经不能用了,编译会出错。这里我只讲2.6.22.2的新接口,至于老的接口我想今后内核不会再有了。从这一点我们可以看出内核发展。

/*需要填充work_struct或delayed_work结构,可以在编译时完成, 宏如下: */

struct work_struct {
    atomic_long_t data;
#define WORK_STRUCT_PENDING 0        /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
    struct list_head entry;
    work_func_t func;
};

struct delayed_work {
    struct work_struct work;
    struct timer_list timer;
};

DECLARE_WORK(n, f)    
/*n 是声明的work_struct结构名称, f是要从工作队列被调用的函数*/
DECLARE_DELAYED_WORK(n, f)
/*n是声明的delayed_work结构名称, f是要从工作队列被调用的函数*/

/*若在运行时需要建立 work_struct 或 delayed_work结构, 使用下面 2 个宏定义:*/
INIT_WORK(struct work_struct *work, void (*function)(void *));
PREPARE_WORK(struct work_struct *work, void (*function)(void *));
INIT_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));
PREPARE_DELAYED_WORK(struct delayed_work *work, void (*function)(void *));
/* INIT_* 做更加全面的初始化结构的工作,在第一次建立结构时使用. PREPARE_* 做几乎同样的工作, 但是它不初始化用来连接 work_struct或delayed_work 结构到工作队列的指针。如果这个结构已经被提交给一个工作队列, 且只需要修改该结构,则使用 PREPARE_* 而不是 INIT_* */

/*有 2 个函数来提交工作给一个工作队列:*/
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct delayed_work *work, unsigned long delay);
/*每个都添加work到给定的workqueue。如果使用 queue_delay_work, 则实际的工作至少要经过指定的 jiffies 才会被执行。 这些函数若返回 1 则工作被成功加入到队列; 若为0,则意味着这个 work 已经在队列中等待,不能再次加入*/

在将来的某个时间, 这个工作函数将被传入给定的 data 值来调用。这个函数将在工作线程的上下文运行, 因此它可以睡眠 (你应当知道这个睡眠可能影响提交给同一个工作队列的其他任务) 工作函数不能访问用户空间,因为它在一个内核线程中运行, 完全没有对应的用户空间来访问。
取消一个挂起的工作队列入口项可以调用:
int cancel_delayed_work(struct delayed_work *work);
void cancel_work_sync(struct work_struct *work)
如果这个入口在它开始执行前被取消,则返回非零。内核保证给定入口的执行不会在调用cancel_delay_work 后被初始化. 如果 cancel_delay_work 返回 0, 但是, 这个入口可能已经运行在一个不同的处理器, 并且可能仍然在调用 cancel_delayed_work 后在运行. 要绝对确保工作函数没有在 cancel_delayed_work 返回 0 后在任何地方运行,你必须跟随这个调用来调用:
void flush_workqueue(struct workqueue_struct *queue);
在 flush_workqueue 返回后, 没有在这个调用前提交的函数在系统中任何地方运行。
而cancel_work_sync会取消相应的work,但是如果这个work已经在运行那么cancel_work_sync会阻塞,直到work完成并取消相应的work。
当用完一个工作队列,可以去掉它,使用:
void destroy_workqueue(struct workqueue_struct *queue);

例子
static struct workqueue_struct *iommu_wq;

void work_func(struct work_struct *work){

}

/*创建工作队列workqueue_struct,该函数会为cpu创建内核线程*/
iommu_wq = create_workqueue("amd_iommu_v2");
/*初始化工作work_struct,指定工作函数*/
        INIT_WORK(&iommu_wq ,work_func);
/*将工作加入到工作队列中,最终唤醒内核线程*/
        queue_work(test_wq, &work);

使用create_workqueue创建的工作队列在工作执行函数work_func中循环调用printk会导致系统卡死
create_workqueue创建工作队列时在每个cpu上都创建了worker_thread内核线程,worker_thread线程处理的事务能够并行化,导致所有的cpu都被printk函数所占用,系统无法调用其他的进程,所以系统出现卡死并且无任何log信息打印

例子2推荐
 //工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,
static struct workqueue_struct *test_wq = NULL; 

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

void work_func(struct work_struct *work){
}

/*创建工作队列workqueue_struct,该函数会为cpu创建内核线程*/
        test_wq = create_singlethread_workqueue("test_wq");  
/*初始化工作work_struct,指定工作函数*/
        INIT_WORK(&work,work_func);
 /*将工作加入到工作队列中,最终唤醒内核线程*/
        queue_work(test_wq, &work);

使用create_singlethread_workqueue创建的工作队列只在一个cpu上创建worker_thread内核线程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值