空闲任务与阻塞延时的实现

空闲任务与阻塞延时的实现

RTOS 中的延时叫阻塞延时,即任务需要延时的时候,任务会放弃 CPU 的使用权,CPU 可以去干其它的事情,当任务延时时间到,重新获取 CPU 使用权,任务继续运行,这样就充分地利用了 CPU 的资源,而不是干等着。

当任务需要延时,进入阻塞状态。此时如果没有其它任务可以运行,RTOS 都会为 CPU 创建一个空闲任务,这个时候 CPU 就运行空闲任务。在FreeRTOS 中,空闲任务是系统在【启动调度器】的时候创建的优先级最低的任务,空闲任 务主体主要是做一些系统内存的清理工作。

1、实现空闲任务

(1)定义空闲任务的栈

/* 在 main.c 中定义空闲任务的栈 */
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) 
StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; 

(2)定义空闲任务的任务控制块

/* 在 main.c 中定义空闲任务的任务控制块 */
TCB_t IdleTaskTCB;

(3)创建空闲任务

extern TCB_t IdleTaskTCB;
void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer,
									StackType_t **ppxIdleTaskStackBuffer,
									uint32_t *pulIdleTaskStackSize );
void vTaskStartScheduler( void )
{
	/*=======================创建空闲任务 start=======================*/ 
	TCB_t *pxIdleTaskTCBBuffer = NULL; /* 用于指向空闲任务控制块 */ 
	StackType_t *pxIdleTaskStackBuffer = NULL; /* 用于空闲任务栈起始地址 */ 
	uint32_t ulIdleTaskStackSize; 

	/* 获取空闲任务的内存:任务栈和任务 TCB */ 
	vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, 
									&pxIdleTaskStackBuffer, 
									&ulIdleTaskStackSize ); 
	/* 创建空闲任务 */ 
	xIdleTaskHandle = 
xTaskCreateStatic( (TaskFunction_t)prvIdleTask, /* 任务入口 */ 
					(char *)"IDLE", /* 任务名称,字符串形式 */ 
					(uint32_t)ulIdleTaskStackSize , /* 任务栈大小,单位为字 */ 
					(void *) NULL, /* 任务形参 */ 
					(StackType_t *)pxIdleTaskStackBuffer, /* 任务栈起始地址 */ 
					(TCB_t *)pxIdleTaskTCBBuffer ); /* 任务控制块 */ 
	/* 将任务添加到就绪列表 */ 
	vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) ); 
	/*==========================创建空闲任务 end=====================*/ 

	/* 手动指定第一个运行的任务 */
	pxCurrentTCB = &Task1TCB;

	/* 启动调度器 */
	if ( xPortStartScheduler() != pdFALSE ){
		/* 调度器启动成功,则不会返回,即不会来到这里 */
	}
}

2、实现阻塞延时

阻塞延时的阻塞是指任务调用该延时函数后,任务会被剥离 CPU 使用权,然后进入阻塞状态,直到延时结束,任务重新获取 CPU 使用权才可以继续运行。

在任务阻塞的这段时间,CPU 可以去执行其它的任务,如果其它的任务也在延时状态,那么 CPU 就将运行空闲任务。

(1)vTaskDelay ()函数

void vTaskDelay( const TickType_t xTicksToDelay )
{
	TCB_t *pxTCB = NULL;
	
	/* 获取当前任务的 TCB */
	pxTCB = pxCurrentTCB; 
	
	/* 设置延时时间 */
	pxTCB->xTicksToDelay = xTicksToDelay; 
	
	/* 任务切换 */
	taskYIELD();
}

xTicksToDelay 定义

typedef struct tskTaskControlBlock
{
	volatile StackType_t *pxTopOfStack; /* 栈顶 */
	
	ListItem_t xStateListItem; /* 任务节点 */
	
	StackType_t *pxStack; /* 任务栈起始地址 */
	/* 任务名称,字符串形式 */
	char pcTaskName[ configMAX_TASK_NAME_LEN ];
	
	TickType_t xTicksToDelay; /* 用于延时 */ 
} tskTCB;

(2)修改 vTaskSwitchContext()函数

调用 tashYIELD()会产生 PendSV中断,在 PendSV中断服务函数中会调用上下文切换函数 vTaskSwitchContext(),该函数的作用是寻找最高优先级的就绪任务,然后更新 pxCurrentTCB。

