【版权声明:尊重原创。转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途】

        工作队列是下半部的第二种将工作推后运行形式。和软中断、tasklet不同,工作队列将工作推后交由一个内核线程去运行,而且该下半部总会在进程上下文中运行。

这样,工作队列同意又一次调度甚至是睡眠。

        所以。假设推后执行的任务须要睡眠。就选择工作队列。假设不须要睡眠。那就选择软中断或tasklet。工作队列是唯一能在进程上下文中执行的下半部实现机制,也仅仅有它才干够睡眠。
        工作队列子系统是一个用于创建内核线程的接口。通过它创建的进程负责运行由内核其它部分排到队列里的任务。

它创建的这些内核线程称作工作者线程。

工作队列能够让你的驱动程序创建一个专门的工作者线程来处理须要推后的工作。只是,工作队列子系统提供了一个缺省的工作者线程来处理这些工作。因此,工作队列最主要的表现形式就转变成一个把须要推后运行的任务交给特定的通用线程这样一种接口。缺省的工作线程叫做event/n.每一个处理器相应一个线程,这里的n代表了处理器编号。除非一个驱动程序或者子系统必须建立一个属于自己的内核线程。否则不妨使用缺省线程。

使用以下命令能够看到默认event工作者线程,每一个处理器相应一个线程:
# ps x | grep event | grep -v grep
9 ? S 0:00 [events/0]
10 ? S 0:00 [events/1]
        工作者线程使用workqueue_struct结构表示(位于<kernel/workqueue.c>中):
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
}
每一个处理器,每一个工作者线程相应相应一个cpu_workqueue_struct结构体(位于<kernel/workqueue.c>中):
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;
        每一个工作者线程类型关联一个自己的workqueue_struct。在该结构体里面,给每一个线程分配一个cpu_workqueue_struct ,因而也就是给每一个处理器分配一个。由于每一个处理器都有一个该类型的工作者线程。

 
      全部的工作者线程都是使用普通的内核线程实现的,他们都要运行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;
}
之后由run_workqueue()函数来完毕实际推后到此的工作:
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);
}
        系统同意有多种类型工作者线程存在,默认情况下内核仅仅有event这一种类型的工作者线程。每一个工作者线程都由一个cpu_workqueue_struct 结构体表示,大部分情况下。驱动程序都使用现存的默认工作者线程。
        工作队列的使用非常easy。

能够使用缺省的events任务队列,也能够创建新的工作者线程。

第一步、创建须要推后完毕的工作。
DECLARE_WORK(name,void (*func)(void *),void *data);        //编译时静态创建
INIT_WORK(struct work_struct *work, void (*func)(void *));    //执行时动态创建
第二步、编写队列处理函数。处理函数会由工作者线程执行,因此。函数会执行在进程上下文中,默认情况下,同意对应中断,而且不持有锁。

假设须要,函数能够睡眠。

须要注意的是,虽然处理函数执行在进程上下文中,但它不能訪问用户空间,由于内核线程在用户空间没有对应的内存映射。函数原型例如以下:

void work_hander(void *data);
第三步、调度工作队列。

调用
schedule_work(&work);
work立即就会被调度。一旦其所在的处理器上的工作者线程被唤醒,它就会被运行。

当然假设不想高速运行,而是想延迟一段时间运行。调用

schedule_delay_work(&work,delay);
delay是要延迟的时间节拍。
默认工作者线程的调度函数事实上就是做了一层封装,降低了 默认工作者线程的參数输入,例如以下:
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);
}
第四步、刷新操作,插入队列的工作会在工作者线程下一次被唤醒的时候运行。有时,在继续下一步工作之前,你必须保证一些操作已经运行完成等等。

因为这些原因。内核提供了一个用于刷新指定工作队列的函数:

void flush_scheduled_work(void);
这个函数会一直等待。直到队列中全部的对象都被运行后才返回。在等待全部待处理的工作运行的时候,该函数会进入休眠状态。所以仅仅能在进程上下文中使用它。须要说明的是,该函数并不取消不论什么延迟运行的工作。

取消延迟运行的工作应该调用: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");

上面的操作是使用缺省的工作队列,以下来看一下创建一个新的工作队列是怎样操作的?

        创建一个新的工作队列和与之对应的工作者线程。方法非常easy,使用例如以下函数:
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);
最后能够调用flush_workqueue(struct workqueue_struct *wq);刷新指定工作队列。
以下为自己定义新的工作队列的演示样例:
#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");
使用ps能够查看到名为sln_wq的工作者线程。
http://download.csdn.net/detail/gentleliu/8941433


        在当前2.6.32版本号中,我们讲了三种下半部机制:软中断、tasklet、工作队列。

