Linux操作系统-进程(三)

一.进程的优先级

针对进程的优先级,下面我从三个方面来讲解:

1.1进程的优先级是什么?

优先级这个东西在我们日常生活中也很常见:我们在上学时,去食堂吃饭都需要排队,这点相信大家都经历过,这其实就是一个体现优先级的现象,吃饭有先后顺序。

那么进程的优先级是什么呢?

从上面的例子中我们知道我们最后是能吃上饭的(排到你了饭没了这种情况不考虑啊),只是有个先后的问题,所以将这个例子映射到linux中可以概括为:**优先级就是进程在已经能得到某种资源的前提下,得到某种资源的先后顺序!!!**这点相信大家都能理解。

1.2为什么要有优先级?

这个问题就和问为什么要排队吃饭是一个道理,所以为什么呢?

因为资源是有限的啊,想一想,如果资源是充裕的,还排队干什么,直接给每个人单开一个窗口不就得了,但我们都知道这是不现实的。映射到linux中,cpu一般情况下只有一个,不可能同时运行所有的进程,所以才有了优先级。

1.3Linux下,是怎么设计优先级的(最重要!!!)

在Linux中用PRI和NI来表示进程的优先级:

PRI就是priority,翻译过来就是优先级的意思,或者通俗点说就是程序被CPU执⾏的先后顺序,此
值越⼩进程的优先级别越⾼。
那NI是什么呢?
NI即nice值,这个值其表⽰进程可被执⾏的优先级的修正数值,通俗点说就是我们要通过改变nice值来间接改变PRI也就是优先级。
PRI值越⼩越快被执⾏,那么加⼊nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice。也就是说,当nice值为负值的时候,那么该程序将会优先级值将变⼩,即其优先级会变⾼,则其越快
被执⾏。
并且一般进程的PRI都默认是80,NI值默认是0.
1.3.1如何修改一个进程的优先级?
说了这么多,那要怎么修改一个进程的优先级呢?我们来看:



改变一个进程的优先级有多种方式,这里我选择较为简单的一种方式来实现。
首先我们通过top指令进入第一幅图所示的页面,之后按下" r "键,上面就会出现这个提示,这就是提示你要改变哪个进程的nice值,我们输入相应的进程pid即可。
之后就会进入这个页面,这就是让你输入要修改的nice值,这里我们修改一个正值,之后按回车即可。
修改后我们从第三张图也就可以看到当前的进程优先级发生了改变,由原先的80变为了90,也就是80+10的结果。
那要是将nice值改为负值呢?这里的现象会跟大家想的不太一样,我们来看看:

当我们再次去修改一个进程的优先级时,突然报了一个上图所示的错误,这是系统拒绝了我们的请求,不让我们改变进程的优先级,这是一个正常现象。

我们此时是一个普通用户,系统是不允许普通用户高频的去改变进程的优先级的,所以这里我们切换为root用户即可解决这个问题。

此时我们切换为root用户重复上面的步骤来改变进程的优先级,此时的答案相信和大家想的并不一样,我想大家觉得答案应该是80对吧。

这是为什么呢?按照上面计算PRI的公式,答案不就应该是80吗?

公式没错,不过里面的PRI(old)并不是修改之前的PRI值,而是固定的80,也就是说我们对nice值的修改,最后在计算时都会用80来进行计算,这就是为什么结果的PRI是70而不是80的原因。

有人会问:为什么这么设计呢?

先说答案:为了方便。我们想一下:如果PRI(old)是固定的,那我们计算PRI时就会很快,不需要看此时的PRI,只需要知道nice值就能快速计算出结果。

而如果是按我们正常的思路来设计,我们每次计算前都要看一眼此时的PRI是多少,以防数据出现错误,这就没有上面更方便。

1.3.2nice值的范围?优先级的变化范围?

既然可以通过nice值来更改优先级,那能设置任意值吗?

答案当然是不可以,那么范围是多少呢?我们来试验一下:

我们以每次改变100为例,发现nice值的变化范围为-20,19,也就是优先级的变化范围为60,99,变化范围为40个值,也就是优先级分为40个梯度。

