Linux的调度与切换(包含进行优先级,基于O(1)的调度算法)

进程优先级

基本概念

  • cpu资源分配的先后顺序,就是指进程的优先权(priority)
  • 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能
  • 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能

存在进程优先级的前提是,进程要访问某种资源,进程通过一定的方式(排队),确定享受资源的先后顺序。

为什么要有优先级

存在优先级的本质还是资源过少,资源的多少永远都是一个相对的概念,在大部分的计算机上一般都只有一个CPU,一个磁盘。而当在计算机上可以同时启动好几个程序,就是好几个进程,所以在计算机中资源永远是少数的。所以存在优先级。

优先级VS权限

权限决定的是能不能做这件事情,优先级决定的是谁先谁后的问题。

Linux下的优先级

PRI

PRI(Priority):这实际上是Linux调度器使用的内部优先级。它基于进程的Nice值和其他因素(如进程是否正在等待I/O)来计算。因此,PRI并不是直接设置的,而是由系统根据进程的Nice值和其他条件动态计算的。

在Linux下的优先级相当于是一个在task_struct中的一个数字,在Linux下的进程的优先级默认是80。

Linux优先级是可以被修改的,Linux优先级是可以被修改的,Linux的优先级的分为[60 , 99] 共计40个。

Linux优先级本质就是数字,数字越小,优先级越高。

查看Linux下的进程PRI

1.使用ps命令

ps命令可以显示当前活动的进程信息。要查看进程的PIDNice值和命令名称,可以使用以下命令

ps -l -a

-a 选项用于显示与终端关联的所有进程,包括其他用户的进程。通常,ps 命令默认只显示与当前终端或会话关联的进程。使用 -a 选项可以扩大显示范围。

在这里插入图片描述

2.使用top命令

top命令是一个动态显示进程信息的工具。在top的输出中,你可以看到每个进程的PR(Priority)列。

top

在这里插入图片描述

NI

Linux系统允许用户调整优先级,单=但是不能直接让你修改PRI,而是修改nice值(不是优先级,而是进程优先级的修正数据)。pri = pri(old) + nice

pri(old)老的优先级都是80开始,自己设置优先级时并不需要看以前的在80的基础上重新设计即可。

nice值的取值范围是[-20 19]所以Linux的优先级就是[60 ,99]。

修改Linux下的进程优先级

修改步骤

  • 首先使用top命令,查看到进程信息。

  • 输入r在输入要修改的进程的pid

  • 输入NI值修改完毕

修改前进程优先级

在这里插入图片描述

修改后进程优先级

在这里插入图片描述

Linux为什么要调整优先级要受限制

如果不加限制,将自己进程的优先级调整的非常高,别人的优先级调整的非常低优先级较高的进程,优先得到资源。但是后续还有源源不断的进程产生,常规进程很难享受到CPU资源,会导致进程饥饿问题。

任何的分时操作系统,调度上,都要较为公平的进行调度。

进程调度与切换

概念准备

  • 进程在运行的时候,放在CPU上,对于现代操作系统都是基于时间片进行轮转执行的。
  • 竞争性:系统进程数目众多,而CPU资源只有少量,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
  • 独立性:多进程运行,需要独享各种资源,多进程运行间互不干扰。
  • 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行。
  • 并发:多个进程在一个CPU下采用进程切换的方式,在一时间之内,让进程都得以推进,称之为并发

进程切换

概念

进程切换(Process Switching)又称为任务切换或上下文切换(Context Switching),是操作系统中一项核心技术。它指的是从一个正在运行的进程切换到另一个要运行进程的过程。这个切换过程包括保存当前进程的上下文(CPU寄存器和程序计数器的内容称为进程的上下文)以及恢复或载入另一个进程的上下文到处理器的过程。

寄存器

CPU中存在着大量的寄存器

eax/ebx/ecx/edx:对CPU中临时的数据进行保存

eds/ecs/fg/gs:用来衡量代码中哪一段是数据,哪一段是代码。

eip:俗称PC,用来记录程序下一条要执行的指令。

程序状态字(psw): 主要用于反映处理器的状态及某些计算结果以及控制指令的执行。

ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。

EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

进程在运算过程中,要产生大量的临时数据,放在CPU的寄存器中。

深度理解进程切换

例子

为了更好的理解进程切换,我们举一个生活中的例子。在大学期间,有的学生想要去当兵,那么当学生去当兵的离开的时候学校要保留该学生当时对应的相关数据,例如课程已经完成了哪些,学生的宿舍信息等等。当学生当兵回来时,先要恢复走的时候但是保留的数据,继续从走的时候开始学习。这就相当于完成了一次切换。

所有的保存都是为了最终的恢复,所有的恢复都是为了继续上次的运行为止继续运行。所以当当前进程被调度的时候,时间片到了而需要进程进行切换,首先需要将CPU上该进程的数据进行临时保存。

CPU内部的所有临时数据,我们叫做进程的硬件上下文,硬件上下文,让我们的进程进行保存。保护上下文

下一个进程进行运行时,CPU中的寄存器中数据可能还是保留的上一个进程的相关数据。但对于我这个进程CPU中保留的上一个进程中的临时数据已经没有用了,直接在运行的时候,把产生的数据进行覆盖式的写入CPU中的寄存器中。

当进程在二次被调度的时候,进程被放在CPU上开始运行,将曾经保存的硬件上下文进行恢复

CPU内的寄存器只有一套,寄存器内部保存的数据可以有多套

**疑问:**既然所有的进程都用这一套寄存那么一个进程的数据会被其他进程看到吗

虽然寄存器数据放在了一个共享的CPU设别里面,但是所有的数据,其实都被进程私有的,进程的上下文并不是指寄存器而是指寄存器中的内容!!!寄存器 != 寄存器内容

