• Author       : Toney
  • Email         : vip_13031075266@163.com
  • Date          : 2020.12.02
  • Copyright : 未经同意不得转载!!!
  • Version    : Linux-2.6.12
  • Referencehttps://www.linux.org/

目录

双向链表操作函数

一、核心数据结构

1. struct work_struct

struct work_struct {

unsigned long pending;

struct list_head entry; /*将工作节点构成链表*/

void (*func)(void *);   /*延时处理函数*/

void *data;

void *wq_data;

struct timer_list timer;

};

2. struct cpu_workqueue_struct

struct cpu_workqueue_struct {

spinlock_t lock;

long remove_sequence;        /* Least-recently added (next to run) */

long insert_sequence;        /* Next to add */

struct list_head worklist;

wait_queue_head_t more_work;

wait_queue_head_t work_done;

struct workqueue_struct *wq;

task_t *thread;

int run_depth;                /* Detect run_workqueue() recursion depth */

} ____cacheline_aligned;

3. struct workqueue_struct

struct workqueue_struct {

struct cpu_workqueue_struct cpu_wq[NR_CPUS];

const char *name;

struct list_head list;         /* Empty if single thread */

};

4. 这三个数据结构之间的关系

5. 工作队列实现框架(singlethread)

二、创建并初始化工作队列

 

#define create_workqueue(name) __create_workqueue((name), 0)

#define create_singlethread_workqueue(name) __create_workqueue((name), 1)

struct workqueue_struct *__create_workqueue(const char *name,

int singlethread)

{

int cpu, destroy = 0;

struct workqueue_struct *wq;

struct task_struct *p;

BUG_ON(strlen(name) > 10);

/*分配一个workqueue结构,其中包括ncore个cpu_workqueue_struct*/

wq = kmalloc(sizeof(*wq), GFP_KERNEL);

if (!wq)

return NULL;

memset(wq, 0, sizeof(*wq));/*初始化workqueue结构*/

wq->name = name;

/* We don't need the distraction of CPUs appearing and vanishing. */

lock_cpu_hotplug();

if (singlethread) {/*如果为singlethread模式,则只需要创建一个workqueue*/

INIT_LIST_HEAD(&wq->list);/*自成一派,无需加入全局workqueue链表*/

p = create_workqueue_thread(wq, 0);/*只创建一个workqueue线程*/

if (!p)

destroy = 1;

else

wake_up_process(p);/*启动workqueue线程*/

} else {

spin_lock(&workqueue_lock);

list_add(&wq->list, &workqueues);/*加入全局的workqueue链表中*/

spin_unlock(&workqueue_lock);

for_each_online_cpu(cpu) {

p = create_workqueue_thread(wq, cpu);/*为每一个CPU都创建一个workqueue线程*/

if (p) {

kthread_bind(p, cpu);/*为每一个线程绑定特定cpu*/

wake_up_process(p);/*启动线程*/

} else

destroy = 1;

}

}

unlock_cpu_hotplug();

/*

* Was there any error during startup? If yes then clean up:

*/

if (destroy) {/*出现错误,则销毁workqueue*/

destroy_workqueue(wq);

wq = NULL;

}

return wq;

}

工作者线程相关参见下一小节。

三、工作者线程

在创建工作队列时,需要创建对应的工作者线程。那么为什么要创建工作者线程呢?原因是这样的:工作队列主要的目的是:为了简化在内核中创建线程而设计的。此外为了实现延迟任务,需要通过异步手段将此任务交由其他进程处理,而当前进程可以继续处理其他事物。通过工作队列相关的基础设施,人们不必再关心内核中如何创建、维护、销毁内核线程,如何调度任务等,而只需要关心与特定功能相关的事务。下面简单介绍下工作者线程:

/*工作队列管理结构初始化,并创建对应的线程*/

static struct task_struct

*create_workqueue_thread(struct workqueue_struct *wq, int cpu)

