刘火良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

}

相关推荐
骁的小小站3 小时前
Verilator 和 GTKwave联合仿真
开发语言·c++·经验分享·笔记·学习·fpga开发
颜颜yan_7 小时前
UU远程——让工作、学习、娱乐跨设备无缝衔接,“远程”更像“身边”
学习·娱乐·远程工作
YJlio8 小时前
Process Monitor 学习笔记(5.24):工具栏参考与高效快捷键指南
笔记·学习·php
deng-c-f8 小时前
Linux C/C++ 学习日记(30):协程(一):同步和异步、协程的简要介绍、用户态CPU调度的实现
学习·协程·同步/异步
hello kitty w9 小时前
Python学习(11) ----- Python的泛型
windows·python·学习
讽刺人生Yan9 小时前
RFSOC学习记录(五)带通采样定理
学习·fpga·rfsoc
报错小能手10 小时前
linux学习笔记(49)Redis详解(1)
linux·笔记·学习
QT 小鲜肉10 小时前
【个人成长笔记】在本地Windows系统中如何正确使用adb pull命令,把Linux系统中的文件或文件夹复制到本地中(亲测有效)
linux·windows·笔记·学习·adb
_李小白13 小时前
【OPENGL ES 3.0 学习笔记】第九天:缓存、顶点和顶点数组
笔记·学习·elasticsearch
洛白白14 小时前
Word文档中打勾和打叉的三种方法
经验分享·学习·word·生活·学习方法