• 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. DNS服务器(一)正向解析

    一.DNS简介 在日常生活中人们习惯便用域名访问服务器,但机器间互相只认IP地址,域名与1P地址之间是多对一的关系,一个ip地址不一定只对应一个域名,且一个域名只可以对应一个ip地址,它们之间的转换工 ...

  2. JavaScript实现,判断一个点是否在多边形内(简直nice,希望能帮到你)

    //定义点的结构体 function point(){ this.x=0; this.y=0; } //计算一个点是否在多边形里,参数:点,多边形数组 function PointInPoly(pt, ...

  3. PHP-Audit-Labs-Day1 - in_array函数缺陷

    函数缺陷原理分析 先看一段简单的源代码 class Challenge{ const UPLOAD_DIRECTORY = './solutions/'; private $file; private ...

  4. 9419页最新一线互联网Android面试题解析大全

    网上高级工程师面试相关文章鱼龙混杂,要么一堆内容,要么内容质量太浅, 鉴于此我整理了如下安卓开发高级工程师面试题以及答案帮助大家顺利进阶,下面进入正题: 一.Android相关 1.Activity ...

  5. 多线程案例:龟兔赛跑-Race

    多线程案例:龟兔赛跑-Race 前置条件: 首先来个赛道距离,然后要离终点越来越近 判断比赛是否结束 打印出胜利者 龟兔赛跑开始 故事中是乌龟赢了,兔子需要睡觉,所以我们来模拟兔子睡觉 乌龟赢得比赛 ...

  6. 别再用CSV了,更高效的Python文件存储方案

    CSV无可厚非的是一种良好的通用文件存储方式,几乎任何一款工具或者编程语言都能对其进行读写,但是当文件特别大的时候,CSV这种存储方式就会变得十分缓慢且低效.本文将介绍几种在Python中能够代替CS ...

  7. 【笔记】scikit-learn中的Scaler(归一化)

    scikit-learn中的数据归一化 在机器学习使用数据归一化的时候有一个重要的注意事项 我们对训练数据进行均值和方差的处理,得到mean_train以及std_train,但是在对测试数据进行归一 ...

  8. RPM包方式安装Oracle21c的方法

    RPM包方式安装Oracle21c的方法 前言 北京时间2021.8.14 Oracle发布了最新的数据库版本Oracle21c, Oracle规划不再发布Oracle20c和Oracle22c, 直 ...

  9. Sqli-Labs less13-16

    less-13 首先,输入用户名和密码,发现只有成功和失败两种显示,没有数据回显: 然后我们抓包拿到数据: 我们通过上述观察,已经知道这是典型的盲注,可以采用布尔盲注或者时间盲注. 构造注入语句:un ...

  10. 【LeetCode】80. 删除有序数组中的重复项 II

    80. 删除有序数组中的重复项 II 知识点:数组:排序:双指针: 题目描述 给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 最多出现两次 ,返回删除后数组的新长度. 不要使 ...