把握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 ...
随机推荐
- !!注意!部署出现the requested resource is not available
避免项目里重复包出现 同时tomcat的lib里避免重复包,也会出现requested resource isnot available!
- C语言内存函数
http://see.xidian.edu.cn/cpp/u/hs3/ 函数 说明 calloc() 分配内存空间 free() 释放内存空间 getpagesize() 取得内存分页大小 mallo ...
- Action的实现方式
[Pojo方式] 1.概述 Pojo(Plain Ordinary Java Object)称为简单Java类,其实就是一个JavaBean. 2.示例 /** * Pojo类方式实现Action * ...
- adb 命令大全
传送门 --> https://github.com/mzlogin/awesome-adb ADB,即 Android Debug Bridge,它是 Android 开发/测试人员不可替代的 ...
- python基础-文件和目录
字符串小练习 >>> s="1a2a3a4a5a" >>> s1=s.split('a') >>> >>> ...
- GET 方法和 POST方法区别
两种 HTTP 请求方法:GET 和 POST 在客户机和服务器之间进行请求-响应时,两种最常被用到的方法是:GET 和 POST. GET - 从指定的资源请求数据. POST - 向指定的资源提交 ...
- conda管理包
清理 conda clean --lock --tarball --package (安装包) 安装或更新包 conda install numpy=1.9.3 conda update numpy= ...
- 自定义Title
xml: <jp.co.view.TitleLayout android:id="@+id/titleLayout" android:layout_width="m ...
- 【Luogu】P3381最小费用最大流模板(SPFA找增广路)
题目链接 哈 学会最小费用最大流啦 思路是这样. 首先我们有一个贪心策略.如果我们每次找到单位费用和最短的一条增广路,那么显然我们可以把这条路添加到已有的流量里去——不管这条路的流量是多大,反正它能 ...
- BZOJ 1009 [HNOI2008]GT考试 ——矩阵乘法 KMP
先用KMP处理所有的转移,或者直接暴力也可以. 然后矩阵快速幂即可. #include <cstdio> #include <cstring> #include <ios ...