(一)多任务系统
1. 单任务系统
在使用C51系列、STM32等单片机进行裸机编程时,大都在main函数中写一个while或者for死循环函数,用来无限轮询任务函数。很多时候会加入硬件中断来完成一些功能处理。相对于多任务系统来说,这种轮询系统就是单任务系统,也称作前后台系统。中断服务函数为前台系统,轮询为后台系统。
前后台系统的缺点:实时性差;除中断之外的所有任务都排队轮流执行,处理器的性能好的话,轮询的速度就会快些,要是处理器性能比较差并且代码很多的话,系统就会比较慢。优点:系统简单、消耗资源小。
2. 多任务系统
多任务系统会将任务并发处理(不意味着同一时刻能够多任务一起执行,并且MCU就一个核),由于每个任务所占用的CPU时间很短,可以看做是多任务在一起执行。在RTOS中任务调度器起了关键性的作用,负责任务的调度。FreeRTOS是一个抢占式的实时多任务系统,其任务调度器也是抢占式的。具体运行过程如下图所示:
3. FreeRTOS任务与协程
在FreeRTOS中任务和协程均可使用,但两者使用的是不同的API函数,因此不能通过队列(或信号量)将数据从任务发送给协程,反之依然。协程是专门为资源很少的MCU准备的。
(1)任务特性
在RTOS中,每一个任务都有自己的运行环境,不依赖于系统中的其它任务或者RTOS调度器。任何一个时间点,只能同时运行一个任务。任务调度器的主要任务是决定哪个任务运行。任务调度器确保一个任务的开始执行时的系统环境(寄存器值,堆栈内容等)和上一次任务退出时保持一致。因此,每个任务都有堆栈,用于保存任务切换时的上下文环境。
任务特性:
- 简单。
- 没有使用限制。
- 支持抢占。
- 支持优先级。
- 每个任务都拥有堆栈导致RAM使用量增大。
- 如果使用抢占的话,必须仔细考虑重入问题。
(2)协程特性
协程与任务在概念上是相似的,但存在一下几点不同:
- 堆栈的使用 -
所有的协程使用同一个堆栈,比任务消耗更小的RAM。 - 调度器和优先级
协程使用合作式调度器,但可以在抢占式调度其中使用协程。 - 宏实现
协程是通过宏定义来实现的。 - 使用限制
为了降低对RAM的消耗做了很多的限制。
(3)任务状态
- 运行态
任务正在运行的状态。
- 就绪态
处于就绪态的任务(没有被阻塞或挂起)是已经准备就绪、可以运行的任务,但还没有正式运行,主要由于当前系统正在运行的同一优先级或者更高优先级的任务。
- 阻塞态
任务当前正在等待某个外部事件时,就处于阻塞态。例如,某任务调用vTaskDelay()函数,进入阻塞态,等待延时完成。任务在等待队列、信号量、事件组、通知或互斥信号量时也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间,任务就会退出阻塞态,即使等待的事件还没有触发。
- 挂起态
任务进入挂起态后不能被任务调度器调用进入运行态,且没有超时时间。任务进入和退出挂起态通过调用函数vTaskSuspend()和xTaskResume()。
任务状态之间的转换如下图所示:
(4)任务优先级
每个任务都可以分配一个从 0 ~ (configMAX_PRIORITIES - 1) 的优先级。configMAX_PRIORITIES 宏定义在FreeRTOSConfig.h头文件中有定义。一般情况下configMAX_PRIORITIES 可以设置为任意值,但是考虑到RAM的消耗,最好设置为一个满足应用的最小值。
注意:如果硬件平台支持类似计算前导零这样的指令(可以通过该指令选择下一个要运行的任务),并且宏configUSE_PORT_OPTIMISED_TASK_SELECTION也设置为1后,那么宏configMAX_PRIORITIES 不能超过32,即优先级不能超过32级。
优先级数字越小表示的优先级越低,0的优先级最低,configMAX_PRIORITIES - 1的优先级最高。空闲任务的优先级最低,为0.
处于就绪态的最高优先级的任务才会运行。当宏configUSE_TIME_SLICING定义为1时,多个任务可以共用一个优先级,数量不限;此时处于就绪态的同优先级任务,会使用时间片轮转调度器,获取运行时间。
(5)任务实现
在FreeRTOS中,使用xTaskCreate()或者xTaskCreateStatic()两个函数来创建任务。FreeRTOS官方任务函数模板:
void vATaskFunction(void *pvParameters)
{
for( ; ; )
{
vTaskDelay();
}
vTaskDelete(NULL);
}
- 任务函数无返回值,且无传入参数;任务函数名可以根据实际应用定义。
- 任务应用程序放在一个死循环中执行,可以是for( ; ; ),也可以是while(1)。
- 死循环中是当前任务真正执行的功能
- vTaskDelay()函数在这里的主要功能是引发任务调度器切换任务;只要是能引起任务切换的函数均可以的,例如请求信号量、队列,甚至可以直接调用任务调度器。
- 任务函数一般不允许跳出死循环,如果一定要跳出,则在跳出后一定要调用vTaskDelete(NULL)函数删除此任务。
(6)任务控制块(任务句柄)
FreeRTOS的每个任务都有一些属性需要存储,FreeRTOS把这些属性集合到了一起,用了一个结构体来表示,这个结构体叫做任务句柄:TaskHandle_t,在调用任务创建函数时,会自动给每个任务分配一个任务控制块。在新版本中使用的是TaskHandle_t,本质上是tskTaskControlBlock结构体。
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack;
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings;
#endif
ListItem_t xStateListItem;
ListItem_t xEventListItem;
UBaseType_t uxPriority;
StackType_t *pxStack;
char pcTaskName[ configMAX_TASK_NAME_LEN ];
#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t *pxEndOfStack;
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber;
UBaseType_t uxTaskNumber;
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority;
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter;
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
struct _reent xNewLib_reent;
#endif
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState;
#endif
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated;
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
#if( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno;
#endif
} tskTCB;
typedef tskTCB TCB_t;
在task.h中有如下定义:
struct tskTaskControlBlock;
typedef struct tskTaskControlBlock* TaskHandle_t;
(7)任务堆栈
任务堆栈的存在保证了FreeRTOS能够正确的恢复一个任务。任务堆栈负责保存任务调度器在进行任务切换时的现场(CPU寄存器值等),等到该任务下次运行时,使用堆栈中的值来恢复现场,保证任务能够在上次停止的地方接着运行下去。
任务创建时需要制定任务堆栈。动态创建任务和静态创建任务直接会影响到堆栈的创建。使用xTaskCreate()函数动态创建任务时,任务堆栈会由函数自动创建。使用xTaskCreateStatic()函数静态创建任务时,需要自定定义任务堆栈,然后以堆栈首地址作为函数的参数puxStackBuffer 传递给函数。
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
无论是创建动态任务(xTaskCreate())或者是静态任务(xTaskCreateStatic())都需要指定任务的堆栈大小。由于采用的uint32_t的类型,单位为4个字节,故实际的堆栈大小为定义数字的4倍。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)