当前位置: 首页 > 科技观察

workqueue

时间:2023-03-16 17:30:36 科技观察

workqueue是除了softirq和tasklet之外最常用的下半部分机制之一。workqueue的本质是将工作交给一个内核线程,在进程上下文调度的时候执行。由于这个特性,workqueue允许重新调度和休眠。这种异步执行进程上下文可以解决softirq和tasklet执行时间过长导致的系统实时性能下降的问题。当驱动程序有在进程上下文中异步执行的工作任务时,可以使用Work来描述工作任务。将工作添加到一个链表worklist中,然后一个内核线程worker遍历链表,串行执行挂在worklist中的所有工作。如果worklist中没有工作,内核线程worker会变为IDLE;如果有工作,则执行工作中的回调函数。workqueue相关的数据结构workqueue中的几个概念都是和work相关的。数据结构非常混乱。可以这样理解:work_struct:工作。一个工作被初始化并加入工作队列后,会被传递给合适的内核线程进行处理,这是进行调度的最小单位。structwork_struct{atomic_long_tdata;structlist_headentry;work_func_tfunc;#ifdefCONFIG_LOCKDEPstructlockdep_maplockdep_map;#endif};data:低位存储状态位,高位存储worker_poolID或pool_workqueue指针entry:用于添加到其他队列中func:工作任务的处理函数,在内核线程中回调workqueue_struct:工作的集合。workqueue和work是一对多的关系。内核中有两种类型的工作队列:bound:绑定处理器的工作队列,每个worker创建的内核线程绑定在特定的CPU上运行。未绑定:未绑定到处理器的工作队列。创建时需要指定WQ_UNBOUND标志,内核线程可以在处理器之间迁移。structworkqueue_struct{structlist_headpwqs;/*WR:allpwqsofthiswq*/structlist_headlist;/*PR:listofallworkqueues*/structlist_headmaydays;/*MD:pwqsrequestingrescue*/structworker*rescuer;/*I:rescueworker*/structpool_workqueon*q*Punlybound;dfl_pw:/charname[WQ_NAME_LEN];/*I:workqueuename*//*hotfieldsusedduringcommandissue,alignedtocacheline*/unsignedintflags____cacheline_aligned;/*WQ:WQ_*flags*/structpool_workqueue__percpu*cpu_pwqs;/*I:per-cpupwqs*q*q*workqueue_pbl__r/*PWR:unboundpwqsindexedbynode*///每个节点创建pool_workqueue...};pwqs:allpool_workqueuesareaddedtothislinkedlistlist:用于将工作队列加入到全局链表workqueuesmaydays:pool_workqueueinrescuestate添加到该链表rescue:rescicerkernelthread,用于处理工作线程创建失败内存紧张时cpu_pwqs:Per-CPUcreatepool_workqueuenuma_pwq_tbl[]:Per-Nodecreatepool_workqueue之间的关系。workqueue和pool_workqueue是一对多的关系。structpool_workqueue{structworker_pool*pool;/*I:theassociatedpool*/structworkqueue_struct*wq;/*I:theowningworkqueue*/intnr_active;/*L:nrofactiveworks*/intmax_active;/*L:maxactiveworks*/structlist_headdelayed_works;/*L:delayedworks*/structlist_headpwqs_node;/*WR:nodeonwq->pwqs*/structlist_headmayday_node;/*MD:nodeonwq->maydays*///用于添加到工作队列列表...}__aligned(1<workers*///加入worker_pool->workers链表/*A:runsthroughworker->node*/...};entry:用于加入worker_pool的空闲列表hentry:用于加入worker_pool的忙列表current_work:当前正在处理的工作current_func:当前正在执行的工作回调函数current_pwq:指向当前工作所属的pool_workqueueScheduled:allscheduledexecution所有工作都会加入到链表中task:指向内核线程池:worker所属的worker_pool节点:加入到worker_pool->workers链表中可以总结为下图:初始化工作队列的内核在开始初始化时会对工作队列做一些事情,workqueue的初始化包括两个阶段,分别是workqueue_init_early和workqueue_initworkqueue_init_early分配worker_pool,并初始化结构体中的字段分配workqueue_struct,初始化结构体alloc_and_link_pwqs中的字段:分配pool_workqueue,关联workqueue_struct和worker_pool这里workqueue_init的主要工作是在之前创建的worker_pool中添加一个initialworker,然后使用函数create_worker创建一个名为kworker/XX:YY或kworker/uXX:YY的内核线程。其中,XX代表worker_pool数量,YY代表worker数量,u代表unbound。分配worker并初始化结构体中的字段,为worker创建内核线程worker_thread将worker添加到worker_poolWorker进入IDLE状态经过以上两个阶段的初始化,workqueue子系统已经基本建立起数据结构的关联了,当有是按计划工作的,可以处理。使用工作队列内核建议驱动程序开发人员使用默认工作队列而不是创建新工作队列。要使用系统默认的工作队列,首先需要初始化工作,内核提供了相应的宏INIT_WORK。初始化工作#defineINIT_WORK(_work,_func)\__INIT_WORK((_work),(_func),0)#define__INIT_WORK(_work,_func,_onstack)\do{\__init_work((_work),_onstack);\(_work)->data=(atomic_long_t)WORK_DATA_INIT();\INIT_LIST_HEAD(&(_work)->entry);\(_work)->func=(_func);\}while(0)初始化工作后,可以调用shedule_work函数将工作挂入系统默认的工作队列。工作调度将工作添加到系统的system_wq工作队列。判断workqueue的类型,如果是绑定类型,则根据CPU获取pool_workqueue。如果是unbound类型,则通过节点号获取pool_workqueue。确定pool_workqueue中活动作品的数量。如果小于最大限制,则将工作添加到pool->worklist,否则将添加到pwq->delayed_works链表。如果__need_more_worker判断没有worker在执行,则通过wake_up_worker唤醒worker内核线程。worker_threadworker内核线程的执行函数是worker_thread。设置标志位PF_WQ_WORKER,调度器在调度时会对任务进行判断,并对workerqueueworker进行特殊处理。当worker被唤醒后,跳转到woke_up执行。在woke_up中,如果此时需要销毁worker,它会清理并返回。否则,离开IDLE状态,进入recheck模块执行。复核时判断是否需要增加工人加工。如果没有任务处理,则跳转到sleep地方睡觉。如果有任务需要处理,则遍历work链表,为链表中的每个节点调用process_one_work,执行work的回调函数,即INIT_WORK中的回调函数。在睡眠中,当没有任务处理时,worker进入空闲状态,将当前内核线程设置为睡眠状态,让出CPU。总结