第9章 空闲任务与阻塞延时的实现--总结

整理 野火 《FreeRTOS 内核实现与应用开发实战指南》—基于野火 STM32 全系列(M3/4/7)开发板

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

  在上一章节中,任务体内的延时使用的是软件延时,即还是让 CPU 空等来达到延时的效果RTOS 中的延时叫阻塞延时,即任务需要延时的时候,任务会放弃 CPU 的使用权,CPU 可以去干其它的事情,当任务延时时间到,重新获取 CPU 使用权,任务继续运行,这样就充分地利用了 CPU 的资源,而不是干等着。

9.1 实现空闲任务

9.1.1 定义空闲任务的栈

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

9.1.2 定义空闲任务的任务控制块

/* 定义空闲任务的任务控制块 */
TCB_t IdleTaskTCB;

9.1.3 创建空闲任务

  当定义好空闲任务的栈,任务控制块后,就可以创建空闲任务。空闲任务在调度器启动函数 vTaskStartScheduler() 中创建。

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 )
	{
		/* 调度器启动成功,则不会返回,即不会来到这里 */
	}
}

vApplicationGetIdleTaskMemory()函数

void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer, 
                                    StackType_t **ppxIdleTaskStackBuffer, 
                                    uint32_t *pulIdleTaskStackSize )
{
		*ppxIdleTaskTCBBuffer=&IdleTaskTCB;
		*ppxIdleTaskStackBuffer=IdleTaskStack; 
		*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}

9.2 实现阻塞延时

9.2.1 vTaskDelay ()函数

  阻塞延时的阻塞是指任务调用该延时函数后,任务会被剥离 CPU 使用权,然后进入阻塞状态,直到延时结束,任务重新获取 CPU 使用权才可以继续运行。在任务阻塞的这段时间,CPU 可以去执行其它的任务,如果其它的任务也在延时状态,那么 CPU 就将运行空闲任务。
vTaskDelay()函数:

void vTaskDelay( const TickType_t xTicksToDelay )
{
    TCB_t *pxTCB = NULL;
    
    /* 获取当前任务的TCB */
    pxTCB = pxCurrentTCB;
    
    /* 设置延时时间 */
    pxTCB->xTicksToDelay = xTicksToDelay;
    
    /* 将任务从就绪列表移除 */
    //uxListRemove( &( pxTCB->xStateListItem ) );
    taskRESET_READY_PRIORITY( pxTCB->uxPriority );
    
    /* 任务切换 */
    taskYIELD();
}

xTicksToDelay 定义:

typedef struct tskTaskControlBlock
{
	volatile StackType_t    *pxTopOfStack;    /* 栈顶 */

	ListItem_t			    xStateListItem;   /* 任务节点 */
    
    StackType_t             *pxStack;         /* 任务栈起始地址 */
	                                          /* 任务名称,字符串形式 */
	char                    pcTaskName[ configMAX_TASK_NAME_LEN ];

    TickType_t              xTicksToDelay;   /* 用于延时 */
    UBaseType_t			    uxPriority;    
} tskTCB;

9.2.2 修改 vTaskSwitchContext()函数

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


#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

9.3 SysTick 中断服务函数

  在任务上下文切换函数 vTaskSwitchContext ()中,会判断每个任务的任务控制块中的延时成员 xTicksToDelay 的值是否为 0,如果为 0就要将对应的任务就绪,如果不为 0 就继续延时。xTicksToDelay 是以什么周期在递减?在哪里递减?在FreeRTOS 中,这个周期由 SysTick 中断提供,操作系统里面的最小的时间单位就是SysTick 的中断周期,我们称之为一个 tickSysTick 中断服务函数在 port.c.c 中实现。

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

	/* 开中断 */
    vPortClearBASEPRIFromISR();
}

9.3.1 xTaskIncrementTick()函数

  更新系统时基,该函数在 task.c 中定义。

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();
}

9.4 SysTick 初始化函数

/* SysTick 控制寄存器 */ 
#define portNVIC_SYSTICK_CTRL_REG (*((volatile uint32_t *) 0xe000e010 ))
/* SysTick 重装载寄存器寄存器 */
#define portNVIC_SYSTICK_LOAD_REG (*((volatile uint32_t *) 0xe000e014 ))

/* SysTick 时钟源选择 */
#ifndef configSYSTICK_CLOCK_HZ 
	#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ
	/* 确保 SysTick 的时钟与内核时钟一致 */ 
	#define portNVIC_SYSTICK_CLK_BIT ( 1UL << 2UL ) 
#else 
	#define portNVIC_SYSTICK_CLK_BIT ( 0 ) 
#endif 

#define portNVIC_SYSTICK_INT_BIT    ( 1UL << 1UL )
#define portNVIC_SYSTICK_ENABLE_BIT ( 1UL << 0UL )


void vPortSetupTimerInterrupt( void )
{
	/* 设置重装载寄存器的值 */
	portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;

	/* 设置系统定时器的时钟等于内核时钟 
	使能 SysTick 定时器中断
	使能 SysTick 定时器 */
	portNVIC_SYSTICK_CTRL_REG = (  portNVIC_SYSTICK_CLK_BIT |
								   portNVIC_SYSTICK_INT_BIT |
								   portNVIC_SYSTICK_ENABLE_BIT );
}

xPortStartScheduler()函数中调用 vPortSetupTimerInterrupt():

BaseType_t xPortStartScheduler( void )
{
    /* 配置PendSV 和 SysTick 的中断优先级为最低 */
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
    
    /* 初始化SysTick */
    vPortSetupTimerInterrupt();

	/* 启动第一个任务,不再返回 */
	prvStartFirstTask();

	/* 不应该运行到这里 */
	return 0;
}

2 configCPU_CLOCK_HZ 与 configTICK_RATE_HZ 宏定义:

#define configCPU_CLOCK_HZ (( unsigned long ) 25000000)
#define configTICK_RATE_HZ (( TickType_t ) 100)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值