Linux内核学习之工作队列
|
|
目录

一、核心数据结构
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内核学习之工作队列的更多相关文章
- Linux 内核学习的经典书籍及途径
from:http://www.zhihu.com/question/19606660 知乎 Linux 内核学习的经典书籍及途径?修改 修改 写补充说明 举报 添加评论 分享 • 邀请回答 ...
- 关于Linux内核学习的误区以及相关书籍介绍
http://www.hzlitai.com.cn/article/ARM9-article/system/1605.html 写给Linux内核新手-关于Linux内核学习的误区 先说句正经的:其实 ...
- linux内核学习之二:编译内核
在linux内核学习系列的第一课中讲述了搭建学习环境的过程(http://www.cnblogs.com/xiongyuanxiong/p/3523306.html),环境搭好后,马上就进入到下一环节 ...
- linux内核学习之一:环境搭建--安装Debian7.3
本系列文章假设读者已对linux有一定的了解,其实学习linux内核不需要有很深的关于linux的知识,只需要了解以下内容:linux基础知识及基本shell命令:现代操作系统的基本概念:C语言和gc ...
- Linux内核学习笔记-2.进程管理
原创文章,转载请注明:Linux内核学习笔记-2.进程管理) By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...
- Linux内核学习笔记-1.简介和入门
原创文章,转载请注明:Linux内核学习笔记-1.简介和入门 By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...
- Linux内核学习趣谈
本文原创是freas_1990,转载请标明出处:http://blog.csdn.net/freas_1990/article/details/9304991 从大二开始学习Linux内核,到现在已经 ...
- Linux 内核学习经验总结
Linux 内核学习经验总结 学习内核,每个人都有自己的学习方法,仁者见仁智者见智.以下是我在学习过程中总结出来的东西,对自身来说,我认为比较有效率,拿出来跟大家交流一下. 内核学习,一偏之见:疏漏难 ...
- Linux内核分析——Linux内核学习总结
马悦+原创作品转载请注明出处+<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux内核学习总结 一 ...
随机推荐
- 背单词(AC自动机+线段树+dp+dfs序)
G. 背单词 内存限制:256 MiB 时间限制:1000 ms 标准输入输出 题目类型:传统 评测方式:文本比较 题目描述 给定一张包含N个单词的表,每个单词有个价值W.要求从中选出一个子序列使 ...
- python中map()和reduce()的使用
map() 会根据提供的函数对指定序列做映射.map(function, iterable, ...)Python 3.x 返回迭代器.print(map()) 返回迭代器地址一般和list一起用 才 ...
- js原始数据类型有哪些,引用数据类型有哪些
js的数据类型划分方式为 原始数据类型和 引用数据类型 栈: 原始数据类型(Undefined,Null,Boolean,Number.String) 堆: 引用数据类型(对象.数组.函数) 两种类型 ...
- 收到字节月薪35k Offer,揭秘面试流程及考点
前段时间,有个朋友又出去面试了,这次他面试目标比较清晰,面的都是业务量大.业务比较核心的部门.前前后后去了不少公司,几家大厂里,他说给他印象最深的是字节. : 组件生命周期初始化、事件系统初始化
vue 源码详解(二): 组件生命周期初始化.事件系统初始化 上一篇文章 生成 Vue 实例前的准备工作 讲解了实例化前的准备工作, 接下来我们继续看, 我们调用 new Vue() 的时候, 其内部 ...
- 删除EFI系统分区(ESP)后Windows无法启动,重建引导分区并修复启动的过程
@ 目录 一.开机故障描述 二.工具: 三.什么是EFI系统分区 四.如何查看EFI系统分区 五.删除后如何重建系统分区 1.建立未分配空间 2.建立ESP分区 3.按下图,ESP分区的大小200M即 ...
- canvas也能实现事件系统????
前言 大家好! 我是热爱图形的fly, 之前在群里和粉丝讨论canvas 如何事件系统, 然后呢? 我自己其实也对这个比较感兴趣, 我看过很多canvas 实现的项目, 比如canvas 实现思维导图 ...
- 【Java笔记】以并发修改异常为例总结的出错解决办法
先来看出错代码: /*需求: 遍历已有集合 如果在集合中发现存在字符串元素"world" 则在"world"后添加元素"javaee" */ ...
- flutter升级2.0
前言 flutter2.0版本带来了很多变化,特别是加入了空安全,升级后的大片报错,让大家望而却步. 现在距2.0发布已经快半年了,大部分插件也支持了空安全,而我们的项目却因为版本低,用不上新东西,所 ...
