Sparrow系列拓展篇:对调度层进行抽象并引入IPC机制信号量

前言

在笔者更新完Sparrow手把手教学系列后,原本是不打算继续更新的。但关于Sparrow系列的读者又渐渐增多,作为作者,总感觉这个系列的文章还是稍微有些不圆满,恐怕多少会让读者有些意兴阑珊。

最近又恰好有一点空闲时间,思来想去,于是决定再更上这么一篇,作为Sparrow系列的补充。

拓展

对调度层进行抽象

Sparrow并没有IPC机制,虽然也可以作为一个内核,但是感觉还是有点残缺。于是作为拓展,笔者决定对Sparrow内核的调度层进行抽象,先引入任务状态,这样就可以对调度层进行封装与抽象了。调度层负责提供线程状态转移的接口,IPC层则利用接口完成线程间的通信。

为了规范抽象层,必须要对任务的状态进行定义:

线程的状态

Sparrow中,任务有五种状态:运行态、就绪态、延时态、阻塞态、挂起态

运行态:任务正在运行,毫无疑问,处在运行态的任务只有一个。当任务处于运行态时,它也处于就绪态。

就绪态:任务可能在运行,也可能准备运行,当任务处于就绪态时,它可能处于运行态(如果它是最高优先级任务的话)。只有就绪态中的任务会被执行。

延时态:任务正在延时中,当任务处于延时态时,它还可能处于阻塞态。例如一个任务正在等待一个事件的发生,任务的等待最长时间是100ms,如果事件一直不发生,那么任务就会等完100ms,如果在这个过程中事件发生了,任务会马上执行。在这种情况下,任务既处于延时态,又处于阻塞态。

阻塞态:任务正在等待某个事件的发生,此时任务也可以处于延时态。

挂起态:当任务很长时间都不需要紧急执行时,可以把该任务挂起。当然,不止挂起任务,也可以挂起调度器。挂起态通常是手动设置的,这取决于用户对任务的管理。

修改Sparrow代码匹配抽象层

查找任务

由于Sparrow最大支持32个任务,通常使用一个uint32_t变量的各个位表示任务的状态,因此快速查找任务成为了一个难题,不过我们已经实现了相关函数FindHighestPriority,对其进行简单修改,传入参数从而找到最优先的任务。

__attribute__( ( always_inline ) ) static inline uint8_t FindHighestPriority( uint32_t Table )
{uint8_t temp,TopZeroNumber;__asm volatile("clz %0, %2\n""mov %1, #31\n""sub %0, %1, %0\n":"=r" (TopZeroNumber),"=r"(temp):"r" (Table));return TopZeroNumber;
}

既然有了这个函数,那么我们可以修改时钟检查函数,当然,由于Sparrow支持的任务数量毕竟小,可能对性能的提升不大。

修改代码

void CheckTicks( void )
{uint32_t LookupTable = Delay;TicksBase += 1;if( TicksBase == 0){TicksTableSwitch( );}//find delaying Taskwhile(LookupTable != 0){uint8_t i = FindHighestPriority(LookupTable);LookupTable &= ~(1 << i );if ( TicksBase >= WakeTicksTable[i] ) {WakeTicksTable[i] = 0;Delay &= ~(1 << i);//it is retained for the sake of specification.Ready |= (1 << i);}}switchTask();
}
封装

封装与接口,就是为每个模块定义清晰的接口),这些接口描述了模块的输入、输出和预期行为。接口应尽量简洁,隐藏模块内部的实现细节。将具体实现封装在模块内部,通过接口暴露功能。实现应尽量保持私有性,避免外部直接访问内部细节。

添加抽象层

//The abstraction layer of scheduling !!!
uint32_t StateAdd( TCB_t *self,uint32_t *State)
{uint32_t xre = xEnterCritical();(*State) |= (1 << self->uxPriority);xExitCritical(xre);return *State;
}uint32_t StateRemove( TCB_t *self, uint32_t *State)
{uint32_t xre1 = xEnterCritical();(*State) &= ~(1 << self->uxPriority);xExitCritical(xre1);return *State;
}uint8_t CheckState( TCB_t *self,uint32_t State )// If task is the State,return the State
{uint32_t xre2 = xEnterCritical();State &= (1 << self->uxPriority);xExitCritical(xre2);return State;
}// the abstraction layer is end

