唐山哪里有建设网站的,thinkphp企业网站源码,客户资源管理系统,流量平台排名背景说明Kernel版本#xff1a;4.14ARM64处理器#xff0c;Contex-A53#xff0c;双核使用工具#xff1a;Source Insight 3.5#xff0c; Visio1. 概述Workqueue工作队列是利用内核线程来异步执行工作任务的通用机制#xff1b;Workqueue工作队列可以用作中断处理的Bott… 背景说明Kernel版本4.14ARM64处理器Contex-A53双核使用工具Source Insight 3.5 Visio1. 概述Workqueue工作队列是利用内核线程来异步执行工作任务的通用机制Workqueue工作队列可以用作中断处理的Bottom-half机制利用进程上下文来执行中断处理中耗时的任务因此它允许睡眠而Softirq和Tasklet在处理任务时不能睡眠来一张概述图在中断处理过程中或者其他子系统中调用workqueue的调度或入队接口后通过建立好的链接关系图逐级找到合适的worker最终完成工作任务的执行2. 数据结构2.1 总览此处应有图先看看关键的数据结构work_struct工作队列调度的最小单位work itemworkqueue_struct工作队列work item都挂入到工作队列中workerwork item的处理者每个worker对应一个内核线程worker_poolworker池内核线程池是一个共享资源池提供不同的worker来对work item进行处理pool_workqueue充当桥梁纽带的作用用于连接workqueue和worker_pool建立链接关系下边看看细节吧2.2 workstruct work_struct用来描述work初始化一个work并添加到工作队列后将会将其传递到合适的内核线程来进行处理它是用于调度的最小单位。关键字段描述如下struct work_struct {atomic_long_t data; //低比特存放状态位高比特存放worker_pool的ID或者pool_workqueue的指针struct list_head entry; //用于添加到其他队列上work_func_t func; //工作任务的处理函数在内核线程中回调
#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};
图片说明下data字段2.3 workqueue内核中工作队列分为两种bound绑定处理器的工作队列每个worker创建的内核线程绑定到特定的CPU上运行unbound不绑定处理器的工作队列创建的时候需要指定WQ_UNBOUND标志内核线程可以在处理器间迁移内核默认创建了一些工作队列用户也可以创建system_mq如果work item执行时间较短使用本队列调用schedule[_delayed]_work[_on]()接口就是添加到本队列中system_highpri_mq高优先级工作队列以nice值-20来运行system_long_wq如果work item执行时间较长使用本队列system_unbound_wq该工作队列的内核线程不绑定到特定的处理器上system_freezable_wq该工作队列用于在Suspend时可冻结的work itemsystem_power_efficient_wq该工作队列用于节能目的而选择牺牲性能的work itemsystem_freezable_power_efficient_wq该工作队列用于节能或Suspend时可冻结目的的work itemstruct workqueue_struct关键字段介绍如下struct workqueue_struct {struct list_head pwqs; /* WR: all pwqs of this wq */ //所有的pool_workqueue都添加到本链表中struct list_head list; /* PR: list of all workqueues */ //用于将工作队列添加到全局链表workqueues中struct list_head maydays; /* MD: pwqs requesting rescue */ //rescue状态下的pool_workqueue添加到本链表中struct worker *rescuer; /* I: rescue worker */ //rescuer内核线程用于处理内存紧张时创建工作线程失败的情况struct pool_workqueue *dfl_pwq; /* PW: only for unbound wqs */char name[WQ_NAME_LEN]; /* I: workqueue name *//* hot fields used during command issue, aligned to cacheline */unsigned int flags ____cacheline_aligned; /* WQ: WQ_* flags */struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */ //Per-CPU都创建pool_workqueuestruct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */ //Per-Node创建pool_workqueue...
};
2.4 worker每个worker对应一个内核线程用于对work item的处理worker根据工作状态可以添加到worker_pool的空闲链表或忙碌列表中worker处于空闲状态时并接收到工作处理请求将唤醒内核线程来处理内核线程是在每个worker_pool中由一个初始的空闲工作线程创建的并根据需要动态创建和销毁关键字段描述如下struct worker {/* on idle list while idle, on busy hash table while busy */union {struct list_head entry; /* L: while idle */ //用于添加到worker_pool的空闲链表中struct hlist_node hentry; /* L: while busy */ //用于添加到worker_pool的忙碌列表中};struct work_struct *current_work; /* L: work being processed */ //当前正在处理的workwork_func_t current_func; /* L: current_works fn */ //当前正在执行的work回调函数struct pool_workqueue *current_pwq; /* L: current_works pwq */ //指向当前work所属的pool_workqueuestruct list_head scheduled; /* L: scheduled works */ //所有被调度执行的work都将添加到该链表中/* 64 bytes boundary on 64bit, 32 on 32bit */struct task_struct *task; /* I: worker task */ //指向内核线程struct worker_pool *pool; /* I: the associated pool */ //该worker所属的worker_pool/* L: for rescuers */struct list_head node; /* A: anchored at pool-workers */ //添加到worker_pool-workers链表中/* A: runs through worker-node */...
};
2.5 worker_poolworker_pool是一个资源池管理多个worker也就是管理多个内核线程针对绑定类型的工作队列worker_pool是Per-CPU创建每个CPU都有两个worker_pool对应不同的优先级nice值分别为0和-20针对非绑定类型的工作队列worker_pool创建后会添加到unbound_pool_hash哈希表中worker_pool管理一个空闲链表和一个忙碌列表其中忙碌列表由哈希管理关键字段描述如下struct worker_pool {spinlock_t lock; /* the pool lock */int cpu; /* I: the associated cpu */ //绑定到CPU的workqueue代表CPU IDint node; /* I: the associated node ID */ //非绑定类型的workqueue代表内存Node IDint id; /* I: pool ID */unsigned int flags; /* X: flags */unsigned long watchdog_ts; /* L: watchdog timestamp */struct list_head worklist; /* L: list of pending works */ //pending状态的work添加到本链表int nr_workers; /* L: total number of workers */ //worker的数量/* nr_idle includes the ones off idle_list for rebinding */int nr_idle; /* L: currently idle ones */struct list_head idle_list; /* X: list of idle workers */ //处于IDLE状态的worker添加到本链表struct timer_list idle_timer; /* L: worker idle timeout */struct timer_list mayday_timer; /* L: SOS timer for workers *//* a workers is either on busy_hash or idle_list, or the manager */DECLARE_HASHTABLE(busy_hash, BUSY_WORKER_HASH_ORDER); //工作状态的worker添加到本哈希表中/* L: hash of busy workers *//* see manage_workers() for details on the two manager mutexes */struct worker *manager; /* L: purely informational */struct mutex attach_mutex; /* attach/detach exclusion */struct list_head workers; /* A: attached workers */ //worker_pool管理的worker添加到本链表中struct completion *detach_completion; /* all workers detached */struct ida worker_ida; /* worker IDs for task name */struct workqueue_attrs *attrs; /* I: worker attributes */struct hlist_node hash_node; /* PL: unbound_pool_hash node */ //用于添加到unbound_pool_hash中...
} ____cacheline_aligned_in_smp;
2.6 pool_workqueuepool_workqueue充当纽带的作用用于将workqueue和worker_pool关联起来关键字段描述如下struct pool_workqueue {struct worker_pool *pool; /* I: the associated pool */ //指向worker_poolstruct workqueue_struct *wq; /* I: the owning workqueue */ //指向所属的workqueueint nr_active; /* L: nr of active works */ //活跃的work数量int max_active; /* L: max active works */ //活跃的最大work数量struct list_head delayed_works; /* L: delayed works */ //延迟执行的work挂入本链表struct list_head pwqs_node; /* WR: node on wq-pwqs */ //用于添加到workqueue链表中struct list_head mayday_node; /* MD: node on wq-maydays */ //用于添加到workqueue链表中...
} __aligned(1 WORK_STRUCT_FLAG_BITS);
2.7 小结再来张图首尾呼应一下3. 流程分析3.1 workqueue子系统初始化workqueue子系统的初始化分成两步来完成的workqueue_init_early和workqueue_init。3.1.1 workqueue_init_earlyworkqueue子系统早期初始化函数完成的主要工作包括创建pool_workqueue的SLAB缓存用于动态分配struct pool_workqueue结构为每个CPU都分配两个worker_pool其中的nice值分别为0和HIGHPRI_NICE_LEVEL并且为每个worker_pool从worker_pool_idr中分配一个ID号为unbound工作队列创建默认属性struct workqueue_attrs属性主要描述内核线程的nice值以及cpumask值分别针对优先级以及允许在哪些CPU上执行为系统默认创建几个工作队列这几个工作队列的描述在上文的数据结构部分提及过不再赘述从图中可以看出创建工作队列的接口为alloc_workqueue如下图alloc_workqueue完成的主要工作包括首先当然是要分配一个struct workqueue_struct的数据结构并且对该结构中的字段进行初始化操作前文提到过workqueue最终需要和worker_pool关联起来而这个纽带就是pool_workqueuealloc_and_link_pwqs函数就是完成这个功能1如果工作队列是绑定到CPU上的则为每个CPU都分配pool_workqueue并且初始化通过link_pwq将工作队列与pool_workqueue建立连接2如果工作队列不绑定到CPU上则按内存节点NUMA参考之前内存管理的文章来分配pool_workqueue调用get_unbound_pool来实现它会根据wq属性先去查找如果没有找到相同的就创建一个新的pool_workqueue并且添加到unbound_pool_hash哈希表中最后也会调用link_pwq来建立连接创建工作队列时如果设置了WQ_MEM_RECLAIM标志则会新建rescuer worker对应rescuer_thread内核线程。当内存紧张时新创建worker可能会失败这时候由rescuer来处理这种情况最终将新建好的工作队列添加到全局链表workqueues中3.1.2 workqueue_initworkqueue子系统第二阶段的初始化主要完成的工作是给之前创建好的worker_pool添加一个初始的workercreate_worker函数中创建的内核线程名字为kworker/XX:YY或者kworker/uXX:YY其中XX表示worker_pool的编号YY表示worker的编号u表示unboundworkqueue子系统初始化完成后基本就已经将数据结构的关联建立好了当有work来进行调度的时候就可以进行处理了。3.2 work调度3.2.1 schedule_work以schedule_work接口为例进行分析schedule_work默认是将work添加到系统的system_work工作队列中queue_work_on接口中的操作判断要添加work的标志位如果已经置位了WORK_STRUCT_PENDING_BIT表明已经添加到了队列中等待执行了否则需要调用__queue_work来进行添加。注意了这个操作是在关中断的情况下进行的因为工作队列使用WORK_STRUCT_PENDING_BIT位来同步work的插入和删除操作设置了这个比特后然后才能执行work这个过程可能被中断或抢占打断workqueue的标志位设置了__WQ_DRAINING表明工作队列正在销毁所有的work都要处理完此时不允许再将work添加到队列中有一种特殊情况销毁过程中执行work时又触发了新的work也就是所谓的chained work判断workqueue的类型如果是bound类型根据CPU来获取pool_workqueue如果是unbound类型通过node号来获取pool_workqueueget_work_pool获取上一次执行work的worker_pool如果本次执行的worker_pool与上次执行的worker_pool不一致且通过find_worker_executing_work判断work正在某个worker_pool中的worker中执行考虑到缓存热度放到该worker执行是更合理的选择进而根据该worker获取到pool_workqueue判断pool_workqueue活跃的work数量少于最大限值则将work加入到pool-worklist中否则加入到pwq-delayed_works链表中如果__need_more_worker判断没有worker在执行则唤醒worker内核线程执行总结schedule_work完成的工作是将work添加到对应的链表中而在添加的过程中首先是需要确定pool_workqueuepool_workqueue对应一个worker_pool因此确定了pool_workqueue也就确定了worker_pool进而可以将work添加到工作链表中pool_workqueue的确定分为三种情况1bound类型的工作队列直接根据CPU号获取2unbound类型的工作队列根据node号获取针对unbound类型工作队列pool_workqueue的释放是异步执行的需要判断refcnt的计数值因此在获取pool_workqueue时可能要多次retry3根据缓存热度优先选择正在被执行的worker_pool3.2.2 worker_threadwork添加到工作队列后最终的执行在worker_thread函数中在创建worker时创建内核线程执行函数为worker_threadworker_thread在开始执行时设置标志位PF_WQ_WORKER调度器在进行调度处理时会对task进行判断针对workerqueue worker有特殊处理worker对应的内核线程在没有处理work的时候是睡眠状态当被唤醒的时候跳转到woke_up开始执行woke_up之后如果此时worker是需要销毁的那就进行清理工作并返回。否则离开IDLE状态并进入recheck模块执行recheck部分首先判断是否需要更多的worker来处理如果没有任务处理跳转到sleep地方进行睡眠。有任务需要处理时会判断是否有空闲内核线程以及是否需要动态创建再清除掉worker的标志位然后遍历工作链表对链表中的每个节点调用process_one_worker来处理sleep部分比较好理解没有任务处理时worker进入空闲状态并将当前的内核线程设置成睡眠状态让出CPU总结管理worker_pool的内核线程池时如果有PENDING状态的work并且发现没有正在运行的工作线程(worker_pool-nr_running 0)唤醒空闲状态的内核线程或者动态创建内核线程如果work已经在同一个worker_pool的其他worker中执行不再对该work进行处理work的执行函数为process_one_workerwork可能在同一个CPU上不同的worker中运行直接退出调用worker-current_func()完成最终work的回调函数执行3.3 worker动态管理3.3.1 worker状态机变换worker_pool通过nr_running字段来在不同的状态机之间进行切换worker_pool中有work需要处理时需要至少保证有一个运行状态的worker当nr_running大于1时将多余的worker进入IDLE状态没有work需要处理时所有的worker都会进入IDLE状态执行work时如果回调函数阻塞运行那么会让worker进入睡眠状态此时调度器会进行判断是否需要唤醒另一个workerIDLE状态的worker都存放在idle_list链表中如果空闲时间超过了300秒则会将其进行销毁Running-Suspend当worker进入睡眠状态时如果该worker_pool没有其他的worker处于运行状态那么是需要唤醒一个空闲的worker来维持并发处理的能力Suspend-Running睡眠状态可以通过wake_up_worker来进行唤醒处理最终判断如果该worker不在运行状态则增加worker_pool的nr_running值3.3.2 worker的动态添加和删除动态删除worker_pool初始化时注册了timer的回调函数用于定时对空闲链表上的worker进行处理如果worker太多且空闲时间太长超过了5分钟那么就直接进行销毁处理了动态添加内核线程执行worker_thread函数时如果没有空闲的worker会调用manage_workers接口来创建更多的worker来处理工作参考Documentation/core-api/workqueue.rsthttp://kernel.meizu.com/linux-workqueue.html洗洗睡了收工如果觉得对您有帮助那就点个在看吧谢谢。推荐阅读专辑|Linux文章汇总专辑|程序人生专辑|C语言嵌入式Linux微信扫描二维码关注我的公众号