捋一下工作队列workqueue

目录

work queue的使用

梳理一下结构体

创建pool

创建wq和pwq

创建worker

worker是如何执行任务的

任务的添加

查看所有工作队列

大多数读者重点可以关注红色部分,想深入了解的需要把结构体梳理。

我对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可以展示系统中所有的工作队列和工作队列池及状态

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值