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 简单内核已实现的功能有

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

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

  1. 不支持时间片轮询

FreeRTOS简单内核实现7 阻塞链表的更多相关文章

  1. Linux内核之数据双链表

    导读 Linux 内核中自己实现了双向链表,可以在 include/linux/list.h 找到定义.我们将会首先从双向链表数据结构开始介绍内核里的数据结构.为什么?因为它在内核里使用的很广泛,你只 ...

  2. 基于mykernel完成时间片轮询多道进程的简单内核

    基于mykernel完成时间片轮询多道进程的简单内核 原创作品转载请注明出处+中科大孟宁老师的linux操作系统分析:https://github.com/mengning/linuxkernel/ ...

  3. [arm驱动]Linux内核开发之阻塞非阻塞IO----轮询操作【转】

    本文转载自:http://7071976.blog.51cto.com/7061976/1392082 <[arm驱动]Linux内核开发之阻塞非阻塞IO----轮询操作>涉及内核驱动函数 ...

  4. 超级简单的数组加单链表实现Map

    /** * 超级简单的数组加单链表实现Map * @author jlj * */ public class MyHashMap { public MyList[] lists; public int ...

  5. 源码级别gdb远程调试(实现OS简单内核)

    最近在学着编写一个操作系统的简单内核,需要debug工具,我们这里使用gdb来进行调试,由于虚拟机运行和本机是两个部分,所以使用 gdb 的远程调试技术,这里对 gdb 常见调试以及远程调试方式做一个 ...

  6. 服务器端IO模型的简单介绍及实现 阻塞 / 非阻塞 VS 同步 / 异步 内核实现的拷贝效率

    小结: 1.在多线程的基础上,可以考虑使用"线程池"或"连接池","线程池"旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲 ...

  7. linux简单内核链表排序

    #include <stdio.h> #include <stdlib.h> #define container_of(ptr, type, mem)(type *)((uns ...

  8. 深度剖析linux内核万能--双向链表,Hash链表模版

    我们都知道,链表是数据结构中用得最广泛的一种数据结构,对于数据结构,有顺序存储,数组就是一种.有链式存储,链表算一种.当然还有索引式的,散列式的,各种风格的说法,叫法层出不穷,但是万变不离其中,只要知 ...

  9. 基于mykernel完成多进程的简单内核

    学号351 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ mykernel简介 mykernel是由孟宁老师建立的一个用于开发您自己的操 ...

  10. 芯灵思Sinlinx A64开发板 Linux内核等待队列poll ---阻塞与非阻塞

    开发平台 芯灵思Sinlinx A64 内存: 1GB 存储: 4GB 开发板详细参数 https://m.tb.cn/h.3wMaSKm 开发板交流群 641395230 阻塞:阻塞调用是指调用结果 ...

随机推荐

  1. [Trading] 日间交易中的成交量分析 - 使用成交量趋势来提高你的效果

    在交易中,成交量代表在特定时期内股票或期货合约的易手单位数量. 交易员将其作为一个关键指标,因为它让他们知道资产的流动性水平,以及他们在接近当前价格的情况下买入或卖出头寸的容易程度,这可能是一个移动的 ...

  2. WPF 制作一个加密文件夹应用

    我有一个需求就是将我的一些文件夹的内容同步到网盘上面去.但是我是不信任现在的各个网盘的,网盘的数据被我认为是会被泄露的数据,我需要同步的文件夹中,可能存在隐私的数据.于是我就想到了将文件夹里面的内容进 ...

  3. dotnet 通过 DockerfileContext 解决项目放在里层文件夹导致 VisualStudio 构建失败

    本文告诉大家,如何解决 csproj 项目文件放入到里层的文件夹,不放在 sln 所在文件夹的第一层子文件夹,导致 VisualStudio 2022 在构建 docker 映像提示找不到文件的问题 ...

  4. Part-DB 配置流程

    介绍 Part-DB是一个开源的器件管理工具,博主用于管理个人的电子器材,最近捣鼓了一下这个工具,由于手头还有一块闲置的赛昉·星光2的开发板,所以我打算一起拿来捣鼓一下,如果不成功,就用树莓派(生气) ...

  5. kubeadm搭建单master多node节点的k8s集群(3)

    一.实验环境准备 K8s集群角色 IP 主机名 安装的组件 配置 控制节点 192.168.1.10 master apiserver.controller-manager.scheduler.etc ...

  6. C语言结构体的内存分配

    一.结构体内存分配原则 原则一:结构体中元素按照定义顺序存放到内存中,但并不是紧密排列.从结构体存储的首地址开始 ,每一个元素存入内存中时,它都会认为内存是以自己的宽度来划分空间的,因此元素存放的位置 ...

  7. SqlServer使用总结

    -- 新增主键,系统随机取名 ALTER TABLE "T_CS1" ADD PRIMARY KEY CLUSTERED ("ID1", "ID2&q ...

  8. 【GUI软件】小红书详情数据批量采集,含笔记内容、转评赞藏等,支持多笔记同时采集!

    目录 一.背景介绍 1.1 爬取目标 1.2 演示视频 1.3 软件说明 二.代码讲解 2.1 爬虫采集模块 2.2 软件界面模块 2.3 日志模块 三.获取源码及软件 一.背景介绍 1.1 爬取目标 ...

  9. phpstudy8.1安装与配置

    环境: window10 phpstudy8.1.1.3 Vmware安装centos7.6 使用场景 window10安装mysql和redis 在Vmware安装centos7.6 桥接模式上网 ...

  10. fastposter发布1.4.5 跨语言的海报生成器

    fastposter发布1.4.5 跨语言的海报生成器 v1.4.5 增加了右键菜单,修复了跨域bug 一分钟完成海报开发任务 future: 增加了右键菜单 删除 图层上移 图层下移 优化项目代码文 ...