聊一聊Linux中的工作队列2
上一篇文章对工作队列原理以及核心数据结构做了简单介绍,本文重点介绍下workqueue的创建以及worker的管理。
一、工作队列的创建(__alloc_workqueue_key)
struct workqueue_struct *__alloc_workqueue_key(const char *fmt,
unsigned int flags,
int max_active,
struct lock_class_key *key,
const char *lock_name, ...)
{
size_t tbl_size = ;
va_list args;
struct workqueue_struct *wq;
struct pool_workqueue *pwq; /* allocate wq and format name */
if (flags & WQ_UNBOUND)
tbl_size = wq_numa_tbl_len * sizeof(wq->numa_pwq_tbl[]);
/*分配workqueue_struct结构*/
wq = kzalloc(sizeof(*wq) + tbl_size, GFP_KERNEL);
if (!wq)
return NULL; if (flags & WQ_UNBOUND) {
wq->unbound_attrs = alloc_workqueue_attrs(GFP_KERNEL);
if (!wq->unbound_attrs)
goto err_free_wq;
}
/*格式化名称*/
va_start(args, lock_name);
vsnprintf(wq->name, sizeof(wq->name), fmt, args);
va_end(args); max_active = max_active ?: WQ_DFL_ACTIVE;
max_active = wq_clamp_max_active(max_active, flags, wq->name); /* init wq */
wq->flags = flags;
wq->saved_max_active = max_active;
mutex_init(&wq->mutex);
atomic_set(&wq->nr_pwqs_to_flush, );
INIT_LIST_HEAD(&wq->pwqs);
INIT_LIST_HEAD(&wq->flusher_queue);
INIT_LIST_HEAD(&wq->flusher_overflow);
INIT_LIST_HEAD(&wq->maydays); lockdep_init_map(&wq->lockdep_map, lock_name, key, );
INIT_LIST_HEAD(&wq->list);
if (alloc_and_link_pwqs(wq) < )
goto err_free_wq;
/*
* Workqueues which may be used during memory reclaim should
* have a rescuer to guarantee forward progress.
*/
if (flags & WQ_MEM_RECLAIM) {
struct worker *rescuer; rescuer = alloc_worker();
if (!rescuer)
goto err_destroy; rescuer->rescue_wq = wq;
rescuer->task = kthread_create(rescuer_thread, rescuer, "%s",
wq->name);
if (IS_ERR(rescuer->task)) {
kfree(rescuer);
goto err_destroy;
} wq->rescuer = rescuer;
rescuer->task->flags |= PF_NO_SETAFFINITY;
wake_up_process(rescuer->task);
} if ((wq->flags & WQ_SYSFS) && workqueue_sysfs_register(wq))
goto err_destroy;
/*
* wq_pool_mutex protects global freeze state and workqueues list.
* Grab it, adjust max_active and add the new @wq to workqueues
* list.
*/
mutex_lock(&wq_pool_mutex);
mutex_lock(&wq->mutex);
for_each_pwq(pwq, wq)
pwq_adjust_max_active(pwq);
mutex_unlock(&wq->mutex);
list_add(&wq->list, &workqueues);
mutex_unlock(&wq_pool_mutex);
return wq;
err_free_wq:
free_workqueue_attrs(wq->unbound_attrs);
kfree(wq);
return NULL;
err_destroy:
destroy_workqueue(wq);
return NULL;
}
该函数主要任务就是通过kzalloc分配一个workqueue_struct结构,然后格式化一个名称,对workqueue进行简单初始化;’接着就调用 和pwd建立关系。我们暂且不考虑WQ_MEM_RECLAIM的情况,那么该函数主要就完成这两个功能。所有的workqueue会链接成一个链表,链表头是 一个全局静态变量
static LIST_HEAD(workqueues); /* PL: list of all workqueues */
本函数比较重要的就是和pwq建立关系了
static int alloc_and_link_pwqs(struct workqueue_struct *wq)
{
bool highpri = wq->flags & WQ_HIGHPRI;
int cpu;
/*如果是普通的work_queue*/
if (!(wq->flags & WQ_UNBOUND)) {
/*为每个CPU 分配pool_workqueue--pwq*/
wq->cpu_pwqs = alloc_percpu(struct pool_workqueue);
if (!wq->cpu_pwqs)
return -ENOMEM;
/*把pwd和wq链接*/
for_each_possible_cpu(cpu) {
struct pool_workqueue *pwq =
per_cpu_ptr(wq->cpu_pwqs, cpu);
struct worker_pool *cpu_pools =
per_cpu(cpu_worker_pools, cpu); init_pwq(pwq, wq, &cpu_pools[highpri]); mutex_lock(&wq->mutex);
link_pwq(pwq);
mutex_unlock(&wq->mutex);
}
return ;
} else {
return apply_workqueue_attrs(wq, unbound_std_wq_attrs[highpri]);
}
}
这里先知考虑普通的workqueue,不考虑WQ_UNBOUND。函数通过alloc_percpu为workqueue分配了pool_workqueue变量,然后通过for_each_possible_cpu,对每个CPU进行处理,实际上就是把对应的pool_workqueue和worker_pool通过init_pwq关联起来。如上一篇文章所描述的,worker_pool分为两种:普通的和高优先级的。普通的为第0项,而高优先级的为第一项。建立关联后在通过link_pwq把pwq接入wq的链表中。
二、worker的创建
在创建好workqueue和对应的pwq以及worker_pool后,需要显示的为worker_pool创建worker。核心函数为create_and_start_worker
static int create_and_start_worker(struct worker_pool *pool)
{
struct worker *worker; mutex_lock(&pool->manager_mutex);
/*创建一个属于pool的worker,实际上是创建一个线程*/
worker = create_worker(pool);
if (worker) {
spin_lock_irq(&pool->lock);
/*启动worker,即唤醒线程*/
start_worker(worker);
spin_unlock_irq(&pool->lock);
} mutex_unlock(&pool->manager_mutex);
return worker ? : -ENOMEM;
}
注意这里是针对worker_pool创建worker,所以worker_pool作为参数传递进来,而具体执行创建任务的是create_worker函数,且由于有专门的worker manager,故这里给worker_pool增加worker需要加锁。
create_worker函数其实也不复杂,核心任务主要包含以下几个步骤:
- 通过alloc_worker分配一个worker结构,并执行简单的初始化
- 在worker和worker_pool之间建立联系
- 通过kthread_create_on_node创建工作线程,处理函数为worker_thread
- 设置线程优先级
初始状态下是为每个worker_pool创建一个worker。创建好之后通过start_worker启动worker
static void start_worker(struct worker *worker)
{
worker->flags |= WORKER_STARTED;
worker->pool->nr_workers++;
worker_enter_idle(worker);
wake_up_process(worker->task);
}
该函数较简单,首先就更新worker状态为WORKER_STARTED,增加pool中worker统计量;然后通过worker_enter_idle标记worker目前处于idle状态;最后通过wake_up_process唤醒worker。我们看下中间设置idle状态的过程
static void worker_enter_idle(struct worker *worker)
{
struct worker_pool *pool = worker->pool; if (WARN_ON_ONCE(worker->flags & WORKER_IDLE) ||
WARN_ON_ONCE(!list_empty(&worker->entry) &&
(worker->hentry.next || worker->hentry.pprev)))
return; /* can't use worker_set_flags(), also called from start_worker() */
worker->flags |= WORKER_IDLE;
pool->nr_idle++;
worker->last_active = jiffies; /* idle_list is LIFO */
list_add(&worker->entry, &pool->idle_list); if (too_many_workers(pool) && !timer_pending(&pool->idle_timer))
mod_timer(&pool->idle_timer, jiffies + IDLE_WORKER_TIMEOUT); /*
* Sanity check nr_running. Because wq_unbind_fn() releases
* pool->lock between setting %WORKER_UNBOUND and zapping
* nr_running, the warning may trigger spuriously. Check iff
* unbind is not in progress.
*/
WARN_ON_ONCE(!(pool->flags & POOL_DISASSOCIATED) &&
pool->nr_workers == pool->nr_idle &&
atomic_read(&pool->nr_running));
}
该函数会设置WORKER_IDLE,递增pool的nr_idle计数,然后更新last_active为当前jiffies。接着把worker挂入pool的idle_list的链表头.默认状态下,一个worker在idle状态停留的最长时IDLE_WORKER_TIMEOUT,超过这个时间就要启用管理工作。这里的last_active便是纪录进入idle状态的时间,
三、worker的管理
系统中会根据实际对worker的需要,动态的增删worker。针对idle worker,worker_pool中有个定时器idle_timer,其处理函数为idle_worker_timeout,看下该处理函数
static void idle_worker_timeout(unsigned long __pool)
{
struct worker_pool *pool = (void *)__pool; spin_lock_irq(&pool->lock); if (too_many_workers(pool)) {
struct worker *worker;
unsigned long expires; /* idle_list is kept in LIFO order, check the last one ,即最先挂入链表的*/
worker = list_entry(pool->idle_list.prev, struct worker, entry);
expires = worker->last_active + IDLE_WORKER_TIMEOUT;
/*idleworker每次最多保持idle状态IDLE_WORKER_TIMEOU,当定时器到期时进行检查,如果还未到最长时间,则延迟定时器,否则
*对pool设置管理标志,唤醒线程进行管理
*/
if (time_before(jiffies, expires))
mod_timer(&pool->idle_timer, expires);//重置到期时间
else {
/* it's been idle for too long, wake up manager */
pool->flags |= POOL_MANAGE_WORKERS;
wake_up_worker(pool);
}
} spin_unlock_irq(&pool->lock);
}
该函数主要是针对系统中出现太多worker的情况进行处理,如何判定worker太多呢?too_many_workers去完成,具体就是 nr_idle > 2 && (nr_idle - 2) * MAX_IDLE_WORKERS_RATIO >= nr_busy决定,其中MAX_IDLE_WORKERS_RATIO为4。当的确idle worker太多了的时候,取最先挂入idle链表中的worker,判定其处于idle状态的时间是否超时,即超过IDLE_WORKER_TIMEOUT,如果没有超时,则通过mod_timer修改定时器到期时间为该定时器对应的最长idle时间,否则设置pool的POOL_MANAGE_WORKERS状态,唤醒pool中的first worker执行管理工作。在worker的处理函数worker_thread中,通过need_more_worker判断当前是否需要更多的worker,如果不需要,则直接goto到sleep
sleep:
if (unlikely(need_to_manage_workers(pool)) && manage_workers(worker))
goto recheck;
need_to_manage_workers就是判断POOL_MANAGE_WORKERS,如果设置了该标志则返回真。 管理worker的核心在manage_workers,其中只有两个关键函数
ret |= maybe_destroy_workers(pool);
ret |= maybe_create_worker(pool);
我们只看maybe_destroy_workers
static bool maybe_destroy_workers(struct worker_pool *pool)
{
bool ret = false; while (too_many_workers(pool)) {
struct worker *worker;
unsigned long expires; worker = list_entry(pool->idle_list.prev, struct worker, entry);
expires = worker->last_active + IDLE_WORKER_TIMEOUT; if (time_before(jiffies, expires)) {
mod_timer(&pool->idle_timer, expires);
break;
}
/*删除最先挂入链表的worker*/
destroy_worker(worker);
ret = true;
}
return ret;
}
该函数中会在此通过too_many_workers判断是否有太多worker,如果是,则再次取最后一个worker,检查idle时间,如果没有超时,则修改定时器到期时间,否则通过destroy_worker销毁worker。为什么要这样判断呢?通过对代码的分析,我感觉manage_work不仅负责删除,还负责增加worker,定时器主要是针对idle worker即目的是销毁多余的worker,但是执行管理任务的工作集成到了worker_thread中,因此就worker_thread而言,有可能需要增加、有可能需要删除、还有可能不需要管理。因此这里需要再次判断。
四、work的添加
static inline bool schedule_work(struct work_struct *work)
{
return queue_work(system_wq, work);
} static inline bool queue_work(struct workqueue_struct *wq,
struct work_struct *work)
{
return queue_work_on(WORK_CPU_UNBOUND, wq, work);
}
bool queue_work_on(int cpu, struct workqueue_struct *wq,
struct work_struct *work)
{
bool ret = false;
unsigned long flags;
local_irq_save(flags);
if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
__queue_work(cpu, wq, work);
ret = true;
}
local_irq_restore(flags);
return ret;
}
因此主体就是__queue_work,其中一个核心工作就是调用了insert_work
static void insert_work(struct pool_workqueue *pwq, struct work_struct *work,
struct list_head *head, unsigned int extra_flags)
{
struct worker_pool *pool = pwq->pool; /* we own @work, set data and link */
set_work_pwq(work, pwq, extra_flags);
list_add_tail(&work->entry, head);
get_pwq(pwq); /*
* Ensure either wq_worker_sleeping() sees the above
* list_add_tail() or we see zero nr_running to avoid workers lying
* around lazily while there are works to be processed.
*/
smp_mb();
/*如果需要更多,则唤醒,主要是判断当前是否有正在运行的worker*/
if (__need_more_worker(pool))
wake_up_worker(pool);
}
函数首先调用set_work_pwq把pwd写入到work的data字段,然后把work加入到worker_pool维护的work链表中,在最后判断现在是否需要更多worker,如果需要,则执行唤醒操作。当然是针对当前worker_pool,且唤醒的是worker_pool的第一个worker。其实在queue_work中,为避免work重入,在选定worker_pool的时候会判断该work是否仍在其他worker_pool上运行,如果是,就把该work挂入对应worker_pool的work_list上,
以马内利
参考资料:
LInux 3.10.1源码
聊一聊Linux中的工作队列2的更多相关文章
- 聊一聊Linux中的工作队列
2018-01-18 工作队列是Linux内核中把工作延迟执行的一种手段,其目的不同于软中断,软中断是提高CPU的响应,尽可能的缩短关中断的时间:而工作队列主要目的是节省资源,其比较适合很微小的任务, ...
- 【转】 Linux中的工作队列
原文网址:http://blog.chinaunix.net/uid-20583479-id-1920134.html 工作队列一般用来做滞后的工作,比如在中断里面要做很多事,但是比较耗时,这时就可以 ...
- Linux中的工作队列
工作队列(work queue)是Linux kernel中将工作推后执行的一种机制.这种机制和BH或Tasklets不同之处在于工作队列是把推后的工作交由一个内核线程去执行,因此工作队列的优势就在于 ...
- 浅析Linux中的进程调度
2016-11-22 前面在看软中断的时候,牵扯到不少进程调度的知识,这方面自己确实一直不怎么了解,就趁这个机会好好学习下. 现代的操作系统都是多任务的操作系统,尽管随着科技的发展,硬件的处理器核心越 ...
- linux中的tasklet机制【转】
转自:http://blog.csdn.net/yasin_lee/article/details/12999099 转自: http://www.kerneltravel.net/?p=143 中断 ...
- Linux中断分层--工作队列
1. 工作队列是一种将任务推后执行的方式,它把推后的任务交由一个内核线程去执行.这样中断的下半部会在进程上下文执行,他允许重新调度甚至睡眠.每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队 ...
- 在 Linux 中安装 Oracle JDK 8 以及 JVM 的类加载机制
参考资料 该文中的内容来源于 Oracle 的官方文档 Java SE Tools Reference .Oracle 在 Java 方面的文档是非常完善的.对 Java 8 感兴趣的朋友,可以直接找 ...
- Linux中find常见用法示例
·find path -option [ -print ] [ -exec -ok command ] {} \; find命令的参数: pathname: find命 ...
- Linux中检索文件
1 , Use locate command It is a fast way to find the files location, but if a file just created ,it w ...
随机推荐
- hive 安装记录
http://www.cnblogs.com/linjiqin/archive/2013/03/04/2942402.html
- VS调试DLL项目代码
如果DLL有对应的lib文件,并且dll工程和调用它的exe属于同一个解决方案,直接打断点调试就可以.例如OSG解决方案中的例子程序可以直接调试osgUtil模块的代码. 如果A.exe项目和A.dl ...
- api 25 PopupWindow会占据整个屏幕
解决方法:if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // Android 7.x中,PopupWindow高度为match_pa ...
- vc下项目的头文件包含目录以及库导入预计库目录设置
1.包含目录:include 头文件包含目录设置: project->setting->C/C++->常规: Additional include directories(附加包含目 ...
- 【docker】使用docker 安装 宝塔面板
拉取centos基础镜像,用容器启动该基础镜像,直接在这个容器中部署 1 拉取纯净系统镜像 docker pull centos: 2 启动镜像,映射主机与容器内8888端口 docker run - ...
- python错误 ImportError: No module named setuptools 解决方法[转]
在python运行过程中出现如下错误: python错误:ImportError: No module named setuptools这句错误提示的表面意思是:没有setuptools的模块,说明p ...
- GOOGLE CODE ANDROID 开源项目 集合
转:http://blog.csdn.net/dellheng/article/details/7163333 1. ZXing http://code.google.com/p/zx ...
- 自定义vue全局组件use使用
自定义一个全局Loading组件,并使用:总结目录:|-components |-loading |-index.js 导出组件,并且install |-loading.vue 定义Loading组件 ...
- 腾讯微博API时间线相关接口返回的微博信息中head值使用问题
腾讯微博API时间线相关接口返回的微博信息中head值表示作者头像url,这个链接直接访问并不能使用,需要再附加一个参数指定图片的大小(100.50),比如:[head]/100.
- for,for-each,for-in,for-of,map的比较
参考: 全面解析JavaScript里的循环方法之forEach,for-in,for-of Iterator 和 for...of 循环 JavaScript Array 对象 常规for for循 ...