1.3.3为什么是这个范围?为什么不能随意改变?

相信看了上面的结果后大家都会有这样的疑问,下面我来为大家解答:

对于第一个问题在下面讲完了进程的调度后大家就能理解,我们先说第二个问题:

在回答问题之前,我们要先知道我们现在用的大部分电脑,手机,平板等电子产品的操作系统都是分时操作系统,那什么叫分时操作系统呢?

分时操作系统会给进程分配时间片(其实就是一个计数器),在时间片的作用下,采用相对公平,公正的调度策略,较为均衡的让不同进程都能在一段时间内得到cpu资源。这就是分时操作系统的特征,这点是符合人和互联网的需求的。

我们在打游戏的同时也能听音乐也正是因为分时操作系统才得以实现,一个进程的时间片执行完就会将当前进程从cpu上剥离下来,切换下一个进程来运行。如果是一个进程执行完再执行下个进程,那我们正打游戏时点开了浏览器,那下一秒游戏进程就被结束了,相信大家都不愿意看到这种现象,这不反人类嘛。

说回正题,既然是分时操作系统,如果我们能随意更改一个进程的优先级,将其改的特别小,那么这个进程就会霸占cpu的资源。因为它优先级高啊,只要你不结束这个进程,那么这个进程长期霸占cpu,也就是它吃肉,其他进程只能喝汤,这与分时操作系统的理念相悖,就不再公平,公正。

我们可以这样想来更好地理解:一个人高考分比你低了100多分,但是能上比你更好的学校,为什么呢?因为他是个关系户,他的优先级比你高,这种事你气不气?这就和上面是一个道理。

所以随意修改nice值这种事肯定是不允许的,只能在一定范围内变化,不能改的太狠。

而与分时操作系统相对的就是实时操作系统,实时操作系统就和上面我们说的那样:一个进程执行完,才会切换到下一个进程,相比分时操作系统逻辑更为简单。

实时操作系统一般在工业领域用的比较多,分时操作系统在我们日常生活中更为常见。

1.3.4补充概念
(一)竞争性: 系统进程数⽬众多,⽽CPU资源只有少量,甚⾄1个,所以进程之间是具有竞争属性的。为了⾼效完成任务,更合理竞争相关资源,便具有了优先级。这点比较好理解,就不过多介绍了
(二)独⽴性: 多进程运⾏,需要独享各种资源,多进程运⾏期间互不⼲扰。这点也好理解,就像我们浏览器和QQ音乐同时开,我们把浏览器关了,但是QQ音乐依旧在运行,我们依旧能听音乐。
这里我们思考一个问题:父子进程之间存在独立性吗?
答案当然是有的,从上面的孤儿进程中我们就可以发现虽然父进程结束了,但是子进程依旧在运行,所以父子进程之间也是存在独立性的。
(三)并⾏: 多个进程在多个CPU下分别,同时进⾏运⾏,这称之为并⾏。
(四)并发: 多个进程在⼀个CPU下采⽤进程切换的⽅式,在⼀段时间之内,让多个进程都得以推进,称之为并发。
并发就是分时操作系统的特征,并行的关键是多个cpu。
优先级讲到这里可能有人会有一个问题:为什么linux中要设计PRI和NI这两个值?如果要改变优先级的话,直接修改PRI不就好了吗?
要解决这个问题,我们先来设想一个场景:
此时某个进程正在cpu上运行,如果你修改了这个正在运行的进程的优先级,就比如将它的优先级调低,那系统是没等时间片运行完直接将其剥离下来切换下个进程吗?
我们想一想是不是这个操作不太合理,所以才要设置NI这个值,为的的是如果出现上面的情况,就和我们小时候玩游戏一样:" 这盘说,下盘定 "。当出现上面的情况时,等当前进程的时间片执行完后,切换进程时系统就会根据新的优先级将其放在合适的位置,这样修改优先级的整个过程才显得合理。
二.LinuxO(1)调度队列
在进程(二)讲解进程状态时,给大家大概说了一下进程的调度队列是什么样,而下面就要对调度队列进行详细介绍,帮助大家更好的去理解调度队列。

