点击上方"蓝字"关注我们
RTOS | 那么什么是RTOS?三大操作系统?(第十四天) |
---|
FreeRTOS | 原理介绍和资源get(第十四天) |
FreeRTOS | STM32F407 FreeRTOS移植(第十四天) |
>>>1.函数xTaskCreate 此函数用来创建一个任务,任务需要RAM来保存与任务有关的状态信息(任务控制块),任务也需要一定的RAM 来作为任务堆栈。如果使用函数xTaskCreate()来创建任务的话那么这些所需的RAM就会自动的从FreeRTOS的堆中分配,因此必须提供内存管理文件,默认我们使用heap_4.c这个内存管理文件,而且宏configSUPPORT_DYNAMIC_ALLOCATION必须为1。如果使用函数xTaskCreateStatic()创建的话这些RAM就需要用户来提供了。新创建的任务默认就是就绪态的,如果当前没有比它更高优先级的任务运行那么此任务就会立即进入运行态开始运行,不管在任务调度器启动前还是启动后,都可以创建任务。此函数也是我们以后经常用到的,本教程所有例程均用此函数来创建任务,函数原型如下:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, const char *const pcName, const uint16_t usStackDepth, void *const pvParameters, UBaseType_t uxPriority, TaskHandle_t *const pxCreatedTask)
>>>参数: pxTaskCode:任务函数。 pcName:任务名字,一般用于追踪和调试,任务名字长度不能超过configMAX_TASK_NAME_LEN。 usStackDepth:任务堆栈大小,注意实际申请到的堆栈是usStackDepth的2倍,例如数值128,即128半字。其中空闲任务的任务堆栈大小为configMINIMAL_STACK_SIZE。 pvParameters:传递给任务函数的参数。 uxPriotiry:任务优先级,范围0~configMAX_PRIORITIES-1。 pxCreatedTask:任务句柄,任务创建成功以后会返回此任务的任务句柄,这个句柄其实就是任务的任务堆栈。此参数就用来保存这个任务句柄。其他API函数可能会使用到这个句柄。 返回值: pdPASS:任务创建成功。 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:任务创建失败,因为堆内存不足!
>>>2.函数xTaskCreateStatic 此函数和xTaskCreate()的功能相同,也是用来创建任务的,但是使用此函数创建的任务所需的 RAM需要用用户来提供。如果要使用此函数的话需要将宏configSUPPORT_STATIC_ALLOCATION定义为1。函数原型如下:
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 );
>>>参数: pxTaskCode:任务函数。 pcName:任务名字,一般用于追踪和调试,任务名字长度不能超过configMAX_TASK_NAME_LEN。 usStackDepth:任务堆栈大小,由于本函数是静态方法创建任务,所以任务堆栈由用户给出,一般是个数组,此参数就是这个数组的大小。 pvParameters:传递给任务函数的参数。 uxPriotiry:任务优先级,范围 0 ~ configMAX_PRIORITIES-1,数值越大,优先级就越高,在FreeRTOSConfig.h可以配置configMAX_PRIORITIES。 puxStackBuffer:任务堆栈,一般为数组,数组类型要为StackType_t类型。 pxTaskBuffer:任务控制块。 返回值: NULL;任务创建失败,puxStackBuffer 或 pxTaskBuffer为 NULL的时候会导致这个错误的发生。 其他值:任务创建成功,返回任务的任务句柄。
注意,若configSUPPORT_STATIC_ALLOCATION有效了,需要为系统的空闲任务和定时器任务提供独立的内存空间,详细添加的代码如下:
static StaticTask_t xIdleTaskTCBBuffer;static StackType_t xIdleStack[configMINIMAL_STACK_SIZE]; /* 空闲任务所需内存 */void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize ){ *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer; *ppxIdleTaskStackBuffer = &xIdleStack[0]; *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; /* place for user code */} static StackType_t TimerTaskStack[configMINIMAL_STACK_SIZE];static StaticTask_t TimerTaskTCB;/* 定时器任务所需内存 */void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize ){ *ppxTimerTaskTCBBuffer=&TimerTaskTCB;*ppxTimerTaskStackBuffer=TimerTaskStack; *pulTimerTaskStackSize=configMINIMAL_STACK_SIZE;}
>>>函数栈大小确定 函数的栈大小计算起来是比较麻烦的,那么有没有简单的办法来计算呢?有的,一般 IDE 开发环境都有这样的功能,比如 MDK 会生成一个 htm 文件,通过这个文件用户可以知道每个被调用函数的最大栈需求以及各个函数之间的调用关系。但是 MDK 无法确定通过函数指针实现函数调用时的栈需求。另外,发生中断或中断嵌套时的现场保护需要的栈空间也不会统计。 路径:\OBJ\demo.htm
app_task_rtc (Thumb, 128 bytes, Stack size 48 bytes, main.o(i.app_task_rtc))[Stack]Max Depth = 200 + Unknown Stack SizeCall Chain = app_task_rtc ⇒ dgb_printf_safe ⇒ xQueueGenericSend ⇒ xTaskResumeAll ⇒ xTaskIncrementTick
>>>3.函数xTaskCreateRestricted 此函数也是用来创建任务的,只不过此函数要求所使用的MCU有MPU(内存保护单元),用此函数创建的任务会受到MPU的保护。其他的功能和函数xTaskCreate()一样。
BaseType_t xTaskCreateRestricted( const TaskParameters_t * const pxTaskDefinition, TaskHandle_t *pxCreatedTask );
>>> 参数: pxTaskDefinition:指向一个结构体TaskParameters_t,这个结构体描述了任务的任务函数、堆栈大小、优先级等。此结构体在文件task.h中有定义。 pxCreatedTask:任务句柄。 返回值: pdPASS:任务创建成功。 其他值:任务创建失败,因为堆栈内存不足!
>>>4.函数vTaskDelete 删除一个用函数xTaskCreate()或者xTaskCreateStatic()创建的任务,被删除了的任务不再存在,也就是说再也不会进入运行态。任务被删除以后就不能再使用此任务的句柄!如果此任务是使用动态方法创建的,也就是使用函数 xTaskCreate()创建的,那么在此任务被删除以后此任务之前申请的堆栈和控制块内存会在空闲任务中被释放掉,因此当调用函数vTaskDelete()删除任务以后必须给空闲任务一定的运行时间。 只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉,比如某个任务中用户调用函数 pvPortMalloc()分配了500字节的内存,那么在此任务被删除以后用户也必须调用函数 vPortFree()将这500字节的内存释放掉,否则会导致内存泄露。此函数原型如下: vTaskDelete( TaskHandle_t xTaskToDelete )
>>>有时候我们需要暂停某个任务的运行,过一段时间以后在重新运行。这个时候要是使用任务删除和重建的方法的话那么任务中变量保存的值肯定丢失了!FreeRTOS给我们提供了解决这种问题的方法,那就是任务挂起和恢复,当某个任务要停止运行一段时间的话就将这个任务挂起,当要重新运行这个任务的话就恢复这个任务的运行。
>>>1.函数vTaskSuspend 此函数用于将某个任务设置为挂起态,进入挂起态的任务永远都不会进入运行态。退出挂起态的唯一方法就是调用任务恢复函数vTaskResume或xTaskResumeFromISR,函数原型如下: void vTaskSuspend( TaskHandle_t xTaskToSuspend) 参数: xTaskToSuspend:要挂起的任务的任务句柄,创建任务的时候会为每个任务分配一个任务句柄。 如果使用函数xTaskCreate()创建任务的话那么函数的参数pxCreatedTask就是此任务的任务句柄,如果使用函数xTaskCreateStatic()创建任务的话那么函数的返回值就是此任务的任务句柄。也可以通过函数xTaskGetHandle()来根据任务名字来获取某个任务的任务句柄。注意!如果参数为NULL的话表示挂起任务自己。 返回值:无。
>>>2.函数vTaskResume 将一个任务从挂起态恢复到就绪态,只有通过函数vTasksSuspend0设 置为挂起态的任务才可以使用vTaskRexume()恢复!函数原型如下: void vTaskResume( TaskHandle_t xTaskToResume) 参数: xTaskToResume:要恢复的任务的任务句柄。 返回值:无。
>>>3.函数vTaskResumeFromISR 此函数是vTaskResume()的中断版本,用于在中断服务函数中恢复一个任务。函数原型如下: BaseType_t xTaskResumeFromISR(TaskHandle t xTaskToResume) 参数: xTaskToResume:要恢复的任务的任务句柄。 返回值: pdTRUE:恢复运行的任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数以后必须进行一次上下文切换。 pdFALSE:恢复运行的任务的任务优先级低于当前正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数的以后不需要进行上下文切换。
>>>
Suspended:挂起态 Running:运行态 Blocked:阻塞态/等待态 Ready:就绪态
>>>FreeRTOS 操作系统支持三种调度方式:抢占式调度,时间片调度和合作式调度。实际应用主要是抢占式调度和时间片调度,合作式调度用到的很少。 合作式调度 亦称为FreeRTOS的协程,实际上是线程并发出来的,每个线程并发出来的协程共用一个栈空间。合作式调度主要用在资源有限的设备上面,现在已经很少使用了。出于这个原因,后面的 FreeRTOS 版本中不会将合作式调度删除掉,但也不会再进行升级了。 抢占式调度 每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的 API 函数,比如 vTaskDelay。 时间片调度 每个任务都有相同的优先级,任务会运行固定的时间片个数或者遇到阻塞式的 API 函数,比如vTaskDelay,才会执行同优先级任务之间的任务切换。如果用户在 FreeRTOS.h 中禁止使用时间片调度,那么每个任务必须配置不同的优先级。 路径:FreeRTOS.h
#ifndef configUSE_TIME_SLICING #define configUSE_TIME_SLICING 1#endif
>>>简单的说,调度器就是使用相关的调度算法来决定当前需要执行的任务。所有的调度器有一个共同的特性: 调度器可以区分就绪态任务和挂起任务(由于延迟,信号量等待,邮箱等待,事件组等待等原因而使得任务被挂起)。 调度器可以选择就绪态中的一个任务,然后激活它(通过执行这个任务)。当前正在执行的任务是运行态的任务。 不同调度器之间最大的区别就是如何分配就绪态任务间的完成时间。 嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法。任务切换的实现在不同的嵌入式实时操作系统中区别不大,基本相同的硬件内核架构,任务切换也是相似的。调度算法就有些区别了。下面我们主要了解一下抢占式调度器和时间片调度器。
>>>在实际的应用中,不同的任务需要不同的响应时间。例如,我们在一个应用中需要使用电机,键盘和LCD 显示。电机比键盘和 LCD 需要更快速的响应,如果我们使用合作式调度器或者时间片调度,那么电机将无法得到及时的响应,这时抢占式调度是必须的。 如果使用了抢占式调度,最高优先级的任务一旦就绪,总能得到 CPU 的控制权。比如,当一个运行着的任务被其它高优先级的任务抢占,当前任务的 CPU 使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了 CPU 的控制权并运行。又比如,如果中断服务程序使一个高优先级的任务进入就绪态,中断完成时,被中断的低优先级任务被挂起,优先级高的那个任务开始运行。 使用抢占式调度器,使得最高优先级的任务什么时候可以得到 CPU 的控制权并运行是可知的,同时使得任务级响应时间得以最优化。 总的来说,学习抢占式调度要掌握的最关键一点是:每个任务都被分配了不同的优先级,抢占式调度器会获得就绪列表中优先级最高的任务,并运行这个任务。
>>>在小型的嵌入式 RTOS 中,最常用的的时间片调度算法就是 Round-robin 调度算法。这种调度算法可以用于抢占式或者合作式的多任务中。另外,时间片调度适合用于不要求任务实时响应的情况。实现 Round-robin 调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务,并为每个任务分配一个时间片(也就是需要运行的时间长度,时间片用完了就进行任务切换)。 在RR调度策略下,一个线程会一直执行,直到: 自愿放弃控制权 被更高优先级的线程抢占 时间片用完 如下图所示,A在用完自己的时间片后,将CPU执行权让给线程B,于是A离开Read队列,而B进入Read队列。
一旦线程的时间片用完,该线程就会被下一个READ的具有同等优先级的线程给抢占。一个时间片通常是一个时钟周期的4倍。
>>>内容有点多,下节公布源码。喜欢就支持一下。已发布资源网盘自取 链接:https://pan.baidu.com/s/1CXlNeKZQbyyZ7mGeTtK7-g?pwd=xc0u 提取码:xc0u
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。