同时支持挂起调度器,如果空闲任务进入了挂起态,代表挂起调度器(因为空闲任务通常是进入低功耗,是为了防止单片机无事可做而衍生出来的任务),修改SysTick_Handler如下:

void SysTick_Handler(void)
{uint32_t xre = xEnterCritical();uint32_t temp = Suspend;temp &= 1;// If the idle task is suspended, the scheduler is suspendedif(temp != 1) {CheckTicks();}xExitCritical(xre);
}

修改命名

将状态表修改如下,这样更符合接口的原则:

uint32_t Ready = 0;
uint32_t Delay = 0;
uint32_t Suspend = 0;
uint32_t Block = 0;

现在,我们对调度层的抽象已经基本完成了,是时候引入IPC机制了。

IPC机制

信号量

笔者第一个引入Sparrow的IPC机制是信号量,它是由Dijkstra大神(Dijkstra算法、三色标记和并行垃圾回收算法等等算法的提出者)发明的。

信号量的出现是在Dijkstra的一篇文章中,有趣的是,文章的内容是关于一个叫THE的操作系统,基于一组并发进程,这些并发进程通过一种名叫信号量的机制相互同步并且与硬件同步。不得不说,信号量的思想真的是太精妙了。

初始化时的值不同,功能不同

信号量有两种功能,一种是互斥,另一种是条件同步,决定信号量的功能的关键在于它初始化时的值。

初始化为1,互斥功能

据笔者所知,2.6.9版本linux内核中,几乎所有的信号量(互斥锁、自旋锁)都是用于互斥,也就是对某个资源的独占访问。

用于互斥时,使用方法如下:

lock.semtake(上锁)
访问资源
lock.semrelease(解锁)

当然,简单的信号量会导致优先级反转现象,所以必须使用优先级继承等方法实现互斥锁,这样就能确保万无一失。

初始化为0,同步功能

有时候,我们往往会设置一个条件变量,当变量触发时,任务内部的代码才会执行,这同样可以通过信号量实现。

使用方法如下:

taskA()
{释放信号量semrelease()}taskB()
{	没信号量,继续阻塞semtake()有信号量了,不阻塞了,任务往下执行执行内部代码
}

信号量的操作

信号量有P和V两种操作,也叫down和up操作。

P操作:如果信号量的值大于1,就减1,并允许任务继续执行,否则阻塞任务。

V操作:对信号量的值加1,如果有任务在等待这个信号量,就唤醒它。

学会了信号量,一般RTOS的IPC机制基本都学会了,这也就是笔者为什么给Sparrow引入信号量的原因。

代码实现信号量

P操作是获取信号量,V操作是释放信号量,这么一对照,代码就显而易见了。

顺便一提,FreeRTOS的信号量是基于队列机制实现的,导致有很多人认为信号量是队列的一种,说实话,笔者很纳闷,这种说法明显是错误的,为什么会流行呢?难道因为一个正方形是由两个三角形组成的,你就认为三角形是正方形的一种吗?还有把信号量看作长度为某个值的消息队列的说法,这些都让笔者感到匪夷所思:FreeRTOS的消息队列就是利用了发送消息和接受消息时的计数来创建信号量的,根本不会开辟内存空间,没有长度这一说法。(不得不说互联网上很多资料漏洞百出,不仅没有帮助作用,反而误导了不少读者)

调度层抽象的应用

有意思的是阻塞和延时的实现,由于我们已经对调度层进行了抽象和封装,对任务状态的转化通过StateRemove和StateAdd接口进行,其实这是很简单的封装,所以读者可能没什么感觉,甚至觉得在画蛇添足。这是因为Sparrow太小了,但是,如果项目非常庞大,建立一层抽象是非常有必要的。笔者只是为读者展示如何建立一层简单的抽象来规范代码。

线程状态的改变

semaphore_take获取信号量时,如果没有信号量,那么线程的状态转变为阻塞态和延时态。