这里我用图的形式将runqueue调度队列中的大概内容展示出来,这里面东西很多,下面我会讲解关键的部分来帮助大家更好地理解linux中调度队列是如何运行的。
2.1 queue140
我们首先来讲这个queue这个数组,那么这个数组是什么呢?我们通过这个数组的类型就能知道了:

没错,这个数组中保存的都是指针,所以这是一个指针数组。而在linux底层实现中类型并不是这样写的,而是:

看到上面的类型可能有人会觉得这个数组里面存的是每个进程的PCB地址,而list_head我们直译过来就是链表的头指针,那为什么linux要这样命名呢?
答案就是数组里面保存的并不是每个进程的PCB地址,而是每个进程队列的头结点,也就是说每个头结点后面链接了一连串的进程,就和上图展示的一样,那为什么要这样设计呢?这就与数组的大小" 140 "这个数字有关,我们接着往下看。

我们将这个数组给画出来,数组的下标是0,139,而前面100个也就是区间在0,99的我们这里先不说,下面会讲,我们先来说下标为100,139这个区间。
这个区间总共有40个位置,而我们在上面讲优先级时也说了优先级是在60,99这个区间,总共40个梯度,这个难道是巧合吗?
当然不是,我们不妨将优先级区间的每个值都加上40,此时我们就发现刚刚好对应的就是100,139,讲到这里想必大家大概都能猜到一点大概是怎么回事了。
没错,数组中的这40个位置对应的就是优先级的40个梯度,60对应的就是下标为100的位置,61对应的就是101,以此类推。讲到这里大家就知道我们系统中运行的进程都在哪里了,大概的结构就长这样:

到这里我们就能解决优先级中遗留的问题:为什么优先级是60,99这个范围?
因为默认PRI是80,而优先级的梯度固定为40个,这样NI值的取值范围就只能在-20,19之间,正数20个,负数同样20个,也就间接决定了优先级的范围就是60,99
并且cpu就是按照这个顺序从上到下依次按照优先级来调用不同的进程。
2.2 bitmap5
讲了上面的queue数组后,那么这个数组是用来干什么呢?
我们现在设想一个场景:如果现在我们系统中的进程优先级都是99,这就是处于queue数组的末尾,那cpu在调用进程时,是不是就要遍历整个数组,判断当前位置是否为空,直到遍历到数组的最后一个位置才能调用进程。
那么上述的遍历过程就需要消耗时间,虽然对我们来说感觉就140又不是多大的数字,遍历也不需要花费很多时间,但是对于系统来说效率就很低了,所以才有了这个bitmap数组。
这个数组的类型是int,也就是个整型数组,那么为什么是五个整数呢?
我们知道1个整数4个字节,也就是32个比特位,那么4个整数就是128个比特位,5个整数就是160个比特位,我们的queue数组是多大呢?
140,所以4个整数不够,5个不但够还有盈余,6个就浪费了。讲到这里有的朋友大概就知道这个数组是干什么的了。
没错,就是位图的思想,5个整数160个比特位,前面140个对应的就是queue数组中的下标,而我们要想判断queue数组的某个位置是否为空,直接判断对应的比特位是0还是1即可,0代表当前位置为空,1表示不为空,这样就能提高一定的效率。
总而言之,bitmap数组就是利用位图来替换遍历数组的操作,从而达到提高效率的目的。
而最后在上图中在蓝色框架中的还有最后一个变量nr_active,这个变量的类型是int,作用就是统计系统中个活跃进程的个数,我们认为这个数代表的就是进程的个数即可,上面的内容用图来表示就如下图所示:

2.3为什么runqueue中有两组相同的内容?
在runqueue那幅图中,相信大家都注意到了蓝色框架和红色框架中的内容是一模一样的,相信大家都疑惑为什么要这样设计?
而在解决这个问题之前我们先来补充一些知识:

上面我们介绍的三个内容在linux的底层中其实是保存在一个结构体中,这里我用p来表示,而上面两个重复的内容是利用这个结构体创建了一个数组:

而这两组内容分别对应的就是array0和array1,并且我们从上面的图中也看到这两者的名字:活跃进程和过期进程。
而在这两个内容的上面我们注意到还有两个指针:active和expired,那么这两个指针又是什么呢?

