400行程序写一个实时操作系统(十六):操作系统中的调度策略
前言
在前面我们完成了Sparrow的临界区的代码,使用临界区,能够解决常见的并发问题,现在该完善我们的调度算法了。
调度算法在操作系统领域常常是热门的话题。不同的用途将会使用不同的调度策略。在本节,笔者将为大家介绍一些操作系统中的调度算法的知识,加深读者对操作系统的理解。
linux内核中的调度策略
调度策略负责进行任务选择和任务切换,linux内核中的调度基于分时技术:多个进程以“时间多路复用”的方式运行,将时间进行分割,不同段时间运行不同的任务,这段时间被称之为时间片。
进程的分类
linux内核中通常有三类进程:
交互式进程:用于和用户发生交互,对时间不是很敏感,时不时卡一卡也是在用户容许范围内。
批处理进程:在后台运行的进程,不与用户直接接触,例如编译程序和科学计算程序等等。
实时进程:对时间要求非常严格的程序,例如自动控制程序。
进程的抢占
linux内核是抢占式的,也就是说,不同的进程有不同的优先级,如果响应的进程的优先级比当前运行的进程的优先级要高,那么当前的进程就会被响应的进程所抢占。
调度算法
早期linux版本的内核的调度算法比较简单:扫描并且计算所有就绪进程的优先级,然后选择最佳的进程来运行,参考的标准是时间片,时间片大的进程往往优先运行。这一算法的缺点非常明显,就是时间不固定。
当然,到了2.6版本之后,linux内核的调度算法已经复杂到不可想象的程度了,这跟多处理器的出现也有很大的关系,此时,调度选择算法会在固定的时间选出任务执行,以确保CPU的运行速度。
Linux0.11版本调度算法
让我们先看看linux内核0.11版本的调度算法。

首先是前几行:
c其实代表的是保存进程时间片的值
next表示下一个进程的序列
i,p都与挂载进程的任务队列有关,i代表数组下标,p是指向队列本身的指针。

其实这就是一个很常见的比较大小的算法,首先检查这个进程的状态是不是运行状态,然后进行比较,如果在运行队列里遇到比当前进程时间片大的进程,那么就替换成它,c更改为这个大的进程的时间片,next更改为这个进程的下标。
通过这个比较算法,我们筛选出了队列里时间片最大的任务。接下来就是切换了,如果要切换的进程的时间片没有用完,也就是c>0时,这个while循环会直接结束,程序会跳转到switch这里,这就是进程切换的函数。

如果这个优先级高的进程的时间片已经用完了,也就是c=.=0时,会进入for循环,这是对时间片的重新分配。因为我们从前面的排序已经知道,c就是被筛选出来的最大的时间片,如果c==0,意味着所有的进程都没有了时间片。
上面的for循环,其实就是根据优先级的大小来调整时间片的大小,最后每一个进程counter的值=原先值/2 +优先级。
可能读者还会疑惑,为什么要加上*p->counter>>1呢?位于运行状态的进程的时间片不是都为0了吗?
进程不止一种状态,显然,还在被阻塞的任务时间片肯定不为0啊,虽然运行状态的进程最终时间片都等于自身优先级的大小,但是阻塞任务就要考虑原先的时间片了,对原先时间片除以2,这是对平均时间片的综合考虑的结果,时间片过短,切换的开销会变大,时间片过长,进程看起来就不会是并行的。
现在让我们来到switch_to函数:

在前文笔者简单讨论过进程的上下文切换,因为进程之间隔绝了资源,这往往意味着切换的步骤会变得简单一些,从代码中可以看出,主要是TSS(一个保存进程状态信息的结构体)的切换,但是在FreeRTOS中,则主要是对arm寄存器的操作,因为进程切换是所有的资源都进行了改变,而线程的切换则是在原有的资源上修修改改,本质是任务栈的切换。
通过schedule和switch_to两个函数,我们知道了linux0.11版本是如何进行调度的,schedule负责时间片的分配和进程的选择,switch_to负责进程的切换。
操作系统的调度器本质就是在进行选择和切换。
现在该来到常见的RTOS内核了
FreeRTOS的调度算法
让我们看看tasks.c文件中的这几行代码:

