本文正在参与 “走过Linux三十年”话题征文活动。
什么是内核工作队列?
在linux中断编程中,需要中断程序分成中断顶部和中断底部两部分,顶部负责做中断标志,然后耗时的事情在中断底部执行。
那么底部分代码实现可以通过内核工作队列实现。我们就必须先知道什么是内核工作对列。 工作队列(work queue)是另外一种将工作推后执行的形式,它和内核定时器推后的情况有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。最重要的就是工作队列允许被重新调度甚至是睡眠。
如前所述,我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,而工作线程就是负责执行工作队列中的工作。系统有默认的工作者线程,自己也可以创建自己的工作者线程。
工作队列结构
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作函数指针 */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
工作结构相关头文件与函数
我们只需要关心一个成员函数:work_func_t func;
工作函数是一个函数指针,这个成员是指向工作函数的指针;
内核使用这个结构来描述一个工作,一个工作简单理解就是对应于一个函数,可以通过内核调度函数来调用work_struct中func指针所指向的函数。
内核共享工作队列用法示例
#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>
typedef struct __mydat{
struct work_struct mywork;
int x;
int y;
int z;
} mydat_t;
//工作服务函数
static void work_handler(struct work_struct *data)
{
mydat_t *p;
//计算结构体首地址container_of(结构体某成员的地址, 结构体, 结构体某成员)
//p = container_of(data, mydat_t, mywork);
//或使用下面一条代码
p = (mydat_t *)data;
printk(KERN_EMERG "data:%p,\n", data);
printk(KERN_EMERG "x:%d,\n", ((mydat_t *)data)->x);
printk(KERN_EMERG "y:%d,\n", ((mydat_t *)data)->y);
printk(KERN_EMERG "x:%d,\n", p->x);
printk(KERN_EMERG "y:%d,\n", p->y);
printk(KERN_EMERG "work handler function \n");
}
/* 初始化函数 */
static int __init test_init(void)
{
//struct work_struct work;
static mydat_t work;
work.x = 123;
work.y = 456;
printk(KERN_EMERG "&work:%p\n", &work);
//把work放置内核共享工作队列中
INIT_WORK(&work.mywork, work_handler);
//调用工作
schedule_work(&work.mywork);
printk("test_init \n");
return 0;
}
/* 卸载函数 */
static void __exit test_exit(void)
{
printk("%s is call\r\n", __FUNCTION__);
}
MODULE_LICENSE("GPL");
module_init(test_init);
module_exit(test_exit);
自定义工作队列
内核有初始化好的工作队列,因为工作队列在内核驱动中使用比较频繁,为了方便使用不用每人都去注册一个工作队列,所以内核提供了一个公共的工作队列。
那么什么时候我们要自己去创建工作队列呢?
共享工作队列是每人都可以使用的,那就意味着你不能一直霸占他去运行你的程序。 不过一般情况不可能运行那么多的代码,而且需要延迟时可以用睡眠。