rk3288工作队列workqueue

适用环境

之前的定时器、下半部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);

使用流程:

  1. 构造一个work_struct结构体,里面有函数。
  2. 把这个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的同时创建内核线程。
Linux2

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 建立联系

linxu4一开始时,每一个 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");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

习惯就好zz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值