prvAddTaskToReadyList函数在每个任务在添加到就绪列表时都会执行,它会与uxTopReadyPriority进行比较,然后uxTopReadyPriority记录两者中的较大者,不断的对每一个新添加进来的任务优先级进行比较,这其实就是在找出最大的优先级。
这样做,遍历时就能直接找到优先级最高的线程。

顺便一提,FreeRTOS中的任务优先级跟我们在mcu中学习到的中断优先级判断是不一样的,也跟RTT和uc/os不一样,mcu中的中断优先级是数字越小,优先级越大,RTT和uc/os的任务优先级也是这样,但FreeRTOS中是任务优先级的数字越大,优先级越大。

taskSELECT_HIGHEST_PRIORITY_TASK就是通用的选择算法的具体函数了,先对uxTopReadyPriority的值进行保存。
configASSERT笔者之前提过,是断言,用来debug,如果uxTopPriority小于0,会触发它。
每一个优先级都有一个特定的链表,while判断就绪链表的这个优先级是否有任务挂载在上面,如果没有,就继续找下一个优先级的链表,也就是–uxTopPriority。
uxTopPriority被设定为是代表最大优先级的数字,自减操作代表从就绪列表最后一个链表(优先级最高的链表)开始查找,直到找到任务链表不为空的优先级任务,那么这个任务肯定也是所有任务中优先级最大的任务。
然后获取这个任务的TCB并更新pxCurrentTCB(切换的具体函数),最后更新uxTopReadyPriority的值。
rt-thread内核的调度算法
rt-thread是一个国产RTOS,和sylixOS这些OS一样,也是头部RTOS了,不过在国内的开源领域确实是当之无愧的第一RTOS。
它是一个偏linux的RTOS,而且用面向对象的思想开发的,非常有特色。
笔者决定再讲一讲rtt的选择算法,因为这种算法很常见,用途也非常广泛。


RTT使用的是查表法,让笔者简单介绍一下这种策略的思想。
我们要查找一个链表中优先级最高的任务,最简便的做法就是for循环,一个个遍历比较,找到优先级最高的任务,这种做法很简便,缺点是复杂度过高,达到了o(n),查询的时间很长。那么有没有使复杂度为1的方法呢?
于是RTT使用了空间换时间的策略。
假设我们有八个优先级,用八个位来表示它们。任务优先级为多少,那么就在哪个位置1。
例如,task1优先级为5,那么优先级的数字就是00010000。
在实时操作系统中,优先级数字越小,优先级越高,那么这个任务代表的1更靠近右侧。
既然知道了这些,那么,把所有8个位的组合都列出来,判断每一个不同的数字最低位的1在哪个位,然后直接查询,不就可以知道任务的优先级最高是多少吗?然后顺着优先级,去执行对应的任务。

通过这个函数,可以将prioritynum最低位的1的位置打印出来。这个函数比较简单,就是循环遍历0到255,也就是00000000到11111111,让每一个数依次右移最多八位,找到最低位的1。prioritynum右移后,会与1进行与运算,也就是说,除非右移后的数最低位是1,否则都会进行下一轮循环,直到找到最低位的1并且打印;随后程序又会跳出内层循环进行下一个prioritynum的运算。
运行结果:

通过这个数组,我们可以可以将prioritynum作为数组下标查询最高优先级。这其实就是一种哈希算法的思想,用特定的数字映射特定的优先级。
这种算法缺点也十分明显,就是数组占用空间太大了,一个int就要4个字节,上面的例子是优先级为8位,如果优先级有32位呢?那么占用空间将会达到:4*2的32次方个字节。