当中tasklet基于软中断,而工作队列靠内核线程实现。

        使用软中断必需要确保共享数据的安全。由于同样类别的软中断可能在不同处理器上同一时候运行。在对于时间要求是否严格和运行频率非常高的应用,或准备利用每一处理器上的变量或类型情形,能够考虑使用软中断,如网络子系统。
        tasklet接口简单,能够动态创建,且两个通知类型的tasklet不能同一时候运行。所以实现起来较简单。驱动程序应该尽量选择tasklet而不是软中断。
        工作队列工作于进程上下文,易于使用。因为牵扯到内核线程或上下文的切换。可能开销较大。

假设你须要把任务推后到进程上下文中。或你须要休眠。那就仅仅有使用工作队列了。

把握linux内核设计思想(五):下半部机制之工作队列及几种机制的选择的更多相关文章

  1. 把握linux内核设计思想系列【转】

    转自:http://blog.csdn.net/shallnet/article/details/47734053 版权声明:本文为博主原创文章,未经博主允许不得转载.如果您觉得文章对您有用,请点击文 ...

  2. 把握linux内核设计思想系列

    [版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 本专栏分析linux内核的设计实现,包含系统调用.中断.下半部机制.时间管理. ...

  3. 把握linux内核设计思想(三):下半部机制之软中断

    [版权声明:尊重原创.转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途]         中断处理程序以异步方式执行,其会打断其它重要代码,其执行时该中 ...

  4. 把握linux内核设计思想(十三):内存管理之进程地址空间

    [版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet.文章仅供学习交流,请勿用于商业用途] 进程地址空间由进程可寻址的虚拟内存组成,Linux 的虚拟地址空间为0~4G字 ...

  5. 把握linux内核设计思想(七):内核定时器和定时运行

    [版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途]         前面章节说到了把工作推后到除如今以外的时间运行的机制是下半部机 ...

  6. 把握linux内核设计思想(二):硬中断及中断处理

    [版权声明:尊重原创.转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途] 操作系统负责管理硬件设备.为了使系统和硬件设备的协同工作不减少机器性能.系统和 ...

  7. 把握linux内核设计思想(十二):内存管理之slab分配器

    [版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流.请勿用于商业用途] 上一节最后说到对于小内存区的请求,假设採用伙伴系统来进行分配,则会在页内产生非 ...

  8. Linux内核设计第五周——扒开系统调用三层皮(下)

    Linux内核设计第五周 ——扒开系统调用三层皮(下) 一.知识点总结 1.给MenuOS增加新的命令的步骤 更新menu代码到最新版 test.c中main函数里,增加MenuConfig() 增加 ...

  9. Linux内核设计笔记8——下半部

    # 下半部笔记 1. 软中断 软中断实现 软中断是在编译期间静态分配,其结构如下所示,结构中包含一个接受该结构体指针作为参数的action函数. struct softirq_action{ void ...

随机推荐

  1. centos 安装 yum apt

    以下地址 http://download.csdn.NET/detail/mimi00x/8081263 执行安装命令 rpm -i rpmforge-release-0.5.3-1.el7.rf.x ...

  2. 163 AJAX

    // 163 AJAX Tab // update 2006.10.18 // 增加鼠标延迟感应特性. // update 2006.10.8 // A 标签 href 属性将保持原有HTML功能.增 ...

  3. Java面试之基础题---对象Object

    参数传递:Java支持两种数据类型:基本数据类型和引用数据类型. 原始数据类型是一个简单的数据结构,它只有一个与之相关的值.引用数据类型是一个复杂的数据结构,它表示一个对象.原始数据类型的变量将该值直 ...

  4. grunt---grunt_test 测试用例

    说明: http://www.gruntjs.net/getting-started --grunt快速入门(创建package.json和Gruntfile.js准备一份新的 Grunt 项目一般需 ...

  5. 【Luogu】P3380树套树模板(线段树套Splay)

    题目链接 幸甚至哉,歌以咏志. 拿下了曾经是那么遥不可及的线段树,学会了曾经高不可攀的平衡树,弄懂了装B的时候才挂在嘴边的树套树. 每道模板都是链上的一颗珠子.把它们挨个串起来,就成为我成长的历程. ...

  6. centos 7如何配置网络、网卡、ip命令

    Linux网络相关配置文件 Linux网络配置相关的文件根据不同的发行版目录名称有所不同,但大同小异,主要有似下目录或文件. (1)/etc/hostname:主要功能在于修改主机名称. (2)/et ...

  7. 自己写的java返回结果集封装

    import java.io.Serializable; import com.fasterxml.jackson.core.JsonProcessingException; import com.f ...

  8. 16.1112 模拟考试 T1

    加密[问题描述]有一种不讲道理的加密方法是: 在字符串的任意位置随机插入字符. 相应的,不讲道理的解密方法就是从字符串中恰好删去随机插入的那些字符.给定原文s和加密后的字符串t,求?有多少子串可以通过 ...

  9. 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 ...

  10. 自定义header参数时的命名要求

    HTTP头是可以包含英文字母([A-Za-z]).数字([0-9]).连接号(-)hyphens, 也可义是下划线(_).在使用nginx的时候应该避免使用包含下划线的HTTP头.主要的原因有以下2点 ...