把握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 ...
随机推荐
- centos 安装 yum apt
以下地址 http://download.csdn.NET/detail/mimi00x/8081263 执行安装命令 rpm -i rpmforge-release-0.5.3-1.el7.rf.x ...
- 163 AJAX
// 163 AJAX Tab // update 2006.10.18 // 增加鼠标延迟感应特性. // update 2006.10.8 // A 标签 href 属性将保持原有HTML功能.增 ...
- Java面试之基础题---对象Object
参数传递:Java支持两种数据类型:基本数据类型和引用数据类型. 原始数据类型是一个简单的数据结构,它只有一个与之相关的值.引用数据类型是一个复杂的数据结构,它表示一个对象.原始数据类型的变量将该值直 ...
- grunt---grunt_test 测试用例
说明: http://www.gruntjs.net/getting-started --grunt快速入门(创建package.json和Gruntfile.js准备一份新的 Grunt 项目一般需 ...
- 【Luogu】P3380树套树模板(线段树套Splay)
题目链接 幸甚至哉,歌以咏志. 拿下了曾经是那么遥不可及的线段树,学会了曾经高不可攀的平衡树,弄懂了装B的时候才挂在嘴边的树套树. 每道模板都是链上的一颗珠子.把它们挨个串起来,就成为我成长的历程. ...
- centos 7如何配置网络、网卡、ip命令
Linux网络相关配置文件 Linux网络配置相关的文件根据不同的发行版目录名称有所不同,但大同小异,主要有似下目录或文件. (1)/etc/hostname:主要功能在于修改主机名称. (2)/et ...
- 自己写的java返回结果集封装
import java.io.Serializable; import com.fasterxml.jackson.core.JsonProcessingException; import com.f ...
- 16.1112 模拟考试 T1
加密[问题描述]有一种不讲道理的加密方法是: 在字符串的任意位置随机插入字符. 相应的,不讲道理的解密方法就是从字符串中恰好删去随机插入的那些字符.给定原文s和加密后的字符串t,求?有多少子串可以通过 ...
- Andrew Stankevich's Contest (21) J dp+组合数
坑爹的,,组合数模板,,, 6132 njczy2010 1412 Accepted 5572 MS 50620 KB C++ 1844 B 2014-10-02 21:41:15 J - 2-3 T ...
- 自定义header参数时的命名要求
HTTP头是可以包含英文字母([A-Za-z]).数字([0-9]).连接号(-)hyphens, 也可义是下划线(_).在使用nginx的时候应该避免使用包含下划线的HTTP头.主要的原因有以下2点 ...