{

struct cpu_workqueue_struct *cwq = wq->cpu_wq + cpu;

struct task_struct *p;

spin_lock_init(&cwq->lock);

cwq->wq = wq;

cwq->thread = NULL;

cwq->insert_sequence = 0;

cwq->remove_sequence = 0;

INIT_LIST_HEAD(&cwq->worklist);/*清空工作列表*/

init_waitqueue_head(&cwq->more_work);

init_waitqueue_head(&cwq->work_done);

/*创建工作者线程并初始化为相应的name: name/cpu

*工作者线程的执行体为:worker_thread

*/

if (is_single_threaded(wq))

p = kthread_create(worker_thread, cwq, "%s", wq->name);

else

p = kthread_create(worker_thread, cwq, "%s/%d", wq->name, cpu);

if (IS_ERR(p))

return NULL;

cwq->thread = p;/*工作队列绑定线程*/

return p;

}

static int worker_thread(void *__cwq)

{

struct cpu_workqueue_struct *cwq = __cwq;

DECLARE_WAITQUEUE(wait, current);

struct k_sigaction sa;

sigset_t blocked;

current->flags |= PF_NOFREEZE;

set_user_nice(current, -5);

/* Block and flush all signals */

sigfillset(&blocked);

sigprocmask(SIG_BLOCK, &blocked, NULL);

flush_signals(current);

/* SIG_IGN makes children autoreap: see do_notify_parent(). */

sa.sa.sa_handler = SIG_IGN;

sa.sa.sa_flags = 0;

siginitset(&sa.sa.sa_mask, sigmask(SIGCHLD));

do_sigaction(SIGCHLD, &sa, (struct k_sigaction *)0);

set_current_state(TASK_INTERRUPTIBLE);

while (!kthread_should_stop()) {/*没有其他线程调用kthread_stop关闭此线程*/

add_wait_queue(&cwq->more_work, &wait);

if (list_empty(&cwq->worklist))/*如果任务列表中没有工作任务,则让出CPU*/

schedule();

else

__set_current_state(TASK_RUNNING);

remove_wait_queue(&cwq->more_work, &wait);

if (!list_empty(&cwq->worklist))/*任务列表中有工作任务,则执行任务*/

run_workqueue(cwq);

set_current_state(TASK_INTERRUPTIBLE);

}

__set_current_state(TASK_RUNNING);

return 0;

}

/*工作者线程执行工作链表上的延时任务*/

static inline void run_workqueue(struct cpu_workqueue_struct *cwq)

{

unsigned long flags;

/*

* Keep taking off work from the queue until

* done.

*/

spin_lock_irqsave(&cwq->lock, flags);

cwq->run_depth++;

if (cwq->run_depth > 3) {

/* morton gets to eat his hat */

printk("%s: recursion depth exceeded: %d\n",

__FUNCTION__, cwq->run_depth);

dump_stack();

}

while (!list_empty(&cwq->worklist)) {

struct work_struct *work = list_entry(cwq->worklist.next,

struct work_struct, entry);

void (*f) (void *) = work->func;/*取出任务操作以及参数*/

void *data = work->data;

list_del_init(cwq->worklist.next);/*从worklist中删除任务*/

spin_unlock_irqrestore(&cwq->lock, flags);

BUG_ON(work->wq_data != cwq);

clear_bit(0, &work->pending);/*标识此任务已经被调度执行*/

f(data);/*执行此任务*/

spin_lock_irqsave(&cwq->lock, flags);

cwq->remove_sequence++;

wake_up(&cwq->work_done);

}

cwq->run_depth--;

spin_unlock_irqrestore(&cwq->lock, flags);

}

四、调度一个任务到工作队列中

将任务work_struct添加到工作队列中相对比较简单:只需要将work_struct结构通过struct_listhead链到workqueue的worklist上即可。代码实现详见下表:

int fastcall queue_work(struct workqueue_struct *wq, struct work_struct *work)

{

int ret = 0, cpu = get_cpu();

/*如果work->pending第0位为1,则说明当前任务已经被提交,但尚未执行

* 如果为work->pending第0位为0,表示该任务尚未提交,可以进行提交

*/

if (!test_and_set_bit(0, &work->pending)) {/*返回pending的第0位,并将其置1*/

if (unlikely(is_single_threaded(wq)))

cpu = 0;

BUG_ON(!list_empty(&work->entry));

__queue_work(wq->cpu_wq + cpu, work);/*调度到特定CPU的worklist上*/

ret = 1;

}

put_cpu();

return ret;

}