进程调度

概念

进程调度是操作系统中的一个核心概念,可以理解为操作系统对正在运行的进程进行优先级排列和调度执行的过程。在单处理器系统中,进程调度决定了哪个进程将拥有处理器的执行权;而在多处理器系统中,进程调度还可以决定将哪些进程分配到哪个处理器上执行。

教材中的进程调度

进程调度算法是操作系统根据一定的规则和方法来决定哪个进程应该获得CPU的使用权。这些算法的目标通常是提高系统的吞吐量、降低响应时间、确保公平性,并充分利用CPU资源。以下是几种常见的进程调度算法:

  1. 先来先服务(FCFS)调度算法:按照进程进入就绪队列的先后顺序进行调度。这种算法简单直观,但可能导致长作业等待时间过长的问题。
  2. 短作业优先(SJF)调度算法:将CPU时间片分配给预计运行时间最短的进程。这种算法可以最大程度地减少平均等待时间,但可能导致长作业被饿死,即长时间无法获得CPU资源。
  3. 优先级调度算法:根据进程的优先级进行调度。优先级更高的进程会优先获得CPU资源。这种算法可以确保重要或紧急的任务得到及时处理。
  4. 时间片轮转(RR)调度算法:每个进程被分配一个固定大小的时间片,当时间片用完时,无论进程是否执行完毕,都会被暂停并放到就绪队列的末尾,等待下一次调度。这种算法常用于分时系统,以确保每个用户都能得到及时的响应。
  5. 最短剩余时间优先(SRTF)调度算法:这是SJF算法的一个变种,它考虑了进程的当前剩余执行时间。当一个新的进程进入就绪队列时,如果它的剩余执行时间比当前正在运行的进程的剩余执行时间更短,那么调度器会立即停止当前进程的执行,转而运行新的进程。
  6. 多级反馈队列(MFQ)调度算法:将就绪队列划分为多个不同级别的队列,每个队列具有不同的时间片长度和优先级。当一个进程在一个队列中运行完其时间片后,它会被降到下一级队列中等待调度。如果进程在某一级队列中等待了较长时间仍未得到执行,它的优先级会被提升,以便尽快获得CPU资源。

Linux实现进程调度算法(O(1)调度算法)

Linux实现进程调度算法,考虑优先级,考虑饥饿,考虑效率。

每个CPU都匹配一个运行队列runqueue,在这个结构体中存在一个queue[140]该数组的类型是task_struct*,里面存放着task_struct结构体的指针。其中数组下标0 - 99不用。只用100 - 139,100 - 139正好有40个优先级。Linux下也只有40个优先级,正好相对应。

在这里插入图片描述

如上图分析所示如果有新进程来了,那么根据进程的优先级,将进程放在对应的进程优先级的后面。当CPU要执行时,就从进程优先级高的开始,找到第一个进程并执行。

但是每次都要进程优先级最高的队列开始遍历,比较麻烦引入了一个bitmap[5]。

bitmap[5]

一个整数是32位,存放5个整型,一共160个比特位。比特位的位置表示哪一个队列,比特位的内容表示该队列是否是空。

一个整型32位,我们可以一次检测一个整型,首先检测bitmap[0]中是否为0,如果为0说明在前32个优先级中没有进程。继续检测,直到数组内容不为0,然后开始检测32个比特位中哪一位为1,找到最开始的1,就找到了在此运行队列中优先级最高的进程。

这样我们就可以把时间复杂度降为O(1)。

在这里插入图片描述

饥饿问题

如果只按照上面的思路设计,那么我们假设有好多进程的优先级为80但是当一直有比优先级为80还高的进程进入队列时,那么CPU处理的为优先级更高的进程,这样会导致进程优先级低的进程一无法上处理机。就会导致饥饿问题。

Linux进程调度算法中,是存在两个 queue那么这两个queue分别代表着两个队列,一个是活动队列,一个是过期队列。

活动队列

  • 时间片还没有结束的所有进程都按照优先级放在该队列

  • nr_active: 总共有多少个运行状态的进程

  • queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!

过期队列

  • 过期队列和活动队列结构一模一样

  • 过期队列上放置的进程,都是时间片耗尽的进程

  • 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算

操作系统会先按照进程的优先级,将活动队列上的进程放入到处理机上,当有新的进程时,不会在放到活动队列上,而是要根据进程的优先级放到过期队列。

当活动队列中的所有进程全部执行了一遍时,就要交换活动队列和过期队列了。那么应当如何交换活动队列和过期队列

在运行队列中存在两个指针分别是active指针和expired指针

active指针和expired指针

  • active指针永远指向活动队列

  • expired指针永远指向过期队列

首先我们可以把活动队列和过期队列看成一个结构体

struct q
{
	int nr_active;
	bitmap[5];
	task_struct queue[140];
}

活动队列上的进程会越来越少,但是过期队列上的进程会越来越多,当活动队列上的进程都执行过了,那么就会执行过期队列上的进程。需要将过期队列和活动队列相交换。

指针永远指向活动队列

  • expired指针永远指向过期队列

首先我们可以把活动队列和过期队列看成一个结构体

struct q
{
	int nr_active;
	bitmap[5];
	task_struct queue[140];
}

活动队列上的进程会越来越少,但是过期队列上的进程会越来越多,当活动队列上的进程都执行过了,那么就会执行过期队列上的进程。需要将过期队列和活动队列相交换。

存在一个长度为2的数组struct q array[2],存放着活动队列和过期队列那么交换两个队列我们就可以看做是交换两个指针。active指针永远指向活动队列,expired指针永远指向过期队列。时间复杂度仍然为O(1)。

本专栏为“小菜”linux学习之路
该文章仅供学习参考,如有问题,欢迎在评论区指出。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值