Linux自从2.6.20之后,工作队列发生了一些变化,目前从网络上搜索的资料一般都是介绍老版本的工作队列,很少见到对新版本的介绍。本文对新老版本都做了简要概述,并分别提供了简单的实作案例。
工作队列(work queue)是Linux kernel中将工作推后执行的一种机制。这种机制和BH或Tasklets不同之处在于工作队列是把推后的工作交由一个内核线程去执行,因此工作队列的优势就在于它允许重新调度甚至睡眠。
工作队列是2.6内核开始引入的机制,在2.6.20之后,工作队列的数据结构发生了一些变化,因此本文分成两个部分对2.6.20之前和之后的版本分别做介绍。
1、2.6.0~2.6.19
数据结构:
struct work_struct {
unsigned long pending;
struct list_head entry;
void (*func)(void *);
void *data;
void *wq_data;
struct timer_list timer;
};
pending是用来记录工作是否已经挂在队列上;
entry是循环链表结构;
func作为函数指针,由用户实现;
data用来存储用户的私人数据,此数据即是func的参数;
wq_data一般用来指向工作者线程(工作者线程参考下文);
timer是推后执行的定时器。
work_struct的这些变量里,func和data是用户使用的,其他是内部变量,我们可以不用太过关心。
API:
1) INIT_WORK(_work, _func, _data)
初始化指定工作,目的是把用户指定的函数_func及_func需要的参数_data赋给work_struct的func及data变量。
2) int schedule_work(struct work_struct *work)
对工作进行调度,即把给定工作的处理函数提交给缺省的工作队列和工作者线程。工作者线程本质上是一个普通的内核线程,在默认情况下,每个CPU均有一个类型为“events”的工作者线程,当调用schedule_work时,这个工作者线程会被唤醒去执行工作链表上的所有工作。
3) int schedule_delayed_work(struct work_struct *work, unsigned long delay)
延迟执行工作,与schedule_work类似。
4) void flush_scheduled_work(void)
刷新缺省工作队列。此函数会一直等待,直到队列中的所有工作都被执行。
5) int cancel_delayed_work(struct work_struct *work)
flush_scheduled_work并不取消任何延迟执行的工作,因此,如果要取消延迟工作,应该调用cancel_delayed_work。
以上均是采用缺省工作者线程来实现工作队列,其优点是简单易用,缺点是如果缺省工作队列负载太重,执行效率会很低,这就需要我们创建自己的工作者线程和工作队列。
API:
1) struct workqueue_struct *create_workqueue(const char *name)
创建新的工作队列和相应的工作者线程,name用于该内核线程的命名。
2) int queue_work(struct workqueue_struct *wq, struct work_struct *work)
类似于schedule_work,区别在于queue_work把给定工作提交给创建的工作队列wq而不是缺省队列。
3) int queue_delayed_work(struct workqueue_struct *wq, struct work_struct *work, unsigned long delay)
延迟执行工作。
4) void flush_workqueue(struct workqueue_struct *wq)
刷新指定工作队列。
5) void destroy_workqueue(struct workqueue_struct *wq)
释放创建的工作队列。
下面一段代码可以看作一个简单的实作:
void my_func(void *data)
{
char *name = (char *)data;
printk(KERN_INFO “Hello world, my name is %s!\n”, name);
}
struct workqueue_struct *my_wq = create_workqueue(“my wq”);
struct work_struct my_work;
INIT_WORK(&my_work, my_func, “Jack”);
queue_work(my_wq, &my_work);
destroy_workqueue(my_wq);
2、2.6.20~2.6.??
自2.6.20起,工作队列的数据结构发生了一些变化,使用时不能沿用旧的方法。
数据结构:
typedef void (*work_func_t)(struct work_struct *work);
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
};
与2.6.19之前的版本相比,work_struct瘦身不少。粗粗一看,entry和之前的版本相同,func和data发生了变化,另外并无其他的变量。
entry我们不去过问,这个和以前的版本完全相同。data的类型是atomic_long_t,这个类型从字面上看可以知道是一个原子类型。第一次看到这个变量时,很容易误认为和以前的data是同样的用法,只不过类型变了而已,其实不然,这里的data是之前版本的pending和wq_data的复合体,起到了以前的pending和wq_data的作用。
func的参数是一个work_struct指针,指向的数据就是定义func的work_struct。
看到这里,会有两个疑问,第一,如何把用户的数据作为参数传递给func呢?以前有void *data来作为参数,现在好像完全没有办法做到;第二,如何实现延迟工作?目前版本的work_struct并没有定义timer。
解决第一个问题,需要换一种思路。2.6.20版本之后使用工作队列需要把work_struct定义在用户的数据结构中,然后通过container_of来得到用户数据。具体用法可以参考稍后的实作。
对于第二个问题,新的工作队列把timer拿掉的用意是使得work_struct更加单纯。首先回忆一下之前版本,只有在需要延迟执行工作时才会用到timer,普通情况下timer是没有意义的,所以之前的做法在一定程度上有些浪费资源。所以新版本中,将timer从work_struct中拿掉,然后又定义了一个新的结构delayed_work用于处理延迟执行:
struct delayed_work {
struct work_struct work;
struct timer_list timer;
};
下面把API罗列一下,每个函数的解释可参考之前版本的介绍或者之后的实作:
1) INIT_WORK(struct work_struct *work, work_func_t func)
2) INIT_DELAYED_WORK(struct delayed_work *work, work_func_t func)
3) int schedule_work(struct work_struct *work)
4) int schedule_delayed_work(struct delayed_work *work, unsigned long delay)
5) struct workqueue_struct *create_workqueue(const char *name)
6) int queue_work(struct workqueue_struct *wq, struct work_struct *work)
7) int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay)
8) void flush_scheduled_work(void)
9) void flush_workqueue(struct workqueue_struct *wq)
10) int cancel_delayed_work(struct delayed_work *work)
11) void destroy_workqueue(struct workqueue_struct *wq)
其中,1), 2), 4) ,7)和以前略有区别,其他用法完全一样。
实作:
struct my_struct_t {
char *name;
struct work_struct my_work;
};
void my_func(struct work_struct *work)
{
struct my_struct_t *my_name = container_of(work, struct my_struct_t, my_work);
printk(KERN_INFO “Hello world, my name is %s!\n”, my_name->name);
}
struct workqueue_struct *my_wq = create_workqueue(“my wq”);
struct my_struct_t my_name;
my_name.name = “Jack”;
INIT_WORK(&(my_name.my_work), my_func);
queue_work(my_wq, &my_work);
destroy_workqueue(my_wq);
相关推荐
- **`thread`**:指向负责执行工作队列中工作的线程的任务结构指针。 - **`run_depth`**:检测`run_workqueue()`函数的递归深度,防止死锁。 另外,`struct workqueue_struct`代表了一个逻辑上的工作队列,它包含了...
Linux操作系统内核中的工作队列(workqueue)是一种机制,用于在后台线程中异步处理非紧急任务,从而避免阻塞系统响应。工作队列是内核调度的一部分,它允许可延迟操作在适当的时间点由工作队列的处理线程执行。下面...
此外,这些工作队列中的任务执行是严格串行的,意味着任务之间的并行处理能力有限,这在多处理器系统中降低了整体效率。 针对这些问题,Linux内核2.6.36引入了受控并发工作队列机制。这个新机制允许内核动态地根据...
很多博客都有深入分析内核队列的工作原理,但是少有能够拿来直接运行的demo,这个demo亲测可用!
一种基于内核定时器和工作队列的Linux rootkit.pdf
- **__kfifo_put**: 向队列中添加数据。如果队列已满,则根据当前可用空间返回实际可写入的数据量。 - **__kfifo_get**: 从队列中取出数据。如果队列为空,则返回实际可读取的数据量。 #### 并发与同步 `kfifo`的...
当资源变得可用时,内核会唤醒等待队列中的某些进程。唤醒的过程会根据不同的情况采取不同的策略。例如,如果资源具有排他性,那么只会唤醒设置了`WQ_FLAG_EXCLUSIVE`标志的进程中的一个。 #### 五、总结 通过...
下面我们将深入探讨队列的数据结构、工作原理以及在Linux中的应用。 队列是一种线性数据结构,其特点是数据元素按照先进先出(First In First Out, FIFO)的规则进行操作。在Linux中,队列通常用于缓存数据、任务...
在使用 wait queue 时,需要首先创建一个等待队列,然后将进程添加到等待队列中。当某个事件发生时,可以使用 wake_up 函数来唤醒等待的进程。 例如,下面的代码演示了如何使用 wait queue 实现设备驱动程序中的...
1. **Linux内核概述**:首先,书中会介绍Linux内核的基本架构,包括内核的模块化设计、进程管理、内存管理和I/O处理机制等,这些都是理解内核工作流程的基础。 2. **进程管理**:深入讲解进程的创建、调度、同步和...
等待队列的使用还包括将进程添加到队列中(add_wait_queue())、从队列中移除(remove_wait_queue())、以及唤醒等待的进程(wake_up())等操作。这些操作都需要在适当的锁保护下进行,以防止并发问题。 总的来说,...
1. Linux内核锁:Linux内核中主要有自旋锁和信号量两种锁机制。自旋锁用于保护短暂的、不会引起阻塞的临界区,而信号量则允许任务在无法获取锁时进入睡眠状态,适合处理可能长时间持有锁的情况。 2. 用户模式与内核...
2. **研究消息的发送和接收**:通过msgsnd和msgrcv函数,了解消息如何被添加到队列、如何从队列中移除以及如何处理满队列或空队列的情况。 3. **分析msgctl操作**:查看如何改变消息队列的属性、删除队列以及进行...
进程间通信(IPC)机制允许不同进程之间共享数据和协调工作,内核提供了多种IPC机制,如信号、管道、消息队列、共享内存和套接字等。 6. 网络协议栈:Linux内核的网络协议栈支持从链路层到应用层的多种网络协议。...
在嵌入式Linux内核中,等待队列是实现进程同步和通信的关键机制,它允许进程在等待某个特定事件发生时进入休眠状态,而当该事件发生时,被唤醒继续执行。等待队列的操作涉及到了进程调度、睡眠与唤醒以及内核的并发...
书中不仅关注内核模块的构建,还涉及驱动程序与内核交互的关键技术,如工作队列、锁机制等。 内核模块是Linux系统中实现设备驱动程序的一种方式,它们是可动态加载和卸载的代码片段。在第一章中,作者详细介绍了...
4. **执行环境(Execution Context)**:工作队列中的任务会在一个称为工作者线程(Worker Thread)的内核线程上下文中执行。这意味着工作函数可以调用用户态函数,使用锁和等待队列,甚至睡眠。 5. **同步机制...
通过阅读《Linux网络体系结构:Linux内核中网络协议的设计与实现》,读者可以深入理解Linux网络的工作原理,这对于系统管理员、开发人员和研究人员来说是不可或缺的知识。书中详细解释了内核源码中的关键部分,并...
拥有写权限的进程可以根据一定规则向队列添加新消息,而具有读权限的进程可以从队列中读取消息。新消息通常被添加到队列的末尾,而读取时不一定从队列头部开始,可以从中部开始读取。消息队列与进程关联但不随进程...
以上这些内容都是《Linux内核设计与实现》第三版可能涵盖的主题,读者可以通过这本书深入了解Linux内核的工作原理,提高对操作系统底层机制的理解。对于系统管理员、软件开发者和计算机科学的学生来说,这是一本极其...