Nuttx 工作队列 work queue

来源:互联网 发布:淘宝怎样刷好评 编辑:程序博客网 时间:2024/06/06 11:36

在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];};
  • delay是线程poll的周期。每次当工作队列poll一次工作队列上的任务,睡眠delay时间。如果工作队列的处理方式采用周期性的poll方式,信号可以唤醒工作队列线程,如果线程处在休眠状态。如果工作队列的处理方式采用信号中断的方式,那么只有当工作队列线程收到信号之后才会从睡眠等待中被唤醒,否则一直处于睡眠状态。
  • 链表头q将任务链接在链表上
  • worker数组中的每一项代表一个线程。高优先级工作队列只创建一个线程,低优先级的工作队列至少创建了一个工作线程。

注意: 为了展现代码逻辑,文中忽略了对临界资源的保护代码

工作队列线程

struct kworker_s{  pid_t             pid;    /* The task ID of the worker thread */  volatile bool     busy;   /* True: Worker is not available */};
  • pid记录了工作队列中某个线程的pid
  • 当busy为真时,表明该线程当前处于运行状态,不能接受信号。

工作任务

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 */};
  • 任务(work)链表。一个任务被添加到工作队列中后,连接到lp_wqueue_s.q 或者 hp_wqueue_s.q 上。
  • worker是任务的回调函数,用于执行具体的任务。
  • arg是任务回调函数的参数
  • qtime记录任务插入工作队列的时间
  • delay控制任务插入队列后至少经过delay时间才能运行

创建工作线程

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;}
  • 初始化工作队列的poll周期
  • 初始化工作队列的任务链表头
  • 创建工作队列线程
  • 初始化工作队列中工作所描述的线程的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;        }    }
  • 参数period是工作队列线程期望的poll周期,先赋值给next。next是本次系统真实需要睡眠等待的时间。在第二部分中会用到。
  • stick 记录本次工作处理的开始时间
  • 从工作队列链表中取出第一任务work(如果链表非空),计算任务从被插入链表到当前经过的时间,如果时间超过了任务期望的延迟时间,那么就要赶快处理该任务。因为任务处理中允许任务睡眠,所以当任务处理完之后,我们有必要再次从工作队列链表头开始寻找已经到期的任务,直到所有的到期任务都被处理完成。对于没有到期的任务,计算出距离到期最近的任务剩余延迟时间。

再看第二部分:

#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);
  • period值为0, 表示工作队列线程只能由信号唤醒。设置线程的状态busy为false,设置接收信号量种类,然后线程进入睡眠状态。此时,如果有新的任务被添加到工作队列中,那么该线程将被唤醒。这种方式对于低优先级的工作队列有用,低优先级的工作队列创建了最少一个线程,第一个线程采用延时poll的机制,剩下其余的采用信号中断的方式,这样既能保证工作队列上的任务不会超时太多(因为第一个线程会周期性Poll),也能保证当有任务插入到工作队列后,其他线程能够被唤醒,立即执行工作队列上(延迟到期)的任务。
  • 如果period非0,那么计算出最小的睡眠时间,让该线程进入睡眠状态。睡眠延迟时间到,线程又开始运行。

添加任务

int work_queue(int qid, FAR struct work_s *work, worker_t worker, FAR void *arg, systime_t delay)
  • qid选择需要添加的工作队列
  • work是对任务描述的数据结构指针
  • worker为任务的回调函数
  • arg是回调函数的参数
  • delay为延迟时间
    通过qid选择将任务添加到高优先级或者低优先级的任务中,然后向工作队列发信号。

添加任务底层函数为

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;}
  • 如果是向高优先级工作队列发信号,那么直接找到高优先级工作队列线程的pid,然后发信号即可。因为高优先级工作队列只有一个线程,即g_hpwork.worker[0]
  • 如果是向低优先级工作队列发信号,则找出低优先级工作队列的线程中的第一个处于睡眠状态的线程,向该线程发送信号。

结束任务

结束工作队列中的任务比较简单,将该任务从工作队列中的任务链表中移除,清除任务的回调函数。

1 0
原创粉丝点击