目录
大多数读者重点可以关注红色部分,想深入了解的需要把结构体梳理。
我对workqueue的结构体是持有吐槽的,这一个机制里的结构体个数简直太多了,各级嵌套来嵌套去,尤其是一堆链表成员,简直比反向映射机制里的结构体还变态。如果你觉得这是一个优点,可以评论发表感想。
work queue的使用
struct work_struct work; //先定义一个工作
void func(struct work_struct *work) {} //工作的内容
INIT_WORK(work, func) //初始工作,func绑定到work中
schedule_work(&work); //kwork线程执行这个工作
上述是一个简单的使用。同中断使用 request_irq一样,一个接口便可加入对应框架。
考虑:
work是如何被执行的
同等待队列一样,工作队列的头是哪个
梳理一下结构体
工作队列结构体描述比较多。不感兴趣的可以跳过。
主体描述对象,和cpu挂钩 ,管理工作池和工作队列,简称pwq
struct pool_workqueue {
struct worker_pool *pool;
struct workqueue_struct *wq;
int nr_active; // 活跃的work
int max_active; // 能放的最大个数的work
struct list_head delayed_works; //当work数 nr_active > max_active,存放到该链表
struct list_head pwqs_node; // 用于挂到wq的pwqs
}
pool_workqueue管理了两个重要对象,一个是workqueue_struct,描述不同的工作队列,简称wq
struct workqueue_struct {
struct list_head pwqs; //存放该wq的所有pwq
struct list_head list; //用于挂到workqueues这个头节点
struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
}
一个是worker_pool,用于存放work_struct,简称pool
struct worker_pool {
int cpu; /* I: the associated cpu */
struct list_head worklist; //存放等待处理的work
struct list_head idle_list; // 新建的workers没被调度前加入idle链表,被调度执行work_thread后从idle链表删除
DECLARE_HASHTABLE(busy_hash, BUSY_WORKER_HASH_ORDER); // 被调度执行的worker加入的链表,执行结束从该链表删除
struct list_head workers; // 存放建立的worker
}
工作队列中的任务描述为work_struct,简称work
struct work_struct {
atomic_long_t data; //后面用于存放pwq
struct list_head entry; //用于挂到pool中的worklist
work_func_t func; //任务要执行的事
};
work会根据pwq中的nr_active来存放到worker_pool的worklist或pwq的delayed_works,nr_active < max_active加入前者,否则加入后者
执行work的对象为worker,简称worker
struct worker {
union {
struct list_head entry; // 用于挂到pool中的idle_list/的节点
struct hlist_node hentry; // 用于挂到pool中的busy_hash的节点
};
struct list_head node; // 用于挂到pool中的workers的节点
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 */
struct task_struct *task; /* I: worker task */
struct worker_pool *pool; /* A: the associated pool */
}
想画图梳理这些关系的,但是太多太乱了。
创建pool
workqueue_init_early函数中,第一个循环,对每个cpu中的NR_STD_WORKER_POOLS个work pool进行初始。
arm64中NR_STD_WORKER_POOLS定义为2,所以每个cpu只有两个pool
#define for_each_cpu_worker_pool(pool, cpu) \
for ((pool) = &per_cpu(cpu_worker_pools, cpu)[0]; \
(pool) < &per_cpu(cpu_worker_pools, cpu)[NR_STD_WORKER_POOLS]; \
(pool)++)
init_worker_pool(pool)
NR_STD_WORKER_POOLS个work pool的优先级是不一样的
int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL };
pool->attrs->nice = std_nice[i++];
其中只有第二个pool定义为高优先级,其余nice都为0.
设置了WQ_HIGHPRI的wq,将会使用到HIGHPRI_NICE_LEVEL,比如system_highpri_wq。具体见wq的创建过程
创建wq和pwq
初始pool后,创建工作队列,并挂到workqueues队列头上。这些工作队列的说明可以在include/linux/workqueue.h的注释中找到。
extern struct workqueue_struct *system_wq; //用于schedule_work函数,耗时短,默认bound
extern struct workqueue_struct *system_highpri_wq; //WQ_HIGHPRI。优先级高
extern struct workqueue_struct *system_long_wq; //耗时长
extern struct workqueue_struct *system_unbound_wq;//WQ_UNBOUND。不指定cpu
kmalloc一个wq空间
static LIST_HEAD(workqueues); //全局变量。所有工作队列的队头
alloc_workqueue:
wq = kzalloc(sizeof(*wq) + tbl_size, GFP_KERNEL);
INIT_LIST_HEAD(&wq->list);
创建pwq,关联wq和pool
对于BOUND类型的wq,比如system_wq,初始其中的成员cpu_pwqs,pwq指向该成员
alloc_and_link_pwqs:
bool highpri = wq->flags & WQ_HIGHPRI; // 用于确定pool的优先级。设置了WQ_HIGHPRI的wq值为1
if (!(wq->flags & WQ_UNBOUND)) {
wq->cpu_pwqs = alloc_percpu(struct pool_workqueue); //per cpu类型
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]); //wq, pwq, pool建立关系 注意选择的pool的优先级
}
}
UNBOUND类型用另一套建立方式,是基于attr。除了类型不同,另一个不同的是pwq的类型,bound类型是基于per cpu的,unbound类型不基于某个cpu。
通俗的说,前者是percpu_alloc,后者是kzalloc。
建立完后,将wq挂到workqueues这个总队头
list_add_tail_rcu(&wq->list, &workqueues);
创建worker
每个cpu中所有pool,创建worker
create_worker:
struct worker *worker = NULL;
worker = alloc_worker(pool->node);
worker->task = kthread_create_on_node(worker_thread, worker, pool->node, "kworker/%s", id_buf);
worker_attach_to_pool(worker, pool);
worker_enter_idle(worker); //先将worker加入pool的idle list
wake_up_process(worker->task); //worker里的线程准备调度,即运行worker_thread
工作队列是以线程形式存在,名字kworker/X类型。
worker是如何执行任务的
worker_thread:
process_one_work:
worker->current_func = work->func;
worker->current_func(work);
如果没有任务要执行,则继续歇息,有任务了,从worklist获取work,执行其中的func。对于绑定cpu的工作队列,当任务多的话,就需要排队等候了,这里真正体现了队列的特点,先进先执行。
任务的添加
将work加入worker
schedule_work(struct work_struct *work)
queue_work(system_wq, work);
queue_work_on(WORK_CPU_UNBOUND, wq, work); 如果work没有设置pending位,则执行
__queue_work(cpu, wq, work);
schedule_work对应的队列是system_wq,类型为bound
if (wq->flags & WQ_UNBOUND) {
if (req_cpu == WORK_CPU_UNBOUND)
cpu = wq_select_unbound_cpu(raw_smp_processor_id()); //unbound类型的,可以挑选cpu
pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu));
} else { //system_wq是bound类型的,就获取当前的cpu
if (req_cpu == WORK_CPU_UNBOUND)
cpu = raw_smp_processor_id();
pwq = per_cpu_ptr(wq->cpu_pwqs, cpu); //找到当前cpu的pwq
}
根据当前工作队列池的负载,选择加入待定工作链表或者延时执行工作链表
if (likely(pwq->nr_active < pwq->max_active)) {
pwq->nr_active++;
worklist = &pwq->pool->worklist;
} else {
work_flags |= WORK_STRUCT_DELAYED;
worklist = &pwq->delayed_works;
}
记录当前的pwq到work的data中,并将work加入worklist,等待被执行。
insert_work(pwq, work, worklist, work_flags);
work_thread检测到worklist不为空的时候,就执行任务。
查看所有工作队列
函数show_workqueue_state可以展示系统中所有的工作队列和工作队列池及状态