在本节中我们一起学习工作队列机制,我们知道飞控程序是运行在STM32F427上的,这个芯片的RAM大小只有250KB,跟普通个人电脑的内存相比较就非常少了,再加上在底层运行Nuttx操作系统需要占用一些内存,所以内存资源就更加有限了。在前面章节中我们已经介绍过了,当用户创建进程和线程时,操作系统会为其分配一定的内存作为PCB存储其调度信息,同时还要为每一个进程和线程分配函数栈和全局变量等等。在Nuttx中每创建一个进程大约需要使用4KB的内存,每使创建一个线程大约需要2KB的内存,这就使得整个单片机的内存资源非常少了。但是我们还是需要让一些功能可以像多线程一样可以并行运行,又尽量减少其内存占用,于是操作系统就引入了另外一个机制——工作队列。
简单来说,工作队列本身就是一个普通的进程,它允许用户向其注册执行函数,并执行经过多长时间后运行此函数,这个运行时间的单位通常采用的是操作系统调度TICK。用户可以向工作队列中注册多个需要运行的函数,这些函数将根据其时间调度顺序存放在一个队列中,而对于时间调度相同的函数也同样采用队列的方式运行。例如:在T0时刻,我们向工作队列注册了3个函数func_0(); func_1(); func_2();并告诉工作队列:
func_0()在100个TICK之后执行;
func_1()在200个TICK之后执行;
func_2()在300个TICK之后执行;
于是工作队列中就存放了3条记录如下:
每一个队列内容中都存放了两部分信息:1)多长时间后执行;2)执行哪一个函数;当然,实际的工作队列中还存放了一些其它信息,例如函数执行时所需要传入的参数等等。于是当操作系统对工作队列这个进程进行调度执行时,工作队列所调度执行的过程如下:
实际上,当工作队列执行执行完func_0()之后,就会将其移除队列,不会再执行了,但我们常常需要让某一个函数在一定的时间间隔内重复执行,所以我们就需要在func_0()函数的正常功能执行完毕后再将其本身重新加入工作队列当中去,例如:
void func_0(void)
{
//正常执行功能内容
... ...
//重新将func_0加入到work_queue中
work_queue(HPWORK, &_work, &func_0, NULL, 100);
}
在这里我们使用了work_queue()这个函数,这是操作系统为用户所提供的系统函数,我们可以通过这个函数向操作系统注册工作队列,Nuttx为用户提供了两种优先级的工作队列,分别为HPWORK和LPWORK也就是高优先级工作队列和低优先级队列,它的原型如下:
/****************************************************************************
* Name: work_queue
*
* 输入参数:
* qid - 工作队列ID高优先级队列HPWORK或低优先级队列LPWORK
* work - 工作队列结构体
* worker - 需要执行的工作函数
* arg - 执行函数时需要传递的参数
* delay - 延迟执行时间,单位为操作系统TICK,也就是系统时间片单位
*
* 返回值:
* 成功返回0;否则返回错误码
*
****************************************************************************/
int work_queue(int qid,
FAR struct work_s *work,
worker_t worker,
FAR void *arg,
systime_t delay)
于是我们就可以编写一个使用工作队列的例子了,与上面例子一样,我们让:
func_0()在100个TICK之后执行,之后重复执行;
func_1()在200个TICK之后执行,之后重复执行;
func_2()在300个TICK之后执行,之后停止执行;
#include <stdio.h>
#include <nuttx/wqueue.h>
#include <nuttx/clock.h>
work_s _work;
void func_0(void)
{
//正常执行功能内容
... ...
//重新将func_0加入到work_queue中
work_queue(HPWORK, &_work, &func_0, NULL, 100);
}
void func_1(void)
{
//正常执行功能内容
... ...
//重新将func_1加入到work_queue中
work_queue(HPWORK, &_work, &func_1, NULL, 100);
}
void func_2(void)
{
//正常执行功能内容
... ...
//不再执行
}
int main(int argc, char *argv[])
{
work_queue(HPWORK, &_work, &func_0, NULL, 100);
work_queue(HPWORK, &_work, &func_1, NULL, 200);
work_queue(HPWORK, &_work, &func_2, NULL, 300);
return 0;
}
操作系统在执行工作队列时,会判断当前时版中的队列节点中是否有函数需要执行,如果有则执行,并将执行后的函数移出队列,之后将后续的每一个节点的时间片减一。需要说明的是,在同一个时间片当中(同一个TICK当中),可能存在多个需要执行的函数内容,它们同样也以队列的方式存储在内存当中,如下图:
上图中横向的就是以操作系统TICK为单位的队列头节点,而纵向的就是以每一个队列头节点并列需要在此TICK中执行的函数队列。也就是说,整个工作队列按时间片的先后顺序执行,如果在同一时间内有多个任务要执行则同样以队列的方式按顺序执行。
Copyright © 2015-2023 问渠网 辽ICP备15013245号