/* Preempt must be disabled. */

static void __queue_work(struct cpu_workqueue_struct *cwq,

struct work_struct *work)

{

unsigned long flags;

spin_lock_irqsave(&cwq->lock, flags);

work->wq_data = cwq;

list_add_tail(&work->entry, &cwq->worklist);/*将任务添加到工作列表中*/

cwq->insert_sequence++;

wake_up(&cwq->more_work);/*唤醒可能正在睡眠的工作者线程*/

spin_unlock_irqrestore(&cwq->lock, flags);

}

五、销毁工作队列

在销毁工作队列时,如果工作链表worklist中仍然有等待执行的任务,可以有两种操作:①全部丢弃;②全部执行完毕后再销毁工作队列。Linux内核中不同版本处理方式不一致,参考Linux-2.6.12源码,是讲工作链表中的任务全部执行完毕后再销毁工作队列。

void destroy_workqueue(struct workqueue_struct *wq)

{

int cpu;

flush_workqueue(wq);/*确保提交到worklist的任务都全部执行完成*/

/* We don't need the distraction of CPUs appearing and vanishing. */

lock_cpu_hotplug();

if (is_single_threaded(wq))

cleanup_workqueue_thread(wq, 0);/*清除工作者线程*/

else {

for_each_online_cpu(cpu)

cleanup_workqueue_thread(wq, cpu);/*清除工作者线程*/

spin_lock(&workqueue_lock);

list_del(&wq->list);/*从全局工作队列链表上摘除*/

spin_unlock(&workqueue_lock);

}

unlock_cpu_hotplug();

kfree(wq);

}

static void cleanup_workqueue_thread(struct workqueue_struct *wq, int cpu)

{

struct cpu_workqueue_struct *cwq;

unsigned long flags;

struct task_struct *p;

cwq = wq->cpu_wq + cpu;

spin_lock_irqsave(&cwq->lock, flags);

p = cwq->thread;

cwq->thread = NULL;

spin_unlock_irqrestore(&cwq->lock, flags);

if (p)

kthread_stop(p);/*停止工作者线程,此函数会阻塞*/

}

 

六、Linux内核维护的工作队列

内核在启动过程中初始化了一个(可能有多个)工作队列keventd_wq(name为events.)

这个工作队列我们都可以使用,如果不想自己创建单独的工作队列,我们完全可以使用events队列来完成我们的任务。需要说明的是:events可能有很多任务需要处理,因此效率上可能不是很高,因此需要视具体情况而定。events调用接口在上面提供的接口基础上又重新做了封装。

/*内核启动过程中定义的工作队列:kevenetd_wq*/

static struct workqueue_struct *keventd_wq;

void init_workqueues(void)

{

hotcpu_notifier(workqueue_cpu_callback, 0);

keventd_wq = create_workqueue("events");/*创建keventd_wq*/

BUG_ON(!keventd_wq);

}

int fastcall schedule_work(struct work_struct *work)

{

return queue_work(keventd_wq, work);

}

int fastcall schedule_delayed_work(struct work_struct *work, unsigned long delay)

{

return queue_delayed_work(keventd_wq, work, delay);

}

Linux内核学习之工作队列的更多相关文章

  1. Linux 内核学习的经典书籍及途径

    from:http://www.zhihu.com/question/19606660 知乎 Linux 内核学习的经典书籍及途径?修改 修改 写补充说明 举报   添加评论 分享 • 邀请回答   ...

  2. 关于Linux内核学习的误区以及相关书籍介绍

    http://www.hzlitai.com.cn/article/ARM9-article/system/1605.html 写给Linux内核新手-关于Linux内核学习的误区 先说句正经的:其实 ...

  3. linux内核学习之二:编译内核

    在linux内核学习系列的第一课中讲述了搭建学习环境的过程(http://www.cnblogs.com/xiongyuanxiong/p/3523306.html),环境搭好后,马上就进入到下一环节 ...

  4. linux内核学习之一:环境搭建--安装Debian7.3

    本系列文章假设读者已对linux有一定的了解,其实学习linux内核不需要有很深的关于linux的知识,只需要了解以下内容:linux基础知识及基本shell命令:现代操作系统的基本概念:C语言和gc ...

  5. Linux内核学习笔记-2.进程管理

    原创文章,转载请注明:Linux内核学习笔记-2.进程管理) By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...

  6. Linux内核学习笔记-1.简介和入门

    原创文章,转载请注明:Linux内核学习笔记-1.简介和入门 By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...

  7. Linux内核学习趣谈

    本文原创是freas_1990,转载请标明出处:http://blog.csdn.net/freas_1990/article/details/9304991 从大二开始学习Linux内核,到现在已经 ...

  8. Linux 内核学习经验总结

    Linux 内核学习经验总结 学习内核,每个人都有自己的学习方法,仁者见仁智者见智.以下是我在学习过程中总结出来的东西,对自身来说,我认为比较有效率,拿出来跟大家交流一下. 内核学习,一偏之见:疏漏难 ...

  9. Linux内核分析——Linux内核学习总结

    马悦+原创作品转载请注明出处+<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux内核学习总结 一 ...

