适用环境
之前的定时器、下半部tasklet,都是在中断上下文中执行,他们无法休眠。
当要处理更复杂、更耗时的工作时,放在定时器或是下半部中,会使得系统很卡,并且循环等待某件事也太浪费CPU资源。
如果使用线程来处理这些耗时的工作,那就可以解决卡顿的问题,因为线程可以休眠。
内核中有一种更简单的使用线程的方法,“工作队列”(workqueue)。
在内核初始化时就创建了内核线程,我们只需要使用“工作队列”,把“工作”放入其队列中“,内核线程就会拿出“工作”执行函数。
工作队列应用场合:
做的事情比较耗时,甚至可能需要休眠,那么可以使用工作队列。
缺点:多个工作(函数)是在某个内核线程中依序执行的,前面函数执行很慢,就会影响到后面的函数。
在多CPU系统下,一个工作队列可以有多个内核线程,可以在一定程度上缓解这个问题。
内核函数
内核线程、工作队列(workqueue)都由内核创建。使用核心是一个work_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
};
typedef void (*work_func_t)(struct work_struct *work);
使用流程:
- 构造一个work_struct结构体,里面有函数。
- 把这个work_struct结构体放入工作队列,内核线程就会运行work中的函数。
定义work
在头文件include\linux\workqueue.h
//用来定义一个work_struct结构体,需要指定它的函数
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
//用来定义一个delayed_work结构体,也需要指定它的函数。delayed的意思是,可以指定某段时间之后再运行。
#define DECLARE_DELAYED_WORK(n, f) \
struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)
//初始化work_struct结构体,可以使用这个宏
#define INIT_WORK(_work, _func) \
__INIT_WORK((_work), (_func), 0)
//调用schedule_work时,会把work_struct结构体放入队列中,并唤醒对应的内核线程。
//内核线程就会从队列里把work_struct结构体取出来,执行里面的函数
static inline bool schedule_work(struct work_struct *work)
{
return queue_work(system_wq, work);
}
其他的相关函数
/* 在 Linux 系统中已经有了现成的 system_wq 等工作队列,你当然也可以自己调用 create_workqueue 创建工作队列,
* 对于 SMP 系统,这个工作队列会有多个内核线程与它对应,创建工作队列时,内核会帮这个工作队列创建多个内核线程
*/
#define create_workqueue(name) \
alloc_workqueue("%s", WQ_MEM_RECLAIM, 1, (name))
/* 如果想只有一个内核线程与工作队列对应,可以用本函数创建工作队列,
* 创建工作队列时,内核会帮这个工作队列创建一个内核线程
*/
#define create_singlethread_workqueue(name) \
alloc_ordered_workqueue("%s", WQ_MEM_RECLAIM, name)
//销毁工作队列
extern void destroy_workqueue(struct workqueue_struct *wq);
//调度执行一个具体的 work,执行的 work 将会被挂入 Linux 系统提供的工作队列
static inline bool schedule_work(struct work_struct *work)
//延迟一定时间去执行一个具体的任务,功能与 schedule_work 类似,多了一个延迟时间
static inline bool schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)
//跟 schedule_work 类似,schedule_work 是在系统默认的工作队列上执行一个 work, queue_work 需要自己指定工作队列
static inline bool queue_work(struct workqueue_struct *wq, struct work_struct *work)
//跟 schedule_delayed_work 类似,schedule_delayed_work 是在系统默认的工作队列上执行一个 work,
//queue_delayed_work 需要自己指定工作队列
static inline bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay)
//等待一个 work 执行完毕,如果这个 work 已经被放入队列,那么本函数等它执行完毕,
//并且返回 true;如果这个 work 已经执行完华才调用本函数,那么直接返回false
extern bool flush_work(struct work_struct *work);
//等待一个 delayed_work 执行完毕,如果这个 delayed_work 已经被放入队列,那么本函数等它执行完毕,并且返回 true;
//如果这个 delayed_work 已经执行完华才调用本函数,那么直接返回 false
extern bool flush_delayed_work(struct delayed_work *dwork);
内部原理
- 在2.xx版本的Linux内核中,创建workqueue时就会同时创建内核线程。
- 在4.xx版本的Linux内核中,内核线程和workqueu是分开创建的,比较复杂。
在Linux2.x的工作队列创建过程
代码在kernel\workqueue.c中:
init_workqueues
keventd_wq = create_workqueue("events");
__create_workqueue((name), 0, 0)
for_each_possible_cpu(cpu) {
err = create_workqueue_thread(cwq, cpu);
p = kthread_create(worker_thread, cwq, fmt, wq->name, cpu);
对于每个CPU,都创建一个名为“events/X”的内核线程,X从0开始。
在创建workqueue的同时创建内核线程。
Linux4.x的工作队列创建过程
Linux4.x中,内核线程中工作队列是分开创建的。
先创建内核线程,代码在kernel\workqueue.c中:
init_workqueues
/* initialize CPU pools */
for_each_possible_cpu(cpu) {
for_each_cpu_worker_pool(pool, cpu) {
/* 对每一个 CPU 都创建 2 个 worker_pool 结构体,它是含有 ID 的 */
/* 一个 worker_pool 对应普通优先级的 work,第 2 个对应高优先级的 work */
}
/* create the initial worker */
for_each_online_cpu(cpu) {
for_each_cpu_worker_pool(pool, cpu) {
/* 对每一个 CPU 的每一个 worker_pool,创建一个 worker */
/* 每一个 worker 对应一个内核线程 */
BUG_ON(!create_worker(pool));
}
}
create_work函数代码如下
static struct worker *create_worker(struct worker_pool *pool)
{
//....
if (pool->cpu >= 0) //%d在哪个cpu运行,%d,poll中第几个线程,H是高优先级
snprintf(id_buf, sizeof(id_buf), "%d:%d%s", pool->cpu, id,
pool->attrs->nice < 0 ? "H" : "");
else
snprintf(id_buf, sizeof(id_buf), "u%d:%d", pool->id, id);
worker->task = kthread_create_on_node(worker_thread, worker, pool->node,
"kworker/%s", id_buf); //内核线程名字
if (IS_ERR(worker->task))
goto fail;
//....
}
创建好内核线程后,再创建workqueue,代码再kernel\workqueue.c中
init_workqueues
system_wq = alloc_workqueue("events", 0, 0);
__alloc_workqueue_key
wq = kzalloc(sizeof(*wq) + tbl_size, GFP_KERNEL); // 分配 workqueue_struct
alloc_and_link_pwqs(wq) // 跟 worker_poll 建立联系
一开始时,每一个 worker_poll 下只有一个线程,但是系统会根据任务繁重程度动态创建、销毁内核线程。所以你可以在 work 中打印线程 ID,发现它可能是变化的。
参考文章:
https://zhuanlan.zhihu.com/p/91106844
https://www.cnblogs.com/vedic/p/11069249.html
https://www.cnblogs.com/zxc2man/p/4678075.html
源码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <linux/irqreturn.h>
#include <linux/gpio/consumer.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
struct gpio_key {
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
struct timer_list timer;
struct tasklet_struct tasklet;
struct work_struct work;
};
static struct gpio_key *myBtn_key;
static int button_major = 0;
static struct class *button_class;
static struct fasync_struct *btn_async;
static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);
#define MaxSize 128
struct QNode {
int Data[MaxSize];
int rear;
int front;
};
typedef struct QNode *Queue;
int IsEmpty(Queue Q);
void AddQ(Queue PtrQ, int item);
int DeleteQ(Queue PtrQ);
int IsEmpty(Queue Q)
{
return (Q->rear == Q->front); //1:empty 0:not empty
}
void AddQ(Queue PtrQ, int item)
{
if((PtrQ->rear+1)%MaxSize == PtrQ->front) {
printk("%s,Queue full\n", __FUNCTION__);
return;
}
PtrQ->rear = (PtrQ->rear+1)%MaxSize;
PtrQ->Data[PtrQ->rear] = item;
}
int DeleteQ(Queue PtrQ)
{
if(PtrQ->front == PtrQ->rear) {
printk("%s,Queue empty\n", __FUNCTION__);
return -1;
} else {
PtrQ->front = (PtrQ->front+1)%MaxSize;
return PtrQ->Data[PtrQ->front];
}
}
static Queue irqBuff;
static ssize_t button_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int err;
int val;
if(IsEmpty(irqBuff) && (file->f_flags & O_NONBLOCK)) {
return -EAGAIN;
}
wait_event_interruptible(gpio_key_wait, !IsEmpty(irqBuff));
val = DeleteQ(irqBuff);
err = copy_to_user(buf, &val, 4);
// if(err != 4) {
// return -1;
// }
return 4;
}
static unsigned int button_poll(struct file *fp, poll_table * wait)
{
printk("%s,button poll\n", __FUNCTION__);
poll_wait(fp, &gpio_key_wait, wait);
return IsEmpty(irqBuff) ? 0 : POLLIN | POLLRDNORM;
}
int button_fasync(int fd, struct file *file, int on)
{
if(fasync_helper(fd, file, on, &btn_async) >= 0)
return 0;
else
return -EIO;
}
static struct file_operations button_ops = {
.owner = THIS_MODULE,
.read = button_read,
.poll = button_poll,
.fasync = button_fasync,
};
static irqreturn_t myBtn_irq_request(int irq, void *dev_id)
{
struct gpio_key *gpio_key = dev_id;
//printk(KERN_WARNING"myBtn_irq_request key %d irq happened\n", gpio_key->gpio);
tasklet_schedule(&myBtn_key->tasklet);
schedule_work(&myBtn_key->work);
mod_timer(&gpio_key->timer, jiffies + HZ/50);
return IRQ_HANDLED;
}
static void myBtn_timer(unsigned long data)
{
struct gpio_key *gpio_key = (struct gpio_key*)data;
int val;
val = gpiod_get_value(gpio_key->gpiod);
printk(KERN_WARNING"key %d %d\n", gpio_key->gpio, val);
val = (myBtn_key->gpio << 8)|val;
AddQ(irqBuff, val);
wake_up_interruptible(&gpio_key_wait);
kill_fasync(&btn_async, SIGIO, POLLIN);
}
static void myBtn_tasklet(unsigned long data)
{
struct gpio_key *gpio_key = (struct gpio_key*)data;
int val;
val = gpiod_get_value(gpio_key->gpiod);
printk(KERN_WARNING"tasklet key %d %d\n", gpio_key->gpio, val);
}
static void myBtn_workqueue(struct work_struct *work)
{
struct gpio_key *gpio_key = container_of(work, struct gpio_key, work);
int val;
val = gpiod_get_value(gpio_key->gpiod);
printk(KERN_WARNING"key_work_func: the process is %s pid %d\n",current->comm, current->pid);
printk(KERN_WARNING"workqueue key %d %d\n", gpio_key->gpio, val);
}
static int my_button_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
int count;
enum of_gpio_flags flag;
int i, err;
count = of_gpio_count(node);
if(!count) {
printk("%s,there isn't any gpio availiable\n", __FUNCTION__);
return -1;
}
myBtn_key = (struct gpio_key*)kzalloc(sizeof(struct gpio_key)*count, GFP_KERNEL);
if(!myBtn_key) {
printk("%s,kzalloc malloc failed\n", __FUNCTION__);
return -1;
}
for(i=0;i<count;i++) {
myBtn_key[i].gpio = of_get_gpio_flags(node, i, &flag);
if(myBtn_key[i].gpio < 0) {
printk("%s, of_get_gpio_flags failed\n", __FUNCTION__);
return -1;
}
myBtn_key[i].gpiod = gpio_to_desc(myBtn_key[i].gpio);
myBtn_key[i].flag = flag & OF_GPIO_ACTIVE_LOW;
myBtn_key[i].irq = gpio_to_irq(myBtn_key[i].gpio);
setup_timer(&myBtn_key[i].timer, myBtn_timer, (unsigned long)&myBtn_key[i]);
myBtn_key[i].timer.expires = ~0;
tasklet_init(&myBtn_key[i].tasklet, myBtn_tasklet, (unsigned long)&myBtn_key[i]);
INIT_WORK(&myBtn_key[i].work, myBtn_workqueue);
err = request_irq(myBtn_key[i].irq, myBtn_irq_request, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"myBtn_key", &myBtn_key[i]);
}
button_major = register_chrdev(0, "mybutton", &button_ops);
if (button_major < 0) {
printk(KERN_ERR "button : couldn't get a major number.\n");
return -1;
}
button_class = class_create(THIS_MODULE, "button_class");
if(IS_ERR(button_class)) {
printk(KERN_ERR "button class: create failed\n");
unregister_chrdev(button_major, "mybutton");
return -1;
}
device_create(button_class, NULL, MKDEV(button_major, 0), NULL, "mybutton%d", 0);
return 0;
}
static int my_button_remove(struct platform_device *pdev)
{
struct device_node *node= pdev->dev.of_node;
int count;
int i;
device_destroy(button_class, MKDEV(button_major, 0));
class_destroy(button_class);
unregister_chrdev(button_major, "mybutton");
count = of_gpio_count(node);
for(i=0;i<count;i++) {
free_irq(myBtn_key[i].irq, &myBtn_key[i]);
del_timer(&myBtn_key[i].timer);
tasklet_kill(&myBtn_key[i].tasklet);
}
kfree(myBtn_key);
return 0;
}
static struct of_device_id mybuttons[] = {
{ .compatible = "mybtn,btn_drv" },
{ },
};
static struct platform_driver my_button_driver = {
.probe = my_button_probe,
.remove = my_button_remove,
.driver = {
.name = "button_dirver",
.of_match_table = mybuttons,
},
};
static int gpio_button_init(void)
{
int err;
irqBuff = (Queue)kzalloc(sizeof(struct QNode), GFP_KERNEL);
err = platform_driver_register(&my_button_driver);
printk(KERN_WARNING"my button dirver init\n");
return 0;
}
static void gpio_button_exit(void)
{
platform_driver_unregister(&my_button_driver);
kfree(irqBuff);
printk(KERN_WARNING"my button dirver exit\n");
}
module_init(gpio_button_init);
module_exit(gpio_button_exit);
MODULE_LICENSE("GPL");