为了解决这个问题,RTT内核使用了四个表,每个表对应8个位,算法也很简洁,先读最低八位的值,判断是否为0,不为0说明优先级肯定在最低八位中,然后直接查表即可。可能有读者好奇为什么要加上1,其实观察就会发现,这个表的第一个0,是在循环之前手动打印的,否则没有256个数,为了避免与32位全为0冲突,所以加上了1。
简单来说,笔者手动让数组里的所有数向后移动了一位,那么相应的,把prioritynum作为下标读取表里的数,也要往后面移动一位。
如果最低八位没有,那就到次八位找,同样先判断是否在次八位中,如果在,那么右移八位然后查表,因为进行了右移操作,所以返回值要在最低八位的返回值的基础加上8。
剩下两个同理,留给读者自己分析了。
总的来说,这种基于哈希思想的查表法应用十分广泛,采用空间换时间的策略,具有一定的学习价值,如果某些程序对查询的时间要求比较高,可以尝试用查表策略代替之前的遍历策略。
总结
笔者介绍了linux内核、FreeRTOS、RT-Thread的调度策略和算法,旨在加深读者对操作系统的理解。当然,本来这些内容应该分成几篇不同的文章细细讲解的,不过笔者还是决定全部放在一篇。
一方面是考虑到放在同一篇文章里更加全面系统,另一方面是考虑到Sparrow的文章数量如果过多了,比如更成了四五十篇。
那么笔者相信屏幕前的读者一定没什么兴趣看下去了。所以笔者还是决定追求简洁明了,不更那么多废话。
Sparrow的教程更到这里,其实已经快接近尾声了。更完调度算法,再更完定时器阻塞延时后,就结束了。
一步步跟着教程实现Sparrow RTOS的小伙伴们,一路看到这里,不知道是否有所收获?
相关文章:
400行程序写一个实时操作系统(十六):操作系统中的调度策略
前言 在前面我们完成了Sparrow的临界区的代码,使用临界区,能够解决常见的并发问题,现在该完善我们的调度算法了。 调度算法在操作系统领域常常是热门的话题。不同的用途将会使用不同的调度策略。在本节,笔者将为大家介绍一些操作…...
从安灯系统看汽车零部件工厂的智能制造转型
在当今快速发展的制造业领域,汽车零部件工厂正面临着日益激烈的市场竞争和不断提高的客户需求。为了在竞争中脱颖而出,实现可持续发展,许多汽车零部件工厂纷纷踏上智能制造转型之路。而安灯系统作为一种重要的生产管理工具,在这场…...
SwiftUI(三)- 渐变、实心形状和视图背景
引言 在现代的应用的UI设计中,渐变和形状背景为界面带来了丰富的层次与视觉效果,而SwiftUI提供了一系列简单且强大的API,可以轻松实现这些效果。在这篇文章中,我们将介绍SwiftUI中的渐变、实心形状和视图背景的基础用法ÿ…...
RK3568-ota升级
ota升级 OTA(Over-the-Air)即空间下载技术。 OTA 升级是 Android 系统提供的标准软件升级方式。它功能强大,可以无损失升级系统,主要通过网络,例如 WIFI、3G/4G 自动下载 OTA 升级包、自动升级,也支持通过…...
GR-ConvNet代码详解
GR-ConvNet代码详解 文章目录 GR-ConvNet代码详解前言一、utils1.dataset_processing1.image.py1.Iamge类2.DepthImage类3.WidthImage类 2.grasp.py1. _gr_text_to_no()方法2.GraspRectangles类3.GraspRectangle类3.Grasp类4.detect_grasps方法 3.generate_cornell_depth.py4.e…...
Excel自带傅里叶分析数据处理——归一化处理
在Excel工具中,默认情况下数据处理---傅里叶分析通常不进行归一化处理,需要用户手动进行归一化处理。 (1)傅里叶变换的原理 傅里叶变换将时域信号转换为频域信号,输出的是复数形式的频率分量,包含了幅值和…...
Centos7.6版本安装mysql详细步骤
操作步骤: 1.下载Linux版本Mysql并上传至linux系统中 2.解压mysql并查询系统中是否有相关软件存在以及配置mysql,启动mysql tar -zxvf mysql-5.7.35-linux-glibc2.12-x86_64.tar.gz tar -zxvf mysql-5.7.35-linux-glibc2.12-x86_64.tar.gz rpm -qa|grep mysql ##查…...
寄宿学校:为自闭症儿童提供全面的教育和关爱
在这个多彩的世界里,每一个生命都值得被温柔以待,每一颗心灵都值得被悉心呵护。然而,自闭症儿童这一特殊群体,他们的世界却常常被误解和忽视。幸运的是,有一种教育模式——寄宿学校,正为这些孩子打开了一扇…...
LLaMA Factory环境配置
LLaMA-Factory官方文档 安装正确的torch和cuda版本 参考: PyTorch 报错解决 1.ImportError: /usr/lib/x86_64-linux-gnu/libstdc.so.6: version GLIBCXX_3.4.29 not found 参考这个解决:丝滑解决ImportError: /usr/lib/x86_64-linux-gnu/libstdc.s…...
STM32实现毫秒级时间同步
提起“时间同步”这个概念,大家可能很陌生。一时间搞不清楚是什么意思。 我理解“时间同步”可以解决多个传感器采集数据不同时的问题,让多个传感器同时采集数据。 打个比方。两个人走路,都是100毫秒走一步(频率相同是前提&…...
瑞吉外卖之com.fasterxml.jackson.dataformat.cbor.CBORFactor相关报错
1.报错:Error creating bean with name routerFunctionMapping defined in class path resource [com/itheima/reggie/config/WebMvcConfig.class]: Failed to instantiate [org.springframework.web.servlet.function.support.RouterFunctionMapping]: Factory met…...
CSS - grid制作表格
1. grid-template-columns:网格布局中的列的数量,也可以设置列的宽度 .grid-container {display: grid;grid-template-columns: 80px 200px auto 40px; }.grid-container {display: grid;grid-template-columns: auto auto auto auto;//表示所有列的宽度…...
【pip】 的换源(临时换源和永久换源)
【pip】 的换源(临时换源和永久换源) 一、临时换源二、永久换源三、Linux换源四、Windows换源 一、临时换源 临时换源只需要在pip安装包时,加上一个-i参数后接源的url即可: 临时换源: 清华源 pip3 install markdown…...
Kaggle 数据集dogs-vs-cats的错误
如果你想用kaggle数据集dogs-vs-cats做深度学习数据,可能会遇到数据bug,大概类似于下面的错误: UnidentifiedImageError: cannot identify image file 其原因不是你的程序有问题,而是数据集本身还有bug: cats/666.jpgdogs/11702.jpg 预览一下…...
【网络原理】网络地址转换----NAT技术详解
💐个人主页:初晴~ 📚相关专栏:计算机网络那些事 我们在 IP协议 一文中介绍过,由于IPv4协议中 IP地址只有32位,导致最多只能表示 42亿9千万个IP地址。但我们需要通过IP地址来标识网络上的每一个设备&#x…...
React怎么创建虚拟dom和挂载到页面
1、🍟你可以直接下载本节配套的资源代码,然后导入vscode看效果,也可以跟着教程一点一点敲,都是没问题的。 2、🤔怎么运行本节代码? 很简单,随便找个浏览器打开index.html即可。💕 代…...
kafka-console-ui的简介及安装使用
kafka-console-ui的简介及安装使用 一、kafka-console-ui的简介二、安装kafka-console-ui2.1 源码安装2.2 docker安装 三、kafka-console-ui功能使用3.1、功能特性3.2、 功能介绍3.2.1 集群3.2.2 topic3.2.3 消费组3.2.4 Acl3.2.5 运维 一、kafka-console-ui的简介 kafka-cons…...
git 的分支管理详解
Git 的分支管理是其强大功能之一,允许开发者在同一代码库中并行开发多个特性或修复 bug,而不干扰主分支的代码。下面是对 Git 分支管理的详解: 1. 查看分支 查看所有分支 git branch # 查看本地分支 git branch -r # 查看远程分支 git br…...
w003基于Springboot的图书个性化推荐系统的设计与实现
🙊作者简介:多年一线开发工作经验,原创团队,分享技术代码帮助学生学习,独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取,记得注明来意哦~🌹赠送计算机毕业设计600个选题excel文…...
医院信息化与智能化系统(6)
医院信息化与智能化系统(6) 这里只描述对应过程,和可能遇到的问题及解决办法以及对应的参考链接,并不会直接每一步详细配置 如果你想通过文字描述或代码画流程图,可以试试PlantUML,告诉GPT你的文件结构,让他给你对应的…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
Leetcode33( 搜索旋转排序数组)
题目表述 整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...
深入解析光敏传感技术:嵌入式仿真平台如何重塑电子工程教学
一、光敏传感技术的物理本质与系统级实现挑战 光敏电阻作为经典的光电传感器件,其工作原理根植于半导体材料的光电导效应。当入射光子能量超过材料带隙宽度时,价带电子受激发跃迁至导带,形成电子-空穴对,导致材料电导率显著提升。…...
