前言
一、实时操作系统是什么?
二、stm32和FreeRTOS之间的点点滴滴
先行必备知识
FreeRTOS的任务函数
void StartTask(void *argument) //StartTask is the name of task { //user's codes begin ... //user's codes end for(;;) { //user's codes begin ... //user's codes end osDelay(1); } }
任务中包含了一个for 循环,也可以使用while循环,但是请注意FreeRTOS任务函数一般不允随意跳出
循环,如果要关闭任务要使用VTaskDelete()来删除任务。
关于任务的状态
在FreeRTOS 中,任务有五种状态:运行态、就绪态、阻塞态、挂起态和删除态。
运行态(Running):说明当前CPU 正在执行该任务,单核处理器任何时刻只有一个任务处于运行态。处于就绪态就说明该任务正在占用CPU。
就绪态(Ready):说明任务已经就绪,可以被任务调度器调用去执行,但是调度只会去执行目前优先级最高的任务。
阻塞态(Blocked):阻塞态的任务不会被任务调度器的调度,并且任务阻塞是有一定时间的,当超过时间结束会退出阻塞状态。处于阻塞状态的作用除了可以使任务处于暂停状态,还有一个作用是灵活调度,当任务处于阻塞状态,调度器就会去执行其它任务,提高效率。比如当某个任务调用TaskDelay 毫秒延时函数时,该任务就会阻塞,同时调度器自动运行其它处于就绪态的任务,等待延时结束时重新进入就绪态。
挂起态(Suspend):处于挂起态的任务同样不会被任务调度器调度,但是对比阻塞态它没有超时时间,当调用当明确的分别调用vTaskSuspend()和xTaskResume() 函数后,任务才会进入或退出挂起状态。
删除态:任务删除之后任务控制块TCB 会保留一段时间,等待内核检查和回收资源,任务也不能再被调度,此时任务处于删除态,由于任务也实际不存在,所以官方的文档中只描述了运行态、就绪态、阻塞态和挂起态这四个状态,并没有把删除态列入任务的状态中。
FreeRTOS任务管理调度的方法
为了满足处理器多任务并发进行的需求,需要通过系统调度来合理安排各个任务占有CPU的时间。任务管理和调度是RTOS的核心功能。
在一般的系统中,任务可以分为Running态和非Running态,而非Running态还可以再细分。(不懂请看任务的状态这部分)
FreeRTOS系统中,任务主要有以下两种调度方法:
抢占式调度:当有新的任务就绪(ready,且优先级大于等于当前任务的优先级时,当前任务就会被抢占;需要用户自己通过configUSE_PREEMPTION配置。
时间片调度:同处于ready态的最高优先级的任务会轮流运行固定的时间片;通过configUSE_TIME_SLICING配置,默认开启
关于任务优先级
FreeRTOS 中任务可以设置优先级,这种方式是全抢占式调度,同时任务优先级的个数可以由用户配置。任务的优先级由数字决定的,数字越大优先级越高,数字越小代表优先级越低。
可以通过FreeRTOSConfig.h 文件中的configMAX_PRIORITIES 宏去配置任务优先级,用户实际可以使用的优先级范围是0到configMAX_PRIORITIES–1。比如我们配置此宏定义为5,那么用户可以使用的优先级号是0,1,2,3,4。假如使能了 configUSE_PORT_OPTIMISED_TASK_SELECTION 这个宏(在 FreeRTOSConfig.h 文件定义),一般强制限定最大可用优先级数目为 32与此同时还有一个容易弄混的概念是中断优先级,中断优先级和任务优先级是没有任何关系的,中断优先级的数字越大代表优先级越低,数字越小代表优先级越高。中断的级别永远高于任务级别。当任务开始执行的时候中断发生,微处理器会立刻执行中断服务程序。
当FreeRTOSConfig.h 文件中的configUSE_TIME_SLICING 宏配置为1 或者没有配置此
宏的时候是开启时间片调度的,多个任务可以共用一个优先级,也就是处于相同优先级的任
务会轮流切换执行。当有更高的优先级处于就绪状态就会去处理更高优先级的任务。
在FreeRTOS 中,任务优先级个数可以由用户配置,可以通过FreeRTOSConfig.h文件中的configMAX_PRIORITIES 宏进行配置的,而CMSIS-RTOS 所提供的接口对freertos中的优先级个数已经定义,在CMSIS-RTOS V2 中的configMAX_PRIORITIES 为56,其中用户可用优先级个数为49个,有5个优先级保留。1 个ISR 延迟线程,该线程优先级最高,不能被用户使用。还有一个osPriorityNone优先级,该优先级是无优先级,未初始化,不能使用。
CMSIS中线程优先级的官方定义:
typedef enum { osPriorityNone = 0, ///< No priority (not initialized). osPriorityIdle = 1, ///< Reserved for Idle thread. ///< 2-7 Reserved osPriorityLow = 8, ///< Priority: low osPriorityLow1 = 8+1, ///< Priority: low + 1 osPriorityLow2 = 8+2, ///< Priority: low + 2 osPriorityLow3 = 8+3, ///< Priority: low + 3 osPriorityLow4 = 8+4, ///< Priority: low + 4 osPriorityLow5 = 8+5, ///< Priority: low + 5 osPriorityLow6 = 8+6, ///< Priority: low + 6 osPriorityLow7 = 8+7, ///< Priority: low + 7 osPriorityBelowNormal = 16, ///< Priority: below normal osPriorityBelowNormal1 = 16+1, ///< Priority: below normal + 1 osPriorityBelowNormal2 = 16+2, ///< Priority: below normal + 2 osPriorityBelowNormal3 = 16+3, ///< Priority: below normal + 3 osPriorityBelowNormal4 = 16+4, ///< Priority: below normal + 4 osPriorityBelowNormal5 = 16+5, ///< Priority: below normal + 5 osPriorityBelowNormal6 = 16+6, ///< Priority: below normal + 6 osPriorityBelowNormal7 = 16+7, ///< Priority: below normal + 7 osPriorityNormal = 24, ///< Priority: normal osPriorityNormal1 = 24+1, ///< Priority: normal + 1 osPriorityNormal2 = 24+2, ///< Priority: normal + 2 osPriorityNormal3 = 24+3, ///< Priority: normal + 3 osPriorityNormal4 = 24+4, ///< Priority: normal + 4 osPriorityNormal5 = 24+5, ///< Priority: normal + 5 osPriorityNormal6 = 24+6, ///< Priority: normal + 6 osPriorityNormal7 = 24+7, ///< Priority: normal + 7 osPriorityAboveNormal = 32, ///< Priority: above normal osPriorityAboveNormal1 = 32+1, ///< Priority: above normal + 1 osPriorityAboveNormal2 = 32+2, ///< Priority: above normal + 2 osPriorityAboveNormal3 = 32+3, ///< Priority: above normal + 3 osPriorityAboveNormal4 = 32+4, ///< Priority: above normal + 4 osPriorityAboveNormal5 = 32+5, ///< Priority: above normal + 5 osPriorityAboveNormal6 = 32+6, ///< Priority: above normal + 6 osPriorityAboveNormal7 = 32+7, ///< Priority: above normal + 7 osPriorityHigh = 40, ///< Priority: high osPriorityHigh1 = 40+1, ///< Priority: high + 1 osPriorityHigh2 = 40+2, ///< Priority: high + 2 osPriorityHigh3 = 40+3, ///< Priority: high + 3 osPriorityHigh4 = 40+4, ///< Priority: high + 4 osPriorityHigh5 = 40+5, ///< Priority: high + 5 osPriorityHigh6 = 40+6, ///< Priority: high + 6 osPriorityHigh7 = 40+7, ///< Priority: high + 7 osPriorityRealtime = 48, ///< Priority: realtime osPriorityRealtime1 = 48+1, ///< Priority: realtime + 1 osPriorityRealtime2 = 48+2, ///< Priority: realtime + 2 osPriorityRealtime3 = 48+3, ///< Priority: realtime + 3 osPriorityRealtime4 = 48+4, ///< Priority: realtime + 4 osPriorityRealtime5 = 48+5, ///< Priority: realtime + 5 osPriorityRealtime6 = 48+6, ///< Priority: realtime + 6 osPriorityRealtime7 = 48+7, ///< Priority: realtime + 7 osPriorityISR = 56, ///< Reserved for ISR deferred thread. osPriorityError = -1, ///< System cannot determine priority or illegal priority. osPriorityReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization. } osPriority_t;
FreeRTOS中的任务控制块
一个任务在创建的时候,会同时创建一个结构体TCB(Task Control Block),中文叫做任务控制块,这个任务控制块包含了任务的堆栈指针、任务名称、任务的形参等,任务调度器需要通过这个任务块结构体来操作该任务。任务控制块的结构体定义为tskTCB 类型,在task.c中可查找到该定义,最新的FreeRTOS 10.01 版本给这个结构体起别名为TCB_t,在新版本中两种类型别名都能用,旧版本的FreeRTOS 只能使用tskTCB。任务被创建后,任务默认状态为就绪态,在系统中等待调度。
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 ]; //任务名字 .... //部分省略,可以参考FreeRTOS 源码 }tskTCB; typedef tskTCB TCB_t; //给tskTCB 类型起别名为TCB_t 类型
任务的堆栈空间
FreeRTOS之所以能正确的恢复一个任务的运行就是因为有任务堆栈在保驾护航,任务调度器在进行任务切换的时候会将当前任务的现场 (CPU寄存器值等 )保存在此任务的任务堆栈中,等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场 ,恢复现场以后任务就会接着从上次中断的地方开始运行。FreeRTOS 中的每个任务都有一个堆栈空间,任务堆栈可以由系统提供,也可以由用户提供。当由系统提供时使用xTaskCreate()函数,此时需要将申请的任务堆栈的大小作为形参传入。当由用户自行提供的时候使用xTaskCreateStatic()函数,此时需要将用户提供的堆栈空间的地址作为形参传入。
任务函数的创建
在FreeRTOS中,任务创建可分为动态方法创建任务和静态方法创建任务。动态静态的区别主要在于堆栈由谁提供。
动态方法创建任务无需用户提供堆栈空间,系统能够自动分配,而静态方法创建用户需要用户先提供堆栈空间,而且两个函数的返回值类型并不相同。
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode, //动态方式创建任务 const char *const pcName, unsigned short usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask ); /** * pvTaskCode:任务入口函数 * pcName:指向任务名字字符串的指针 * usStackDepth:任务堆栈大小 * pvParameters:任务函数的参数 * uxPriority:任务的优先级 * pxCreatedTask:任务控制块指针 **/ /** 返回值(return): pdPASS:任务创建成功。 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:创建任务 失败,一般是系统空间没有足够的内存来分配任务的堆栈空间。 **/ TaskHandle_t xTaskCreateStatic( TaskFunction_t pvTaskCode, //静态方式创建任务 const char * const pcName, uint32_t ulStackDepth, void *pvParameters, UBaseType_t uxPriority, StackType_t * const puxStackBuffer, StaticTask_t * const pxTaskBuffer ); /** * pvTaskCode:任务入口函数 * pcName:指向任务名字字符串的指针 * usStackDepth:任务堆栈大小 * pvParameters:任务函数的参数 * uxPriority:任务的优先级 * puxStackBuffer:指向任务堆栈的指针 **/ /** 返回值(return): 创建失败:返回NULL 创建成功:返回任务句柄。 /**
线程的创建
osThreadDetach();//分离线程(线程终止时可以回收线程存储 osThreadEnumerate();//枚举活动线程 osThreadExit();//终止当前正在运行的线程的执行 osThreadGetCount();//获取活动线程的数量 osThreadGetId();//返回当前正在运行的线程的线程ID osThreadGetName();//获取线程的名称 osThreadGetPriority();//获取线程的当前优先级 osThreadGetStackSize();//获取线程的堆栈大小 osThreadGetStackSpace();//根据执行期间的堆栈水印记录获取线程的可用堆栈空间 osThreadGetState();//获取线程的当前线程状态 osThreadJoin();//等待指定线程终止 osThreadNew();//创建一个线程并将其添加到活动线程中。 osThreadResume();//恢复线程的执行。 osThreadSetPriority();//更改线程的优先级。 osThreadSuspend();//暂停执行线程。 osThreadTerminate();//终止线程的执行。 osThreadYield();//将控制权传递给处于就绪状态的下一个线程
CMSIS-RTOS 拥有四个状态:就绪态、运行态、阻塞态和终止态,它和FreeRTOS中的状态有所区别,CMSIS-RTOS 的阻塞态合并了FreeRTOS 中的挂起态和阻塞态,终止态对应的是FreeRTOS 中的删除态。注意在终止态时调用osThreadTerminate()函数,线程被终止,处于终止态的时候如果线程支持可连接属性资源并未被释放,当线程处于终止态的时候线程不仅会被终止,资源也会被释放。FreeRTOS 的CMSIS-RTOS 目前暂不支持可连接线程,所以当调用osThreadTerminate()时,如果线程是动态方式创建的,线程的资源同时也会被释放,使得线程处于终止态。
osThreadId_t osThreadNew(osThreadFunc_t func, void * argument, const osThreadAttr_t * attr );//线程创建函数 /** 功能: 该功能为创建一个新的的线程,并将该线程设置为就绪。使用参 数指针* argument 传递线程函数的参数。当创建的线程函数的 优先级高于当前运行的线程时,创建的线程立即执行。线程属性 通过attr 控制,属性包括线程优先级,堆栈大小或内存分配的设置。 **/ /** * func:线程函数,即线程要实现的功能函数。 * argument:线程函数的参数,以指针形式传入。 * attr:控制线程的参数,为一个结构体指针 **/ /** 返回值(return): 线程的ID,之后线程的其它操作都通过该ID来控制。 /**
CMSIS-RTOS 的接口把FreeRTOS 的任务创建函数xTaskCreate()和xTaskCreateStatic()封装成一个osThreadNew()函数,堆栈空间是否由用户自行提供可以通过控制osThreadNew()的第三个参数attr来实现(即是动态创建线程还是静态创建线程)。
attr结构体:
typedef struct { const char *name; //线程的名称 uint32_t attr_bits; //线程的属性位,可以设置为以分离形式创建线程和可连接 //方式创建线程,分别是osThreadDetached和osThreadJoinable void *cb_mem; //线程控制块指针,如果设置为NULL将从系统空间自动分配空间用于线程控制块 uint32_t cb_size;//线程控制块所占内存的大小,注意仅在用户自行分配线程堆栈时配置, //如果采用从系统空间也就是内存池分配线程堆栈时设置为0,默认值也是0 void *stack_mem; //指向线程堆栈的指针,注意以64 位对齐,注意尽在用户自行分配线程堆栈 //时配置,如果采用从系统空间也就是内存池分配线程堆栈时设置为NULL,默 //认值也是NULL uint32_t stack_size;//线程堆栈的大小,以字节为单位,最好是4 的倍数 osPriority_t priority; //线程的优先级 TZ_ModuleId_t tz_module; //TrustZone 模块标识符 uint32_t reserved; //保留的变量以备之后使用 }osThreadAttr_t;
在FreeRTOS中有两种创建线程的方法,一是动态创建线程,二是静态创建线程。
在使用静态方法创建线程时,必须先定义静态的线程控制块,并且定义好堆栈空间。采用这种方式,线程控制块和堆栈占用的内存会放在 RW/ZI段(详细请看ARM架构中RO段,RW段,ZI段的区别),这段空间在编译时就已经确定,它不是动态分配的,所以不能简单用osThreadTerminate()释放空间,只能使用它将该线程控制块从任务管理器中脱离。
使用动态方法创建线程时, 线程会动态申请线程控制块和堆栈空间。在编译时, 编译器是不会感知到这段空间的,只有在程序运行时, 线程才会从系统堆中申请分配这段内存空间,当不需要使用该线程时,在CMSIS提供的接口中,调用osThreadTerminate()函数就会将这段申请的内存空间重新释放到内存堆中。
这两种方式各有利弊,静态定义方式会占用 RW/ZI空间,但是不需要动态分配内存,运行时效率较高,实时性较好。 动态方式不会占用额外的 RW/ZI空间,占用空间小,但是运行时需要动态分配内存,效率没有静态方式高。 总的来说,这两种方式就是空间和时间效率的平衡,可以根据实际环境需求选择采用具体的分配方式。
动态创建线程:
int main(void) { osKernelInitialize(); //初始化内核 //结构体变量Task_attributes 存放线程相关配置信息 osThreadAttr_t Task_attributes = { .name = "task_name", //线程的名称,注意并非是线程函数的名称 .priority = (osPriority_t)osPriorityNormal, // 设置线程优先级为osPriorityNormal .stack_size = 128 //设置堆栈大小为128 字,即512 字节 }; osThreadNew(Task_name,NULL,&Task_attributes); //创建一个新的线程 osKernelStart(); //开启内核调度 } /*线程函数的实现*/ void Task_name(void *argument) { //user's codes begin ... //user's codes end for(;;) { //user's codes begin ... //user's codes end } }
静态创建线程:
void Task_name(void *argument); //声明线程函数 uint32_t myTaskBuffer[ 128 ]; //定义由用户提供的线程堆栈空间 int main(void) { osKernelInitialize(); //初始化内核 //结构体变量Task_attributes 存放线程配置信息 osThreadAttr_t Task_attributes = { .name = "task_name", //线程的名称,注意并非是线程函数的名称 .stack_mem = &myTaskBuffer[0], //用户提供堆栈空间的地址 .stack_size = sizeof(myTaskBuffer), //用户提供堆栈空间的大小,此处为 512 字节 .priority = (osPriority_t) osPriorityNormal, // 设置线程优先级为 osPriorityNormal }; osThreadNew(Task_name,NULL,&Task_attributes); //创建一个新的线程 osKernelStart(); //开启内核调度 } /*线程函数的实现*/ void Task_name(void *argument) { //user's codes begin ... //user's codes end for(;;) { //user's codes begin ... //user's codes end } }
总结
这次主要先行了解学习FreeRTOS的简单的知识点,FreeRTOS虽然小,但是内容挺丰富的,学无止境,一起加油。还有借鉴了许多大佬的文章,在此感谢各位大佬,就不一一列出来了,敬礼!(小白选手,有错的话各位大佬请见谅,麻烦各位告知一下这位可怜的小白ღ( ´・ᴗ・` )比心)