FreeRTOS简单内核实现7 阻塞链表

news/2024/10/1 17:34:38

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)
{// 省略创建空闲任务函数......// 初始化滴答定时器计数值,感觉有点儿多余?全局变量定义时候已被初始化为 0xTickCount = (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;// 更新系统时基计数器 xTickCountconst 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 简单内核已实现的功能有

  1. 静态方式创建任务
  2. 手动切换任务
  3. 临界段保护
  4. 任务阻塞延时
  5. 支持任务优先级
  6. 阻塞链表

当前 RTOS 简单内核存在的缺点有

  1. 不支持时间片轮询

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/44986.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

零基础写框架(3): Serilog.NET 中的日志使用技巧

.NET 中的日志使用技巧 Serilog Serilog 是 .NET 社区中使用最广泛的日志框架,所以笔者使用一个小节单独讲解使用方法。 示例项目在 Demo2.Console 中。 创建一个控制台程序,引入两个包: Serilog.Sinks.Console Serilog.Sinks.File除此之外,还有 Serilog.Sinks.Elasticsear…

危急值上报及闭环管理全解析

什么是危急值制度? 危急值制度是指对提示患者处于生命危急状态的检查、检验结果建立复核、报告、记录等管理机制,以保障患者安全的制度。 管理体系(组织体系+制度建设+管理要素+宣教培训) 针对管理体系中的组织体系、制度建设、管理要素、宣教培训多为线下的制度、流程建立。…

手术分级管理制度

01手术分级管理体系 ● 医院手术分级管理实行院、科两级负责制 ● 医院医疗技术临床应用管理委员会总体负责全院手术分级管理工作,日常工作由医务办公室负责组织、协调,主要职责包括: (一)制定手术分级管理制度规范,定期检查提出改进要求 (二)审定手术分级管理目录,定…

总体估计中的相关公式 | 高一使用

总体估计中的相关公式和相关性质前言 相关公式 【人教 2019 A 版 \(P_{215}\) 练习 2】 数据 \(x_1\),\(x_2\), \(\cdots\), \(x_n\) 的方差为 \(s_x^2\), 数据 \(y_1\), \(y_2\), \(\cdots\), \(y_n\) 的方差为 \(s_y^2\), \(a\)、 \(b\) 为常数. 证明: (1) . 如果 \(…

LangChain结合LLM做私有化文档搜索

我们知道LLM(大语言模型)的底模是基于已经过期的公开数据训练出来的,对于新的知识或者私有化的数据LLM一般无法作答,此时LLM会出现“幻觉”。针对“幻觉”问题,一般的解决方案是采用RAG做检索增强。我们知道LLM(大语言模型)的底模是基于已经过期的公开数据训练出来的,对…

深入分析四层/七层网关

1 简要介绍 随着云计算、大数据和物联网技术的迅猛发展,网络通信的复杂性和需求日益增加。在这种背景下,网关技术作为网络通信中的重要组成部分,扮演着关键的角色。 作为连接不同网络或协议的桥梁,四层网关和七层网关是两种常见且重要的类型。本文将对这两种网关进行深入分…

dotnet C# 使用 using 关键字释放 IDisposable 的结构体是否会装箱

在 C# 里面的 using 关键字可以非常方便调用 IDisposable 接口的 Dispose 方法,进行一些资源的释放或实现有趣的逻辑的执行配合 using 关键字使用的类型需要继承 IDisposable 接口,根据基础的 C# 知识,大家都知道 using 关键字其实会自动在 IL 层拆开为在 finally 里面调用 …

VSCode修改扩展和用户文件夹目录位置(Windows)

原文链接:https://blog.csdn.net/weixin_53510183/article/details/126906182 文章目录`VSCode`便携版(不推荐)缺点`VSCode`安装版(推荐)终端使用`code .` 命令打开项目 问题解决办法终极解决办法!(强烈推荐)vscode的扩展和用户数据都是默认在C盘下的 extensions:C:\Users\.vs…