2.FreeRTOS的基础知识总结

一、单任务和多任务系统

1. 单任务系统

单任务系统的编程方式,即裸机的编程方式,这种编程方式的框架一般都是在 main()函数中使用一个大循环,在循环中顺序地调用相应的函数以处理相应的事务,这个大循环的部分可以视为应用程序的后台,而应用程序的前台,则是各种中断的中断服务函数。因此单任务系统也叫做前后台系统,前后台系统的运行示意图,如下图所示:
在这里插入图片描述
从上图可以看出,前后台系统的实时性很差,因为大循环中函数处理的事务没有优先级之分,必须是顺序地被执行处理的,不论待处理事务的紧急程度有多高,没轮到只能等着,虽然中断能够处理一些紧急的事务,但是在一些大型的嵌入式应用中,这样的单任务系统就会显得力不从心。

2.多任务系统

多任务系统在处理事务的实时性上比单任务系统要好得多,从宏观上来看,多任务系统的多个任务是可以“同时”运行的,因此紧急的事务就可以无需等待 CPU 处理完其他事务,在被处理。

要注意的是多任务系统的多个任务可以“同时”运行,是从宏观的角度而言的,对于单核的 CPU 而言,CPU 在同一时刻只能够处理一个任务,但是多任务系统的任务调度器会根据相关的任务调度算法,将 CPU 的使用权分配给任务,在任务获取 CPU 使用权之后的极短时间(宏观角度)后,任务调度器又会将 CPU 的使用权分配给其他任务,如此往复,在宏观的角度看来,就像是多个任务同时运行了一样。

多任务系统的运行示意图,如下图所示:
在这里插入图片描述
从上图可以看出,相较于单任务系统而言,多任务系统的任务也是具有优先级的,高优先级的任务可以像中断的抢占一样,抢占低优先级任务的 CPU 使用权;优先级相同的任务则各自轮流运行一段极短的时间(宏观角度),从而产生“同时”运行的错觉。以上就是抢占式调度和时间片调度的基本原理。

在任务有了优先级的多任务系统中,用户就可以将紧急的事务放在优先级高的任务中进行处理,那么整个系统的实时性就会大大地提高。

二、FreeRTOS 任务状态

在这里插入图片描述
FreeRTOS 中任务存在四种任务状态,分别为运行态、就绪态、阻塞态和挂起态。FreeRTOS运行时,任务的状态一定是这四种状态中的一种,下面就分别来介绍一下这四种任务状态。

1. 运行态
如果一个任务得到 CPU 的使用权,即任务被实际执行时,那么这个任务处于运行态。如果运行 RTOS 的 MCU 只有一个处理器核心,那么在任务时刻,都只能有一个任务处理运行态。

2. 就绪态
如果一个任务已经能够被执行(不处于阻塞态后挂起态),但当前还未被执行(具有相同优先级或更高优先级的任务正持有 CPU 使用权),那么这个任务就处于就绪态。

3. 阻塞态
如果一个任务因延时一段时间或等待外部事件发生,那么这个任务就处理阻塞态。例如任务调用了函数 vTaskDelay(),进行一段时间的延时,那么在延时超时之前,这个任务就处理阻塞态。任务也可以处于阻塞态以等待队列、信号量、事件组、通知或信号量等外部事件。通常情况下,处于阻塞态的任务都有一个阻塞的超时时间,在任务阻塞达到或超过这个超时时间后,即使任务等待的外部事件还没有发生,任务的阻塞态也会被解除。要注意的是,处于阻塞态的任务是无法被运行的。

4. 挂起态
任务一般通过函数 vTaskSuspend()和函数 vTaskResums()进入和退出挂起态与阻塞态一样,处于挂起态的任务也无法被运行。

四种任务状态之间的转换图如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、FreeRTOS 任务优先级

任务优先级是决定任务调度器如何分配 CPU 使用权的因素之一。每一个任务都被分配一个0~(configMAX_PRIORITIES-1)的任务优先级,宏 configMAX_PRIORITIES 在 FreeRTOSConfig.h文件中定义。

如果在 FreeRTOSConfig.h文件中,将宏configUSE_PORT_OPTIMISED_TASK_SELECTION定义为 1,那么 FreeRTOS 则会使用特殊方法计算下一个要运行的任务,这种特殊方法一般是使用硬件计算前导零指令,对于 STM32 而言,硬件计算前导零的指令,最大支持 32 位的数,因
此宏 configMAX_PRIORITIES 的值不能超过 32。当然,系统支持的优先级数量越多,系统消耗的资源也就越多,因此读者在实际的工程开发当中,应当合理地将宏 configMAX_PRIORITIES定义为满足应用需求的最小值。

