FreeRTOS内核实现04:空闲任务与阻塞延时
目录
1. 引入原因
2. 实现空闲任务
2.1 定义空闲任务组件
2.2 创建空闲任务
3. 实现阻塞延时
3.1 vTaskDelay函数实现
3.2 修改vTaskSwitchContext函数
3.3 SysTick初始化函数实现
3.4 SysTick中断服务函数实现
3.5 xTaskIncrementTick函数实现
4. 测试用例与实验现象
1. 引入原因
① 首先是引入阻塞延时,之前的延时通过忙等实现,延时过程中任务不会放弃CPU,从而浪费了CPU资源
在阻塞延时中,当任务需要延时时即进入阻塞状态,此时CPU可以运行其他任务
② 如果系统中所有任务均进入阻塞状态,CPU运行什么任务 ? 这就需要引入空闲任务,即当系统中没有其他任务可运行时,就运行空闲任务
说明:空闲任务特性
① 空闲任务在系统中优先级最低
② 在实际应用中,当系统进入空闲任务时,可在空闲任务中执行休眠或低功耗等操作
2. 实现空闲任务
2.1 定义空闲任务组件
// file: task.c// 空闲任务栈
StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
// 空闲任务TCB
TCB_t IdleTaskTCB;
// 空闲任务句柄
TaskHandle_t xIdleTaskHandle = NULL;
// 空闲任务函数(暂时为空函数)
static void prvIdleTask(void *p_arg)
{for (;;){/* do nothing */}
}
2.2 创建空闲任务
由于已经静态定义了空闲任务的各个组件,此时需要调用xTaskCreateStatic函数将这些组件关联起来,我们在vTaskStartScheduler函数中创建空闲任务
// file: task.cvoid vApplicationGetIdleTaskMemory(TCB_t **ppxIdleTaskTCBBuffer,StackType_t **ppxIdleTaskStackBuffer,uint32_t *pulIdleTaskStackSize)
{*ppxIdleTaskTCBBuffer = &IdleTaskTCB;*ppxIdleTaskStackBuffer = IdleTaskStack;*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}/* 启动调度器 */
void vTaskStartScheduler(void)
{TCB_t *pxIdleTaskTCBBuffer = NULL;StackType_t *pxIdleTaskStackBuffer = NULL;uint32_t ulIdleTaskStackSize = 0;/* 获取空闲任务信息 */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]),&(pxIdleTaskTCBBuffer->xStateListItem));/* 其他代码,启动调度器 */
}
3. 实现阻塞延时
3.1 vTaskDelay函数实现
/* 阻塞延时函数 */
void vTaskDelay(const TickType_t xTicksToDelay)
{TCB_t *pxTCB = NULL;/* 获取当前任务TCB */pxTCB = pxCurrentTCB;/* 设置延时Tick值 */pxTCB->xTicksToDelay = xTicksToDelay;/* 触发任务切换,放弃CPU */taskYIELD();
}
说明:vTaskDelay函数的延时单位为系统时钟Tick值,因此还需要设置SysTick,详见下文
同时,为支持该功能,TCB中要增加记录延时Tick值的xTicksToDelay字段
3.2 修改vTaskSwitchContext函数
// file: task.cvoid vTaskSwitchContext(void)
{if (pxCurrentTCB == &IdleTaskTCB){if (Task1TCB.xTicksToDelay == 0){pxCurrentTCB = &Task1TCB;}else if (Task2TCB.xTicksToDelay == 0){pxCurrentTCB = &Task2TCB;}else{return; // 不切换任务}}else{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; // 不切换任务}}}
}
说明:由于尚未实现任务优先级,此处的调度器实现依然非常简单,总体思路如下,
① 如果当前任务是空闲任务,则判断其他任务是否处于延时状态,如果延时到期则运行其他任务;否则继续执行空闲任务
② 如果当前任务不是空闲任务,则判断另一个任务与当前任务是否处于延时状态,如果仍有任务可以运行,则切换任务或仍运行当前任务;否则执行空闲任务
3.3 SysTick初始化函数实现
在阻塞延时函数vTaskDelay中会设置任务的延时Tick值,之后便放弃CPU,而任务延时Tick值的递减更新则是在SysTick Handler中进行,因此先要使能SysTick
//file: FreeRTOSConfig.h
/* 系统时钟为25MHz */
#define configCPU_CLOCK_HZ ((unsigned long)25000000)
/* 每秒产生100次SysTick中断,即SysTick周期为10ms */
#define configTICK_RATE_HZ ((TickType_t)100)//file: port.c/* SysTick控制寄存器 */
#define portNVIC_SYSTICK_CTRL_REG (*((volatile uint32_t *)0xe000e010))
/* SysTick重装载寄存器 */
#define portNVIC_SYSTICK_LOAD_REG (*((volatile uint32_t *)0xe000e014))#ifndef configSYSTICK_CLOCK_HZ#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ#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);
}
说明1:使用与系统相同的时钟
在Keil Cortex-M3的startup_ARMCM3.c中,系统时钟设置为25MHz
说明2:使用FreeRTOSConfig.h中定义的configCPU_CLOCK_HZ和configTICK_RATE_HZ宏,可以计算出重装载寄存器的值,用于重复持续产生SysTick中断
说明3:在xPortStartScheduler函数中调用vPortSetupTimerInterruput函数,使能SysTick。由于开中断要在prvStartFirstTask中才进行,所以此处使能了SysTick中断也是没有风险的,不会导致任务的错误调度
3.4 SysTick中断服务函数实现
// file: port.c
void xPortSysTickHandler(void)
{/* 关中断 */vPortRaiseBASEPRI();/* 更新系统时基 */xTaskIncrementTick();/* 开中断 */vPortClearBASEPRIFromISR();
}
说明:关于在中断服务函数中关中断的操作
在下面这篇笔记中有讨论到在中断服务函数中关中断,个人感觉不是一定需要使用带中断保护的操作,此处就是一个实例。由于调用者明知不会发生中断嵌套,所以只是简单的开关中断
https://mp.csdn.net/editor/html/112117556
3.5 xTaskIncrementTick函数实现
//file: tasks.c/* 更新时基函数 */
void xTaskIncrementTick(void)
{TCB_t *pxTCB = NULL;BaseType_t i = 0;const TickType_t xConstTickCount = xTickCount + 1;xTickCount = xConstTickCount;/* 更新任务延时计数 */for (i = 0; i < configMAX_PRIORITIES; i++){pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(&(pxReadyTasksLists[i]));if (pxTCB->xTicksToDelay > 0){pxTCB->xTicksToDelay--;}}/* 触发任务切换 */portYIELD();
}
说明1:xTickCount变量
xTickCount全局变量为系统时基计数器,在tasks.c文件中定义,用于记录系统累计的Tick数(和Linux中的jiffies值类似),该全局变量在vTaskStarScheduler函数中初始化
此处需要说明的是,在tasks.c层和port.c层均定义了一些全局变量,其中,
① tasks.c层的全局变量在vTaskStartScheduler函数中初始化
② port.c层的全部变量在xPortStartScheduler函数中初始化
// 调用关系
vTaskStartScheduler
--> xPortStartScheduler
注意:体系结构相关的port.c和体系结构无关的task.c之间会有相互调用
说明2:此处的遍历仅涉及每个优先级就绪列表的第一个元素,后续会逐步完善
说明3:xTaskIncrementTick触发任务切换的原因
由于延时计数结束的任务可能比当前任务优先级更高,所以在此处触发了任务切换,这也是RTOS时刻运行当前可运行任务中优先级最高任务的特性
4. 测试用例与实验现象
void Task1_Entry(void *p_arg)
{for (;;){flag1 = 1;vTaskDelay(2);flag1 = 0;vTaskDelay(2);}
}void Task2_Entry(void *p_arg)
{for (;;){flag2 = 1;vTaskDelay(2);flag2 = 0;vTaskDelay(2);}
}
此时用例中使用阻塞等待函数替换原先的忙等操作,从波形可见,当一个Task放弃CPU后,CPU立即转向运行另一个Task,所以波形上几乎同步,这也就是操作系统实现的宏观上的并行
FreeRTOS内核实现04:空闲任务与阻塞延时相关推荐
- freertos空闲任务、阻塞延时
freertos空闲任务.阻塞延时 空闲任务 阻塞延时 SysTick 实验现象 阻塞态:如果一个任务当前正在等待某个外部事件,则称它处于阻塞态. rtos中的延时叫阻塞延时,即任务需要延时的时候,会 ...
- 从0到1写RT-Thread内核——空闲线程与阻塞延时的实现
在之前写的另外一篇文章--<从0到1写RT-Thread内核--线程定义及切换的实现>中线程体内的延时使用的是软件延时,即还是让CPU空等来达到延时的效果.RTOS中的延时叫阻塞延时,即线 ...
- freertos内核 任务定义与切换 原理分析
freertos内核 任务定义与切换 原理分析 主程序 任务控制块 任务创建函数 任务栈初始化 就绪列表 调度器 总结任务切换 主程序 这个程序目的就是,使用freertos让两个任务不断切换.看两个 ...
- FreeRTOS内核笔记(一):基本知识和命名规则
FreeRTOS内核笔记(一):基本知识和命名规则 FreeRTOS内核笔记 命名规则: 常用宏定义 Thread运行状态: RTOS Tick Context切换: 实时调度器Scheduler F ...
- FreeRTOS内核实现06:任务延时列表
目录 1. 任务延时列表工作原理 1.1 任务延时列表组件 1.2 任务记录要被唤醒的绝对时间 1.3 避免遍历任务延时列表 2. 任务延时列表组件定义 2.1 定义任务延时列表 2.2 定义下一任 ...
- FreeRTOS内核实现07(完):支持时间片
目录 1. 时间片概念与测试 1.1 时间片概念 1.2 时间片实现原理 1.2.1 taskSELECT_HIGHEST_PRIORITY_TASK宏 1.2.2 taskRESET_READY_P ...
- 更换任意Linux内核 Ubuntu18.04 内核降级升级
更换任意Linux内核 Ubuntu18.04 内核降级升级 一.grub设置 二.安装内核 最后 一.grub设置 grub用于引导操作系统启动,通常情况下Ubuntu默认会 HIDDEN 状态,在 ...
- FreeRTOS一天一个小知识之任务延时函数vTaskDelay
想必各位嵌入式工程师对于Delay延时函数再也熟悉不过了~ 但对于各位刚入RTOS的小白来说,有操作系统的延时函数,真的和裸机中的延时函数一样吗?FreeRTOS的任务调度是怎么调度的?如何分配系统的 ...
- 精妙的单片机非阻塞延时程序设计
http://blog.chinaunix.net/uid-29673749-id-4425603.html 对于每个单片机爱好者及工程开发设计人员,在刚接触单片机的那最初的青葱岁月里,都有过点亮跑马 ...
最新文章
- Ubuntu中安装Eclipse的SVN插件——subclipse
- C语言构建一个链表以及操作链表
- 第55课 分解质因数 《小学生C++编程入门》
- 88. [ExtJS2.1教程-5]ToolBar(工具栏)
- 今天终于安装了Snippet Compiler!!!
- 对“xxx”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。...
- python下载pps视频
- 自己的域名邮箱用GMAIL:申请注册GMAIL的免费企业邮箱
- JAVA数据库访问控制框架设计与使用
- Python学习笔记:PYQT5 文字及绘图旋转
- ffmpeg 截取切割视频报错
- Win11系统svchost.exe一直在下载怎么办?
- Android Button设置边框 和背景
- 阿里云下载git镜像路径
- 使用NSIS脚本制作一个安装包
- 玉米田 炮兵阵地 状态压缩DP
- python 自定义 计算向量投影 正交 函数
- 你还不会用python进行数据分析吗
- 面试归来——梳理社招面试以及浅述对程序员职业生涯的看法
- python web微信应用(二) webwx 模块源码
热门文章
- fvdm 跟驰模型 matlab仿真_MATLAB数值计算在光学仿真和教学中的应用
- html语言中glyphicon,Bootstrap字体图标无法正常显示的解决方法
- Postman查看完整的请求报文
- Python中计算文件的MD5值
- python 图片生成视频_python--通过cv2多张图片生成视频
- CentOs7.5离线静默安装Oracle12c
- pymysql.err.OperationalError: (2006, “MySQL server has gone away (BrokenPipe
- 东财计算机应用基础在线作业一,东财《计算机应用基础》综合作业
- hannoi塔java程序_基于Java实现的Hannoi汉诺塔自动演示程序
- android开发入门与实践_我的新书《Android App开发入门与实战》已经出版