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内核学习总结 一 ...
随机推荐
- vue 传参动态
方法一: router/index.js { path: '/src/views/activitiesDetails', name: activitiesDetails, component: act ...
- dython:Python数据建模宝藏库
尽管已经有了scikit-learn.statsmodels.seaborn等非常优秀的数据建模库,但实际数据分析过程中常用到的一些功能场景仍然需要编写数十行以上的代码才能实现. 而今天要给大家推荐的 ...
- PTA 朋友圈 (25 分) 代码详解 (并查集)
1.题目要求: 某学校有N个学生,形成M个俱乐部.每个俱乐部里的学生有着一定相似的兴趣爱好,形成一个朋友圈.一个学生可以同时属于若干个不同的俱乐部.根据"我的朋友的朋友也是我的朋友" ...
- AttributeError: module 'numpy' has no attribute 'num'
AttributeError: module 'numpy' has no attribute 'num' 写在前面 总的来说,先看看自己用的计算方式是不是写对了先,多个一起使用的话记得都看看 通过想 ...
- Supervisord远程命令执行漏洞(CVE-2017-11610)复现
Supervisord远程命令执行漏洞(CVE-2017-11610)复现 文章首发在安全客 https://www.anquanke.com/post/id/225451 写在前面 因为工作中遇到了 ...
- 51单片机—使用PWM对直流电机调速
文章目录 - 什么是PWM - PWM是怎么对直流电机进行调速的 - 通过定时器中断实现PWM调速 - 上代码 - 什么是PWM PWM(脉宽调制),是靠改变脉冲宽度来控制输出电压,通过改变周期来控制 ...
- Kafka丢数据、重复消费、顺序消费的问题
面试官:今天我想问下,你觉得Kafka会丢数据吗? 候选者:嗯,使用Kafka时,有可能会有以下场景会丢消息 候选者:比如说,我们用Producer发消息至Broker的时候,就有可能会丢消息 候选者 ...
- 启动Django报错:SyntaxError: Generator expression must be parenthesized 解决办法
这是因为版本不兼容所导致的. 此错误已知与Python问题#32012相关.基于Django 1.11.16及以下的项目将在Python 3.7启动时引发此异常.此问题的补丁已合并到Django 2. ...
- Git在IDEA中的日常使用
1.Git介绍Git对于做开发的小伙伴并不陌生,Git是现在比较流行的版本控制工具.Git的仓库分为本地仓库和远程仓库,当代码开发完成后,先提交(commit)到本地仓库,再推送(push)到远程仓库 ...
- Go优秀开源项目推荐
前言 本文主要是收集Go语言开发的一些优秀项目和框架,个人见识有限肯定还有很多优秀的项目没有收录的,假如大家有好的Go项目可以在文末留言. Go优秀项目收集仓库 GolangFamilygithub ...
