上一篇文章对工作队列原理以及核心数据结构做了简单介绍,本文重点介绍下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的更多相关文章

  1. 聊一聊Linux中的工作队列

    2018-01-18 工作队列是Linux内核中把工作延迟执行的一种手段,其目的不同于软中断,软中断是提高CPU的响应,尽可能的缩短关中断的时间:而工作队列主要目的是节省资源,其比较适合很微小的任务, ...

  2. 【转】 Linux中的工作队列

    原文网址:http://blog.chinaunix.net/uid-20583479-id-1920134.html 工作队列一般用来做滞后的工作,比如在中断里面要做很多事,但是比较耗时,这时就可以 ...

  3. Linux中的工作队列

    工作队列(work queue)是Linux kernel中将工作推后执行的一种机制.这种机制和BH或Tasklets不同之处在于工作队列是把推后的工作交由一个内核线程去执行,因此工作队列的优势就在于 ...

  4. 浅析Linux中的进程调度

    2016-11-22 前面在看软中断的时候,牵扯到不少进程调度的知识,这方面自己确实一直不怎么了解,就趁这个机会好好学习下. 现代的操作系统都是多任务的操作系统,尽管随着科技的发展,硬件的处理器核心越 ...

  5. linux中的tasklet机制【转】

    转自:http://blog.csdn.net/yasin_lee/article/details/12999099 转自: http://www.kerneltravel.net/?p=143 中断 ...

  6. Linux中断分层--工作队列

    1. 工作队列是一种将任务推后执行的方式,它把推后的任务交由一个内核线程去执行.这样中断的下半部会在进程上下文执行,他允许重新调度甚至睡眠.每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队 ...

  7. 在 Linux 中安装 Oracle JDK 8 以及 JVM 的类加载机制

    参考资料 该文中的内容来源于 Oracle 的官方文档 Java SE Tools Reference .Oracle 在 Java 方面的文档是非常完善的.对 Java 8 感兴趣的朋友,可以直接找 ...

  8. Linux中find常见用法示例

    ·find   path   -option   [   -print ]   [ -exec   -ok   command ]   {} \; find命令的参数: pathname: find命 ...

  9. Linux中检索文件

    1 , Use locate command It is a fast way to find the files location, but if a file just created ,it w ...

随机推荐

  1. osgExp只能将3dmax中的动画导出为路径动画osg::AnimationPath,而不能导出osgAnimation::Animation。osg播放骨骼动画应该使用FBX格式

    通过实际的模型测试,导出为.osg文本格式,搜索animation,只能搜索到AnimationPathCallback,而搜索不到osgAnimation相关类 在OSGExp1.5.0源代码中搜索 ...

  2. 在 Linux 使用 GCC 编译C语言共享库

    对任何程序员来说库都是必不可少的.所谓的库是指已经编译好的供你使用的代码.它们常常提供一些通用功能,例如链表和二叉树可以用来保存任何数据,或者是一个特定的功能例如一个数据库服务器的接口,就像MySQL ...

  3. Expected BEGIN_ARRAY but was BEGIN_OBJECT

    Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 3519 path $.data[1].inspector_user Gson 中 ...

  4. python中的数据类型与json的数据类型之间的转化

    什么是json: JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.易于人阅读和编写.同时也易于机器解析和生成.它基于JavaScript Programm ...

  5. JQuery学习的尾声

    今天是最后一天学习JQuery,上周我们在狠狠的学习JavaScript,然后在这周我们又把JQuery扼杀在了摇篮里面,纵然学习的太快我们导致我们知识不牢固,可是我们没有那么多的时间学习的如此详细, ...

  6. cygwin设置NDK环境变量ANDROID_NDK_ROOT

    cygwin安装目录下的“home/当前用户名”的.bash_profile下以UltraEdit(Unix方式)或者eclipse打开,最后添加一句: ANDROID_NDK_ROOT=/cygdr ...

  7. no matching function for call to ‘std::basic_string<char>::assign(std::string&, int)

    使用string中的assign赋值函数报错,代码为: text0.assign(line,i+); 其中text0与line都为string类型 最后发现assign函数的原型为 string &a ...

  8. html2canvas - 解决办法之图片跨域导致的截图空白

    1. 后端支持:图片要是cdn上的地址,并且允许图片跨域,header头中设置应为 Access-Control-Allow-Origin:  * 2. 前端配置 var opts = { scale ...

  9. Elasticsearch学习之深入聚合分析二---案例实战

    以一个家电卖场中的电视销售数据为背景,来对各种品牌,各种颜色的电视的销量和销售额,进行各种各样角度的分析,首先建立电视销售的索引,然后 添加几条销售记录 PUT /tvs { "mappin ...

  10. [工具] 将Sublime Text 3配置为Java代码编辑器

    新建编译器选项 选择菜单栏中的 Tools ——> Build System ——> New Build System ,输入: { "cmd": ["jav ...