FreeRtos详解学习日记3:FreeRTOS任务基础知识

1.0 任务的特性

在使用 RTOS 的时候一个实时应用可以作为一个独立的任务。每个任务都有自己的运行环
境,不依赖于系统中其他的任务或者 RTOS 调度器。任何一个时间点只能有一个任务运行,具
体运行哪个任务是由 RTOS 调度器来决定的,RTOS 调度器因此就会重复的开启、关闭每个任
务。任务不需要了解 RTOS 调度器的具体行为,RTOS 调度器的职责是确保当一个任务开始执
行的时候其上下文环境(寄存器值,堆栈内容等)和任务上一次退出的时候相同。为了做到这一
点,每个任务都必须有个堆栈,当任务切换的时候将上下文环境保存在堆栈中,这样当任务再
次执行的时候就可以从堆栈中取出上下文环境,任务恢复运行。

FreeRTOS任务特性

1、任务使用编写简单。

2、没有数量使用限制,一个优先级下面支持多个任务。

3、支持抢占,FreeRTOS抢占式内核,高优先级任务可以抢占低优先级的CPU使用权。

4、支持优先级。

5、每个任务都拥有堆栈,但是会导致RAM使用量增大。

6、如果使用抢占的话必须仔细的考虑重入的问题。

2.0 协程(Co-routine)的特性

协程是为那些资源很少的 MCU 而做的,但是随着 MCU 的飞速发展,性能越来越强大,现
在协程几乎很少用到了!但是 FreeRTOS 目前还没有把协程移除的计划,但是 FreeRTOS 是绝对
不会再更新和维护协程了,因此协程大家了解一下就行了。在概念上协程和任务是相似的,但
是有如下根本上的不同:
1、堆栈使用
所有的协程使用同一个堆栈(如果是任务的话每个任务都有自己的堆栈),这样就比使用任
务消耗更少的 RAM。
2、调度器和优先级
协程使用合作式的调度器,但是可以在使用抢占式的调度器中使用协程。
3、宏实现
协程是通过宏定义来实现的。
4、使用限制
为了降低对 RAM 的消耗做了很多的限制。

3.0 任务状态

FreeRTOS 中的任务永远处于下面几个状态中的某一个:
● 运行态
当一个任务正在运行时,那么就说这个任务处于运行态,处于运行态的任务就是当前正在
使用处理器的任务。如果使用的是单核处理器的话那么不管在任何时刻永远都只有一个任务处
于运行态。
● 就绪态
处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起),可以运行的任务,
但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行!
● 阻塞态
如果一个任务当前正在等待某个外部事件的话就说它处于阻塞态,比如说如果某个任务调
用了函数 vTaskDelay()的话就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事
件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过
这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临!
● 挂起态
像阻塞态一样,任务进入挂起态以后也不能被调度器调用进入运行态,但是进入挂起态的
任务没有超时时间。任务进入和退出挂起态通过调用函数 vTaskSuspend()和 xTaskResume()。
任务状态之间的转换如图

4.0 任务优先级
每 个 任 务 都 可 以 分 配 一 个 从 0~(configMAX_PRIORITIES-1) 的 优 先 级 ,
configMAX_PRIORITIES 在文件 FreeRTOSConfig.h 中有定义,前面我们讲解 FreeRTOS 系统配
置的时候已经讲过了。如果所使用的硬件平台支持类似计算前导零这样的指令(可以通过该指令
选 择 下 一 个 要 运 行 的 任 务 , Cortex-M 处 理 器 是 支 持 该 指 令 的 ) , 并 且 宏
configUSE_PORT_OPTIMISED_TASK_SELECTION 也 设 置 为 了 1 , 那 么 宏
configMAX_PRIORITIES 不 能 超 过 32 ! 也 就 是 优 先 级 不 能 超 过 32 级 。 其 他 情 况 下 宏
configMAX_PRIORITIES 可以为任意值,但是考虑到 RAM 的消耗,宏 configMAX_PRIORITIES
最好设置为一个满足应用的最小值。
优先级数字越低表示任务的优先级越低,0 的优先级最低,configMAX_PRIORITIES-1 的优
先级最高。空闲任务的优先级最低,为 0。
FreeRTOS 调度器确保处于就绪态或运行态的高优先级的任务获取处理器使用权,换句话说
就是处于就绪态的最高优先级的任务才会运行。当宏 configUSE_TIME_SLICING 定义为 1 的时
候多个任务可以共用一个优先级,数量不限。默认情况下宏 configUSE_TIME_SLICING 在文件
FreeRTOS.h 中已经定义为 1。此时处于就绪态的优先级相同的任务就会使用时间片轮转调度器
获取运行时间。

5.0 任务实现