FreeRTOS 的任务优先级高低与其对应的优先级数值,是成正比的,也就是说任务优先级数值为 0 的任务优先级是最低的任务优先级,任务优先级数值为(configMAX_PRIORITIES-1)的任务优先级是最高的任务优先级。FreeRTOS 的任务优先级高低与其对应数值的逻辑关系正好与STM32 的中断优先级高低与其对应数值的逻辑关系相反,如下图所示,因此作为刚入门FreeRTOS 的读者,特别注意。
在这里插入图片描述

四、FreeRTOS 任务调度方式

FreeRTOS 一共支持三种任务调度方式,分别为抢占式调度、时间片调度和协程式调度。
在这里插入图片描述

1.抢占式调度

抢占式调度主要时针对优先级不同的任务,每个任务都有一个优先级,优先级高的任务可以抢占优先级低的任务,只有当优先级高的任务发生阻塞或者被挂起,低优先级的任务才可以运行。
在这里插入图片描述

2.时间片调度

时间片调度主要针对优先级相同的任务,当多个任务的优先级相同时,任务调度器会在每一次系统时钟节拍到的时候切换任务,也就是说 CPU 轮流运行优先级相同的任务,每个任务运行的时间就是一个系统时钟节拍。有关系统时钟节拍的相关内容,在下文讲解 FreeRTOS 系统时钟节拍的时候会具体分析。
在这里插入图片描述

五、FreeRTOS 任务控制块

FreeRTOS 中的每一个已创建任务都包含一个任务控制块,任务控制块是一个结构体变量,
FreeRTOS 用任务控制块结构体存储任务的属性。
任务控制块的定义如以下代码所示:

typedef struct tskTaskControlBlock
{
 /* 指向任务栈栈顶的指针 */
 volatile StackType_t * pxTopOfStack;
 
#if ( portUSING_MPU_WRAPPERS == 1 )
 /* MPU 相关设置 */
 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;
 /* 由函数 vTaskSetTaskNumber()设置,用于调试 */
 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 )
 /* 记录任务处于运行态的时间 */
 configRUN_TIME_COUNTER_TYPE ulRunTimeCounter;
#endif
 
#if ( configUSE_NEWLIB_REENTRANT == 1 )
 /* 用于 Newlib */
 struct _reent xNewLib_reent;
#endif
 
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
 /* 任务通知值 */
 volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
 /* 任务通知状态 */
 volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#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 )
 /* 用于 POSIX */
 int iTaskErrno;
#endif
} tskTCB;
typedef struct tskTaskControlBlock * TaskHandle_t;

从上面的代码可以看出,FreeRTOS 的任务控制块结构体中包含了很多成员变量,但是,大
部分的成员变量都是可以通过 FreeRTOSConfig.h 配置文件中的配置项宏定义进行裁剪的。

六、FreeRTOS 任务栈

不论是裸机编程还是 RTOS 编程,栈空间的使用的非常重要。函数中的局部变量、函数调
用时的现场保护和函数的返回地址等都是存放在栈空间中的。

对于 FreeRTOS,当使用静态方式创建任务时,需要用户自行分配一块内存,作为任务的栈
空间,静态方式创建任务的函数原型如下所示:

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)

其中函数的参数 ulStackDepth,为任务栈的大小;参数 puxStackBuffer,为任务的栈的内存
空间。FreeRTOS 会根据这两个参数,为任务设置好任务的栈。

而使用动态方式创建任务时,系统则会自动从系统堆中分配一块内存,作为任务的栈空间,
动态方式创建任务的函数原型如下所示:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
 const char * const pcName,
 const configSTACK_DEPTH_TYPE usStackDepth,
 void * const pvParameters,
 UBaseType_t uxPriority,
 TaskHandle_t * const pxCreatedTask)

其中函数的参数usStackDepth,即为任务栈的大小。FreeRTOS会根据栈的大小,从FreeRTOS
的系统堆中分配一块内存,作为任务的栈空间。

值得一提的是,参数 usStackDepth 表示的任务栈大小,实际上是以字为单位的,并非以字节为单位。对于静态方式创建任务的函数 xTaskCreateStatic(),参数 usStackDepth 表示的是作为任务栈且其数据类型为 StackType_t 的数组 puxStackBuffer 中元素的个数;而对于动态方式创建任务的函数 xTaskCreate(),参数 usStackDepth 将被用于申请作为任务栈的内存空间,其内存申请相关代码,如下所示:

pxStack = pvPortMallocStack((((size_t)usStackDepth) * sizeof(StackType_t)));

可以看出,静态和动态创建任务时,任务栈的大小都与数据类型 StackType_t 有关,对于
STM32 而言,该数据类型的相关定义,如下所示:

#define portSTACK_TYPE uint32_t
typedef portSTACK_TYPE StackType_t;

因此,不论是使用静态方式创建任务还是使用动态方式创建任务,任务的任务栈大小都应
该为 ulStackDepth*sizeof(uint32_t)字节,即 ulStackDepth 字。

  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值