刘火良FreeRTOS内核实现与应用学习之4——空闲任务与阻塞延时

在《刘火良 FreeRTOS内核实现与应用之2------任务的定义与切换》的基础上构建了:

  1. 空闲任务:

a. 修改了任务控制块,增加了一个用于延时的变量:TickType_t xTicksToDelay;

b.

  1. 任务的阻塞延时函数:void vTaskDelay( const TickType_t xTicksToDelay )

a. 增加了SysTick中断服务函数;

b. 修改了portYIELD()函数;

c. 修改了vT

空闲任务构建

和普通任务创建的步骤类似,如下。

定义空闲任务的栈

#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )

StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];

定义空闲任务的控制块

TCB_t IdleTaskTCB;

创建空闲任务

和普通任务的区别:

a. 普通任务直接在main函数中创建;

b. 空闲任务在main函数调用vTaskStartScheduler()函数,在vTaskStartScheduler()中创建;

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;

/* 初始化系统时基计数器 */

xTickCount = ( TickType_t ) 0U;

/* 启动调度器 */

if( xPortStartScheduler() != pdFALSE )

{

/* 调度器启动成功,则不会返回,即不会来到这里 */

}

}

任务的阻塞延时实现

void vTaskDelay( const TickType_t xTicksToDelay )实现

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

b. 在该任务阻塞的这段时间,CPU可以去执行其他任务,若果其他任务也处于延时状态,那么CPU将运行空闲任务。

位置:在task.c文件中定义;

void vTaskDelay( const TickType_t xTicksToDelay )

{

TCB_t *pxTCB = NULL;

/* 获取当前任务的TCB */

pxTCB = pxCurrentTCB;

/* 设置延时时间 */

pxTCB->xTicksToDelay = xTicksToDelay;

/* 任务切换 */

taskYIELD();

}

本函数调用了taskYIELD函数,在FreeRTOS的调度系统中触发任务切换,确保能安全地交出当前任务的控制权给调度程序。

使用 PendSV 中断来实现调度,使任务切换变得高效。

#define portYIELD() \

{ \

/* 设置 PendSV 的中断挂起位,产生上下文切换 */ \

portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \

\

/* Barriers are normally not required but do ensure the code is completely \

within the specified behaviour for the architecture. */ \

__dsb( portSY_FULL_READ_WRITE ); \

__isb( portSY_FULL_READ_WRITE ); \

}

void vTaskSwitchContext( void )修改

taskYIELD函数会产生PendSV中断,在PendSV中断函数中会调用上下文切换函数vTaskSwitchContext。该函数的功能:寻找最高优先级的就绪任务;更新pxCurrentTCB。

位置:在task.c文件中定义;

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; /* 返回,不进行切换,因为两个线程都处于延时中 */

}

}

}

}

PendSV中断函数

位置:在port.c文件中定义;

/*

*************************************************************************

* PendSV_Handler

*************************************************************************

*/

__asm void xPortPendSVHandler( void )

{

// extern uxCriticalNesting;

extern pxCurrentTCB;

extern vTaskSwitchContext;

PRESERVE8

/* 当进入PendSVC Handler时,上一个任务运行的环境即:

xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)

这些CPU寄存器的值会自动保存到任务的栈中,剩下的r4~r11需要手动保存 */

/* 获取任务栈指针到r0 */

mrs r0, psp

isb

ldr r3, =pxCurrentTCB /* 加载pxCurrentTCB的地址到r3 */

ldr r2, [r3] /* 加载pxCurrentTCB到r2 */

stmdb r0!, {r4-r11} /* 将CPU寄存器r4~r11的值存储到r0指向的地址 */

str r0, [r2] /* 将任务栈的新的栈顶指针存储到当前任务TCB的第一个成员,即栈顶指针 */

stmdb sp!, {r3, r14} /* 将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext,

调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护;

R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护 */

mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY /* 进入临界段 */

msr basepri, r0

dsb

isb

bl vTaskSwitchContext /* 调用函数vTaskSwitchContext,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */

mov r0, #0 /* 退出临界段 */

msr basepri, r0

ldmia sp!, {r3, r14} /* 恢复r3和r14 */

ldr r1, [r3]

ldr r0, [r1] /* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/

ldmia r0!, {r4-r11} /* 出栈 */

msr psp, r0

isb

bx r14 /* 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、

使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,

然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,

当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。*/

nop

}

相关推荐
烟雨柳成烟9 分钟前
Qt学习Day0:Qt简介
开发语言·qt·学习
北极有牛2 小时前
cpp学习笔记2--class
c++·笔记·学习
Non importa2 小时前
【初阶数据结构】树——二叉树——堆(中)
java·c语言·数据结构·学习·算法
Jet45052 小时前
第100+40步 ChatGPT学习:R语言实现多轮建模
学习·chatgpt·r语言·多轮建模
A_aspectJ3 小时前
【Bootstrap V4系列】学习入门教程之 组件-卡片(Card)高级用法
前端·学习·bootstrap
jz_ddk3 小时前
[学习]RTKLib详解:rtkcmn.c与rtkpos.c
c语言·学习·算法
虾球xz4 小时前
游戏引擎学习第261天:切换到静态帧数组
c++·学习·游戏引擎
霖005 小时前
FPGA实战项目1——坦克大战
人工智能·经验分享·嵌入式硬件·学习·fpga开发·fpga
Lucky高5 小时前
学习Python网络爬虫的实例
爬虫·python·学习
一只码代码的章鱼5 小时前
5.4学习记录
学习·算法·动态规划