linux自从引入工作队列之后,越来越多的工作都交给了它,工作队列有什么优点?有进程上下文?可延迟?是的,这些都是它的优点,但是总不能因为它的这些有限的优势而将所有的工作都交给它来做吧,可是驱动或者子系统的开发者却把它当成了银弹,动不动就create_workqueue,然后随便一个什么小小不言的动作就queue_work,结果呢?结果导致了内核空间充斥了大量的工作队列,这些工作队列的本质是每个工作队列(仅限于MT)都会在每一个cpu上创建一个内核线程,实际上没有被queue_work根本就没有机会工作,从而大量的内核线程中只有少量的会投入工作,这就浪费了宝贵的内核内存资源,而且还加重了调度器的调度负担,这还不是最令人遗憾的,试想,如果一些任务对在哪个cpu上执行并不是很在意,但是这些任务凑巧被queue到了同一个cpu上的一个工作队列上,然后该cpu上的该工作队列上的当前工作陷入了sleep,这就导致后续queue的工作无法被执行,因为对于同一个工作队列来讲,其上的工作是严格串行执行的,只有一个工作完成之后,其它的工作才能被执行。本质上,每一个工作队列在每一个cpu上都有且只有一个队列,而其它工作队列的每cpu队列不能互相帮助的,即使它们闲着也不行。这就类似严格的流水线作业,最近看了郎咸平的一本书,他批判了严格的流水线作业,比如上世纪20年代福特和现在富士康使用的那种,这种流水线的每一个环节的工人做的事情很单调,很简单,然而他们很忙,一个请假或离岗将会导致整个流水线停滞断掉,流水线作业没有到达的工人将会闲着而不能休息一下或者做点别的,同时郎咸平赞扬了佳能的“圆桌”,佳能没有使用流水线生产,而是让所有的工人能分成若干组,一组的成员围坐在一个圆桌边上,大家边聊边工作,一个人会所有的工作,即使一个人请假别人也可以帮他完成工作,如果一个人闲着,他可以去接一些别人当前的工作,最后,效率提高了,工人们的工作更轻松了。我们的linux内核的工作队列能否做类似的改进呢?
内核的开发者总是那么的睿智,2.6内核引入了拥有进程上下文的工作队列,2.6.20之后,工作队列的数据结构进行了一次瘦身,管理机构瘦身了,然而工作队列仍然浪费了大量的资源,并且效率不高,总的原因就在于大家各行其是,丝毫没有合作的可能,为了为工作队列彻底减负,内核开发者决定在2.6.36以后将所有的工作队列合并成一个全局的队列,仅仅按照工作重要性和时间紧迫性等做简单的区分,每一类这样的工作仅拥有一个工作队列,而不管具体的工作,也就是说,每一类这样的工作组成一个“圆桌”,这样大家就可以互相帮忙了,不会出现牵一发而动全身的被动局面,也不会出现一个人很闲的情况。 也就是说,新的内核不按照具体工作创建队列,而是按照cpu创建队列,然后在这个每cpu的唯一队列中按照工作的性质做一个简单的区分,这个区分将影响工作被执行的顺序。
新内核中的所有工作被排队到一个global_cwq的每cpu的队列当中,你仍然可以调用create_workqueue创建很多具体的工作队列,然而这样创建的所谓工作队列除了其参数中的flag起作用之外,对排队其中的具体工作没有任何的约束性,所有的工作都排队到了一个每cpu队列中,然后原则上按照排队的顺序进行执行,期间受到排队workqueue的flag进行微调,比如说如果一个工作排入了一个工作队列w1,该w1创建时使用highpri标志,那么它就在queue之后马上执行...新工作队列的另一大特点就是在创建工作队列的时候并不创建工作者内核线程,这些内核线程由内核创建,原则上在内核初始化的时候,每一个cpu将会创建至少一个工作队列线程-worker0,当创建一个工作队列的时候,实际上什么也没有做,当创建一个具体的工作的时候,会将该工作委派给一个工作队列,该工作队列的具体执行者就是worker0,如果有必要(比如每cpu的全局队列中有很多工作要做,比如有很多高优先级的工作要做),内核将会在worker0中创建更多的workerN,由这些worker尽可能并行完成所有的工作,具体我们可以看一下工作队列的执行函数,注意,该内核线程并不依赖任何工作队列,而是在系统初始化的时候被创建的:
static int worker_thread(void *__worker)
{
struct worker *worker = __worker;
struct global_cwq *gcwq = worker->gcwq;
worker->task->flags |= PF_WQ_WORKER;
woke_up:
spin_lock_irq(&gcwq->lock);
...
worker_leave_idle(worker);
recheck:
if (!need_more_worker(gcwq)) //是否需要处理,如果有高优先级的工作,如果工作队列中有工作要做然而该cpu的全局队列中却已经没有空闲处理内核线程,那就有必要处理了
goto sleep;
if (unlikely(!may_start_working(gcwq)) && manage_workers(worker)) //如果需要创建一个新的工作者内核线程,那么就创建一个,该新建的内核线程为了处理更多的工作,内核绝不像以前那样浪费内核资源,只有在有必要的时候才会创建内核线程来处理手头比较紧急的工作。
goto recheck;
...
do {
struct work_struct *work =
list_first_entry(&gcwq->worklist, struct work_struct, entry);
...
} else {
move_linked_works(work, &worker->scheduled, NULL);
process_scheduled_works(worker); //处理一个一个的工作
}
} while (keep_working(gcwq));
worker_set_flags(worker, WORKER_PREP, false);
sleep: //如果当前没有工作要做,那么继续睡眠
if (unlikely(need_to_manage_workers(gcwq)) && manage_workers(worker))
goto recheck;
worker_enter_idle(worker);
__set_current_state(TASK_INTERRUPTIBLE);
spin_unlock_irq(&gcwq->lock);
schedule();
goto woke_up;
}
static void process_one_work(struct worker *worker, struct work_struct *work)
{
...
if (unlikely(gcwq->flags & GCWQ_HIGHPRI_PENDING)) {
struct work_struct *nwork = list_first_entry(&gcwq->worklist,
struct work_struct, entry);
if (!list_empty(&gcwq->worklist) &&
get_work_cwq(nwork)->wq->flags & WQ_HIGHPRI)
wake_up_worker(gcwq); //有必要的话,比如有高优先级的工作,那么唤醒另一个空闲的工作者线程来工作,这样很好,只按照工作性质来处理,而不管它具体属于哪个工作队列
}
if (unlikely(cpu_intensive)) //如果是原始的cpu型工作,那么将按照原始的分时处理,此时有必要唤醒另一个工作者来处理其它工作
worker_set_flags(worker, WORKER_CPU_INTENSIVE, true);
...
f(work); //执行工作
...
}
可以看出,在新的工作队列的体系结构中,在处理具体工作之前,首先看看有没有必要开启另一个工作者线程进行处理,这一切之所以可以被做到的根源在于所有工作队列的工作调度全部属于一个中央机构,而不再由以前的各个工作队列分而治之,不该创建的工作者内核线程可以不创建,内核工作者线程只有在需要的时候才被创建。在新工作队列的体系下如果一个工作队列的一个工作陷入了sleep,那么其它的工作可以被其它的工作者内核线程执行,内核的进程调度器会调度到这个该cpu上“其它”的工作者内核线程的,if (!need_more_worker(gcwq))判断会使得该内核线程继续下去,从而执行别的任务,由于属于所有工作队列的工作都排在一个队列中,那么任何的工作者内核线程都可以执行任意的工作,这就不存在谁阻塞谁的问题了,只要调度器和工作队列的核心逻辑密切配合,只要知道在什么时候该创建一个工作者内核线程就可以了。新工作队列的queue核心代码如下:
static void insert_work(struct cpu_workqueue_struct *cwq,
struct work_struct *work, struct list_head *head,
unsigned int extra_flags)
{
struct global_cwq *gcwq = cwq->gcwq;
set_work_cwq(work, cwq, extra_flags);
list_add_tail(&work->entry, head); //head参数表示的是每cpu一个的全局队列
if (__need_more_worker(gcwq)) //如果是诸如高优先级之类的工作或者当前已经没有空闲的工作者了,那么唤醒一个工作者,系统起码要保持一个空闲的工作者进程以备用。
wake_up_worker(gcwq);
}
就是这样,this is it!如果需要更详细的信息,我觉得内核代码文档中(2.6.36以上)的workqueue.txt是一个不错的选择,另外还有两篇在线文档:http://lwn.net/Articles/403891/;http://lwn.net/Articles/355347/;http://lwn.net/Articles/355700/,它们都很不错的。最关键的,我们不应该用纯技术的观点来改进技术,小的方面说生活中的方方面面都是一致,大的方面说,历史中的很多事实都是相通的,只要我们用这种心理考虑技术,所有的技术创新我们就会发现原来是这么简单。佳能的管理者和linux2.6.36工作队列子系统研发者的思路是一致的,我想我们的工作和生活中还会有很多这样的一致,没准...
现在的女人如此喜欢逛街,我觉得这也许是她们遗留了蒙昧时期食物采集的传统,男人不喜欢逛街,只喜欢瞄准之后买了走人,我觉得这也许也是遗留了蒙昧时期狩猎的传统,因为既然在狩猎,猎物们根本不会给你逛的机会...
linux2.6.36之后对工作队列的改进
最新推荐文章于 2023-07-08 10:10:36 发布