FreeRTOS原理剖析:任务的基础知识

1. 任务的基础知识

1.1 前后台和多任务系统
  • 在裸机程序中,一般方式是在main()函数的while(1)中循环执行所有的程序,有时,使用中断去执行一些紧急的功能,中断中执行的程序称为前台,main()函数中执行的程序称为后台。
  • 对于前后台框架的程序,必须当前函数执行完,才会去执行下一个函数,其实时性比较差,但它耗用的资源少。
  • 在实际项目中,main()中会有一个周期(如1ms)轮询去执行main()函数中所有函数,同时专门使用一个定时器以一定周期(如500us)执行比较重要的函数,对于实时性要求不是很精确的项目,这种框架还是可以满足。
  • FreeRTOS是一个可剥夺性内核的多任务系统。在多任务系统中,高优先级可以打断低优先级,能让更高优先级的任务得到CPU资源,完成这种功能的是任务调度器,其任务调度器也是可剥夺性任务调度器。
1.2 协程(Co-routine)的特性

协程是为那些资源很少的MCU而做的,FreeRTOS 存在协程功能,但不再更新和维护。协程和任务在概念上是相似的,但有如下不同:

  • 堆栈使用方面: 所有的协程使用同一个堆栈,这样消耗比较少的RAM,而使用FreeRTOS任务中,每个任务都有自己的堆栈。
  • 调度器和优先级: 协程使用合作式的调度器,但是可以在使用抢占式的调度器中使用协程。
  • 宏实现: 协程是通过宏定义来实现的。
  • 使用限制: 为了降低对RAM 的消耗做了很多的限制。
1.3 任务和任务状态

在FreeRTOS中,可以将任务理解为进程。任务由FreeRTOS调度器调度,有优先级,有独立堆栈空间等。每个时间只能执行一个任务,具体执行那个任务,由FreeRTOS调度器决定,每个任务必须有一个任务状态,如下:
在这里插入图片描述

  • 运行态: 当前正在运行的任务称为运行态,对于单核微控制器,运行态的任务有且仅有一个。
  • 就绪态: 就绪态的任务在就绪列表中,表示当前无阻塞和挂起,只是有更高优先级的任务在执行,随时等待调度器调度。刚刚创建的任务处于就绪态。
  • 阻塞态: 阻塞态的任务不在就绪列表中,不能被调度器调用。它表示正在等待一个外部事件,如队列,信号量,互斥量、事件标志组或通知等。阻塞态的任务一般会设置一个超时事件,外部事情不满足但超出时间,会退出阻塞态。
  • 挂起态: 挂起态的任务不能被调度器调用,无超时时间。唯一退出挂起态的方法就是调用vTaskResume()函数或vTaskResumeFromISR()函数。
1.4 任务优先级
  • 优先级数字越低,任务优先级越低,优先级处于0 ~ (configMAX_PRIORITIES-1)。
  • 空闲任务的优先级最低,在启动调度器的时候被创建,任务优先级为0。
  • 任务调度器确保处于就绪态和运行态的高优先级任务获取CPU所有权。
  • 就绪态中,优先级相同的任务会使用时间片轮转调度器获取运行时间。
  • configUSE_PORT_OPTIMISED_TASK_SELECTION设置为1时,通过硬件平台支持类似计算前导零指定获取下一个要运行的任务,优先级数量不能超过32级,即configMAX_PRIORITIES不能超过32。
1.5 任务控制块

每个任务都有一个任务控制块,本质是定义了一个结构体,包含了任务的所有信息,如堆栈的储存地址,优先级,状态列表和事件列表等等属性。在创建任务时,会给任务分配一个任务控制块TCB_t,用来储存任务的所有属性。该任务控制块结构体在task.c中定义,如下:

