刘火良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 小时前
K8S学习之基础三十八:Kube-static-metrics监控
学习·云原生·容器·kubernetes·prometheus
虾球xz4 小时前
游戏引擎学习第175天
java·学习·游戏引擎
梦幻精灵_cq5 小时前
三个print优雅打印datetime模块的“时间密码”
学习
LN花开富贵7 小时前
【单片机通信技术应用——学习笔记三】液晶屏显示技术,取模软件的应用
笔记·stm32·单片机·嵌入式硬件·学习
执念斩长河8 小时前
go-zero学习笔记
笔记·学习·golang
辰辰大美女呀9 小时前
【Zephyr】【一】学习笔记
前端·笔记·学习
哦豁灬10 小时前
CUDA 学习(2)——CUDA 介绍
学习·cuda
却道天凉_好个秋10 小时前
音视频学习(三十):fmp4
学习·音视频·fmp4
做一个码农都是奢望10 小时前
MATLAB+Arduino控制小车直行+转向
学习·计算机控制系统
背水11 小时前
Enhancing Zero-shot Text-to-Speech Synthesis with Human Feedback论文学习
人工智能·学习·多模态·tts