【Linux】进程5——进程优先级

1.进程优先级

1.1.什么是进程优先级

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

1.2.为什么要进程优先级

  1. 资源是有限的,进程是多个的,注定了进程之间是竞争关系
  2. 操作系统必须保证大家是良心竞争,如果我们进程长时间得不到CPU资源,该进程的代码长时间无法得到推进——该进程的饥饿问题

如果长时间得不到CPU资源,那么这个在windows上表现为该程序长时间无响应 

2.查看系统进程

在linux或者unix系统中,用ps –l命令则会类似输出以下几个内容:

默认情况下,ps -l只会显示当前账号的进程,看不到别的终端的进程

我们ps -al就能看到别的终端的进程了

干扰信息太多了 ,我们筛选一下自己创建的myproc进程

此命令会显示当前用户下所有进程的内容。

我们很容易注意到其中的几个重要信息,有下:

  1. UID : 代表执行者的身份
  2. PID : 代表这个进程的代号
  3. PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  4. PRI :代表这个进程可被执行的优先级,其值越小越早被执行
  5. NI :代表这个进程的nice值

2.1.UID

我们很明显的知道了,root的UID是0,zs_108的UID是1000

2.1.PRI和NI 

  • PRI也还是比较好理解的,不就是priority嘛,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高。
  • Linux的默认优先级为80。Linux的优先级是可以被修改的,范围在[60,99]。
  • NI,就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
  • PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
  • 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
  • 所以,调整进程优先级,在Linux下,就是调整进程nice值
  • nice其取值范围是-20至19,一共40个级别

        看完了上面的定义我们的问题就来了,在Linux中为什么调整优先级是受限制的呢?Linux为什么不能可以将[60,99]调整为[-∞,+∞]。

        那是因为如果不加限制,如果恶意将自己的优先级调整的非常高,而给其余人的优先级调整的非常低(优先级较高的进程先享受CPU的资源),那些系统开启自启动的进程也就是正常系统进程很难再享受到CPU的资源,会变得卡顿。这样的情况叫做进程饥饿。

        任何的分时操作系统在调度上都要保证较为公平的调度

 2.3.调整优先级

更改优先级的方法有挺多的,nice和renice指令是不错的选择,大家可以去百度一下

我们这里讲用top指令来更改优先级

 我们先开启两个账号

这个test的优先级是80

我们打开top

打开了之后,我们点击r——重新调整优先级

然后输入test的PID 

 按回车,接下来通过修改nice值来修改PRI

普通用户不能调优先级 

我们换root账号来重复上面的步骤,完成了

NICE值变成了-20,这个是因为他的范围限制!!!!

PRI的也是60,也是因为它的范围限制

我们此时再去调整PRI,我们把nice值设置为10,我们发现NI变成了10,但是PRI变成了90

注意:pri(old)的优先级再每一次设置时都是80,而不是前一次的优先级!!!

 

2.4.优先级队列实现原理

         我们所对应的优先级[60,99]在运行时会转换成[100,139]。
        进程在进程优先级队列当中是从上往下,从左往右进行调度的,当runing中的进程被调度完之后,通过指针交换来继续调度waiting中的进程,以此循环,就构成了我们所看到的优先级队列。 

3.几个小问题

 我们先来回答几个问题??

3.1.为什么函数的返回值会被外部拿到呢?

        这是因为我们CPU上有很多的寄存器,我们平时函数返回的时候会将返回数据,写入到寄存器中,外部再通过读取寄存器中的值来获取函数返回值。

return a -> mov eax a //返回值a会转化成mov指令,将变量a中的值保存在eax寄存器中

3.2.系统怎么知道我们的进程当前执行到哪行代码了呢

        在我们计算机的CPU中有一个程序计数器PC指针,或者eip,这两个都是用来记录当前进程正在执行的下一行指令的地址。
        程序计数器通常被存储在寄存器中,这样可以让CPU在读取指令时更快地访问。由于CPU是按照顺序执行指令的,因此程序计数器的主要功能就是告诉CPU下一个要执行的指令在哪里。

3.3.在CPU中寄存器扮演什么角色呢?

   在我们CPU中有很多的寄存器例如,

  1. 通用寄存器:eax,ebx,ecx,edx等等。通用寄存器从字面意思来理解就是没有什么用的寄存器,只要你需要,我就可以来供你使用,保存一些数据了什么的。
  2. 栈帧寄存器:ebp,esp,eip等等。用来维护函数栈帧结构的寄存器。
  3. 状态寄存器status。是一个包含有关处理器状态信息的寄存器。它通常包含一些标志位,例如:溢出标志,进位标志,奇偶标志等等。

        CPU中有大量的寄存器主要是为了提高效率,进程的高频数据会被放入寄存器中,寄存器中保存了进程的相关数据,以方便进行对数据的访问或者修改。

        cpu寄存器中保存的进程的临时数据,也包含进程的上下文数据,进程上下文包括执行该进程有关的各种寄存器(例如通用寄存器、程序计数器PC、程序状态字寄存器PS等)的值。

