把握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 ...
随机推荐
- 关于学习Mongodb的几篇文章
一.Mongodb分片的使用 http://www.caiyiting.com/blog/2014/mongodb-sharding.html 二.MongoDB分布式高可用集群实现 http://w ...
- 【01】国内外git托管平台(总结by魔芋)
[01]国内git托管平台介绍 01, github:代码协作平台,协同开发. 代码托管平台. git:项目版本控制系统 02, 最好的托管方式: github 关闭或小众的托管方式: geakit( ...
- Tinkoff Challenge - Final Round (Codeforces Round #414, rated, Div. 1 + Div. 2) 继续跪一把
这次的前三题挺简单的,可是我做的不快也不对. A. Bank Robbery time limit per test 2 seconds memory limit per test 256 megab ...
- .NET重构(二):ArrayList,List,IList的联系和区别
导读:在机房重构的时候,为了降低耦合,不能返回DataTable型数据,而需要转换为泛型集合.我一直使用的就是IList,那天师傅过来帮我挑错,问我:你为什么一直写IList呢,不应该是List吗?好 ...
- 安装oracle提示swap交换分区太小
1.用dd命令创建一个16G的文件 #dd if=/dev/zero of=/var/swapfile bs=1G count=16 2.将它创建为Linux Swap虚拟交换文件 #mkswap ...
- RDDs基本操作、RDDs特性、KeyValue对RDDs、RDD依赖
摘要:RDD是Spark中极为重要的数据抽象,这里总结RDD的概念,基本操作Transformation(转换)与Action,RDDs的特性,KeyValue对RDDs的Transformation ...
- BZOJ 3850: ZCC Loves Codefires【贪心】
Though ZCC has many Fans, ZCC himself is a crazy Fan of a coder, called "Memset137". It wa ...
- 刷题总结——射箭(bzoj2732)
题目: Description 沫沫最近在玩一个二维的射箭游戏,如下图 1 所示,这个游戏中的 x 轴在地面,第一象限中有一些竖直线段作为靶子,任意两个靶子都没有公共部分,也不会接触坐标轴.沫沫控制一 ...
- 用 Jackson 来处理 JSON
Jackson 是一个 Java 用来处理 JSON 格式数据的类库,性能非常好. 首先创建一个User对象类 (User.java) package com.sivalabs.json; impor ...
- POJ 2096 (dp求期望)
A - Collecting Bugs Time Limit:10000MS Memory Limit:64000KB 64bit IO Format:%I64d & %I64 ...