多增加了一个空闲任务,则需要让pxCurrentTCB 在这三个任务中切换,算法需要改变:

#if 0
void vTaskSwitchContext( void )
{
	/* 两个任务轮流切换 */
	if ( pxCurrentTCB == &Task1TCB ){
		pxCurrentTCB = &Task2TCB;
	}
	else{
		pxCurrentTCB = &Task1TCB;
	}
}
#else

void vTaskSwitchContext( void ) 
{ 
	/* 如果当前任务是空闲任务,那么就去尝试执行任务 1 或者任务 2, 
	看看他们的延时时间是否结束,如果任务的延时时间均没有到期, 
	那就返回继续执行空闲任务 */ 
	if ( pxCurrentTCB == &IdleTaskTCB ){ 
		if (Task1TCB.xTicksToDelay == 0){ 
			pxCurrentTCB =&Task1TCB; 
		} 
		else if (Task2TCB.xTicksToDelay == 0) { 
			pxCurrentTCB =&Task2TCB; 
		} 
		else { 
			return; /* 任务延时均没有到期则返回,继续执行空闲任务 */ 
		} 
	} 
	else /* 当前任务不是空闲任务则会执行到这里 */
	{ 
		/*如果当前任务是任务 1 或者任务 2 的话,检查下另外一个任务, 
		如果另外的任务不在延时中,就切换到该任务 
		否则,判断下当前任务是否应该进入延时状态, 
		如果是的话,就切换到空闲任务。否则就不进行任何切换 */ 
		if (pxCurrentTCB == &Task1TCB) { 
			if (Task2TCB.xTicksToDelay == 0) { 
				pxCurrentTCB =&Task2TCB; 
			} 
			else if (pxCurrentTCB->xTicksToDelay != 0) { 
				pxCurrentTCB = &IdleTaskTCB;
			} 
			else { 
				return; /* 返回,不进行切换,因为两个任务都处于延时中 */ 
			} 
		} 
		else if (pxCurrentTCB == &Task2TCB) { 
			if (Task1TCB.xTicksToDelay == 0) { 
				pxCurrentTCB =&Task1TCB; 
			} 
		else if (pxCurrentTCB->xTicksToDelay != 0) { 
			pxCurrentTCB = &IdleTaskTCB; 
			} 
		else { 
			return; /* 返回,不进行切换,因为两个任务都处于延时中 */ 
			} 
		} 
	} 
} 

#endif

3、 SysTick 中断服务函数

在任务上下文切换函数 vTaskSwitchContext ()中,会判断每个任务的任务控制块中的延时成员 xTicksToDelay 的值是否为 0,如果为 0就要将对应的任务就绪,如果不为 0 就继续延时。

如果一个任务要延时,一开始 xTicksToDelay 肯定不为 0,当 xTicksToDelay 变为0 的时候表示延时结束。

xTicksToDelay 是以什么周期在递减?在哪里递减在FreeRTOS 中,这个周期由 SysTick 中断提供,操作系统里面的最小的时间单位就是SysTick 的中断周期,我们称之为一个 tick。

SysTick 中断服务函数

void xPortSysTickHandler( void )
{
	/* 关中断 */
	vPortRaiseBASEPRI();
	
	/* 更新系统时基 */
	xTaskIncrementTick(); 
	
	/* 开中断 */
	vPortClearBASEPRIFromISR(); 
}

xTaskIncrementTick()函数:

void xTaskIncrementTick( void )
{
	TCB_t *pxTCB = NULL;
	BaseType_t i = 0;
	/* 更新系统时基计数器 xTickCount,xTickCount 是一个在 port.c 中定义的全局变量 */
	const TickType_t xConstTickCount = xTickCount + 1;
	xTickCount = xConstTickCount;

	/* 扫描就绪列表中所有任务的 xTicksToDelay,如果不为 0,则减 1 */
	for (i=0; i<configMAX_PRIORITIES; i++){
		pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( 		&pxReadyTasksLists[i] ) );
		if (pxTCB->xTicksToDelay > 0){
			pxTCB->xTicksToDelay --;
		}
	}

	/* 任务切换 */
	portYIELD();
}

4、SysTick 初始化函数

SysTick 的中断服务函数要想被顺利执行,则 SysTick 必须先初始化。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值