FreeRTOS的任务理论

2 FreeRTOS的任务理论

2.1 任务及任务优先级

​ 简单来说,任务是指可独立运行的基本执行单元。任务是并发执行的最小单位,每个任务都有自己的代码逻辑和资源。FreeRTOS多任务执行其实是多任务交替执行实现的。实现多任务交替执行的基础是tick中断,滴答中断,周期性的定时器中断。类比Linux,我们可以类比的认为一个任务相当于一个线程,同时,任务也有不同的种类和实现方式,比如说定时器任务等等。

​ 在FreeRTOS中,任务具有不同的优先级,不过FreeRTOS中,优先级是与大多数操作系统相反的,其值越小,优先级越低,**反之越大,优先级越高。**和CubeMX中的配置也是相反的。关于优先级我们需要注意以下几点:

  • 优先级相同时,任务是可以交替进行的,但是需要同优先级正在运行的任务释放自己的资源,例如使用vTaskDelete(NULL)退出,taskYIELD()释放等。或使用vTaskDelay()等函数暂时的释放了CPU资源时,才会轮到另一个任务执行;
  • 优先级不同时,高优先级的任务先执行,执行结束后才轮到低优先级的任务。这里需要注意的是,高优先级的任务中如果也有vTaskDelete(NULL),或vTaskDelay()等暂时的释放CPU资源的函数存在,这时候调度器会自动执行低优先级的的任务。而不是等着高优先级的delay延迟结束。
  • 任务的优先级不可以无限制的高,在FreeRTOSConfig.h中有一个宏configMAX_PRIORITIES规定了能到达的最高的优先级,其优先级的取值范围是( 0 ~ configMAX_PRIORITIES-1 )。 ps:注意是最大值是 configMAX_PRIORITIES-1而不是 configMAX_PRIORITIES-1
  • 这里我要再次强调,任务的优先级不是中断的优先级,任务也不是中断,任务优先级的高低仅仅决定了在就绪态的队伍中的排队次序,若低优先级的任务一直不释放CPU资源,那么再高的优先级也无法执行!!!
/*
  @brief  设置任务的优先级
  @retval None
  @param  pxTask:要修改优先级的任务句柄,通过NULL改变任务自身优先级
          uxNewPriority:要修改的任务优先级 
*/
void vTaskPrioritySet(TaskHandle_t pxTask, UBaseType_t uxNewPriority);
 
/*
  @brief  获取任务优先级
  @retval 任务优先级
  @param  pxTask:要获取任务优先级的句柄,通过NULL获取任务自身优先级
*/
UBaseType_t uxTaskPriorityGet(TaskHandle_t pxTask);

2.2 任务状态理论

2.2.1 任务状态的转换

在通用操作系统中有5态(或3态),而在FreeRTOS中稍有不同,他拥有4个状态。同时要注意,FreeRTOS中的任务函数,是一个永远不会退出的C函数,是无法使用return来退出的,要彻底清除他我们必须使用vTaskDelete(NULL)删除它。

  1. (RUNNING)运行态:正在运行的任务,只能有一个。
  2. (Ready)就绪态:条件Event都满足了,只等待正在运行的任务释放CPU后就轮到他来执行了。
  3. (Block)阻塞态:相当于等待态,在等待IO或者条件。
  4. (Suspended)挂起态:将运行到一半的程序挂起(主动暂停),暂时脱离调度器的调度,但是不可以直接回到运行态,只能回到就绪态。

他们的转换关系如下:

在这里插入图片描述

2.2.2 任务状态改变相关函数

/*
  @brief  查询一个任务当前处于什么状态
  @retval 任务状态的枚举类型
  @param  pxTask:要查询任务状态的任务句柄,NULL查询自己
*/
eTaskState eTaskGetState(TaskHandle_t pxTask);
 
