当前位置: 首页 > news >正文

400行程序写一个实时操作系统(十六):操作系统中的调度策略

前言

在前面我们完成了Sparrow的临界区的代码,使用临界区,能够解决常见的并发问题,现在该完善我们的调度算法了。

调度算法在操作系统领域常常是热门的话题。不同的用途将会使用不同的调度策略。在本节,笔者将为大家介绍一些操作系统中的调度算法的知识,加深读者对操作系统的理解。

linux内核中的调度策略

调度策略负责进行任务选择和任务切换,linux内核中的调度基于分时技术:多个进程以“时间多路复用”的方式运行,将时间进行分割,不同段时间运行不同的任务,这段时间被称之为时间片。

进程的分类

linux内核中通常有三类进程:

交互式进程:用于和用户发生交互,对时间不是很敏感,时不时卡一卡也是在用户容许范围内。

批处理进程:在后台运行的进程,不与用户直接接触,例如编译程序和科学计算程序等等。

实时进程:对时间要求非常严格的程序,例如自动控制程序。

进程的抢占

linux内核是抢占式的,也就是说,不同的进程有不同的优先级,如果响应的进程的优先级比当前运行的进程的优先级要高,那么当前的进程就会被响应的进程所抢占。

调度算法

早期linux版本的内核的调度算法比较简单:扫描并且计算所有就绪进程的优先级,然后选择最佳的进程来运行,参考的标准是时间片,时间片大的进程往往优先运行。这一算法的缺点非常明显,就是时间不固定。

当然,到了2.6版本之后,linux内核的调度算法已经复杂到不可想象的程度了,这跟多处理器的出现也有很大的关系,此时,调度选择算法会在固定的时间选出任务执行,以确保CPU的运行速度。

Linux0.11版本调度算法

让我们先看看linux内核0.11版本的调度算法。

img

首先是前几行:

c其实代表的是保存进程时间片的值

next表示下一个进程的序列

i,p都与挂载进程的任务队列有关,i代表数组下标,p是指向队列本身的指针。

img

其实这就是一个很常见的比较大小的算法,首先检查这个进程的状态是不是运行状态,然后进行比较,如果在运行队列里遇到比当前进程时间片大的进程,那么就替换成它,c更改为这个大的进程的时间片,next更改为这个进程的下标。

通过这个比较算法,我们筛选出了队列里时间片最大的任务。接下来就是切换了,如果要切换的进程的时间片没有用完,也就是c>0时,这个while循环会直接结束,程序会跳转到switch这里,这就是进程切换的函数。

img

如果这个优先级高的进程的时间片已经用完了,也就是c=.=0时,会进入for循环,这是对时间片的重新分配。因为我们从前面的排序已经知道,c就是被筛选出来的最大的时间片,如果c==0,意味着所有的进程都没有了时间片。

上面的for循环,其实就是根据优先级的大小来调整时间片的大小,最后每一个进程counter的值=原先值/2 +优先级。

可能读者还会疑惑,为什么要加上*p->counter>>1呢?位于运行状态的进程的时间片不是都为0了吗?

进程不止一种状态,显然,还在被阻塞的任务时间片肯定不为0啊,虽然运行状态的进程最终时间片都等于自身优先级的大小,但是阻塞任务就要考虑原先的时间片了,对原先时间片除以2,这是对平均时间片的综合考虑的结果,时间片过短,切换的开销会变大,时间片过长,进程看起来就不会是并行的。

现在让我们来到switch_to函数:

img

在前文笔者简单讨论过进程的上下文切换,因为进程之间隔绝了资源,这往往意味着切换的步骤会变得简单一些,从代码中可以看出,主要是TSS(一个保存进程状态信息的结构体)的切换,但是在FreeRTOS中,则主要是对arm寄存器的操作,因为进程切换是所有的资源都进行了改变,而线程的切换则是在原有的资源上修修改改,本质是任务栈的切换。

通过schedule和switch_to两个函数,我们知道了linux0.11版本是如何进行调度的,schedule负责时间片的分配和进程的选择,switch_to负责进程的切换。

操作系统的调度器本质就是在进行选择和切换。

现在该来到常见的RTOS内核了

FreeRTOS的调度算法

让我们看看tasks.c文件中的这几行代码:

img

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

这样做,遍历时就能直接找到优先级最高的线程。

img

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

img

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的选择算法,因为这种算法很常见,用途也非常广泛。

img

img

RTT使用的是查表法,让笔者简单介绍一下这种策略的思想。

我们要查找一个链表中优先级最高的任务,最简便的做法就是for循环,一个个遍历比较,找到优先级最高的任务,这种做法很简便,缺点是复杂度过高,达到了o(n),查询的时间很长。那么有没有使复杂度为1的方法呢?

于是RTT使用了空间换时间的策略。

假设我们有八个优先级,用八个位来表示它们。任务优先级为多少,那么就在哪个位置1。

例如,task1优先级为5,那么优先级的数字就是00010000。

在实时操作系统中,优先级数字越小,优先级越高,那么这个任务代表的1更靠近右侧。

既然知道了这些,那么,把所有8个位的组合都列出来,判断每一个不同的数字最低位的1在哪个位,然后直接查询,不就可以知道任务的优先级最高是多少吗?然后顺着优先级,去执行对应的任务。

img

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

运行结果:

img

通过这个数组,我们可以可以将prioritynum作为数组下标查询最高优先级。这其实就是一种哈希算法的思想,用特定的数字映射特定的优先级。

这种算法缺点也十分明显,就是数组占用空间太大了,一个int就要4个字节,上面的例子是优先级为8位,如果优先级有32位呢?那么占用空间将会达到:4*2的32次方个字节。

img

为了解决这个问题,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中的渐变、实心形状和视图背景的基础用法&#xff…...

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你的文件结构,让他给你对应的…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...

MySQL的pymysql操作

本章是MySQL的最后一章&#xff0c;MySQL到此完结&#xff0c;下一站Hadoop&#xff01;&#xff01;&#xff01; 这章很简单&#xff0c;完整代码在最后&#xff0c;详细讲解之前python课程里面也有&#xff0c;感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...

comfyui 工作流中 图生视频 如何增加视频的长度到5秒

comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗&#xff1f; 在ComfyUI中实现图生视频并延长到5秒&#xff0c;需要结合多个扩展和技巧。以下是完整解决方案&#xff1a; 核心工作流配置&#xff08;24fps下5秒120帧&#xff09; #mermaid-svg-yP…...