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内核学习总结 一 ...
随机推荐
- 零基础涂鸦智能面板SDK开发记录(一)
前言 本人基础背景:在学校学了点JS,在blbl上看过几节node.js视频,现在是一名Android开发工程师,因公司需要学习涂鸦面板SDK开发.说真的除了官方的一些文档外,我真的找不到其他的资料. ...
- Hadoop 3.1.1 - 概述 - 单节点安装
Hadoop: 单节点安装 目标 本文描述了如何安装和配置单机的 Hadoop,这样你可以使用 Hadoop MapReduce 和 Hadoop 分布式文件系统(HDFS)快速地尝试简单的操作. 前 ...
- 单片机学习(一)项目的建立和vscode代码编辑环境的设置
目录 Keil项目的建立 使用vscode进行开发 工欲善其事必先利其器,因此我们先搭建一个比较舒服的开发环境. Keil项目的建立 打开Keil软件点击Project/New uVision Pro ...
- appium自动化测试(4)部分方法&unitest初步使用
捕捉弹窗 https://github.com/appium/appium/issues/968完整有截屏的例子:https://github.com/bitbar/testdroid-samples ...
- Windows影子用户创建与3389连接
#当获得一条shell后,可以创建一个影子用户,通过影子用户可以行驶正常用户的所有权限与功能,并且只可在注册表中被检测出来---(应急响应注册表很重要) 1.首先需要拥有权限创建一个Administr ...
- C++ //继承同名成员处理方式
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 5 class Base 6 { 7 pu ...
- 只要套路对,薪资直接翻一倍!保姆级Android面试葵花宝典,肝完面试犹如开挂
跳槽,这在 IT 互联网圈是非常普遍的,也是让自己升职加薪,走上人生巅峰的重要方式.那么作为一个普通的Android程序猿,我们如何才能斩获大厂offer 呢? 疫情向好.面试在即,还在迷茫踌躇中的后 ...
- Notes about multiboot usb creator
U盘上的多系统启动工具,Windows上YUMI比较好,Ubuntu上MultiSystem用法复杂,unetbootin是另外一款,需要安装p7zip(apt-get install p7zip-f ...
- Nginx配置websocket的安全协议wss
//nginx配置wss访问方式 map $http_upgrade $connection_upgrade { default upgrade; '' close; } upstream webso ...
- 答应我,安装chromedriver,按照版本号,v70就安装v2.42,
下载chromedriver,链接:http://chromedriver.storage.googleapis.com/index.html ----------ChromeDriver v2.42 ...