在Linux操作系统中,工作队列(work queue)是Linux kernel中将工作推后执行的一种机制。这种机制和BH或Tasklets不同之处在于工作队列是把推后的工作交由一个内核线程去执行,因此工作队列的优势就在于它允许重新调度甚至睡眠。
Nuttx操作系统中工作队列的实现和在Linux中类似,Nuttx在内核中创建了内核线程,用于调度执行工作队列中的任务,在工作队列中允许任务睡眠。
Nuttx中工作队列内核线程有高低两种优先级,高优先级的工作队列用于执行一些优先级比较高的任务,比如将内存buffer中的数据换出到sd卡上。低优先的任务做一些相对不是很紧迫的工作,比如内存片回收。
本节首先介绍工作中队列涉及到的数据结构,然后以高优先级工作队列为例,说明工作队列线程的创建,添加任务到工作队列,工作队列任务处理。
struct hp_wqueue_s
{
systime_t delay; /* Delay between polling cycles (ticks) */
struct dq_queue_s q; /* The queue of pending work */
struct kworker_s worker[1]; /* Describes the single high priority worker */
};
struct lp_wqueue_s
{
systime_t delay; /* Delay between polling cycles (ticks) */
struct dq_queue_s q; /* The queue of pending work */
/* Describes each thread in the low priority queue's thread pool */
struct kworker_s worker[CONFIG_SCHED_LPNTHREADS];
};
注意: 为了展现代码逻辑,文中忽略了对临界资源的保护代码
struct kworker_s
{
pid_t pid; /* The task ID of the worker thread */
volatile bool busy; /* True: Worker is not available */
};
struct work_s
{
struct dq_entry_s dq; /* Implements a doubly linked list */
worker_t worker; /* Work callback */
FAR void *arg; /* Callback argument */
systime_t qtime; /* Time work queued */
systime_t delay; /* Delay until work performed */
};
lp_wqueue_s.q
或者 hp_wqueue_s.q
上。int work_hpstart(void)
{
pid_t pid;
g_hpwork.delay = CONFIG_SCHED_HPWORKPERIOD / USEC_PER_TICK;
dq_init(&g_hpwork.q);
pid = kernel_thread(HPWORKNAME, CONFIG_SCHED_HPWORKPRIORITY,
CONFIG_SCHED_HPWORKSTACKSIZE,
(main_t)work_hpthread,
(FAR char * const *)NULL);
...
g_hpwork.worker[0].pid = pid;
g_hpwork.worker[0].busy = true;
return pid;
}
高优先级的工作队列只创建了一个线程,线程的函数名work_hpthread
,线程的pid记录在g_hpwork.worker[0].pid
中。最后标记该线程为busy状态,即该线程处于睡眠过程中,不能接受任何信号。
static int work_hpthread(int argc, char *argv[])
{
for (; ; )
{
#ifndef CONFIG_SCHED_LPWORK
sched_garbage_collection();
#endif
work_process((FAR struct kwork_wqueue_s *)&g_hpwork, g_hpwork.delay, 0);
}
return OK; /* To keep some compilers happy */
如果没有定义低优先级的工作队列,那么在高优先级的任务中会周期性的调用内存垃圾回收函数,整理内存碎片。
void work_process(FAR struct kwork_wqueue_s *wqueue, systime_t period, int wndx)
这个函数比较长,我们分为两部分看,第一部分是对延迟任务的处理。第二部分是任务线程进入睡眠,由睡眠状态到运行状态的处理方式。
首先看第一部分
next = period;
stick = clock_systimer();
work = (FAR struct work_s *)wqueue->q.head;
while (work)
{
ctick = clock_systimer();
elapsed = ctick - work->qtime;
if (elapsed >= work->delay)
{
(void)dq_rem((struct dq_entry_s *)work, &wqueue->q);
worker = work->worker;
if (worker != NULL)
{
arg = work->arg;
work->worker = NULL;
worker(arg);
flags = enter_critical_section();
work = (FAR struct work_s *)wqueue->q.head;
}
else
{
work = (FAR struct work_s *)work->dq.flink;
}
}
else /* elapsed < work->delay */
{
elapsed += (ctick - stick);
if (elapsed > work->delay)
{
elapsed = work->delay;
}
remaining = work->delay - elapsed;
if (remaining < next)
{
next = remaining;
}
work = (FAR struct work_s *)work->dq.flink;
}
}
再看第二部分:
#if defined(CONFIG_SCHED_LPWORK) && CONFIG_SCHED_LPNTHREADS > 0
if (period == 0)
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGWORK);
wqueue->worker[wndx].busy = false;
DEBUGVERIFY(sigwaitinfo(&set, NULL));
wqueue->worker[wndx].busy = true;
}
else
#endif
{
elapsed = clock_systimer() - stick;
if (elapsed < period && next > 0)
{
remaining = period - elapsed;
next = MIN(next, remaining);
wqueue->worker[wndx].busy = false;
usleep(next * USEC_PER_TICK);
wqueue->worker[wndx].busy = true;
}
}
leave_critical_section(flags);
int work_queue(int qid, FAR struct work_s *work, worker_t worker, FAR void *arg, systime_t delay)
添加任务底层函数为
static void work_qqueue(FAR struct kwork_wqueue_s *wqueue, FAR struct work_s *work, worker_t worker, FAR void *arg, systime_t delay)
{
...
work->worker = worker; /* Work callback. non-NULL means queued */
work->arg = arg; /* Callback argument */
work->delay = delay; /* Delay until work performed */
work->qtime = clock_systimer(); /* Time work queued */
dq_addlast((FAR dq_entry_t *)work, &wqueue->q);
...
}
int work_signal(int qid)
{
...
#ifdef CONFIG_SCHED_HPWORK
if (qid == HPWORK)
{
pid = g_hpwork.worker[0].pid;
}
else
#endif
#ifdef CONFIG_SCHED_LPWORK
if (qid == LPWORK)
{
int i;
for (i = 0; i < CONFIG_SCHED_LPNTHREADS; i++)
{
if (!g_lpwork.worker[i].busy)
{
break;
}
}
if (i >= CONFIG_SCHED_LPNTHREADS)
{
return OK;
}
pid = g_lpwork.worker[i].pid;
}
else
#endif
{
return -EINVAL;
}
ret = kill(pid, SIGWORK);
if (ret < 0)
{
int errcode = errno;
return -errcode;
}
return OK;
}
g_hpwork.worker[0]
结束工作队列中的任务比较简单,将该任务从工作队列中的任务链表中移除,清除任务的回调函数。