随机推荐

  1. 大数据学习(20)—— Zookeeper介绍

    ZooKeeper是什么 就像相声大师冯巩每次出场都说:"亲爱的观众朋友们,我想死你们啦"一样,我再强调一次,学习大数据官网很重要.Zookeeper官网看这里ZooKeeper ...

  2. 学习笔记:数学-GCD与LCM-素数筛法

    筛法 埃筛 埃拉托斯特尼筛法的缩写,EraSieve (这个英文其实是为了方便做函数名不要再写shake了) 它的核心思想其实是当确认了一个数是质数以后,把它的所有倍数打上标记说这玩意不是质数.那现在 ...

  3. azure删除ns时一直处于terminating状态

    写个脚本 #!/bin/bash NAMESPACE=corekubectl proxy &kubectl get namespace $NAMESPACE -o json |jq '.spe ...

  4. 以TiDB热点问题来谈Region的调度流程

    什么是热点问题 说这个话题之前我们先回顾一下TiDB的主要结构和概念. TiDB的核心架构分为TiDB.TiKV.PD三个部分,其中TiKV是一个分布式数据存储引擎用来存储真实的数据,在TiKV中又对 ...

  5. Postman API 接口测试、Jenkin 持续集成测试

    Postman 是一个创建和使用API的应用,Postman 对于Web开发者来说非常有用,Postman 带来的便利有很多,诸如: RESTFul接口测试不依赖其他端,进度不受影响 测试脚本即文档, ...

  6. MySQL-04-SQL简单介绍

    SQL介绍 SQL 结构化查询语言 5.7 以后符合SQL92严格模式 通过sql_mode参数来控制 常用SQL分类 DDL:数据定义语言 DCL:数据控制语言 DML:数据操作语言 DQL:数据的 ...

  7. [数据库系列之MySQL] Mysql整体架构浅析一

    一.引言 平时我们在做Java系统时,一般情况下都会连接到一个MySQL数据库上去,执行各种增删改查的语句.大部分的Java工程师对MySQL的了解和掌握程度,大致就停留在这么一个阶段:对MySQL可 ...

  8. 《手把手教你》系列技巧篇(二十一)-java+ selenium自动化测试-浏览器窗口的句柄(详细教程)

    1.简介 今天本来就要分享和讲解三大延时等待的,但是在写作过程中发了问题,会用到这一个知识点,于是就提前介绍一下,以便后边用到了可以更好的理解和掌握.本文就是要介绍如何获得浏览器窗体的句柄或者叫编号, ...

  9. 数据结构与算法-排序(十)桶排序(Bucket Sort)

    摘要 桶排序和基数排序类似,相当于基数排序的另外一种逻辑.它是将取值范围当做创建桶的数量,桶的长度就是序列的大小.通过处理比较元素的数值,把元素放在桶的特定位置,然后遍历桶,就可以得到有序的序列. 逻 ...

  10. 轻松让你的nginx服务器支持HTTP2协议

    目录 简介 HTTP1.1和HTTP2 安装最新的nginx 开启HTTP2支持 添加SSL支持 修改加密算法 Diffie–Hellman对消息进行加密 重定向所有的HTTP请求到HTTPS 启动n ...