semaphore_release释放信号量时,如果有线程因为该信号量阻塞,那么线程的状态从阻塞态和延时态中移除,并转变为就绪态。

当延时结束或者阻塞被唤醒时,线程会继续执行。

为了防止任务没有进行调度就往下执行,笔者建立了一个空循环,PendSV是标志位,如果调度没发生,那么就会慢慢等。

获取信号量的两种结果

1.释放信号量时被唤醒

此时线程的状态从阻塞态和延时态中移除,并转变为就绪态。说明此时信号量可用,可以执行获取信号量的操作。

2.任务超时

此时线程的状态从延时态中移除,但是阻塞态并没有移除,需要被移除阻塞态。同时说明等了半天信号量都不能用,只能返回错误。

通过调度层提供的接口,判断任务的状态,可以得到是哪一种结果,并改变对应的状态。

由于每个任务可能阻塞在不同的信号量上,因此不能都使用总的阻塞表,每个任务都需要有自己的阻塞表,但同时需要更新总阻塞表,这是一种状态转移的规范。

Class(Semaphore_struct)
{uint8_t value;uint32_t Block;
};Semaphore_struct *semaphore_creat(uint8_t value)
{Semaphore_struct *xSemaphore = heap_malloc(sizeof (Semaphore_struct) );xSemaphore->value = value;xSemaphore->Block = 0;return xSemaphore;
}void semaphore_delete(Semaphore_struct *semaphore)
{heap_free(semaphore);
}uint8_t semaphore_release( Semaphore_struct *semaphore)
{uint32_t xre = xEnterCritical();if (semaphore->Block) {uint8_t i =  FindHighestPriority(semaphore->Block);StateRemove(TcbTaskTable[i],&semaphore->Block);StateRemove(TcbTaskTable[i],&Block);// Also synchronize with the total blocking stateStateRemove(TcbTaskTable[i],&Delay);StateAdd(TcbTaskTable[i], &Ready);}semaphore->value++;switchTask();xExitCritical(xre);return true;
}uint8_t semaphore_take(Semaphore_struct *semaphore,uint32_t Ticks)
{uint32_t xre = xEnterCritical();if( semaphore->value > 0) {semaphore->value--;switchTask();xExitCritical(xre);return true;}if(Ticks == 0 ){return false;}uint8_t volatile temp = PendSV;if(Ticks > 0){StateAdd(pxCurrentTCB,&Block);StateAdd(pxCurrentTCB,&semaphore->Block);TaskDelay(Ticks);}xExitCritical(xre);while(temp == PendSV){ }//It loops until the schedule is start.uint32_t xReturn = xEnterCritical();//Check whether the wake is due to delay or due to semaphore availabilityif( CheckState(pxCurrentTCB,Block) ){//if true ,the task is Block!StateRemove(pxCurrentTCB,&semaphore->Block);StateRemove(pxCurrentTCB,&Block);xExitCritical(xReturn);return false;}else{semaphore->value--;switchTask();xExitCritical(xReturn);return true;}
}

总结

调度层的抽象和IPC机制中最经典的信号量机制已经介绍完毕,笔者就介绍这么多。只要搞定了信号量,其他的的IPC机制都大差不差,读者甚至可以根据需求自己定制IPC机制,比如:信号量的同步都是一个又一个的同步,有没有办法让一个信号量的释放同步多个任务呢?当然可以,只要在信号量的基础上简单修改为唤醒所有阻塞事件就行。这其实跟linux内核中的completion机制非常相似。

笔者希望读者能动手自己完成消息队列、互斥锁、事件等IPC机制,在Sparrow系列的学习中能够学有所获。

所以剩下的IPC机制笔者就懒得敲了( ̄_, ̄ ),这是留给读者自己的拓展!就像苏格拉底所说,除了你自己,没有人能够教会你知识,其他人只能激发你的知识!

Sparrow系列的文章一般都是一边是算法思路,一边是代码,而且代码里往往没什么注释,因为笔者并不希望读者看代码看半天,而是希望读者动手敲起来,然后进行代码的调试,通过代码来理解算法的思路。

综上,Sparrow拓展篇结束!o( ̄▽ ̄)d

Sparrow源码的地址:https://github.com/skaiui2/SKRTOS_sparrow/tree/source

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用最新版的wvp和ZLMediaKit搭建Gb28181测试服务器

文章目录 说明安装1.安装nodejs简介安装步骤 2.安装java环境3.安装mysql安装修改密码 4.安装redis5.安装编译器6.安装cmake7.安装依赖库8.编译ZLMediaKit9.编译wvp-GB28181-pro 配置1.ZLMediaKit配置2.wvp-GB28181-pro配置2.1.配置ZLMediaKit连接信息2.2.28181服务器的配置2.3.…

Python程序设计 生成器

1. 基础概念 在讲迭代之前&#xff0c;先搞清楚这些名词&#xff1a; 循环&#xff08;loop&#xff09;&#xff0c;指的是在满足条件的情况下&#xff0c;重复执行同一段代码。比如&#xff0c;while 语句。迭代&#xff08;iterate&#xff09;&#xff0c;指的是按照某种…

mac m1 docker本地部署canal 监听mysql的binglog日志

mac m1 docker本地部署canal监听mysql的binglog日志(虚拟机同理) 根据黑马视频部署 1.docker 部署mysql 1.docker拉取mysql 镜像 因为m1是arm架构.需要多加一条信息 正常拉取 docker pull mysql:tagm1拉取 5.7的版本. tag需要自己指定版本 docker pull --platform linux/x…

[linux]docker基础

常见命令 Docker最常见的命令就是操作镜像、容器的命令&#xff0c;详见官方文档: Docker Docs 案例: 查看DockerHub&#xff0c;拉取Nginx镜像&#xff0c;创建并运行Nginx容器 在DockerHub中搜索Nginx镜像 拉取Nginx镜像 查看本地镜像列表 把镜像保持到本地 查看保持命令的…

C++builder中的人工智能(10)神经网络中的Sigmoid函数

在这篇文章中&#xff0c;我们将探讨最受欢迎的激活函数之一——Sigmoid函数。我们将解释什么是Logistic函数&#xff0c;以及它与Sigmoid函数的区别&#xff0c;并展示如何在C应用中使用这些函数。 目录 人工神经网络&#xff08;ANN&#xff09;中的激活函数是什么&#xff…

cursor:如何注销帐号和使用流量

点击右上角的设定图标 点击管理 在弹出的网页点登入 点”continue" 点SETING 了解最新信息请扫码关注&#xff1a;

如何选择适合小团队的项目管理工具?免费与开源软件推荐

目录 一、小团队项目管理工具的重要性 二、热门项目管理工具介绍 &#xff08;一&#xff09;禅道 &#xff08;二&#xff09;Trello &#xff08;三&#xff09;Asana &#xff08;四&#xff09;JIRA 三、免费项目管理软件推荐 &#xff08;一&#xff09;ES 管理器 …

Scaffold-ETH 2:颠覆传统开发的区块链神器,快速构建你的去中心化应用!

目录 引言一、Scaffold-eth框架二、前期准备三、搭建Scaffold-ETH 2&#xff08;一&#xff09;使用npx create-ethlatest进行设置&#xff08;二&#xff09;使用git clone进行设置1、克隆仓库&#xff1a;2、进入到此目录3、安装依赖项 四、配置Scaffold ETH-2的开发环境&…

kafka+zookeeper的搭建

kafka从2.8版本开始&#xff0c;就可以不用配置zookeeper了&#xff0c;但是也可以继续配置。我目前使用的kafka版本是kafka_2.12-3.0.0.tgz&#xff0c;其中前面的2.12表示是使用该版本的scala语言进行编写的&#xff0c;而后面的3.00才是kafka当前的版本。 通过百度网盘分享…

恢复rm -rf删除的数据

注&#xff1a;本文演示的是ext4文件系统格式数据恢复 系统版本&#xff1a;ubuntu16.04 恢复数据目录&#xff1a;数据盘&#xff08;非根&#xff09;目录 恢复工具&#xff1a;extundelete 0.2.4 恢复所有被删除数据 ext4magic 恢复指定目录数据 一、注意事项&#xff1a; …

Elasticsearch(三):Elasticvue使用及DSL执行新增、查询操作

Elasticvue使用及DSL执行CURD 1 概述2 什么是Elasticsearch DSL3 基本结构4 客户端工具介绍4.1 索引介绍4.2 创建简单索引4.3 创建相对完整的索引4.4 插入数据4.4.1 基本插入操作4.4.2 批量插入操作 5 常用的DSL查询类型5.1 match查询5.1.1 match工作原理5.1.2 operator 参数5.…

静态库、动态库、framework、xcframework、use_frameworks!的作用、关联核心SDK工程和测试(主)工程、设备CPU架构

1.1库的概念 库&#xff1a;程序代码的集合&#xff0c;编译好的二进制文件加上头文件供使用&#xff0c;共享程序代码的一种方式。 1.2库的分类 根据开源情况分为&#xff1a;开源库&#xff08;能看到具体实现&#xff09;、闭源库&#xff08;只公开调用的的接口&#xf…

C++【string类,模拟实现string类】

&#x1f31f;个人主页&#xff1a;落叶 &#x1f31f;当前专栏: C专栏 目录 为什么学习string类 C语言中的字符串 标准库中的string类 auto和范围for auto关键字 迭代器 范围for string类的常用接口说明和使用 1. string类对象的常见构造 2.string类对象的容量操作 3…

Me-LLaMA——用于医疗领域的新型开源大规模语言模型

摘要 大规模语言模型的出现是提高病人护理质量和临床操作效率的一个重大突破。大规模语言模型拥有数百亿个参数&#xff0c;通过海量文本数据训练而成&#xff0c;能够生成类似人类的反应并执行复杂的任务。这在改进临床文档、提高诊断准确性和管理病人护理方面显示出巨大的潜…

关于在VS中使用Qt不同版本报错的问题

最开始需要配置的地方 首先看一下我的Qt有关的环境变量&#xff1a; Path环境变量里&#xff1a; 这里就是把对应Qt编译器环境下的bin目录放进来&#xff1a;比如你使用的是msvc2017_64或者MinGW QMAKESPEC环境变量&#xff1a; 这个就选择Qt对应的编译器目录下的\mkspecs\w…

Windows Server 怎么关闭IE增强安全配置(关闭IE弹窗)

首先第一步打开IE浏览器&#xff0c;根据下图所示&#xff0c;访问网页时会弹出警告窗口。 打开【控制面板】图标。查看方式改为小图标&#xff0c;打开【管理工具】 第五步进入【管理工具】页面后&#xff0c;找到并双击【服务器管理器】选项。 第六步在弹出的窗口中&#…

中肿团队提出的“免疫三明治”(放疗+化疗+免疫治疗),成功登上柳叶刀肿瘤|顶刊精析·24-11-08

小罗碎碎念 该研究首次发现在同期放化疗基础上增加特瑞普利单抗&#xff08;PD-1抗体&#xff09;新辅助和辅助治疗显著提高了高危局部晚期鼻咽癌患者生存率。 如果大家看完这篇推送以后&#xff0c;有什么好的医工交叉点子&#xff0c;欢迎和我一起探讨&#xff01;&#xff0…

Golang--协程和管道

1、概念 程序&#xff1a; 是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。(程序是静态) 进程&#xff1a; 是程序的一次执行过程。正在运行的一个程序&#xff0c;进程作为资源分配的单位&#xff0c;在内存中会为每个进程分配不同的内存区域&#xff0…

动力商城-02 环境搭建

1.父工程必须满足&#xff1a;1.1删除src目录 1.2pom 2.依赖继承 //里面的依赖&#xff0c;后代无条件继承<dependencies></dependencies>//里面的依赖&#xff0c;后代想要继承&#xff0c;得自己声明需要使用&#xff0c;可以不写版本号&#xff0c;自动继承&l…

JavaWeb开发9

ResponseBody 类型&#xff1a;方法注解、类注解 位置&#xff1a;Controller方法上/类上 作用&#xff1a;将方法返回值直接响应&#xff0c;如果返回值类型是实体对象/集合&#xff0c;将会转换为JSON格式响应 说明&#xff1a;RestControllerControllerResponseBody; 统…