不卖关子,这两个值指向的就是上面的两个进程的地址。
讲到这里,我们就知道原来cpu在调用进程时不是在runqueue中直接去找queue这个数组,而是通过active指针来找到对应的内容。
而要解决我们最初的问题,就要知道另一个规则:
新增进程,或者时间片到了的进程,被从cpu上剥离下来,被剥离下来的进程,只能重新入队列,入过期队列!!!
这就是为什么要有过期进程,cpu调用进程是从活跃队列中调的,而不是在过期队列中调,我们要用过期队列来保存已经执行过的进程。
那么此时可能有人会问:为什么不将已经执行过的进程重新连接到相应优先级队列的后面呢?
我们上面也说了,cpu会按照顺序从上到下按照优先级来一个个调用进程,就比如说:优先级为60的进程有多个,cpu需要先将优先级为60的进程一个个调取完,才能接着调取优先级为61的进程。
但是如果按上面所说,调取过的进程接着重新链接到当前队列的后面,那岂不是这个队列就会一直被的调用,就跟死循环一样,这样后面的进程就无法享用到cpu的资源,这肯定是不行的啊。只有将调用过的进程链接到过期队列中,减少活跃队列中进程的个数,cpu才能按照优先级调用一个一个进程。
那么此时就又会产生一个问题:当活跃队列中的进程全部调用完了该怎么办呢?
这个问题很好解决:

直接通过swap函数交换两个指针的内容不就行了,讲到这里大家就能明白为什么两个部分的内容是一样的了,并且所有队列中的进程顺序保持不变。
而上面的完整过程就叫做linux的O(1)调度队列,有了上面所有内容的铺垫,相信大家就能理解linux中进程调度的过程:通过active指针访问queue数组来调用进程,并且将调用过的进程链接到过期队列中,在活跃队列调用完后,交换活跃队列和过期队列的内容,接着重复上面的过程。
2.4 queue数组中的0,99这100位置是干什么的?
先说结论:是给实时操作系统的那部分功能使用的。
相信大家看到这个结论都很疑惑,上面不是说linux是分时操作系统吗?怎么还加入了实时操作系统的内容呢?
我们上面在提到实时操作系统时说了,它更多应用在工业领域当中,而为了回答上面的问题,我们来设想一个场景:
现在你是一个品牌汽车的老板,汽车上要搭载你们自主研发的车载系统,那么此时你会如何选择呢?
答案显而易见:linux,因为它免费啊,并且因为开源的缘故,linux具有高效,稳定等特点,还有比这更好的选择吗?
那么好,你现在选择了linux来开发你们自己的车载系统,假设此时有人正开着你们的汽车上,而汽车的感应器感应到前方20米有障碍物,要紧急刹车。
那么此时要完成这个任务是不是就得创建一个刹车的进程,并且要立即执行这个进程。但是linux是分时操作系统啊,管你这的那的,新建进程给我去等着,轮到你了再说,那么开车的人如果是你,你是不是直接???
虽然时间可能不长,但是这期间浪费的时间可能会造成什么结果想必大家都清楚,所以分时操作系统在这里是行不通的。那么这时候就轮到实时操作系统发力了,实时操作系统是优先处理优先级高的进程,并且把当前这个进程执行完才会去执行下个进程,我们只需要将刹车这个进程的优先级调到最高,那么通过实时操作系统就能避免上面的问题。
而正是因为linux在工业领域中也有涉猎,所以才针对性的开发出了实时操作系统所对用的功能,而0,99这个范围就是实时操作系统优先级的范围。
以上就是进程(三)的全部内容。

相关推荐
groundhappy4 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕4 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫5 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生6 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.117 小时前
linux基础IO
linux·运维·服务器
BelongPanda9 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯9 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_777998349 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器
yunwei379 小时前
eBPF 教程:检查 exec 后真正安装的可执行镜像
linux·开源
笑锝没心没肺10 小时前
yum update 报错:GPG key retrieval failed: [Errno 14] HTTPS Error 404 - Not Found
linux·运维·服务器