typedef struct tskTaskControlBlock
{
	volatile StackType_t	*pxTopOfStack;	/* 栈顶指针,中断或任务切换时,会对任务压栈或入栈,该指针指向SP */

	/* 启用MPU的情况下设置 */
	#if ( portUSING_MPU_WRAPPERS == 1 )
		/* 设置任务访问的内存权限 */
		xMPU_SETTINGS	xMPUSettings;		
	#endif
	
	ListItem_t			xStateListItem;		/* 状态列表任务会处于不同的状态,该项会被插入到对应的链表,如就绪列表、延时列表和挂起列表 */
	ListItem_t			xEventListItem;		/* 事件列表,表示等待的具体事件,如等待queue资源 */
	UBaseType_t			uxPriority;			/* 任务优先级 */
	StackType_t			*pxStack;			/* 任务堆栈指针,指向任务堆栈的首地址 */
	char				pcTaskName[ configMAX_TASK_NAME_LEN ];	/* 任务名字 */

	#if ( portSTACK_GROWTH > 0 )
		StackType_t		*pxEndOfStack;		/* 对于向上生长的栈,用于指明栈的上边界,用于判断是否溢出 */
	#endif
	
	/* 保存临界区嵌套深度 */
	#if ( portCRITICAL_NESTING_IN_TCB == 1 )
		UBaseType_t		uxCriticalNesting;	
	#endif

	/* trace或debug时候用到 */
	#if ( configUSE_TRACE_FACILITY == 1 )
		/* 用于调试,表示本任务是第几个创建,每创建一个任务,系统有一个全局变量加1,并将该值赋给新任务 */
		UBaseType_t		uxTCBNumber;	
			
		/* 调试用,用户通过API函数vTaskSetTaskNumber()来设置,数值由函数参数指定 */
		UBaseType_t		uxTaskNumber;		
	#endif
	
	/* 如果使用任务互斥量信号 */
	#if ( configUSE_MUTEXES == 1 )
		UBaseType_t		uxBasePriority;	/* 优先级提升前,保存原优先级,优先级反转时候用到 */
		UBaseType_t		uxMutexesHeld;	/* 任务获取的互斥信号量个数,为0时,将其优先级还原到初始优先级*/
	#endif

	/* 如果使能任务标签功能 */
	#if ( configUSE_APPLICATION_TASK_TAG == 1 )
		TaskHookFunction_t pxTaskTag;
	#endif

	/* 
	 * 与本地储存有关 
	 * 有pvTaskGetThreadLocalStoragePointer和vTaskSetThreadLocalStoragePointer配合使用
	 */
	#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
		void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
	#endif

	#if( configGENERATE_RUN_TIME_STATS == 1 )
		uint32_t		ulRunTimeCounter;		/* 记录任务运行的总时间,实现获取任务cpu占用率信息*/
	#endif
	
	/* 
	 * 如果使用Newlib运行库
	 * 为了保证重入性,就不得不为每个任务实现一套struct _reent xNewLib_reent,默认使用的glibc
	 * 你的操作系统资源不够,而不得不选择newlib,就必须打开该宏
	 */
	#if ( configUSE_NEWLIB_REENTRANT == 1 )
		struct	_reent xNewLib_reent;			/* 定义一个newlib结构体变量 */
	#endif

	/* 与任务通知相关 */
	#if( configUSE_TASK_NOTIFICATIONS == 1 )
		volatile uint32_t ulNotifiedValue;		/* 任务通知值 */
		volatile uint8_t ucNotifyState;			/* 任务通知状态 */
	#endif
	
	#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
		/*
		 * 标记任务是动态创建还是静态创建
		 * 动态创建时,为pdTURE;静态创建时,为pdFALSE。 
		 */
		uint8_t	ucStaticallyAllocated; 		
	#endif

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

} tskTCB;

/* 新版本将任务控制块重命名为TCB_t,旧版本定义为takTCB,兼容旧版本 */
typedef tskTCB TCB_t;
1.6 任务堆栈
  • 在创建任务时,需要指定任务堆栈深度ulStackDepth,该值是uint32_t类型,即实际定义的堆栈大小为 4 * ulStackDepth。内存申请函数参数为size_t类型,即堆栈能分配的最大字节数为类型size_t能表示的最大数字。
  • 函数xTaskCreate()动态创建任务时,只需要定义任务堆栈深度ulStackDepth,堆栈空间函数自动创建,函数xTaskCreateStatic()静态方法创建需要自定义任务堆栈,然后将首地址作为参数参数带入。
  • 在任务切换时,栈用来保存任务现场,如CPU 寄存器值。如下次任务恢复时,从栈中恢复现场值。
