RTOS学习笔记--任务管理
相关目录
推荐学习原文《Mastering the FreeRTOS Real Time Kernel - a Hands On Tutorial Guide》
任务管理
任务函数
-
函数原型
void ATaskFunction( void *pvParameters );
必须声明为void,并且没有返回值。函数内必须为死循环。
-
创建任务
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode, const char * const pcName, uint16_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask );
pvTaskCode
函数主体指针,也即是函数名pcName
任务描述名称,RTOS不会以任意方式使用它。最大长度由configMAX_TASK_NAME_LEN
决定。usStackDepth
堆栈深度。用于给任务运行提供空间。实际大小为堆栈宽度×堆栈长度,最大不能超多uint16_t所能提供的最大值。任务剩余堆栈可以调试得出,因此不用担心如何分配。只需要分配足够的值,经过调试后在确定其最佳值。
Idle task
所占据的任务大小由configMINIMAL_STACK_SIZE
决定。pvParameters
传递到任务的参数指针,必须是void
型,在后面有示例如何使用它。uxPriority
任务优先级,由configMAX_PRIORITIES
决定最大优先级。0最低,configMAX_PRIORITIES=1
最高。pxCreatedTask
传递给任务的句柄,后续可以通过此句柄更改优先级或删除任务。创建任务有两种返回值,
pdPASS
或pdFAIL
,后者表示由于堆栈不足,任务创建失败。
任务优先级
任务优先级由xTaskCreate()
在创建任务时决定,也可以通过vTaskPrioritySet()
实时修改。
任务运行有两种方法,由configUSE_PORT_OPTIMISED_TASK_SELECTION
决定。为0时使用泛型,泛型的兼容性最好,可以在任何设备上运行,但是运行速度较慢。configMAX_PRIORITIES
的值会影响最坏运行时间。
设为1时,将使用架构优化型。此方法通过针对不同架构的汇编优化,运行时间快很多,并且不会受到configMAX_PRIORITIES
的影响。
修改任务
改变任务优先级
vTaskPrioritySet()
函数原型void vTaskPrioritySet( TaskHandle_t pxTask, UBaseType_t uxNewPriority );
pxTask
是任务句柄,可以传入NULL,表示修改正在运行的任务。uxNewPriority
新的优先级,不能大于configMAX_PRIORITIES – 1
。
获取任务优先级
uxTaskPriorityGet()
同上。
删除任务
vTaskDelete()
同上。
时钟测量和嘀嗒中断
时间切片(time slicing)
时间分片的核心思想是:如果任务不能在50毫秒内执行完,那么为了不阻塞主线程,这个任务应该让出主线程的控制权,使浏览器可以处理其他任务。让出控制权意味着停止执行当前任务,让浏览器去执行其他任务,随后再回来继续执行没有执行完的任务。
所以时间分片的目的是不阻塞主线程,而实现目的的技术手段是将一个长任务拆分成很多个不超过50ms的小任务分散在宏任务队列中执行。
时间分片是一项使用得比较广的技术方案,它的本质就是将长任务分割为一个个执行时间很短的任务,然后再一个个地执行。
为了防止单个任务阻塞运行长时间占用主线程,通常将一个任务分解成数个小任务,每个小任务占据的时间由切片决定。系统在单个小任务执行完后,会轮流执行其他的小任务,以确保每个任务都能按时执行。
需要注意的是,时间切片一般只针对同等优先级任务之间的资源分配,在不同优先级之间,仍然优先执行高优先级的任务。
在嵌入式中,由于需要频繁和各种设备之间通信,例如UART,I2C,SPI等,在通信中是不可以被打断的,不然会造成数据读取失败,更严重时会造成系统怠机,就需要分配不同优先级。
configTICK_RATE_HZ
定义了时间切片频率,也即是嘀嗒中断(tick interrupt)频率。一般不超过1000,即时间片大小不超过1ms。两个嘀嗒中断之间的间隔叫做嘀嗒周期(tick period),一个时间片占据一个嘀嗒周期。
不同的tick数会导致嘀嗒周期不同。vTaskDelay
函数用来在设置任务每次执行的间隔,不幸的是,它传入的是tick数,不同的嘀嗒中断频率会导致执行时间不一致。可以使用pdMS_TO_TICKS()
来解决这个问题。它可以将时间转为tick数,并且一般延时具体的时间才符合我们的正常逻辑,而不是延时某个周期数。
延时500ms:vTaskDelay(pdMS_TO_TICKS(500))
饿死
如果一个任务没有等待时间,即没有vTaskDelay
,且该任务处于较高优先级,那么在这个任务会永远处于正在运行状态,优先级更低的任务将永远得不到机会运行,也即是被该任务"饿死"。
但是在相同优先级的情况下,由于时间切片的存在,系统将会轮流执行不同的任务。
任务状态
任务并非只有正在运行和不在运行两种状态。
很容易理解,当一个任务不在运行时,它还是有很多不同的情况,比如等待一定时间(Temporal events)后运行,等待某件事发生后运行(Synchronization events),或者已经可以运行,等待被运行。
因此,不在运行状态又被细分成以下几种状态
-
The Suspended State
已挂起,被挂起的任务不再参与任务调度。唯一进入挂起状态的方法是
vTaskSuspend()
。已挂起的任务可以通过vTaskResume() or xTaskResumeFromISR()
恢复。 -
The Ready State
就绪,未被挂起或者阻塞的任务,这些任务已经准备运行,·· 但还未处于正在运行状态。
-
The Blocked State
阻塞,在等待触发事件的任务被称作阻塞态。事件有两种,一种和时间有关的Temporal events,另外一种是和某种信号相关的Synchronization events,例如queues, binary semaphores, counting semaphores, mutexes, recursive mutexes, event groups and direct to task notifications等。
任务延时
FreeRTOS提供了两种任务延时函数。
-
相对延时
vTaskDelay()
可以将任务置入阻塞态,直到达到延时时间。相对延时指的是计时从调用该函数后开始,也就是两次任务之间的时间间隔是固定的。但是任务本身执行时间每次都不一样,使用该延时不能保证任务以固定频率执行。 -
绝对延时
vTaskDelayUntil()
和vTaskDelay()
基本相同,不一样的是该函数计时时间是绝对的,可以保证程序以固定时间运行。也因此,延时时间不能小于任务单次最大执行时间。强迫机器以5ms的间隔执行20ms的任务显然是件难为机器的事。该函数的调用可以直接参考以下示例:void vTaskFunction( void *pvParameters ) { char *pcTaskName; TickType_t xLastWakeTime; /* The string to print out is passed in via the parameter. Cast this to a character pointer. */ pcTaskName = ( char * ) pvParameters; /* The xLastWakeTime variable needs to be initialized with the current tick count. Note that this is the only time the variable is written to explicitly. After this xLastWakeTime is automatically updated within vTaskDelayUntil(). */ xLastWakeTime = xTaskGetTickCount(); /* As per most tasks, this task is implemented in an infinite loop. */ for( ;; ) { /* Print out the name of this task. */ vPrintString( pcTaskName ); /* This task should execute every 250 milliseconds exactly. As per the vTaskDelay() function, time is measured in ticks, and the pdMS_TO_TICKS() macro is used to convert milliseconds into ticks. xLastWakeTime is automatically updated within vTaskDelayUntil(), so is not explicitly updated by the task. */ vTaskDelayUntil( &xLastWakeTime, pdMS_TO_TICKS( 250 ) ); } }