2018-01-18node
工做队列是Linux内核中把工做延迟执行的一种手段,其目的不一样于软中断,软中断是提升CPU的响应,尽量的缩短关中断的时间;而工做队列主要目的是节省资源,其比较适合很微小的任务,好比执行某个唤醒工做等。经过建立线程一样能够达到目的,可是线程毕竟有其自身的资源开销如CPU、内存等。若是某个任务很小的话,就不至于建立一个线程,所以Linux内核提供了工做队列这种方式。本文参考内核代码3.10.1版本,而此时的工做队列称为Concurrency Managed Workqueue (cmwq),对于传统的工做队列,本文就不作介绍。数据结构
1、整体描述并发
在详细介绍工做队列前,咱们先看下相关的核心数据结构app
struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func;//工做处理函数 #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
这是工做队列机制暴露给外部(使用方)的工做对象,entry维护该结构在worker_pool中的链表,func是一个函数指针,指向该工做须要执行的处理函数,而data成员从代码还未看出具体做用。一个驱动程序后者内核模块要使用工做队列,建立一个work_struct结构,填充其中的func字段便可,以后调用schedule_work提交给对象便可。关于schedule_work后面咱们在描述,下面开始展开内核对于工做队列的管理。函数
内核中既然把工做队列做为一种资源使用,其天然有其自身的管理规则,所以在内核中涉及到一下对象:this
几个对象之间的关系以下图所示:atom
如前所述,外部使用的意思就是若是要使用工做队列,就是建立好work_struct结构,而后调用schedule_work便可,剩下的处理任务就是系统部分完成了。每一个和外部交互的workqueue_struct,对应有多个pwq(pool_workqueue ),pool_workqueue 连接workqueue_struct和worker_pool的桥梁,worker_pool是核心所在,其包含有全部的worker,以及该pool对应的item即work_struct。其中worker其实就是一个线程,根据busy后者空闲位于hash表或者链表中。而全部的item就经过双链表的方式连接到worker_pool维护的链表头上。spa
2、具体介绍线程
2.1 workqueue(workqueue_struct)debug
该结构是 externally visible workqueue,即外部可见的工做队列,而其自己主要描述队列的属性,既不包含worker也不包含work。一个workqueue对应多个pwd,这些pwq连接在workqueue_struct结构中的pwqs链表头上。而系统中全部的workqueue经过list字段连接成双链表。系统内部已经定义了几个workqueue,以下所示
struct workqueue_struct *system_wq __read_mostly; EXPORT_SYMBOL(system_wq); struct workqueue_struct *system_highpri_wq __read_mostly; EXPORT_SYMBOL_GPL(system_highpri_wq); struct workqueue_struct *system_long_wq __read_mostly; EXPORT_SYMBOL_GPL(system_long_wq); struct workqueue_struct *system_unbound_wq __read_mostly; EXPORT_SYMBOL_GPL(system_unbound_wq); struct workqueue_struct *system_freezable_wq __read_mostly; EXPORT_SYMBOL_GPL(system_freezable_wq);
而通常状况下,系统中经过schedule_work均是把work加入到system_wq中。从代码来看,系统中的workqueue根据使用状况能够分为两种:普通的workqueue和unbound workqueue。前者的worker通常是和CPU绑定的,系统会为每一个CPU建立一个pwd,而针对后者,就不和单个CPU绑定,而是针对NUMA节点,建立pwd。
2.2 worker
worker是具体处理work的对象,系统把worker做为一种资源管理,提出了worker_pool的概念,一个worker一定会属于某个worker_pool,worker结构以下
struct worker { /* on idle list while idle, on busy hash table while busy */ union { struct list_head entry; /* L: while idle */ struct hlist_node hentry; /* L: while busy */ }; struct work_struct *current_work; /* L: work being processed */ work_func_t current_func; /* L: current_work's fn */ struct pool_workqueue *current_pwq; /* L: current_work's pwq */ bool desc_valid; /* ->desc is valid */ struct list_head scheduled; /* L: scheduled works */ /* 64 bytes boundary on 64bit, 32 on 32bit */ struct task_struct *task; /* I: worker task */ struct worker_pool *pool; /* I: the associated pool */ /* L: for rescuers */ unsigned long last_active; /* L: last active timestamp */ unsigned int flags; /* X: flags */ int id; /* I: worker id */ /* * Opaque string set with work_set_desc(). Printed out with task * dump for debugging - WARN, BUG, panic or sysrq. */ char desc[WORKER_DESC_LEN]; /* used only by rescuers to point to the target workqueue */ struct workqueue_struct *rescue_wq; /* I: the workqueue to rescue */ };
一个worker根据自身状态不一样会处于不一样的数据结构中,当worker没有任务要处理就是idle状态,处于worker_pool维护的链表中;当worker在处理任务,就处于worker_pool维护的hash表中。task字段指向该worker对象线程的task_struct结构。pool指向其隶属的worker_pool。而若是该worker是一个rescuer worker,最后一个字段指向其对应的workqueue。当worker在处理任务时,current_work指向正在处理的work,current_func是work的处理函数,current_pwd指向对应的pwq。worker的线程处理函数为worker_thread。
static int worker_thread(void *__worker) { struct worker *worker = __worker; struct worker_pool *pool = worker->pool; /* tell the scheduler that this is a workqueue worker */ worker->task->flags |= PF_WQ_WORKER; woke_up: spin_lock_irq(&pool->lock); /* am I supposed to die? */ if (unlikely(worker->flags & WORKER_DIE)) { spin_unlock_irq(&pool->lock); WARN_ON_ONCE(!list_empty(&worker->entry)); worker->task->flags &= ~PF_WQ_WORKER; return 0; } /*worker只有在执行任务时才是idle状态*/ worker_leave_idle(worker); recheck: /* no more worker necessary? */ if (!need_more_worker(pool)) goto sleep; /* do we need to manage? */ if (unlikely(!may_start_working(pool)) && manage_workers(worker)) goto recheck; /* * ->scheduled list can only be filled while a worker is * preparing to process a work or actually processing it. * Make sure nobody diddled with it while I was sleeping. */ WARN_ON_ONCE(!list_empty(&worker->scheduled)); /* * Finish PREP stage. We're guaranteed to have at least one idle * worker or that someone else has already assumed the manager * role. This is where @worker starts participating in concurrency * management if applicable and concurrency management is restored * after being rebound. See rebind_workers() for details. */ worker_clr_flags(worker, WORKER_PREP | WORKER_REBOUND); do { //从pool中摘下一个work_struct struct work_struct *work = list_first_entry(&pool->worklist, struct work_struct, entry); if (likely(!(*work_data_bits(work) & WORK_STRUCT_LINKED))) { /* optimization path, not strictly necessary */ process_one_work(worker, work); if (unlikely(!list_empty(&worker->scheduled))) process_scheduled_works(worker); } else { move_linked_works(work, &worker->scheduled, NULL); process_scheduled_works(worker); } } while (keep_working(pool)); worker_set_flags(worker, WORKER_PREP, false); sleep: if (unlikely(need_to_manage_workers(pool)) && manage_workers(worker)) goto recheck; /* * pool->lock is held and there's no work to process and no need to * manage, sleep. Workers are woken up only while holding * pool->lock or from local cpu, so setting the current state * before releasing pool->lock is enough to prevent losing any * event. */ /*恢复idle状态*/ worker_enter_idle(worker); __set_current_state(TASK_INTERRUPTIBLE); spin_unlock_irq(&pool->lock); schedule(); goto woke_up; }
从该函数能够看出worker只有在处理任务时,才是idle状态。在执行任务前经过worker_leave_idle把worker从idle链表摘下并清除idle标志。而后会检查当前pool是否须要更多的worker,若是不须要则继续睡眠。怎么判断是否须要呢?这里有一个函数need_more_worker
static bool need_more_worker(struct worker_pool *pool) { /*若是工做者链表不为空且如今没有并发*/ return !list_empty(&pool->worklist) && __need_more_worker(pool); } static bool __need_more_worker(struct worker_pool *pool) { return !atomic_read(&pool->nr_running); }
针对unbound pool,只要存在work,那么该函数就返回true,由于unbound的pool并不计算nr_running。可是从这里看,针对普通的pool,只有在worklist不为空且没有正在运行的worker时才会返回true,那么怎么同时让多个worker同时运行呢??不解!若是确实须要则检查下是否须要管理worker,由于此时须要worker,因此须要判断下有没有idle的worker,若是没有则调用manage_workers进行管理,该函数中两个核心处理函数就是maybe_destroy_workers和maybe_create_worker。待检查事后,就开始具体的处理了,核心逻辑都在一个循环体中。
具体处理过程比较明确,先从pool的worklist中摘下一个work,若是该work没有设置WORK_STRUCT_LINKED标志,就直接调用process_one_work函数进行处理,若是worker->scheduled链表不为空,则调用process_scheduled_works对链表上的work进行处理;若是work设置了WORK_STRUCT_LINKED标志,则须要把work移动到worker的scheduled链表上,而后经过process_scheduled_works进行处理。而循环的条件是keep_working(pool),即只要worklist不为空且在运行的worker数目小于等于1(这里也不太明白,为什么是小于等于1)。处理单个work的流程看process_one_work
该函数一个比较重要的验证就是判断当前work是否已经有别的worker在处理,若是存在则须要把work加入到对应worker的scheduled链表,以免多个worker同时处理同一work;若是没问题就着手开始处理。具体处理过程比较简单,把worker加入到busy的hash表,而后设置worker的相关字段,主要是current_work、current_func和current_pwq。而后把work从链表中删除,以后就执行work的处理函数进行处理。当worker处理完成后,须要把worker从hash表中删除,并把相关字段设置默认值。
process_scheduled_works就比较简单,就是循环对worker中scheduled链表中的work执行处理,具体处理方式就是调用process_one_work。
2.3 worker_pool
顾名思义,worker_pool自己的重要任务就是管理worker,除此以外,worker_pool还管理用户提交的work。在worker_pool中有一个链表头idle_list,连接worker中的entry,对应于空闲的worker;而hash表busy_hash连接worker中的hentry,对应正在执行任务的worker。nr_workers和nr_idle表明worker和idle worker的数量。系统中worker_pool是一个perCPU变量,看下worker_pool的声明
static DEFINE_PER_CPU_SHARED_ALIGNED(struct worker_pool [NR_STD_WORKER_POOLS], cpu_worker_pools);
每一个CPU对应有两个worker_pool,一个针对普通的workqueue,一个针对高优先级workqueue。而PWQ也是perCPU变量,即一个workqueue在每一个CPU上都有对应的pwq,也就有对应的worker_pool。、
下篇文章介绍下workqueue的建立以及worker的管理。
以马内利
参考资料:
LInux内核3.10.1源码