1.7 任务相关的全局变量
/* 指向当前正在运行的任务 */
PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL;

/* 
 * 定义了configMAX_PRIORITIES 个就绪列表,按照优先级进行排序
 * 不同优先级的任务挂在不同的就绪列表中 
 * 在调度时就很方便从优先级高的就绪表中先进行调度
 */
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];

/* 定义了两个延时列表,任务延时功能用了两个延时列表 */
PRIVILEGED_DATA static List_t xDelayedTaskList1;						
PRIVILEGED_DATA static List_t xDelayedTaskList2;

/* 
 * 为两个延时列表定义了两个指针
 * pxDelayedTaskList 指向 xDelayedTaskList1
 * pxOverflowDelayedTaskList 指向 xDelayedTaskList2
 */		
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;				
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;	
	
/* 任务准备就绪,但是调度器挂起,则将该任务放入列表,在xTaskResumeAll时,重新放入就绪表中*/
PRIVILEGED_DATA static List_t xPendingReadyList;						


#if( INCLUDE_vTaskDelete == 1 )
	/* 等待结束的任务,对于一些特殊删除的任务,如任务删除本身,因为还没有执行完,不能立即释放空间,则会先放在此列表中 */
	PRIVILEGED_DATA static List_t xTasksWaitingTermination;				
	PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp = ( UBaseType_t ) 0U;
#endif


#if ( INCLUDE_vTaskSuspend == 1 )
	/* 任务挂起列表 */
	PRIVILEGED_DATA static List_t xSuspendedTaskList;					
#endif

/* 当前任务的总数 */
PRIVILEGED_DATA static volatile UBaseType_t uxCurrentNumberOfTasks 	= ( UBaseType_t ) 0U;

/* 系统开始后所有的tick计数 */
PRIVILEGED_DATA static volatile TickType_t xTickCount 				= ( TickType_t ) 0U;

/* 
 * 记录当前就绪表中优先级最高的任务,32位的无符号整形数据,表示最高32个优先级 
 * 需要将configUSE_PORT_OPTIMISED_TASK_SELECTION置1,才有效
 */
PRIVILEGED_DATA static volatile UBaseType_t uxTopReadyPriority 		= tskIDLE_PRIORITY;

/* 表示调度器是否运行 */
PRIVILEGED_DATA static volatile BaseType_t xSchedulerRunning 		= pdFALSE;

/* 暂停调度器期间,记录未被处理的ticks个数 */
PRIVILEGED_DATA static volatile UBaseType_t uxPendedTicks 			= ( UBaseType_t ) 0U;

/* 在某种临界状态下,任务状态发生改变,需要等待从新调度 */
PRIVILEGED_DATA static volatile BaseType_t xYieldPending 			= pdFALSE;

/* 记录tick计数翻转的次数 */
PRIVILEGED_DATA static volatile BaseType_t xNumOfOverflows 			= ( BaseType_t ) 0;

/* 记录全局任务数,为新建的任务分配一个TaskNumber */
PRIVILEGED_DATA static UBaseType_t uxTaskNumber 					= ( UBaseType_t ) 0U;

/* 记录了延时链表中,第一个需要被唤醒的任务时间点 */
PRIVILEGED_DATA static volatile TickType_t xNextTaskUnblockTime		= ( TickType_t ) 0U; 

/* 表示空闲任务的句柄 */
PRIVILEGED_DATA static TaskHandle_t xIdleTaskHandle					= NULL;			

/* 调度器暂定标志 */
PRIVILEGED_DATA static volatile UBaseType_t uxSchedulerSuspended	= ( UBaseType_t ) pdFALSE;

#if ( configGENERATE_RUN_TIME_STATS == 1 )
	PRIVILEGED_DATA static uint32_t ulTaskSwitchedInTime = 0UL;	/* 用于记住任务切换的时间 */
	PRIVILEGED_DATA static uint32_t ulTotalRunTime = 0UL;		/* 系统的总运行时间 */
#endif

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值