/*任务状态枚举类型返回值*/
typedef enum
{
	eRunning = 0,		/* 任务正在查询自身的状态,因此肯定是运行状态 */
	eReady,				  /* 就绪状态 */
	eBlocked,		 		/* 阻塞状态 */
	eSuspended,			/* 挂起状态 */
	eDeleted,				/* 正在查询的任务已被删除,但其 TCB 尚未释放 */
	eInvalid				/* 无效状态 */
} eTaskState;
  1. (RUNNING)运行态:

    没有主动使得任务进入运行态的函数,同时要注意,单核处理器,那么同一时间只可能有一个任务再执行
    
  2. (Ready)就绪态:

  3. (Block)阻塞态:

    //vTaskDelay()和vTaskDelayUntil()这两个函数,都会使得任务进入阻塞状态。
      
    //当一个任务因为延时函数或者其他同步事件进入阻塞状态后,可以通过 xTaskAbortDelay() API 函数终止任务的阻塞状态,即使事件任务等待尚未发生,或者任务进入时指定的超时时间阻塞状态尚未过去,都会使其进入就绪状态,具体函数描述如下所述
    /*
      @brief  终止任务延时,退出阻塞状态
      @retval pdPASS:任务成功从阻塞状态中删除,pdFALSE:任务不属于阻塞状态导致删除失败
      @param  xTask:操作的任务句柄
    */
    BaseType_t xTaskAbortDelay(TaskHandle_t xTask);
    
  4. (Suspended)挂起态:

    /*
      @brief  将某个任务挂起
      @retval None
      @param  pxTaskToSuspend:被挂起的任务的句柄,通过传入NULL来挂起自身
    */
    void vTaskSuspend(TaskHandle_t pxTaskToSuspend);
     
    /*
      @brief  将某个任务从挂起状态恢复
      @retval None
      @param  pxTaskToResume:正在恢复的任务的句柄
    */
    void vTaskResume(TaskHandle_t pxTaskToResume);
     
    /*
      @brief  vTaskResume的中断安全版本
      @retval 返回退出中断之前是否需要进行上下文切换(pdTRUE/pdFALSE)
      @param  pxTaskToResume:正在恢复的任务的句柄
    */
    BaseType_t xTaskResumeFromISR(TaskHandle_t pxTaskToResume);
    

2.2.3 调度器相关函数

/*
  @brief  启动调度器
  @retval None
*/
void vTaskStartScheduler(void);
 
/*
  @brief  停止调度器
  @retval None
*/
void vTaskEndScheduler(void);
 
/*
  @brief  挂起调度器
  @retval None
*/
void vTaskSuspendAll(void);
 
/*
  @brief  恢复调度器
  @retval 返回是否会导致发生挂起的上下文切换(pdTRUE/pdFALSE)
*/
BaseType_t xTaskResumeAll(void);


/*
  @brief  让位于另一项同等优先级的任务
  @retval None
*/
void taskYIELD(void);
 
/*
  @brief  ISR 退出时是否执行上下文切换(汇编)
  @retval None
  @param  xHigherPriorityTaskWoken:pdFASLE不请求上下文切换,反之请求上下文切换
*/
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
 
/**
  @brief  ISR 退出时是否执行上下文切换(C语言)  
  @retval None
  @param  xHigherPriorityTaskWoken:pdFASLE不请求上下文切换,反之请求上下文切换
*/
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

2.3 FreeRTOS延时

​ 在裸机开发中,我们常常使用HAL_Delay()来实现延时功能,但FreeRTOS开发中,此时系统滴答是FreeRTOS主导的,所以我们应使用FreeRTOS中的延时函数来实现延迟。这两个函数都会使得当前的任务进入阻塞状态,使其他的任务有机会运行

2.3.1 vTaskDelay延时

/*			
		@brief:相对延时函数,宏定义INCLUDE_vTaskDelay必须定义为1,此函数才可用。相对延时,指的是从调用函数后开始计时,直到延时					 指定的时间结束。观察源代码发现,原来他只是把任务挂起(挂起态),等延时时间到,再把任务恢复(注意此时不是直接进入到					 运行态,而是就绪态)。
		@retval:None
		@param:const TickType_t xTicksToDelay:表示延时的时钟周期数(tick),configTICK_RATE_HZ配置FreeRTOS的内核时钟周期。																				 例如,如果内核时钟为1kHz,那么1tick对应的时间为1ms。																														  这个函数是仅仅通过tick数来计算延时持续的时间。
*/
void vTaskDelay(const TickType_t xTicksToDelay);

