本期主题:
讲清workqueue和waitqueu:
- 从中断讲起
- waitqueue是什么
- workqueue
- 总结
往期链接:
- linux设备驱动中的并发
- linux设备驱动中的编译乱序和执行乱序
- linux设备驱动之内核模块
- linux字符驱动
- linux字符驱动之ioctl部分
- linux字符驱动之read、write部分
- linux驱动调试之Debugfs
- Linux下如何操作寄存器(用户空间、内核空间方法讲解)
- petalinux的module编译
目录
- 1.从中断讲起
- 2.workqueue
- Linux上workqueue例子
- workqueue API
- example
- 3.waitqueue
- Linux上waitqueue例子
- waitqueue API
- example
- 4.总结
1.从中断讲起
从操作系统角度而言,实际上有两种中断:
- 硬件中断(hardware IRQ),由系统自身和外设产生的中断,例如在ARM侧就是GIC中断;
- 软中断(software IRQ),用于实现延时执行操作(这是重点,后面会有解释);
我们来分析一下 中断处理程序 的需求
需求:
- 中断处理程序需要尽可能简短,这样能够快速处理中断;
- 允许在处理A中断时,被B中断打断然后去处理B的事情,允许中断的嵌套;
由于有快速、简短这样的需求,所以操作系统设计出了这样的机制:
- 关键操作(可以理解为清除一些中断状态位的操作),必须在中断发生后立刻去做;
- 非关键操作也应尽快执行,但允许启用中断;
- 可延期操作不是特别重要,不必在中断处理程序中运行,可以延迟这些操作,在时间充裕时进行;
软中断就是这样的一个作用,可以使操作延时去做
;
2.workqueue
workqueue顾名思义就是工作队列,他有几个特点:
- 处理不那么紧急的任务,我们常说的Linux中断处理的底半部就可以使用workqueue来做;
- 函数执行环境的切换,从中断环境切到了线程环境;
- 总而言之,workqueue适用于
异步执行
的场景;
workqueue就相当于在worklist(工作链表)上挂上了一个个的work item(工作节点),每次都从中取出这些item来运行
Linux上workqueue例子
workqueue API
讲解以下几个API:
- workqueue的结构体定义
- work_struct的结构体定义(work item)
- delay_work接口
1. workqueue定义:
//kernel/workqueue.c
其中的pwqs是 pool workqueues,代表着这个workqueue所管理的pool,pool在这里的意思理解是一个池子,存放着当前wq的信息
2. workstruct定义
其中的func就是work_item的callback函数,entry就是添加到前面的workqueue_strcut的pwqs list中,然后遍历
3. queue_delayed_work
example
看一个例子,我们写一个ko,实现的功能如下:
- 在init的时候创建workqueue,并且添加delay work,delay时间设置为1s
- 1s之后,调用我们的work handler,做一个异步处理的典型场景
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/workqueue.h>
static void mykmod_work_handler(struct work_struct *w);
static struct workqueue_struct *wq = 0;
static DECLARE_DELAYED_WORK(mykmod_work, mykmod_work_handler);
static unsigned long onesec;
static void
mykmod_work_handler(struct work_struct *w)
{
pr_info("mykmod work %u jiffies\n", (unsigned)onesec);
}
static int mykmod_init(void)
{
onesec = msecs_to_jiffies(1000);
pr_info("mykmod loaded %u jiffies\n", (unsigned)onesec);
wq = create_singlethread_workqueue("mykmod");
if (!wq)
pr_err("wq init failed!\n");
queue_delayed_work(wq, &mykmod_work, onesec);
return 0;
}
static void mykmod_exit(void)
{
if (wq) {
cancel_delayed_work_sync(&mykmod_work);
destroy_workqueue(wq);
}
pr_info("mykmod exit\n");
}
module_init(mykmod_init);
module_exit(mykmod_exit);
MODULE_DESCRIPTION("mykmod");
MODULE_LICENSE("GPL");
测试结果:
3.waitqueue
等待队列用于在进程中等待某一特定事件发生,无须频繁轮询。进程在等待期间休眠,事件发生时,内核唤醒进程。总结一下有几个特点:
- 进程等不到时会休眠;
- 事件发生时,内核会把进程唤醒;
Linux上waitqueue例子
waitqueue API
介绍waitqueue结构体以及下面三种API:
1.waitqueue结构体
2. 初始化等待队列,DECLARE_WAIT_QUEUE_HEAD
3. 将进程加入等待队列,wait_event
4. 唤醒等待队列中的进程,wakeup
1. waitqueue 结构体
- waitqueue head只有一个自旋锁和双向链表;
- wait_queue_entry是对一个等待任务的抽象,每个等待任务都会抽象成一个wait_queue_t,并且挂载到wait_queue_head_t上;
2. 初始化等待队列
初始化等待队列,实际上就是waitqueue_head的初始化,可以使用静态初始化也可使用动态初始化;
- 静态初始化: DECLARE_WAITQUEUE
- 动态初始化:init_waitqueue_head
3. 将进程加入等待队列
wait_event(wq_head, condition)
等待事件接口,sleep直至conditon满足
其中,__wait_event的代码如下:
4. wakeup
通过遍历wq_head来找到entry,并且调用entry的func;
example
例子讲解:
- 创建一个静态等待队列,并在Moudule init的时候建立一个内核进程;
- 注册好proc_fs,上层通过Cat读取/proc文件时,会将flag置1,并wakeup等待进程;
- 在rmmod 模块时,会将flag置2,并wakup等待进程,此时内核进程退出;
下面是代码以及测试结果:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/wait.h>
#include <linux/kthread.h>
static int read_count = 0;
static struct task_struct *wait_thread;
// Initializing waitqueue statically
DECLARE_WAIT_QUEUE_HEAD(test_waitqueue);
static int wait_queue_flag = 0;
static int my_waitqueue_show(struct seq_file *m, void *v)
{
printk(KERN_ALERT "Read function\n");
seq_printf(m, "read_count = %d\n", read_count);
wait_queue_flag = 1;
wake_up_interruptible(&test_waitqueue); // wake up only one process from wait queue
return 0;
}
static int my_waitqueue_open(struct inode *inode, struct file *filp)
{
return single_open(filp, my_waitqueue_show, NULL);
}
static struct proc_fs test_wait_queue_fops = {
.proc_open = my_waitqueue_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
static int wait_function(void *unused)
{
while(1) {
printk(KERN_ALERT "Waiting For Event...\n");
// sleep until wait_queue_flag != 0
wait_event_interruptible(test_waitqueue, wait_queue_flag != 0);
if (wait_queue_flag == 2) {
printk(KERN_ALERT "Event Came From Exit Function\n");
return 0;
}
printk(KERN_ALERT "Event Came From Read Function - %d\n", ++read_count);
wait_queue_flag = 0;
}
return 0;
}
static int mywaitqueue_init(void)
{
struct proc_dir_entry *pe;
printk(KERN_ALERT "[Hello] mywaitqueue \n");
pe = proc_create("test_wait_queue", 0644, NULL, &test_wait_queue_fops);
if (!pe)
return -ENOMEM;
// Create the kernel thread with name "MyWaitThread"
wait_thread = kthread_create(wait_function, NULL, "MyWaitThread");
if (wait_thread) {
printk(KERN_ALERT "Thread created successfully\n");
wake_up_process(wait_thread);
} else {
printk(KERN_ALERT "Thread creation failed\n");
}
return 0;
}
static void mywaitqueue_exit(void)
{
wait_queue_flag = 2;
wake_up_interruptible(&test_waitqueue);
printk(KERN_ALERT "[Goodbye] mywaitqueue\n");
remove_proc_entry("test_wait_queue", NULL);
}
module_init(mywaitqueue_init);
module_exit(mywaitqueue_exit);
MODULE_LICENSE("GPL");
测试结果:
进行了3次cat读取,最后rmmod才真正退出
4.总结
waitqueue
和 workqueue
是实时操作系统中用于线程间通信和任务调度的两种机制,它们有一些区别和不同的应用场景。
waitqueue
是一种线程等待队列,用于实现线程间的同步和通信。它允许线程等待某个条件的发生,并在条件满足时被唤醒继续执行。通过将线程放入等待队列中,可以避免线程在条件不满足时的忙等待,提高系统的效率和资源利用率。waitqueue
主要用于实现线程间的同步和事件通知,例如一个线程等待某个资源的可用性或某个事件的发生。
workqueue
是一种任务队列,用于调度和执行后台任务。它可以在后台异步执行一些耗时的操作,而不阻塞当前线程的执行。workqueue
将任务添加到队列中,并由系统自动调度和执行。相比于直接在当前线程中执行任务,使用 workqueue
可以将任务的执行延迟到后台,提高系统的响应性和并发性。workqueue
主要用于处理一些耗时的、非实时性的任务,例如文件系统操作、网络请求、数据处理等。
下面是 waitqueue
和 workqueue
的一些主要差异:
-
功能目的:
waitqueue
用于线程间的同步和通信,而workqueue
用于后台任务的调度和执行。 -
调度方式:
waitqueue
中的线程需要显式地被唤醒,可以通过条件的满足或其他线程的通知来触发唤醒操作;而workqueue
中的任务由系统自动调度,无需显式触发。 -
使用场景:
waitqueue
主要用于实现同步和事件通知的场景,例如等待某个资源的可用性或某个事件的发生;而workqueue
主要用于执行后台任务,例如文件系统操作、网络请求、数据处理等。