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
相关文章:
Sparrow系列拓展篇:对调度层进行抽象并引入IPC机制信号量
前言 在笔者更新完Sparrow手把手教学系列后,原本是不打算继续更新的。但关于Sparrow系列的读者又渐渐增多,作为作者,总感觉这个系列的文章还是稍微有些不圆满,恐怕多少会让读者有些意兴阑珊。 最近又恰好有一点空闲时间…...

天塌了!!!SQL竟也可以做预测分析?| 商品零售额的预测
目录 0 问题背景 1 数据准备 2 问题解决 2.1 模型构建 (1)符号规定 (2)基本假设 (3)模型的分析与建立 2.2 模型求解 3 小结 0 问题背景 1960年—1985年全国社会商品零售额如图1 所示 表1全国社…...

VSCode本地C/C++环境配置
基本环境下载 1.我的系统是windows,自己先下载安装VSCode,网上视频实在太多,我建议跟着B站视频操作。 2.下载安装好后你需要明白:VSCode只是一个编辑工具,我们要写C/C代码得编译运行,所以我们要配置它在w…...

【智能算法应用】淘金优化算法求解二维路径规划问题
摘要 本文基于智能算法的淘金优化算法(Gold Panning Optimization, GPO)求解二维路径规划问题。该算法模拟淘金过程中个体寻找最优金矿路径的行为,利用适应度函数优化路径规划,能够在复杂环境下实现从起点到目标点的最优路径搜索…...

Linux挖矿病毒(kswapd0进程使cpu爆满)
一、摘要 事情起因:有台测试服务器很久没用了,突然监控到CPU飙到了95以上,并且阿里云服务器厂商还发送了通知消息,【阿里云】尊敬的xxh: 经检测您的阿里云服务(ECS实例)i-xxx存在挖矿活动。因此很明确服务器中挖矿病毒…...
【java】ArrayList与LinkedList的区别
目录 1. 说明2. 内部实现2.1 ArrayList2.2 LinkedList 3. 性能特点3.1 插入和删除操作3.2 访问操作3.1 遍历操作 4. 使用场景5. 扩容机制6. 空间开销 1. 说明 1.Java中的ArrayList和LinkedList是两种常用的集合实现类,都属于Java集合框架的一部分,但它们…...

【LangChain系列6】【Agent模块详解】
目录 前言一、LangChain1-1、介绍1-2、LangChain抽象出来的核心模块1-3、特点1-4、langchain解决的一些行业痛点1-5、安装 二、Agent模块详解2-0、Agent核心思想——React介绍2-0-1、React的介绍以及由来2-0-2、伪代码介绍React的执行顺序 2-1、Agent介绍2-1、Self ask with se…...
JavaScript Cookie 与 服务器生成的 Cookie 的区别与应用
JavaScript Cookie 与 服务器生成的 Cookie 的区别与应用 Cookie是一种甜点,同时也是web前端开发中一种非常常见且重要的技术,它用于在客户端和服务器之间存储和传递信息。用户身份验证、会话管理,还是用户个性化设置,都离不开Coo…...
深入了解Git、GitHub、GitLab及其应用技巧
在现代软件开发中,掌握版本控制系统(VCS)是至关重要的,其中Git是最流行的分布式版本控制工具之一。本文将详细介绍Git的用途及其基本操作,并深入探讨GitLab、GitHub、和Git Desktop的使用方法,同时总结Git的…...

ctfshow(316,317,318)--XSS漏洞--反射性XSS
反射型XSS相关知识 Web316 进入界面: 审计 显示是关于反射性XSS的题目。 思路 首先想到利用XSS平台解题,看其他师傅的wp提示flag是在cookie中。 当前页面的cookie是flagyou%20are%20not%20admin%20no%20flag。 但是这里我使用XSS平台,…...

Visual Studio2022版本的下载与安装
1-首先打开微软的官网,下面就是链接 下载 Visual Studio Tools - 免费安装 Windows、Mac、Linux免费下载 Visual Studio IDE 或 VS Code。 在 Windows、Mac 上试用 Visual Studio Professional 或企业版。https://visualstudio.microsoft.com/zh-hans/downloads/?…...
nodeJS程序如何引入依赖包
在 Node.js 运行时中引入依赖包通常通过以下步骤完成: 初始化项目: 首先,你需要初始化一个 Node.js 项目。如果你还没有 package.json 文件,可以使用 npm init 命令来创建它。运行以下命令并按提示输入相关信息: npm i…...

建网站怎么建?只需几个步骤
在这个网络飞速发展的时代,越来越多的人都渴望拥有自己的网站。然而,对于大多数新手来说,如何建立自己的网站可能充满了挑战。本文将为您详细介绍建网站的关键步骤,让您能够轻松搭建自己的网站。 选择适合的建站工具 虽然市面上有…...
机器学习课程总结(个人向)
前言 通过看课件PPT整理的笔记,没有截图 由于大部分内容已经耳熟能详了,故记录比较简略,只记录了一些概念和需要记忆的地方。 里面有较多的个人观点,未必正确。如有错误,还请各位大佬指正 正文 绪论 机器学习的定…...
数据分析-43-时间序列预测之深度学习方法GRU
文章目录 1 时间序列1.1 时间序列特点1.1.1 原始信号1.1.2 趋势1.1.3 季节性和周期性1.1.4 噪声1.2 时间序列预测方法1.2.1 统计方法1.2.2 机器学习方法1.2.3 深度学习方法2 GRU2.1 模拟数据2.2 数据归一化2.3 生成滞后特征2.4 切分训练集和测试集2.5 模型训练2.6 模型预测3 参…...

Pandas | 数据分析时将特定列转换为数字类型 float64 或 int64的方法
类型转换 传统方法astype使用value_counts统计通过apply替换并使用astype转换 pd.to_numericx对连续变量进行转化⭐参数:返回值:示例代码: isnull不会检查空字符串 数据准备 有一组数据信息如下,其中主要将TotalCharges、MonthlyC…...

Elasticsearch的自定义查询方法到底是啥?
Elasticsearch主要的目的就是查询,默认提供的查询方法是查询全部,不满足我们的需求,可以定义查询方法 自定义查询方法 单条件查询 我们查询的需求:从title中查询所有包含"鼠标"这个分词的商品数据 SELECT * FROM it…...

Jenkins找不到maven构建项目
有的可能没有出现maven这个选项 解决办法:需要安装Maven项目插件 输入Maven Integration plugin...
怎么更换IP地址 改变IP归属地的三种方法
要更换自己的IP地址,您可以按照以下步骤进行操作: 1. 了解IP地址类型:首先,您需要了解您当前使用的IP地址类型。IP地址分为静态IP和动态IP两种。静态IP地址是固定的,使用第三方软件比如S深度IP转换器;而使用…...
C#-异步查询示例
文章速览 CancellationTokenSource 概述代码示例 坚持记录实属不易,希望友善多金的码友能够随手点一个赞。 共同创建氛围更加良好的开发者社区! 谢谢~ CancellationTokenSource 概述 使用System.Threading下的CancellationTokenSource类,进…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...