把握linux内核设计思想(五):下半部机制之工作队列及几种机制的选择
工作队列是下半部的第二种将工作推后运行形式。和软中断、tasklet不同,工作队列将工作推后交由一个内核线程去运行,而且该下半部总会在进程上下文中运行。
这样,工作队列同意又一次调度甚至是睡眠。
它创建的这些内核线程称作工作者线程。
工作队列能够让你的驱动程序创建一个专门的工作者线程来处理须要推后的工作。只是,工作队列子系统提供了一个缺省的工作者线程来处理这些工作。因此,工作队列最主要的表现形式就转变成一个把须要推后运行的任务交给特定的通用线程这样一种接口。缺省的工作线程叫做event/n.每一个处理器相应一个线程,这里的n代表了处理器编号。除非一个驱动程序或者子系统必须建立一个属于自己的内核线程。否则不妨使用缺省线程。
# ps x | grep event | grep -v grep
9 ? S 0:00 [events/0]
10 ? S 0:00 [events/1]
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq; //该数组每一项相应系统中的一个处理器
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
}
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;
全部的工作者线程都是使用普通的内核线程实现的,他们都要运行worker_thread()函数。
在它初始化完以后,这个函数运行一个死循环运行一个循环并開始休眠,当有操作被插入到队列的时候,线程就会被唤醒。以便运行这些操作。
当没有剩余的时候,它又会继续休眠。工作由work_struct(位于<kernel/workqueue.c>中)结构表示:
struct work_struct {
atomic_long_t data;
......
struct list_head entry;//连接全部链表
work_func_t func;
.....
};
工作一旦运行完成,它就将对应的work_struct对象从链表上移去,当链表不再有对象时,它就继续休眠。woker_thread()函数例如以下:
static int worker_thread(void *__cwq)
{
struct cpu_workqueue_struct *cwq = __cwq;
DEFINE_WAIT(wait); if (cwq->wq->freezeable)
set_freezable(); for (;;) {
//线程将自己设置为休眠状态并把自己增加等待队列
prepare_to_wait(&cwq->more_work, &wait, TASK_INTERRUPTIBLE);
if (!freezing(current) &&
!kthread_should_stop() &&
list_empty(&cwq->worklist))
schedule();//假设工作对列是空的,线程调用schedule()函数进入睡眠状态
finish_wait(&cwq->more_work, &wait); try_to_freeze(); //假设链表有对象,线程就将自己设为执行态,脱离等待队列
if (kthread_should_stop())
break; //再次调用run_workqueue()执行推后的工作
run_workqueue(cwq);
} return 0;
}
static void run_workqueue(struct cpu_workqueue_struct *cwq)
{
spin_lock_irq(&cwq->lock);
while (!list_empty(&cwq->worklist)) {
//链表不为空时,选取下一个节点对象
struct work_struct *work = list_entry(cwq->worklist.next,
struct work_struct, entry);
//获取希望运行的函数func及其參数data
work_func_t f = work->func;
......
trace_workqueue_execution(cwq->thread, work);
cwq->current_work = work;
//把该结点从链表上解下来
list_del_init(cwq->worklist.next);
spin_unlock_irq(&cwq->lock); BUG_ON(get_wq_data(work) != cwq);
//将待处理标志位pending清0
work_clear_pending(work);
lock_map_acquire(&cwq->wq->lockdep_map);
lock_map_acquire(&lockdep_map);
//运行函数
f(work);
lock_map_release(&lockdep_map);
lock_map_release(&cwq->wq->lockdep_map); ......
spin_lock_irq(&cwq->lock);
cwq->current_work = NULL;
}
spin_unlock_irq(&cwq->lock);
}
能够使用缺省的events任务队列,也能够创建新的工作者线程。
假设须要,函数能够睡眠。
须要注意的是,虽然处理函数执行在进程上下文中,但它不能訪问用户空间,由于内核线程在用户空间没有对应的内存映射。函数原型例如以下:
当然假设不想高速运行,而是想延迟一段时间运行。调用
int schedule_work(struct work_struct *work)
{
return queue_work(keventd_wq, work);
} int schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)
{
return queue_delayed_work(keventd_wq, dwork, delay);
}
因为这些原因。内核提供了一个用于刷新指定工作队列的函数:
取消延迟运行的工作应该调用:int cancel_delayed_work(struct work_struct *work);这个函数能够取消不论什么与work_struct 相关挂起的工作。
#include <linux/init.h>
#include <linux/module.h> #include <linux/workqueue.h> //work_strcut //struct work_struct ws;
struct delayed_work dw; void workqueue_func(struct work_struct *ws) //处理函数
{
printk(KERN_ALERT"Hello, this is shallnet!\n");
} static int __init kwq_init(void)
{
printk(KERN_ALERT"===%s===\n", __func__); //INIT_WORK(&ws, workqueue_func); //建须要推后完毕的工作
//schedule_work(&ws); //调度工作 INIT_DELAYED_WORK(&dw, workqueue_func);
schedule_delayed_work(&dw, 10000); return 0;
} static void __exit kwq_exit(void)
{
printk(KERN_ALERT"===%s===\n", __func__); flush_scheduled_work();
} module_init(kwq_init);
module_exit(kwq_exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("shallnet");
MODULE_DESCRIPTION("blog.csdn.net/shallnet");
上面的操作是使用缺省的工作队列,以下来看一下创建一个新的工作队列是怎样操作的?
struct workqueue_struct *create_workqueue(const char *name);
name是新内核线程的名字。比方缺省events队列的创建是这样使用的:
struct workqueue_struct *keventd_wq;
kevent_wq = create_workqueue("event");
然后调用例如以下函数进行调度:
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *wq,struct delayed_work *work,unsigned long delay);
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h> //work_strcut struct workqueue_struct *sln_wq = NULL;
//struct work_struct ws;
struct delayed_work dw; void workqueue_func(struct work_struct *ws)
{
printk(KERN_ALERT"Hello, this is shallnet!\n");
} static int __init kwq_init(void)
{
printk(KERN_ALERT"===%s===\n", __func__); sln_wq = create_workqueue("sln_wq"); //创建名为sln_wq的工作队列 //INIT_WORK(&ws, workqueue_func);
//queue_work(sln_wq, &ws); INIT_DELAYED_WORK(&dw, workqueue_func); //
queue_delayed_work(sln_wq, &dw, 10000); // return 0;
} static void __exit kwq_exit(void)
{
printk(KERN_ALERT"===%s===\n", __func__); flush_workqueue(sln_wq);
} module_init(kwq_init);
module_exit(kwq_exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("shallnet");
MODULE_DESCRIPTION("blog.csdn.net/shallnet");
当中tasklet基于软中断,而工作队列靠内核线程实现。
假设你须要把任务推后到进程上下文中。或你须要休眠。那就仅仅有使用工作队列了。
把握linux内核设计思想(五):下半部机制之工作队列及几种机制的选择的更多相关文章
- 把握linux内核设计思想系列【转】
转自:http://blog.csdn.net/shallnet/article/details/47734053 版权声明:本文为博主原创文章,未经博主允许不得转载.如果您觉得文章对您有用,请点击文 ...
- 把握linux内核设计思想系列
[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 本专栏分析linux内核的设计实现,包含系统调用.中断.下半部机制.时间管理. ...
- 把握linux内核设计思想(三):下半部机制之软中断
[版权声明:尊重原创.转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 中断处理程序以异步方式执行,其会打断其它重要代码,其执行时该中 ...
- 把握linux内核设计思想(十三):内存管理之进程地址空间
[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet.文章仅供学习交流,请勿用于商业用途] 进程地址空间由进程可寻址的虚拟内存组成,Linux 的虚拟地址空间为0~4G字 ...
- 把握linux内核设计思想(七):内核定时器和定时运行
[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 前面章节说到了把工作推后到除如今以外的时间运行的机制是下半部机 ...
- 把握linux内核设计思想(二):硬中断及中断处理
[版权声明:尊重原创.转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 操作系统负责管理硬件设备.为了使系统和硬件设备的协同工作不减少机器性能.系统和 ...
- 把握linux内核设计思想(十二):内存管理之slab分配器
[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流.请勿用于商业用途] 上一节最后说到对于小内存区的请求,假设採用伙伴系统来进行分配,则会在页内产生非 ...
- Linux内核设计第五周——扒开系统调用三层皮(下)
Linux内核设计第五周 ——扒开系统调用三层皮(下) 一.知识点总结 1.给MenuOS增加新的命令的步骤 更新menu代码到最新版 test.c中main函数里,增加MenuConfig() 增加 ...
- Linux内核设计笔记8——下半部
# 下半部笔记 1. 软中断 软中断实现 软中断是在编译期间静态分配,其结构如下所示,结构中包含一个接受该结构体指针作为参数的action函数. struct softirq_action{ void ...
随机推荐
- 关于django form验证是否用户名已存在
想通过django的Form模块进行数据库中是否已存在用户名的验证,首先我先调用了数据库用户名字段所有的值,发现是个queryset对象. 随后经过查询后发现queryset查询集对象可以调用list ...
- Java-获取Class对象的名称
package com.tj; public class MyClass2 { public static void main(String[] args) { Class cls = java.la ...
- [uiautomator篇]uiwatcher 的使用场景
http://www.yangyanxing.com/article/use-watcher-in-uiautomator.html 今天在uiautomator中实践了watcher的用法,这个 ...
- CF Educational Codeforces Round 21
A. Lucky Year time limit per test 1 second memory limit per test 256 megabytes input standard input ...
- ActionProxy相关实现类
package com.opensymphony.xwork2; import com.opensymphony.xwork2.config.Configuration; import com.ope ...
- HDU-4612 Warm up,tarjan求桥缩点再求树的直径!注意重边
Warm up 虽然网上题解这么多,感觉写下来并不是跟别人竞争访问量的,而是证明自己从前努力过,以后回头复习参考! 题意:n个点由m条无向边连接,求加一条边后桥的最少数量. 思路:如标题,tarjan ...
- POJ-2318 TOYS,暴力+叉积判断!
TOYS 2页的提交记录终于搞明白了. 题意:一个盒子由n块挡板分成n+1块区 ...
- ios弹性头部
很久没写博客了,金天有点时间来写下,一直觉得弹性头部很炫,看起来高大上,写起来蛮简单的 层次分析 一共有3层,最底部是图像层,中间是scrollView或者它的子类,最上层是scrollView上面添 ...
- iOS-runtime-根据协议名调某一个类有与协议里面放的相同的方法
// // ViewController.m // ObserverTrampoline // // Created by Rob Napier on 9/7/11. // Copyright (c) ...
- spring之lazy-init
lazy-init:延迟实例化 ApplicationContext实现的默认行为就是在启动服务器时将所有singleton bean提前进行实例化.提前实例化意味着作为初始化过程的一部分,appli ...