在使用 FreeRTOS 的过程中,我们要使用函数 xTaskCreate()或 xTaskCreateStatic()来创建任
务,这两个函数的第一个参数 pxTaskCode,就是这个任务的任务函数。什么是任务函数?任务
函数就是完成本任务工作的函数。我这个任务要干嘛?要做什么?要完成什么样的功能都是在
这个任务函数中实现的。 比如我要做个任务,这个任务要点个流水灯,那么这个流水灯的程序
就是任务函数中实现的。FreeRTOS 官方给出的任务函数模板如下:
void vATaskFunction(void *pvParameters)                                                            (1)
{

        for( ; ; ) (2)
        {
        --任务应用程序-- (3)
        vTaskDelay(); (4)
        }
        /* 不 能 从 任 务 函 数 中 返 回 或 者 退 出 , 从 任 务 函 数 中 返 回 或 退 出 的 话 就 会 调 用configASSERT(),前提是你定义了 configASSERT()。如果一定要从任务函数中退出的话那一定
要调用函数 vTaskDelete(NULL)来删除此任务。*/
        vTaskDelete(NULL); (5)
}
(1)、任务函数本质也是函数,所以肯定有任务名什么的,不过这里我们要注意:任务函数
的返回类型一定要为 void 类型,也就是无返回值,而且任务的参数也是 void 指针类型的!任务
函数名可以根据实际情况定义。
(2)、任务的具体执行过程是一个大循环,for(; ; )就代表一个循环,作用和 while(1)一样,笔
者习惯用 while(1)。
(3)、循环里面就是真正的任务代码了,此任务具体要干的活就在这里实现!
(4)、FreeRTOS 的延时函数,此处不一定要用延时函数,其他只要能让 FreeRTOS 发生任务
切换的 API 函数都可以,比如请求信号量、队列等,甚至直接调用任务调度器。只不过最常用
的就是 FreeRTOS 的延时函数。
(5)、任务函数一般不允许跳出循环,如果一定要跳出循环的话在跳出循环以后一定要调用
函数 vTaskDelete(NULL)删除此任务!
FreeRTOS 的任务函数和 UCOS 的任务函数模式基本相同的,不止 FreeRTOS,其他 RTOS
的任务函数基本也是这种方式的。

6.0 任务控制块

FreeRTOS 的每个任务都有一些属性需要存储,FreeRTOS 把这些属性集合到一起用一个结
构体来表示,这个结构体叫做任务控制块:TCB_t,在使用函数 xTaskCreate()创建任务的时候就
会自动的给每个任务分配一个任务控制块。在老版本的 FreeRTOS 中任务控制块叫做 tskTCB,
新版本重命名为 TCB_t,但是本质上还是 tskTCB,本教程后面提到任务控制块的话均用 TCB_t
表示,此结构体在文件 tasks.c 中有定义,如下:
typedef struct tskTaskControlBlock
{
        volatile StackType_t *pxTopOfStack; //任务堆栈栈顶
        #if ( portUSING_MPU_WRAPPERS == 1 )
        xMPU_SETTINGS xMPUSettings; //MPU 相关设置
        #endif
        ListItem_t xStateListItem; //状态列表项
        ListItem_t xEventListItem; //事件列表项
        UBaseType_t uxPriority; //任务优先级
        StackType_t *pxStack; //任务堆栈起始地址

        char pcTaskName[ configMAX_TASK_NAME_LEN ];//任务名字
        #if ( portSTACK_GROWTH > 0 )
        StackType_t *pxEndOfStack; //任务堆栈栈底
        #endif
        #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t uxCriticalNesting; //临界区嵌套深度
        #endif
        #if ( configUSE_TRACE_FACILITY == 1 ) //trace 或到 debug 的时候用到
        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; //定义一个 newlib 结构体变量
        #endif
        #if( configUSE_TASK_NOTIFICATIONS == 1 ) //任务通知相关变量
        volatile uint32_t ulNotifiedValue; //任务通知值
        volatile uint8_t ucNotifyState; //任务通知状态
        #endif
        #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )

        //用来标记任务是动态创建的还是静态创建的,如果是静态创建的此变量就为 pdTURE,
        //如果是动态创建的就为 pdFALSE
        uint8_t ucStaticallyAllocated;
        #endif
        #if( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
        #endif
        } tskTCB;
//新版本的 FreeRTOS 任务控制块重命名为 TCB_t,但是本质上还是 tskTCB,主要是为了兼容
//旧版本的应用。
        typedef tskTCB TCB_t;
可以看出来 FreeRTOS 的任务控制块中的成员变量相比 UCOSIII 要少很多,而且大多数与
裁剪有关,当不使用某些功能的时候与其相关的变量就不参与编译,任务控制块大小就会进一
步的减小。

7.0 任务堆栈

FreeRTOS 之所以能正确的恢复一个任务的运行就是因为有任务堆栈在保驾护航,任务调
度器在进行任务切换的时候会将当前任务的现场(CPU 寄存器值等)保存在此任务的任务堆栈中,
等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场,恢复现场以后任务就会接着
从上次中断的地方开始运行。
创建任务的时候需要给任务指定堆栈,如果使用的函数 xTaskCreate()创建任务(动态方法)
的话那么任务堆栈就会由函数 xTaskCreate()自动创建,后面分析 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, (1)
                                                        StaticTask_t * const pxTaskBuffer )
(1)、任务堆栈,需要用户定义,然后将堆栈首地址传递给这个参数。
堆栈大小:
我们不管是使用函数 xTaskCreate()还是 xTaskCreateStatic()创建任务都需要指定任务堆栈大
小。任务堆栈的数据类型为 StackType_t,StackType_t 本质上是 uint32_t,在 portmacro.h 中有定
义,如下:
#define portSTACK_TYPE uint32_t
#define portBASE_TYPE long
typedef portSTACK_TYPE StackType_t;

typedef long BaseType_t;
typedef unsigned long UBaseType_t;
可以看出 StackType_t 类型的变量为 4 个字节,那么任务的实际堆栈大小就应该是我们所
定义的 4 倍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值