-
工作队列
软中断运行在中断上下文中,因此不能阻塞和睡眠,而tasklet使用软中断实现,当然也不能阻塞和睡眠。但如果某延迟处理函数需要睡眠或者阻塞呢?没关系工作队列就可以如您所愿了。
把推后执行的任务叫做工作(work
),描述它的数据结构为work_struct
,这些工作以队列结构组织成工作队列(workqueue
),其数据结构为workqueue_struct
,而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events
。
工作队列(work queue
)是另外一种将工作推后执行的形式。工作队列可以把工作推后,交由一个内核线程去执行—这个下半部分总是会在进程上下文执行,但由于是内核线程,其不能访问用户空间。最重要特点的就是工作队列允许重新调度甚至是睡眠。
通常,在工作队列和软中断/tasklet
中作出选择非常容易。可使用以下规则:- 如果推后执行的任务需要睡眠,那么只能选择工作队列。
- 如果推后执行的任务需要延时指定的时间再触发,那么使用工作队列,因为其可以利用timer延时(内核定时器实现)。
- 如果推后执行的任务需要在一个
tick
之内处理,则使用软中断或tasklet
,因为其可以抢占普通进程和内核线程,同时不可睡眠。 - 如果推后执行的任务对延迟的时间没有任何要求,则使用工作队列,此时通常为无关紧要的任务。
实际上,工作队列的本质就是将工作交给内核线程处理,因此其可以用内核线程替换。但是内核线程的创建和销毁对编程者的要求较高,而工作队列实现了内核线程的封装,不易出错,所以我们也推荐使用工作队列。
-
工作队列相关数据结构
实际上,工作队列的本质就是将工作交给内核线程处理,因此其可以用内核线程替换。但是内核线程的创建和销毁对编程者的要求较高,而工作队列实现了内核线程的封装,不易出错,所以我们也推荐使用工作队列。
-
工作结构体
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; /* 工作函数,用户自定义实现 */ #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif }; /* 工作队列执行函数的原型: * 该函数会由一个工作者线程执行,因此其在进程上下文中,可以睡眠也可以中断。但只能在内核中运行, * 无法访问用户空间 */ void (*work_func_t) (struct work_struct *work);
-
延迟工作结构体(延迟的实现是在调度时延迟插入相应的工作队列)
struct delayed_work { struct work_struct work; struct timer_list timer; /* 定时器,用于实现延迟处理 */ };
-
工作队列结构体
struct workqueue_struct { struct cpu_workqueue_struct *cpu_wq; /* 指针数组,其每个元素为per-cpu的工作队列 */ struct list_head list; const char *name; int singlethread; /* 标记是否只创建一个工作者线程 */ int freezeable; /* Freeze threads during suspend */ int rt; #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
-
cpu核工作队列(每个cpu核都对应一个工作者线程worker_thread)
struct cpu_workqueue_struct { spinlock_t lock; struct list_head worklist; wait_queue_head_t more_work; struct work_struct *current_work; struct workqueue_struct *wq; struct task_struct *thread; } cacheline_aligned;
-
缺省工作队列API
// 静态创建 DECLARE_WORK(name,function); /* 定义正常执行的工作项 */ DECLARE_DELAYED_WORK(name,function);/* 定义延后执行的工作项 */ // 动态创建 INIT_WORK(_work, _func) /* 创建正常执行的工作项 */ INIT_DELAYED_WORK(_work, _func) /* 创建延后执行的工作项 */ // 调度默认工作队列 int schedule_work(struct work_struct *work) /* 对正常执行的工作进行调度,即把给定工作的处理函数提交给缺省的工作队列和工作者线程。工作者线程本质上 * 是一个普通的内核线程,在默认情况下,每个CPU核均有一个类型为“events”的工作者线程,当调用schedule_work * 时,这个工作者线程会被唤醒去执行工作链表上的所有工作。 * 系统默认的工作队列名称是:keventd_wq, 默认的工作者线程叫:events/n,这里的n是处理器的编号,每个处 * 理器对应一个线程。比如,单处理器的系统只有events/0这样一个线程。而双处理器的系统就会多一个events/1 * 线程。 * 默认的工作队列和工作者线程由内核初始化时创建: */ start_kernel()-->rest_init-->do_basic_setup-->init_workqueues // 调度延迟工作 int schedule_delayed_work(struct delayed_work *dwork,unsigned long delay) // 刷新缺省工作队列 void flush_scheduled_work(void) /* 此函数会一直等待,直到队列中的所有工作都被执行 */ /* 取消延迟工作 */ static inline int cancel_delayed_work(struct delayed_work *work) // flush_scheduled_work并不取消任何延迟执行的工作,因此,如果要取消延迟工作,应该调用cancel_delayed_work。
以上均是采用内核线程来实现工作队列,其优点是简单易用,缺点是如果缺省工作队列负载太重,执行效率会很低,这就需要我们创建自己的工作者线程和工作队列。
-
自定义工作队列
// 宏定义 返回值为工作队列,name为工作线程名称。创建新的工作队列和相应的工作者线程,name用于该内核线程的命名。 create_workqueue(name) // 类似于schedule_work,区别在于queue_work把给定工作提交给创建的工作队列wq而不是缺省队列。 int queue_work(struct workqueue_struct *wq, struct work_struct *work) // 调度延迟工作。 int queue_delayed_work(struct workqueue_struct *wq,struct delayed_work *dwork, unsigned long delay) // 刷新指定工作队列。 void flush_workqueue(struct workqueue_struct *wq) // 释放创建的工作队列。 void destroy_workqueue(struct workqueue_struct *wq)
-
工作队列的组织结构
即workqueue_struct
、cpu_workqueue_struct
与work_struct
的关系。
一个工作队列对应一个work_queue_struct
,工作队列中每cpu的工作队列由cpu_workqueue_struct
表示,而work_struct
为其上的具体工作。
关系如下图所示: -
工作队列的工作过程
-
应用
linux
各个接口的状态(up/down
)的消息需要通知netdev_chain
上感兴趣的模块同时上报用户空间消息。这里使用的就是工作队列。具体流程图如下所示:
-
-
以按键进行编程实践
3.1 按键设备驱动文件
在button_drv.c
文件中,- 加载模块时,
btn_hw_drv_probe
函数中调用INIT_WORK
将按键工作处理函数button_workqueue_fun
赋给.func
成员; gpio_btn_isr
按键中断服务程序(中断上半部)调用schedule_work
函数,将按键.wq
放入队列(链表);- 待中断上半部、下半部执行完毕,唤醒对应的内核线程,从队列中把
work_struct
结构体取出来,执行里面的函数button_workqueue_fun
;
- 加载模块时,
/**
* 文件 : button_drv.c
* 作者 : glen
* 描述 : button driver文件
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/fs.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/workqueue.h>
#define KEY_BUF_LEN 16
#define NEXT_POS(x) ((x+1) % KEY_BUF_LEN)
struct gbtn_irq {
int gpio;
struct gpio_desc *gpiod;
int flag;
int irq;
int idx;
char kval[KEY_BUF_LEN];
int r, w;
struct fasync_struct *fp;
struct timer_list key_timer;
struct tasklet_struct tasklet;
struct work_struct wq;
};
struct button_drv {
struct class *class;
struct gbtn_irq *gbtn_irq;
char *name;
int count;
int major;
};
static struct button_drv *btn_drv;
static int is_key_buf_empty(void *arg)
{
struct gbtn_irq *p = (struct gbtn_irq *)arg;
return (p->r == p->w);
}
static int is_key_buf_full(void *arg)
{
struct gbtn_irq *p = (struct gbtn_irq *)arg;
return (p->r == NEXT_POS(p->w));
}
static void put_key(char key, void *arg)
{
struct gbtn_irq *p = (struct gbtn_irq *)arg;
if (!is_key_buf_full(arg)) {
p->kval[p->w] = key;
p->w = NEXT_POS(p->w);
}
}
static char get_key(void *arg)
{
char key = 'N';
struct gbtn_irq *p = (struct gbtn_irq *)arg;
if (!is_key_buf_full(arg)) {
key = p->kval[p->r];
p->r = NEXT_POS(p->r);
}
return key;
}
/* 等待队列头的静态初始化 */
static DECLARE_WAIT_QUEUE_HEAD(gpio_button_wait);
static void key_timer_expire(unsigned long data)
{
int val;
char key;
struct gbtn_irq *ops = (struct gbtn_irq *)data;
/* 读取按键的值 */
val = gpiod_get_value(ops->gpiod);
printk("button%d %d %d\n", ops->idx, ops->gpio, val);
key = (ops->gpio << 4) | val;
put_key(key, ops);
/* 唤醒等待队列 */
wake_up_interruptible(&gpio_button_wait);
kill_fasync(&ops->fp, SIGIO, POLL_IN);
/* enable btn*/
enable_irq(ops->irq);
}
/* 实现file_operations结构体成员 read 函数 */
ssize_t button_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *pos)
{
int minor = iminor(filp->f_inode);
struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;
char kval;
size = (size >= 1) ? 1 : 0;
if (ops == NULL) {
printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
if (is_key_buf_empty(ops) && (filp->f_flags & O_NONBLOCK))
return -EAGAIN;
wait_event_interruptible(gpio_button_wait, !is_key_buf_empty(ops));
kval = get_key(ops);
if (copy_to_user(buf, &kval, size))
return -EFAULT;
printk("Read button%d value successfully:", minor);
return size;
}
/* 实现file_operations结构体成员 open 函数 */
int button_drv_open(struct inode *nd, struct file *filp)
{
int ret;
int minor = iminor(nd);
struct gbtn_irq *ops;
if (btn_drv == NULL) {
printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
ops = &btn_drv->gbtn_irq[minor];
ret = gpiod_direction_input(ops->gpiod);
if (ret)
printk("Set the button pin as input error %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
else
printk("Set the button%d pin as input successfully!\n", minor);
filp->private_data = ops;
return 0;
}
/* 实现file_operations结构体成员 release 函数 */
int button_drv_release (struct inode *nd, struct file *filp)
{
struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;
if (ops == NULL) {
printk("Please register button instance %s %s, %d\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
filp->private_data = NULL;
return 0;
}
/* 实现file_operations结构体成员 poll 函数 */
unsigned int button_drv_poll (struct file *filp, struct poll_table_struct * wait)
{
int minor = iminor(filp->f_inode);
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
poll_wait(filp, &gpio_button_wait, wait);
return (is_key_buf_empty(&btn_drv->gbtn_irq[minor]) ? 0 : POLLIN | POLLRDNORM);
}
static int button_drv_fasync(int fd, struct file *filp, int on)
{
struct gbtn_irq *ops = (struct gbtn_irq *)filp->private_data;
if (fasync_helper(fd, filp, on, &ops->fp) >= 0)
return 0;
else
return -EIO;
}
/**
* 1. 构造file_operations结构体
*/
static struct file_operations button_drv_ops = {
.owner = THIS_MODULE,
.read = button_drv_read,
.open = button_drv_open,
.release = button_drv_release,
.poll = button_drv_poll,
.fasync = button_drv_fasync,
};
/* 中断服务函数 */
static irqreturn_t gpio_btn_isr (int irq, void *dev_id)
{
struct gbtn_irq *ops = dev_id;
// printk("gpio_btn_isr key %d irq happened\n", ops->gpio);
tasklet_schedule(&ops->tasklet);
mod_timer(&ops->key_timer, jiffies + HZ / 50);
schedule_work(&ops->wq);
disable_irq_nosync(irq);
return IRQ_HANDLED;
}
/* tasklet action function */
static void button_tasklet_func (unsigned long data)
{
int val;
struct gbtn_irq *ops = (struct gbtn_irq *)data;
/* 读取按键的值 */
val = gpiod_get_value(ops->gpiod);
printk("button_tasklet_func key%d %d %d\n", ops->idx, ops->gpio, val);
}
static void button_work_func (struct work_struct *work)
{
int val;
struct gbtn_irq *ops = container_of(work, struct gbtn_irq, wq);
/* 读取按键的值 */
val = gpiod_get_value(ops->gpiod);
printk("button_work_func: the process is %s pid %d\n", current->comm, current->pid);
printk("button_work_func key%d %d %d\n", ops->idx, ops->gpio, val);
}
/* platform_driver结构体的 probe成员函数实现 */
int btn_hw_drv_probe (struct platform_device *pdev)
{
int i;
int ret;
int count;
// enum of_gpio_flags flag;
struct device_node *node = pdev->dev.of_node;
/* 从设备节点获取gpio数量 */
count = of_gpio_count(node);
if (!count) {
printk("%s %s line %d, there isn't any gpio available!\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
btn_drv = kzalloc(sizeof(struct button_drv), GFP_KERNEL);
if (btn_drv == NULL)
return -ENOMEM;
btn_drv->gbtn_irq = kzalloc(sizeof(struct gbtn_irq) * count, GFP_KERNEL);
if (btn_drv->gbtn_irq == NULL)
return -ENOMEM;
for (i = 0; i < count; i++) {
btn_drv->gbtn_irq[i].gpiod = gpiod_get_index_optional(&pdev->dev, NULL, i, GPIOD_ASIS);
if (btn_drv->gbtn_irq[i].gpiod == NULL) {
printk("%s %s line %d, gpiod_get_index_optional failed!\n", __FILE__, __FUNCTION__, __LINE__);
return -EIO;
}
btn_drv->gbtn_irq[i].irq = gpiod_to_irq(btn_drv->gbtn_irq[i].gpiod);
btn_drv->gbtn_irq[i].gpio = desc_to_gpio(btn_drv->gbtn_irq[i].gpiod);
setup_timer(&btn_drv->gbtn_irq[i].key_timer, key_timer_expire, &btn_drv->gbtn_irq[i]);
btn_drv->gbtn_irq[i].key_timer.expires = ~0;
add_timer(&btn_drv->gbtn_irq[i].key_timer);
tasklet_init(&btn_drv->gbtn_irq[i].tasklet, button_tasklet_func, &btn_drv->gbtn_irq[i]);
INIT_WORK(&btn_drv->gbtn_irq[i].wq, button_work_func);
btn_drv->gbtn_irq[i].idx = i;
}
for (i = 0; i < count; i++)
/* 申请irq中断, 将中断服务程序注册到上半部 */
ret = request_irq(btn_drv->gbtn_irq[i].irq, gpio_btn_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
"gpio_btn", &btn_drv->gbtn_irq[i]);
/* 注册file_operationss结构体对象 -- button_drv_ops */
btn_drv->major = register_chrdev(btn_drv->major, "gbtn", &button_drv_ops);
btn_drv->class = class_create(THIS_MODULE, "gbtn");
if (IS_ERR(btn_drv->class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(btn_drv->major, "gbtn");
return PTR_ERR(btn_drv->class);
}
for (i = 0; i < count; i++)
device_create(btn_drv->class, NULL, MKDEV(btn_drv->major, i), NULL, "gbtn%d", i);
btn_drv->count = count;
return 0;
}
/* platform_driver结构体的 remove成员函数实现 */
int btn_hw_drv_remove(struct platform_device *pdev)
{
int i;
struct device_node *node = pdev->dev.of_node;
int count = of_gpio_count(node);
for (i = 0; i < count; i++) {
device_destroy(btn_drv->class, MKDEV(btn_drv->major, i));
free_irq(btn_drv->gbtn_irq[i].irq, &btn_drv->gbtn_irq[i]);
del_timer(&btn_drv->gbtn_irq[i].key_timer);
tasklet_kill(&btn_drv->gbtn_irq[i].tasklet);
}
class_destroy(btn_drv->class);
unregister_chrdev(btn_drv->major, "gbtn");
kfree(btn_drv);
return 0;
}
/* 构造用于配置的设备属性 */
static const struct of_device_id gbtns_id[] = {
{.compatible = "glen,gbtn"},
{ },
};
/* 构造(初始化)file_operations结构体 */
static struct platform_driver btn_hw_drv = {
.driver = {
.name = "gbtn",
.of_match_table = gbtns_id,
},
.probe = btn_hw_drv_probe,
.remove = btn_hw_drv_remove,
};
/* 初始化 */
static int __init button_drv_init(void)
{
int ret;
ret = platform_driver_register(&btn_hw_drv);
if (ret)
pr_err("Unable to initialize button driver\n");
else
pr_info("The button driver is registered.\n");
return 0;
}
module_init(button_drv_init);
static void __exit button_drv_exit(void)
{
platform_driver_unregister(&btn_hw_drv);
printk(" %s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
}
module_exit(button_drv_exit);
/* insert author information for module */
MODULE_AUTHOR("glen");
/* insert license for module */
MODULE_LICENSE("GPL");
probe函数采用了先获取按键节点数量,然后分别读取gpio描述符并通过其获取为gpio和irq号,并申请注册中断服务程序。
3.2 设备树文件(不作更改)
pinctrl_btn0:btn0 {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
>;
};
pinctrl_btn1:btn1 {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xF080 /* KEY1 此按键不存在 */
>;
};
/* 在根节点下添加基于pinctrl的gbtns设备节点 */
gbtns {
compatible = "glen,gbtn";
#address-cells = <1>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_btn0
&pinctrl_btn1>;
gpio-controller;
#gpio-cells = <2>;
gpios = <&gpio1 18 GPIO_ACTIVE_LOW /* button0 */
&gpio1 3 GPIO_ACTIVE_LOW>; /* button1 */
};
-
取消了gpios前缀“xxx-",相应地,在驱动程序用gpiod_get_index_optional函数获取gpio描述符时,第2个形参 ”const char *con_id“ 传递NULL即可;
-
将pinctrl-0、gpios属性值由 “<>,<>;” 改为 “<>;",效果是一样的
3.3 应用程序
应用程序文件button_drv_test.c提供:
- 定义sig_fun信号处理函数并注册信号,以后APP收到SIGIO信号时,这个函数会被自动调用;
- fcntl(fd, F_SETOWN, getpid()); 把APP的PID(进程ID)告诉驱动程序,这个调用不涉及驱动程序,在内核的文件系统层次记录PID;
- oflags = fcntl(fd, F_GETFL); 读取驱动程序文件oflags
- fcntl(fd, F_SETFL, oflags | FASYNC); 设置oflags里面的FASYNC位为1:当FASYNC位发生变化时,会导致驱动程序的fasync被调用
/*
* 文件名 : button_drv_test.c
* 作者 : glen
* 描述 : button_drv应用程序
*/
#include "stdio.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
int fd;
static void sig_fun(int sig)
{
char kval;
read(fd, &kval, 1);
printf("The glen button value is: %d!\n", kval);
}
/**
* @brief : main函数
* @par : argc argv数组元素的个数
* argv 参数数组
* @retval : 0 成功 其它 失败
*/
int main(int argc, char *argv[])
{
int ret;
int oflags;
char *filename;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
signal(SIGIO, sig_fun);
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
fcntl(fd, F_SETOWN, getpid());
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);
while (1) {
sleep(2);
printf("Read the glen button in sleepping!\n");
}
/* 关闭文件 */
ret = close(fd);
if (ret < 0) {
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
3.4 在alientek_linux_alpha开发板实测验证如下
/drv_module # insmod button_drv.ko
The button driver is registered.
/drv_module # ./btn_drv_test /dev/gbtn0
./btn_drv_test: line 1: syntax error: unexpected "("
/drv_module # button_tasklet_func key0 18 0
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 1
button0 18 1
button_tasklet_func key0 18 1
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 1
button0 18 1
button_tasklet_func key0 18 0
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 0
button0 18 0
button_tasklet_func key0 18 1
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 1
button0 18 1
button_tasklet_func key0 18 0
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 0
button0 18 0
button_tasklet_func key0 18 1
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 1
button0 18 1
button_tasklet_func key0 18 0
button_work_func: the process is kworker/0:2 pid 45
button_work_func key0 18 0
button0 18 0