在学习RTOS的时候,个人觉得带着问题去学习,会了解到更多。
1、什么是任务?
------ 在FreeRTOS中,每个执行线程都被称为”任务”。每个任务都是在自己权限范围内的一个小程序。其具有程序入口每个任务都是在自己权限范围内的一个小程序。。其具有程序入口通常会运行在一个死循环中,也不会退出。
一个任务函数可以用来创建若干个任务——创建出的任务均是独立的执行实例,拥有属于自己的栈空间,以及属于自己的自动变量(栈变量),即任务函数本身定义的变量。
任务从非运行态转移到运行态被称为”切换入或切入(switched in)”或”交换入(swapped in)”。相反,任务从运行态转移到非运行态。被称为”切换出或切出(switched out)”或”交换出(swapped out)”。FreeRTOS的调度器是能让任务切入切出的唯一实体。
2、如何创建一个或多个任务的实例
------- 创建任务使用FreeRTOS的API函数xTaskCreate()。
portBASE_TYPE xTaskCreate(pdTASK_CODE pvTaskCode,
const signed portCHAR * const pcName,
unsigned portSHORT usStackDepth,
void *pvParameters,
unsigned portBASE_TYPE xPriority,
xTaskHandle *pxCreatedTask);
pvTaskCode : 任务只是永不退出的C函数,实现常通常是一个死循环。参数pvTaskCode只一个指向任务的实现函数的指针 (效果上仅仅是函数名)
pcName : 具有描述性的任务名。这个参数不会被FreeRTOS使用。其只是单纯地用于辅助调试。识别一个具有可读性的名 字总是比通过句柄来识别容易得多。
usStackDepth : 当任务创建时,内核会分为每个任务分配属于任务自己的唯一状态。usStackDepth值用于告诉内核为它分 配多大的栈空间。这个值指定的是栈空间可以保存多少个字(word),而不是多少个字节(byte)。比如说,如 果是32位宽的栈空间传入的usStackDepth值为100,则将会分配400字节的栈空间(100 * 4bytes)。栈深度乘 以栈宽度的结果千万不能超过一个size_t类型变量所能表达的最大值。
pvParameters : 任务函数接受一个指向void的指针(void*)。pvParameters的值即是传递到任务中的值。
xPriority : 指定任务执行的优先级。
pxCreatedTask : pxCreatedTask用于传出任务的句柄。这个句柄将在API调用中对该创建出来的任务进行引用,比如改变 任务优先级,或者删除任务
返回值 : 有两个可能的返回值: 1、pdTRUE 表明任务创建成功;
2、errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
由于内存堆空间不足, FreeRTOS无法分配足够的空间来保存任务结构数据和任务 栈,因此无法创建任务。
3、任务优先级如何影响系统行为。
-------------
调度器保证总是在所有可运行的任务中选择具有最高优先级的任务,并使其进入运行态。如果被选中的优先级上具有不止一个任务,调度器会让这些任务轮流执行。所以每个任务都执行一个“时间片”,任务在时间片起始时刻进入运行态,在时间片结束时刻又退出运行态。
要能够选择下一个运行的任务,调度器需要在每个时间片的结束时刻运行自己本身。一个称为心跳(tick,有些地方被称为时钟滴答、时钟心跳)中断的。周期性中断用于此目的。时间片的长度通过心跳中断的频率进行设定。
4、任务存在哪些状态。
-------------
为了使我们的任务切实有用,我们需要通过某种方式来进行事件驱动。一个事件驱动任务只会在事件发生后触发工作(处理),而在事件没有发生时是不能进入运行态的。
调度器总是选择所有能够进入运行态的任务中具有最高优先级的任务。一个高优先级但不能够运行的任务意味着不会被调度器选中,而代之以另一个优先级虽然更低但能够运行的任务。
因此,采用事件驱动任务的意义就在于任务可以被创建在许多不同的优先级上,并且最高优先级任务不会把所有的低优先级任务饿死。
阻塞状态(blocked) :
如果一个任务正在等待某个事件,则称这个任务处于”阻塞态(blocked)”。阻塞态是非运行态的一个子状态。
任务可以进入阻塞态以等待以下两种不同类型的事件:
1.、定时(时间相关)事件——这类事件可以是延迟到期或是绝对时间到点。比如说某个任务可以进入阻塞态以延迟10ms。
2.、同步事件——源于其它任务或中断的事件。比如说,某个任务可以进入阻塞态以等待队列中有数据到来。同步事件囊括 了所有板级范围内的事件类型。
FreeRTOS的队列,二值信号量,计数信号量,互斥信号量(recursive semaphore,递归信号量,本文一律称为互斥信号量,因为其主要用于实现互斥访问)和互斥量都可以用来实现同步事件。
任务可以在进入阻塞态以等待同步事件时指定一个等待超时时间,这样可以有效地实现阻塞状态下同时等待两种类型的事件。比如说,某个任务可以等待队列中有数据到来,但最多只等10ms。如果10ms内有数据到来,或是10ms过去了还没有数据到来,这两种情况下该任务都将退出阻塞态。
//利用阻塞态进行延迟
void uTaskDelay(portTickType xTickToDelay);
/* xTickToDelay : 延迟多少个心跳周期。调用该延迟函数的任务将进入阻塞态,经延迟指定的心跳周期数
后,再转移到就绪态。
*/
挂起状态(suspended) :
“挂起(suspended)”也是非运行状态的子状态。处于挂起状态的任务对调度器而言是不可见的。让一个任务进入挂起状态的唯一办法就是调用vTaskSuspend() API函数;而把一个挂起状态的任务唤醒的唯一途径就是调用vTaskResume()或vTaskResumeFromISR()API函数。大多数应用程序中都不会用到挂起状态。
就绪状态(ready) :
如果任务处于非运行状态,但既没有阻塞也没有挂起,则这个任务处于就绪(ready,准备或就绪)状态。处于就绪态的任务能够被运行,但只是”准备(ready)”运行,而当前尚未运行。
完整的状态转移图:
5、空闲任务何时运行,可以用来干什么
--------
创建的任务大部份时间都处于阻塞态。这种状态下所有的任务都不可运行,所以也不能被调度器选中。但处理器总是需要代码来执行——所以至少要有一个任务处于运行态。为了保证这一点,当调用vTaskStartScheduler()时,调度器会自动创建一个空闲任务。空闲任务是一个非常短小的循环——总是可以运行。
空闲任务拥有最低优先级(优先级0)以保证其不会妨碍具有更高优先级的应用任务进入运行态——当然,没有任何限制说是不能把应用任务创建在与空闲任务相同的优先级上;如果需要的话,你一样可以和空闲任务一起共享优先级。
运行在最低优先级可以保证一旦有更高优先级的任务进入就绪态,空闲任务就会立即切出运行态。
空闲任务钩子函数 :
通过空闲任务钩子函数(或称回调,hook, or call-back),可以直接在空闲任务中添加应用程序相关的功能。空闲任务钩子函数会被空闲任务每循环一次就自动调用一次。
通常空闲任务钩子函数被用于:
1、执行低优先级,后台或需要不停处理的功能代码。
2、测试处系统处理裕量(空闲任务只会在所有其它任务都不运行时才有机会执行,所以测量出空闲任务占用的处理时间就可以清楚的知道系统有多少富余的处理时间)。
3、将处理器配置到低功耗模式——提供一种自动省电方法,使得在没有任何应用功能需要处理的时候,系统自动进入省电模式。
空闲任务钩子函数的实现必须遵从以下规则:
1.、绝不能阻塞或挂起。空闲任务只会在其它任务都不运行时才会被执行(除非有应用任务共享空闲任务优先级)。以任何方式阻塞空闲任务都可能导致没有任务能够进入运行态。
2、如果应用程序用到了vTaskDelete() AP函数,则空闲钩子函数必须能够尽快返回。因为在任务被删除后,空闲任务负责回收内核资源。如果空闲任务一直运行在钩子函数中,则无法进行回收工作。
8、如何删除任务
-----------
任务可以使用API函数vTaskDelete()删除自己或其它任务。任务被删除后就不复存在,也不会再进入运行态。
空闲任务的责任是要将分配给已删除任务的内存释放掉。
因此有一点很重要,那就是使用vTaskDelete() API函数的任务千万不能把空闲任务的执行时间饿死。
需要说明一点,只有内核为任务分配的内存空间才会在任务被删除后自动回收。任务自己占用的内存或资源需要由应用程序自己显式地释放。
void vTaskdelete(xTaskHandle pxTaskToDelete)
/* pxTaskToDelete : 被删除任务的句柄(目标任务),任务可以通过传入NULL值来删除自己. */