3.4.为什么要保存进程的上下文数据呢?

        所有的保存都是为了恢复,如果我们不保存进程的上下文数据,那么进程再被切换出去之后,再被切换回来的时候,CPU就不知道该从哪里开始运行,所以我们进程在被切换的时候会将自己的上下文保存好甚至带走,等到再次被调度的时候,首先做的第一件事就是恢复上下文,随后再接着执行代码。

3.5.5进程数据上下文保存在哪里呢?

       一般情况下我们把进程的上下文是以结构体形式保存在进程的PCB数据结构(task_struct)里的。

struct reg_info
{
	int eax;
	int ebc;
	int eip;
	.......
}

4.补充知识

  1. 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  2. 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
  3. 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
  4. 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

 5.进程的调度与切换

  • 我们都知道在程序都被task_struct进行管理的,当进程需要运行时,就需要进行排队。
  • 我们也知道CPU中有个时间片的东西来控制各个进程一次在CPU中最长占用时间。
  • 当某些程序过大导致在规定的时间片内执行不完时,我们就需要切换到下一个队列中的程序。
  • 那之前的程序应该怎么办?

  CPU中存在大量的寄存器,我们在VS中调用堆栈可以看到,有一些:eds/ecs/fg/gs,eip,cr0-cr4,eax/ebx/ecx/edx,等待。这些寄存器可以帮助我们进行对这些代码数据进行记录保存,例如eip(也叫pc指针),这个寄存器可以记录我们的代码执行到了哪一部分。所以说进程在运行过程中要产生大量临时数据放在CPU的寄存器中!!!这些临时数据被我们保存在各个进程的PCB中。 后来恢复运行的时候我们寄存器只需从这里读取数据便可继续执行。

        CPU内部的所有临时数据我们叫做进程硬件的上下文。保存我们的进程上下文叫做保护上下文。在首次调度时,我们只需要将代码的起始地址放到eip中,然后逐步进行,进行时生成的临时数据被我们放到寄存器中。而二次调度时,我们只需要将上下文数据恢复到寄存器中即可!

 进程在被切换的时候的基本步骤如下:

  1. 保存上下文
  2. 恢复上下文

上图是早期进程切换源码 。

总结:所有的保存都是为了最终的恢复,所有的恢复都是为了在上一次保存的位置继续执行!!!

CPU内的寄存器只有一套,而寄存器内部保存的数据可以有多套。所以虽然寄存器数据放在了一个共享的CPU中,但是所有的数据其实都是被私有的!!

4.内核进程调度队列

上图是Linux2.6内核中进程队列的数据结构,之间关系也已经给大家画出来。在这里我们只看红框和蓝框对应的部分。

首先我们来看queue[140],真正的类型:task_struct* queue[40]。他其实是一个指针数组,里面存放的是task_struct指针,那为什么是140个呢?前0~99我们不用,因为0~99中我们存放的时实时操作,剩下的100~130一共40个刚好对应的是我们上文所提到的优先级的范围差值,正好在每一个数组中可以存放相同优先级的task_struct。这就好比一个C++中的哈希桶结构。

当我们执行进程时,我们就从优先级最高的开始依次往下执行。但是有些队列是为空的,我们需要依次进行扫描判断吗?这就要出现第二个数据int bitmap[5]。 一个int4个字节,32bit,那么这个数组就是32*5 = 160比特位。所以比特位的位置表示哪一个队列,比特位的内容表示这个队列是否为空!就是所谓的位图算法。

我们可以注意到蓝框与红框的内容是一样的,为了避免进程的饥饿问题,Linux操作系统就想出了以对策:

 我们可以看到上图中有一个array结构体数组,他里面存放了蓝框与红框的内容,蓝框中的queue被称为活跃队列,红框中被称为过期队列。当活跃队列中的进程开始被cpu进行调度时,后来的进程就不能在放入到活跃队列中去,而是放到过期队列中。直到活跃队列中的进程全部执行完毕后,再将活跃队列与过期队列进行交换,交换时只需要改变指针变量的内容即可。

  • 51
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 25
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值