2.3.2 vTaskDelayUntil延时

/*			
		@brief:绝对延时函数,允许任务在指定的绝对时间再次激活。
		@retval:None
		@param:TickType_t *pxPreviousWakeTime:一个指针,指向一个变量,这个变量存储着上次任务唤醒的时刻。
					 TickType_t xTimeIncrement:每次唤醒后,需要延时的时间。
*/
void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement);

2.3.3 pdMS_TO_TICKS(x)宏

//是一个宏而不是函数,前面所使用的Delay函数是使用tick来计时的,但是我们人更喜欢用ms,s来作为延时的单位,而这个宏就是用来将毫秒数转化为相应的时钟滴答数(tick次数)的。
#ifndef pdMS_TO_TICKS
    #define pdMS_TO_TICKS( xTimeInMs ) 																																\
( (TickType_t) ( ( (TickType_t) (xTimeInMs) * (TickType_t) configTICK_RATE_HZ) / (TickType_t)1000U ) )
#endif

2.4 TCB任务控制块

​ 对于每一个task任务来讲,都有一个TCB_t结构体。这个结构体就叫做任务控制块。我们创建任务时最后的句柄handle就是指向的这个结构体。这个控制块里面包含的是任务的一些基本信息,例如任务状态,任务的优先级,任务栈的头指针,尾指针,任务的标识符等。(下面我们只看一些最重要的变量,重要的变量我将用中文进行标注)

typedef struct tskTaskControlBlock //TCB_T的旧名字      
{
    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 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
        StackType_t * pxEndOfStack; //指向栈最深的有效地址,也就是栈底。
    #endif

    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t uxCriticalNesting; 
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxTCBNumber; //存储一个特定的值来标识这个TCB任务块
        UBaseType_t uxTaskNumber; //对于任务,存储了一个特殊的值来表示任务,方便我们进行追踪,错误的定位等。
    #endif

    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
        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 )
        configRUN_TIME_COUNTER_TYPE ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
    #endif

    #if ( ( configUSE_NEWLIB_REENTRANT == 1 ) || ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 ) )
        configTLS_BLOCK_TYPE xTLSBlock; /*< Memory block used as Thread Local Storage (TLS) Block for the task. */
    #endif

    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
        volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    #endif

    /* See the comments in FreeRTOS.h with the definition of
     * tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
    #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
        uint8_t ucStaticallyAllocated;                     /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
    #endif

    #if ( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif

    #if ( configUSE_POSIX_ERRNO == 1 )
        int iTaskErrno;
    #endif
} tskTCB;

typedef tskTCB TCB_t;

2.5 任务调度宏与任务调度算法

FreeRTOS的调度算法一般有三种,是否启用的宏定义在FreeRTOSConfig.h文件夹下可以看到,他们分别是:

#define configUSE_PREEMPTION //是否允许高优先级抢占 1允许 0不允许
#define configUSE_TIME_SLICING //是否允许时间片轮转 1允许 0不允许
#define configIDLE_SHOULD_YIELD	//是否允许空闲任务让步 1允许 0不允许

接下来我们来介绍这三种调度方式:

  1. 优先级抢占:即高优先级的任务是否可以优先抢占,其实对应的就是在就绪态任务中任务排队的次序,优先级越高,排队越靠前,越优先抢占。
  2. 时间片轮转:即同优先级的任务是否可以轮流运转,当宏定义为1时,可以轮流运行。
  3. 空闲任务让步:如果配置了让步的宏,空闲函数可以执行时,空闲函数只触发一次调度,调度后,又主动让出CPU资源让用户task执行。如果未配置的话,将会在空闲函数的while循环里多次循环,也就是说一直处于空闲任务执行的状态。

PS:我们一般使用的是111,也就是全使能的模式,允许高优先级抢占,允许时间片轮转,允许空闲任务让步。

  • 43
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值