FreeRTOS简单内核实现7 阻塞链表
0、思考与回答
0.1、思考一
如何处理进入阻塞状态的任务?
为了让 RTOS 支持多优先级,我们创建了多个就绪链表(数组形式),用每一个就绪链表表示一个优先级,对于阻塞状态的任务显然要从就绪链表中移除,但是阻塞状态的任务并不是永久阻塞了,等待一段时间后应该从阻塞状态恢复,所以我们需要创建一个阻塞链表用来存放进入阻塞状态的任务
0.2、思考二
还有一个问题,xTicksToDelay
是一个 32 位的变量,如何处理其潜在的溢出问题?
假设使用一个 32 位的 xNextTaskUnblockTime
变量表示任务下次解除阻塞的时间,其一般应该由如下所示的程序代码计算
// 任务下次解除阻塞的时间 = 当前滴答定时器计数值 + 要延时的滴答次数
xNextTaskUnblockTime = xConstTickCount + xTicksToWait;
可以看出 xNextTaskUnblockTime
变量随着运行时间流逝存在溢出风险,因此我们需要再定义一个溢出阻塞链表用来存放所有下次解除阻塞的时间溢出的任务,这样我们就拥有两个阻塞链表,在滴答定时器中断服务函数中如果一旦发现滴答定时器计数值全局变量溢出,就通过链表指针将这两个链表交换,保证永远处理的是正确的阻塞链表
1、阻塞链表
1.1、定义
/* task.c */
// 阻塞链表和其指针
static List_t xDelayed_Task_List1;
static List_t volatile *pxDelayed_Task_List;
// 溢出阻塞链表和其指针
static List_t xDelayed_Task_List2;
static List_t volatile *pxOverflow_Delayed_Task_List;
1.2、prvInitialiseTaskLists( )
由于新增加了阻塞链表和溢出阻塞链表,因此在链表初始化函数中除了需要初始化就绪链表数组外,还需要增加对阻塞链表和溢出阻塞链表的初始化操作,如下所示
/* task.c */
// 就绪列表初始化函数
void prvInitialiseTaskLists(void)
{
// 省略未修改部分
......
// 初始化延时阻塞链表
vListInitialise(&xDelayed_Task_List1);
vListInitialise(&xDelayed_Task_List2);
// 初始化指向延时阻塞链表的指针
pxDelayed_Task_List = &xDelayed_Task_List1;
pxOverflow_Delayed_Task_List = &xDelayed_Task_List2;
}
1.3、taskSWITCH_DELAYED_LISTS( )
为什么需要阻塞链表和溢出阻塞链表需要交换?
阅读 ” 0.2、思考二“ 小节内容
阻塞链表和溢出阻塞链表是如何实现交换的?
利用两个指针进行交换
/* task.c */
// 记录溢出次数
static volatile BaseType_t xNumOfOverflows = (BaseType_t)0;
// 延时阻塞链表和溢出延时阻塞链表交换
#define taskSWITCH_DELAYED_LISTS()\
{\
List_t volatile *pxTemp;\
pxTemp = pxDelayed_Task_List;\
pxDelayed_Task_List = pxOverflow_Delayed_Task_List;\
pxOverflow_Delayed_Task_List = pxTemp;\
xNumOfOverflows++;\
prvResetNextTaskUnblockTime();\
}
1.4、prvResetNextTaskUnblockTime( )
由于将任务插入溢出阻塞链表时不会更新 xNextTaskUnblockTime
变量,只有在将任务插入阻塞链表中时才会更新xNextTaskUnblockTime
变量,所以对于溢出阻塞链表中存在的任务没有对应的唤醒时间,因此当心跳溢出切换阻塞链表时候,需要重设 xNextTaskUnblockTime
变量的值
/* task.c */
// 记录下个任务解除阻塞时间
static volatile TickType_t xNextTaskUnblockTime = (TickType_t)0U;
// 函数声明
static void prvResetNextTaskUnblockTime(void);
// 重设 xNextTaskUnblockTime 变量值
static void prvResetNextTaskUnblockTime(void)
{
TCB_t *pxTCB;
// 切换阻塞链表后,阻塞链表为空
if(listLIST_IS_EMPTY(pxDelayed_Task_List) != pdFALSE)
{
// 下次解除延时的时间为可能的最大值
xNextTaskUnblockTime = portMAX_DELAY;
}
else
{
// 如果阻塞链表不为空,下次解除延时的时间为链表头任务的阻塞时间
(pxTCB) = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(pxDelayed_Task_List);
xNextTaskUnblockTime=listGET_LIST_ITEM_VALUE(&((pxTCB)->xStateListItem));
}
}
1.5、prvAddCurrentTaskToDelayedList( )
将当前任务加入到阻塞链表中,具体流程可以参看程序注释,对于延时到期时间未溢出的任务会插入到阻塞链表中,而对于延时到期时间溢出的任务会插入溢出阻塞链表中
/* task.c */
// 将当前任务添加到阻塞链表中
static void prvAddCurrentTaskToDelayedList(TickType_t xTicksToWait)
{
TickType_t xTimeToWake;
// 当前滴答定时器中断次数
const TickType_t xConstTickCount = xTickCount;
// 成功从就绪链表中移除该阻塞任务
if(uxListRemove((ListItem_t *)&(pxCurrentTCB->xStateListItem)) == 0)
{
// 将当前任务的优先级从优先级位图中删除
portRESET_READY_PRIORITY(pxCurrentTCB->uxPriority, uxTopReadyPriority);
}
// 计算延时到期时间
xTimeToWake = xConstTickCount + xTicksToWait;
// 将延时到期值设置为阻塞链表中节点的排序值
listSET_LIST_ITEM_VALUE(&(pxCurrentTCB->xStateListItem), xTimeToWake);
// 如果延时到期时间会溢出
if(xTimeToWake < xConstTickCount)
{
// 将其插入溢出阻塞链表中
vListInsert((List_t *)pxOverflow_Delayed_Task_List,
(ListItem_t *)&(pxCurrentTCB->xStateListItem));
}
// 没有溢出
else
{
// 插入到阻塞链表中
vListInsert((List_t *)pxDelayed_Task_List,
(ListItem_t *) &( pxCurrentTCB->xStateListItem));
// 更新下一个任务解锁时刻变量 xNextTaskUnblockTime 的值
if(xTimeToWake < xNextTaskUnblockTime)
{
xNextTaskUnblockTime = xTimeToWake;
}
}
}
2、修改内核程序
2.1、vTaskStartScheduler( )
/* task.c */
void vTaskStartScheduler(void)
{
// 省略创建空闲任务函数
......
// 初始化滴答定时器计数值,感觉有点儿多余?全局变量定义时候已被初始化为 0
xTickCount = (TickType_t)0U;
if(xPortStartScheduler() != pdFALSE){}
}
2.2、vTaskDelay( )
阻塞延时函数,当任务调用阻塞延时函数时会将任务从就绪链表中删除,然后加入到阻塞链表中
/* task.c */
// 阻塞延时函数
void vTaskDelay(const TickType_t xTicksToDelay)
{
// 将当前任务加入到阻塞链表
prvAddCurrentTaskToDelayedList(xTicksToDelay);
// 任务切换
taskYIELD();
}
2.3、xTaskIncrementTick( )
利用 RTOS 的心跳(滴答定时器中断服务函数)对阻塞任务进行处理,具体流程如下所示
/* task.c */
// 任务阻塞延时处理
BaseType_t xTaskIncrementTick(void)
{
TCB_t *pxTCB = NULL;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
// 更新系统时基计数器 xTickCount
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
// 如果 xConstTickCount 溢出,则切换延时列表
if(xConstTickCount == (TickType_t)0U)
{
taskSWITCH_DELAYED_LISTS();
}
// 最近的延时任务延时到期
if(xConstTickCount >= xNextTaskUnblockTime)
{
for(;;)
{
// 延时阻塞链表为空则跳出 for 循环
if(listLIST_IS_EMPTY(pxDelayed_Task_List) != pdFALSE)
{
// 设置下个任务解除阻塞时间为最大值,也即永不解除阻塞
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else
{
// 依次获取延时阻塞链表头节点
pxTCB=(TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(pxDelayed_Task_List);
// 依次获取延时阻塞链表中所有节点解除阻塞的时间
xItemValue = listGET_LIST_ITEM_VALUE(&(pxTCB->xStateListItem));
// 当阻塞链表中所有延时到期的任务都被移除则跳出 for 循环
if(xConstTickCount < xItemValue)
{
xNextTaskUnblockTime = xItemValue;
break;
}
// 将任务从延时列表移除,消除等待状态
(void)uxListRemove(&(pxTCB->xStateListItem));
// 将解除等待的任务添加到就绪列表
prvAddTaskToReadyList(pxTCB);
#if(configUSE_PREEMPTION == 1)
// 如果解除阻塞状态的任务优先级比当前任务优先级高,则需要进行任务调度
if(pxTCB->uxPriority >= pxCurrentTCB->uxPriority)
{
xSwitchRequired = pdTRUE;
}
#endif
}
}
}
return xSwitchRequired;
}
/* task.h */
// 修改函数声明
BaseType_t xTaskIncrementTick(void);
/* FreeRTOSConfig.h */
// 支持抢占优先级
#define configUSE_PREEMPTION 1
2.4、xPortSysTickHandler( )
无其他变化,只是将任务切换从函数体内修改到函数体外
/* port.c */
// SysTick 中断
void xPortSysTickHandler(void)
{
// 关中断
vPortRaiseBASEPRI();
// 更新系统时基
if(xTaskIncrementTick() != pdFALSE)
{
taskYIELD();
}
// 开中断
vPortSetBASEPRI(0);
}
3、实验
3.1、测试
与 FreeRTOS简单内核实现6 优先级 文章中 "3.1、测试" 小节内容一致
如果使用的开发环境为 Keil 且程序工作不正常,可以勾选 Use MicroLIB
试试,如下图所示

3.2、待改进
当前 RTOS 简单内核已实现的功能有
- 静态方式创建任务
- 手动切换任务
- 临界段保护
- 任务阻塞延时
- 支持任务优先级
- 阻塞链表
当前 RTOS 简单内核存在的缺点有
- 不支持时间片轮询
FreeRTOS简单内核实现7 阻塞链表的更多相关文章
- Linux内核之数据双链表
导读 Linux 内核中自己实现了双向链表,可以在 include/linux/list.h 找到定义.我们将会首先从双向链表数据结构开始介绍内核里的数据结构.为什么?因为它在内核里使用的很广泛,你只 ...
- 基于mykernel完成时间片轮询多道进程的简单内核
基于mykernel完成时间片轮询多道进程的简单内核 原创作品转载请注明出处+中科大孟宁老师的linux操作系统分析:https://github.com/mengning/linuxkernel/ ...
- [arm驱动]Linux内核开发之阻塞非阻塞IO----轮询操作【转】
本文转载自:http://7071976.blog.51cto.com/7061976/1392082 <[arm驱动]Linux内核开发之阻塞非阻塞IO----轮询操作>涉及内核驱动函数 ...
- 超级简单的数组加单链表实现Map
/** * 超级简单的数组加单链表实现Map * @author jlj * */ public class MyHashMap { public MyList[] lists; public int ...
- 源码级别gdb远程调试(实现OS简单内核)
最近在学着编写一个操作系统的简单内核,需要debug工具,我们这里使用gdb来进行调试,由于虚拟机运行和本机是两个部分,所以使用 gdb 的远程调试技术,这里对 gdb 常见调试以及远程调试方式做一个 ...
- 服务器端IO模型的简单介绍及实现 阻塞 / 非阻塞 VS 同步 / 异步 内核实现的拷贝效率
小结: 1.在多线程的基础上,可以考虑使用"线程池"或"连接池","线程池"旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲 ...
- linux简单内核链表排序
#include <stdio.h> #include <stdlib.h> #define container_of(ptr, type, mem)(type *)((uns ...
- 深度剖析linux内核万能--双向链表,Hash链表模版
我们都知道,链表是数据结构中用得最广泛的一种数据结构,对于数据结构,有顺序存储,数组就是一种.有链式存储,链表算一种.当然还有索引式的,散列式的,各种风格的说法,叫法层出不穷,但是万变不离其中,只要知 ...
- 基于mykernel完成多进程的简单内核
学号351 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ mykernel简介 mykernel是由孟宁老师建立的一个用于开发您自己的操 ...
- 芯灵思Sinlinx A64开发板 Linux内核等待队列poll ---阻塞与非阻塞
开发平台 芯灵思Sinlinx A64 内存: 1GB 存储: 4GB 开发板详细参数 https://m.tb.cn/h.3wMaSKm 开发板交流群 641395230 阻塞:阻塞调用是指调用结果 ...
随机推荐
- 阿里云IoT Studio升级版新增解决方案引擎 大幅提升方案交付效率
简介: 8月25日,阿里云发布IoT Studio升级版,新增了解决方案引擎,让设备方案商复用之前搭建的解决方案模板进行简单的定制化修改,即可交付.使整个物联网解决方案的交付过程由几个月,缩短到几小时 ...
- SQL server 游标使用实例
--创建一个游标 DECLARE my_cursor CURSOR FOR SELECT id, Bran_number, Bran_taxis FROM dbo.Base_Branch; --打开游 ...
- K8s控制器(8)---Deployment
一.Deployment控制器概念.原理解读 1.1 Deployment概述 # Deployment官方文档 https://kubernetes.io/docs/concepts/workloa ...
- 习题8 #第8章 Verilog有限状态机设计-2 #Verilog #Quartus #modelsim
2. 设计一个"1001"串行数据检测器,其输入.输出如下: 输入x:000 101 010 010 011 101 001 110 101 输出z:000 000 000 010 ...
- angualr2+ 性能优化-trackBy
1.使用trackBy提高性能 为什么使用trackBy进行性能优化,在平时的开发中,我们对数组的处理基本都是通过接口获取新的数组进行替换或push,但是在这个过程中,Angular不知道你是要做什么 ...
- 安全高效 | AIRIOT智慧工地管理解决方案
建筑工地施工材料.机械设备.工程车.人员各个环节管理相对复杂.建筑业也是安全事故频发的高危行业,安全管控尤为重要.建筑施工单位想要保障安全生产,做好能源消耗管控降低生产成本,需要解决掉很多现状问题 ...
- 基于Ubuntu20.04在k8s 1.25部署gin+MySQL服务
0. 前言 某天突发奇想,既然都学了 docker 了,那干脆,顺便把 kubernetes 也学了,于是开始了我长达一个月的环境搭建.踩坑历程. 最开始,我的想法是,在我的物理机使用 WSL + d ...
- linux下 IPv6组播(C++)
Server #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <s ...
- 『手撕Vue-CLI』函数柯里化优化代码
开篇 在上一篇文章中,给 nue-cli 添加了拉取版本号的功能,这一次来优化一下代码,使用函数柯里化的方式来优化代码. 实现 函数柯里化 函数柯里化是一种将使用多个参数的一个函数转换成一系列使用一个 ...
- VALL-EX下载介绍:只需3秒录音,即可克隆你的声音
VALL-EX是一个强大和创新的多语言文本转语音模型,支持对中文.英文和日语的语音进行合成和克隆,使用者只需上传一段3-10秒的录音,就可以生成高质量的目标音频,同时保留了说话人的声音.情感和声学环境 ...