目录

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:空闲任务与阻塞延时相关推荐

  1. freertos空闲任务、阻塞延时

    freertos空闲任务.阻塞延时 空闲任务 阻塞延时 SysTick 实验现象 阻塞态:如果一个任务当前正在等待某个外部事件,则称它处于阻塞态. rtos中的延时叫阻塞延时,即任务需要延时的时候,会 ...

  2. 从0到1写RT-Thread内核——空闲线程与阻塞延时的实现

    在之前写的另外一篇文章--<从0到1写RT-Thread内核--线程定义及切换的实现>中线程体内的延时使用的是软件延时,即还是让CPU空等来达到延时的效果.RTOS中的延时叫阻塞延时,即线 ...

  3. freertos内核 任务定义与切换 原理分析

    freertos内核 任务定义与切换 原理分析 主程序 任务控制块 任务创建函数 任务栈初始化 就绪列表 调度器 总结任务切换 主程序 这个程序目的就是,使用freertos让两个任务不断切换.看两个 ...

  4. FreeRTOS内核笔记(一):基本知识和命名规则

    FreeRTOS内核笔记(一):基本知识和命名规则 FreeRTOS内核笔记 命名规则: 常用宏定义 Thread运行状态: RTOS Tick Context切换: 实时调度器Scheduler F ...

  5. FreeRTOS内核实现06:任务延时列表

    目录 1. 任务延时列表工作原理 1.1 任务延时列表组件 1.2 任务记录要被唤醒的绝对时间 1.3 避免遍历任务延时列表 2. 任务延时列表组件定义 2.1 定义任务延时列表 2.2  定义下一任 ...

  6. FreeRTOS内核实现07(完):支持时间片

    目录 1. 时间片概念与测试 1.1 时间片概念 1.2 时间片实现原理 1.2.1 taskSELECT_HIGHEST_PRIORITY_TASK宏 1.2.2 taskRESET_READY_P ...

  7. 更换任意Linux内核 Ubuntu18.04 内核降级升级

    更换任意Linux内核 Ubuntu18.04 内核降级升级 一.grub设置 二.安装内核 最后 一.grub设置 grub用于引导操作系统启动,通常情况下Ubuntu默认会 HIDDEN 状态,在 ...

  8. FreeRTOS一天一个小知识之任务延时函数vTaskDelay

    想必各位嵌入式工程师对于Delay延时函数再也熟悉不过了~ 但对于各位刚入RTOS的小白来说,有操作系统的延时函数,真的和裸机中的延时函数一样吗?FreeRTOS的任务调度是怎么调度的?如何分配系统的 ...

  9. 精妙的单片机非阻塞延时程序设计

    http://blog.chinaunix.net/uid-29673749-id-4425603.html 对于每个单片机爱好者及工程开发设计人员,在刚接触单片机的那最初的青葱岁月里,都有过点亮跑马 ...

最新文章

  1. Ubuntu中安装Eclipse的SVN插件——subclipse
  2. C语言构建一个链表以及操作链表
  3. 第55课 分解质因数 《小学生C++编程入门》
  4. 88. [ExtJS2.1教程-5]ToolBar(工具栏)
  5. 今天终于安装了Snippet Compiler!!!
  6. 对“xxx”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。...
  7. python下载pps视频
  8. 自己的域名邮箱用GMAIL:申请注册GMAIL的免费企业邮箱
  9. JAVA数据库访问控制框架设计与使用
  10. Python学习笔记:PYQT5 文字及绘图旋转
  11. ffmpeg 截取切割视频报错
  12. Win11系统svchost.exe一直在下载怎么办?
  13. Android Button设置边框 和背景
  14. 阿里云下载git镜像路径
  15. 使用NSIS脚本制作一个安装包
  16. 玉米田 炮兵阵地 状态压缩DP
  17. python 自定义 计算向量投影 正交 函数
  18. 你还不会用python进行数据分析吗
  19. 面试归来——梳理社招面试以及浅述对程序员职业生涯的看法
  20. python web微信应用(二) webwx 模块源码

热门文章

  1. fvdm 跟驰模型 matlab仿真_MATLAB数值计算在光学仿真和教学中的应用
  2. html语言中glyphicon,Bootstrap字体图标无法正常显示的解决方法
  3. Postman查看完整的请求报文
  4. Python中计算文件的MD5值
  5. python 图片生成视频_python--通过cv2多张图片生成视频
  6. CentOs7.5离线静默安装Oracle12c
  7. pymysql.err.OperationalError: (2006, “MySQL server has gone away (BrokenPipe
  8. 东财计算机应用基础在线作业一,东财《计算机应用基础》综合作业
  9. hannoi塔java程序_基于Java实现的Hannoi汉诺塔自动演示程序
  10. android开发入门与实践_我的新书《Android App开发入门与实战》已经出版