物联网操作系统AliOS Things探索与实践
上QQ阅读APP看书,第一时间看更新

2.3 工作队列(work queue)

2.3.1 概念

在一个操作系统中,如果要进行一项工作处理,往往需要创建一个任务来加入内核的调度队列。一个任务对应一个处理函数,如果要进行不同的事务处理,则需要创建多个不同的任务。任务作为CPU调度的基础单元,数量越大,则调度成本越高。work queue机制简化了基本的任务创建和处理机制,一个work实体对应一个实体task的处理,work queue下面可以挂接多个work实体,每一个work实体都能对应不同的处理接口,即用户只需要创建一个work queue,就可以完成多个挂接不同处理函数的work queue。

当某些实时性要求较高的任务中,需要进行较繁重钩子(hook)处理时,可以将其处理函数挂接在work queue中,其执行过程将位于work queue的上下文,而不会占用原有任务的处理资源。

另外,work queue还提供了work的延时处理机制,用户可以选择立即执行或是延时处理。

由上可见,在需要创建大量实时性要求不高的任务时,可以使用work queue来统一调度;或者将任务中实时性要求不高的部分处理延后到work queue中处理。如果需要设置延后处理,则需要使用work机制,即用户在创建work时需指定work的延迟执行时间。work机制不支持周期work的处理。

2.3.2 work queue机制原理

work queue的处理依赖于task。一个work queue会创建关联其对应的task,一个work queue会挂载多个work处理,每个work处理对应一个处理函数。当work queue得到调度,即其关联的task得到运行,在每次task的调度期间,都会从work queue中按照先后顺序取出一个work来进行处理。下面是work queue的基本数据结构,具体数据结构对应实际代码。

参考kworkqueue_t的结构体定义:

    typedef struct {
        klist_t     workqueue_node;  /*挂载workqueue列表*/
        klist_t     work_l ist;      /*workqueue下挂载的work列表*/
        kwork_t     *work_current;   /*current work ,正在被处理的work*/
        const name_t *name;
        ktask_t     worker;          /*workqueue关联并创建的任务*/
        ksem_t      sem;             /*workqueue创建并阻塞执行的信号量*/
    }kworkqueue_t;

1.work queue的初始化

初始化函数:void workqueue_init(void)

该函数首先初始化名为g_workqueue_list_head的工作队列链表,该链表将挂接所有的workqueue。同时还通过krhino_workqueue_create接口创建了一个默认的工作队列g_workqueue_default。

2.work queue的创建

函数原型:kstat_t krhino_workqueue_create(kworkqueue_t *workqueue, const name_t *name, uint8_t pri, cpu_stack_t *stack_buf, size_t stack_size)

可以看到,work queue的创建除了基本的管理结构和name外,还需要优先级、栈起始和栈大小。这三个参数用来在work queue内部创建对应的调度任务。另外,该函数还创建了一个信号量sem,初始信号值为0。

该任务将会循环获取此sem信号量,当获取不到时,则该任务永久阻塞;一旦获取信号量,就从work queue中取出一个work来进行处理。

2.3.3 work的创建与触发

1.work的创建

work创建函数原型:kstat_t krhino_work_init(kwork_t *work, work_handle_t handle, void *arg, tick_t dly)

此函数用例创建一个work单元,参数包含处理函数钩子,处理参数,dly表示该work是否需要延时处理。

该接口首先初始化了work内基本的数据结构。当dly大于0时,为了work的延迟执行,还需要创建一个时长为dly的定时器。

2.work的触发

work触发函数原型:kstat_t krhino_work_run(kworkqueue_t *workqueue, kwork_t *work)

该函数的目的是将某个work推送到一个work queue中,并且通过释放workqueue阻塞的信号量来触发work的调度处理。每释放一次信号量,处理一个work。

如果work在创建时,设置的是延迟处理,则启动对应的定时器,将work和work queue句柄传给定时器的处理函数并启动定时器。在定时器处理函数中再将work和work queue挂接,并触发处理机制。

3.work queue资源释放

work queue的资源释放:kstat_t krhino_workqueue_del(kworkqueue_t *workqueue)

该函数首先判断当前work queue中是否存在待处理work,如果存在,则释放失败;释放资源包括work queue关联任务、信号量,并将自身从g_workqueue_list_head队列中删除。

work的资源释放:kstat_t krhino_work_cancel(kwork_t *work)

如果work从未和work queue关联,则只需要释放work->dly>0时所创建的定时器;否则,判断该work是否正在被处理(wq->work_current==work),或者待处理(work->work_exit==1),如果都不是,则从work queue队列中删除该work。

2.3.4 work queue使用样例

work queue一般按照下述方式来使用:

(1)进行模块的初始化,可以直接调用workqueue_init,生成一个默认的workqueue,对应的处理优先级为RHINO_CONFIG_WORKQUEUE_TASK_PRIO。

用户还可以调用krhino_workqueue_create来创建一个自己的work queue,可以直接参考workqueue_init的实现:

    krhino_workqueue_create(&g_workqueue_default, "DEFAULT-WORKQUEUE",
                          RHINO_CONFIG_WORKQUEUE_TASK_PRIO,
                          g_workqueue_stack,
                          RHINO_CONFIG_WORKQUEUE_STACK_SIZE);

其中,g_workqueue_default为队列结构体,“DEFAULT-WORKQUEUE”为任意名字,RHINO_CONFIG_WORKQUEUE_TASK_PRIO为处理优先级,g_workqueue_stack为栈起始地址,RHINO_CONFIG_WORKQUEUE_STACK_SIZE为栈大小。

(2)work创建、触发示例。

    ret=krhino_work_init(&work0 , work0_func, "WORK 0",0); /*初始化一个work,传入
    参数分别为work结构体指针、处理函数、处理函数参数以及是否延时处理*/
        krhino_work_run(&g_workqueue_default, &work0); /*将work推送到workqueue队
    列,并且触发workqueue处理,参数分别为workqueue的指针变量,work的指针变量*/

对于krhino_work_init函数,用户需要执行的处理函数和入参,通过参数2和3传入。如果需要延时执行,则参数4传入延时tick数。

krhino_work_run会将work推送到work queue,并通过触发信号量来触发,如果krhino_work_init设置了延时触发,则通过启动对应的定时器来执行上述触发流程。每触发一次work都需要调用一次krhino_work_run, work被触发后,立即从work queue中删除。

(3)work queue和work的删除。

      ret=krhino_work_cancel(&work0);
      ret=krhino_workqueue_del(&g_workqueue_default);

删除work前,要确保work没有正在或将要被work queue执行,否则会返回错误;

删除work queue需要确保没有待处理或正在处理的work,否则会返回错误。