【Linux】进程优先级、调度、命令行参数:从理论到实践(二)

🌈 个人主页:Zfox_

🔥 系列专栏:Linux

目录

  • [🚀 前言](#🚀 前言)
  • [一: 🔥 进程优先级](#一: 🔥 进程优先级)
    • [🍵 基本概念](#🍵 基本概念)
    • [🍵 查看系统进程](#🍵 查看系统进程)
    • [🍵 PRI and NI](#🍵 PRI and NI)
    • [🍵 PRI vs NI](#🍵 PRI vs NI)
    • [🍵 用top命令更改已存在进程的nice:](#🍵 用top命令更改已存在进程的nice:)
    • [🍵 为什么Linux优先级调整会被限制?](#🍵 为什么Linux优先级调整会被限制?)
    • [🍵 其他概念](#🍵 其他概念)
  • [二:🔥 进程调度切换](#二:🔥 进程调度切换)
    • [🍵 进程切换](#🍵 进程切换)
    • [🍵 进程调度](#🍵 进程调度)
      • [✈️ 位图判断](#✈️ 位图判断)
      • [✈️ 过期队列](#✈️ 过期队列)
    • 📒✏️总结
  • [三:🔥 命令行参数](#三:🔥 命令行参数)
  • [四:🔥 共勉](#四:🔥 共勉)

🚀 前言

一: 🔥 进程优先级

🍵 基本概念

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

🍵 查看系统进程

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

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

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

🍵 PRI and NI

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

🍵 PRI vs NI

  • 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。
  • 可以理解nice值是进程优先级的修正数据。

🍵 用top命令更改已存在进程的nice:

🎯 查看进程优先级指令:

cpp 复制代码
ps -al / l

🎯 调整进程优先级

第一步:top
第二步:输入r

第三步:输入需要调整优先级的进程id

第四步:输入想要增加的优先级值(比如输入10,就是优先级就降低10,输入-10,就是优先级就升高10)

结果:使用ps -al 查看myexePRI发现更改为 90了 NI 值更改为10。

🍵 为什么Linux优先级调整会被限制?

  • 如果不受限制,自己可以将自己的进程的优先级设置非常高,而系统的,或者别人的非常低,优先级较高的进程获得资源,后续还有很多今后曾源源不断产生,会导致常规进程享受不到资源。造成进程饥饿问题。

🍵 其他概念

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

二:🔥 进程调度切换

🎯 进程在运行的时候,放在CPU上,并不是需要将该进程的代码全部执行完才会被拿下CPU,现代操作系统,都是基于时间片进行轮转执行,一个进程在CPU上有一个执行最大时间,即时间片,在CPU上执行了该时间,就会被拿下。

🍵 进程切换

🍊 CPU 里面有大量的寄存器,比如:eax,ebx,ecx,edc,eds/ecs,eip... 等等,当一个进程在CPU 上被运行的时候,这些寄存器会围绕这个进程进行展开运算,保存相关的在执行该进程代码中的信息,临时数据,比如变量,函数等等。

  • 其中 epi 也就是我们所说的 pc指针,记录进程执行到哪里了(比如PC记录的是一个进程代码的50行,则说明当前执行的是第49行)。这能保证进程在切换回来后还是继续往后执行。

  • 当一个进程在CPU 上的时间到了,要被拿下CPU 时,需要将CPU 上关于该进程的所有数据(大多是寄存器上的数据)(被称为进程的硬件上下文)全部保存带走,这些数据有些保存到进程的PCB 中,有些保存在其他地方,较为复杂。这个过程叫 保护上下文

  • 这个进程被拿下后,CPU里面的寄存器的数据还是上一个进程的旧数据,当这些寄存器需要存储新的进程的相关数据时,直接覆盖式的写入即可。

  • 如果是首次调度该进程,就直接从代码开头运行即可,如果不是首次调度,进程被放到CPU 上运行时,则需要先把上次的硬件上下文数据进行恢复(恢复上下文),然后根据 eip寄存器 中保存的上次代码执行的位置继续执行。

  • CPU 内的寄存器只有一套,虽然寄存器数据放在了共享的CPU是设备里面,但是所有的数据,其实都是被进程私有的。

🍵 进程调度

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

  • 🍊 我们来看看Linux的运行队列,如下图(runqueue):

  • 我们首先看蓝色框内的内容,有一个叫做 queue[140] 的数组,这里的 queue数组表示活动状态进程的进程队列。

  • 其中在queue数组中,索引0~99号下标我们是不用的,这是因为0-99号下标对应的是 实时进程的优先级,实时进程是内核里更加重要的进程,放 在前100位由操作系统控制,避免系统抢占的情况。

  • 所以我们只剩下 100-139 这个范围可操控,其实这也就和我们优先级的可控范围大小相同,正好对应队列的四十个空位,而OS通过某种映射关系,将可控优先级映射到数组 100-139的下标。

✈️ 位图判断

🍊 我们看蓝色框内还有一项 bitmap数组,类型为int,这个数组用来干嘛呢?只能存储5个整形变量。

数组的名字叫做bitmap已经很明显了,就是位图,5个整形元素有 32 * 5 = 160 个比特位,比特位的位置,表示哪一个队列。比特位的内容,表示该队列为不为空。

比如:0000 ... 0000 ,如果最左侧0对应queue[100]的位置,那么如果该比特位为0表示在该下标映射的优先级下该队列为空,否则不为空。

有人会问:为什么要用位图?

  • 遍历整个队列的时间开销要远大于查找位图。
  • 所以,bitmap是用来检测队列中是否有进程,检测对应的比特位是否为1!

而蓝色框内还有一个元素:nr_active,在Linux中,nr_active 是运行队列中用于表示活跃进程数量的计数器。nr_active 的值可以告诉内核有多少进程正在等待执行,从而帮助内核进行进程调度和资源分配。

✈️ 过期队列

🍊 在红色框中的三项属性与蓝色框中的三项属性完全相同,也就是另外一个队列,被称为------过期队列。

活跃队列表示当前CPU正在执行的运行队列,而 正在执行的运行队列(也就是活跃队列)是不可以增加新的进程的。

所以操作系统设置了一个 和活跃队列相同属性的过期队列,当活跃队列正在执行时如果有进程需要添加进运行队列,那么就会添加至过期队列当中,也就是说 活跃队列的进程一直在减少,而过期队列中的进程一直在增多!

当活跃队列的进程执行完毕后,就会和过期队列进行交换,它们交换的方式是通过两个结构体指针:

  • 就是 active 和 expired 结构体指针,它们分别指向活跃队列和过期队列,而活跃队列与过期队列由于属性完全相同,于是被放在了一个叫做 prio_arry_t[2] 的数组里,prio_arry_t[0]指向活跃队列,prio_arry_t[1]指向过期队列:

  • 当活跃队列被CPU执行完毕后,我们 只需要交换两个指针的内容即可,这样仅仅是指向的内容变了,活跃队列变为过期队列,过期队列变活跃队列,并且时间复杂度为 O(1):

  • 新增进程在过期队列里插入,此时正在执行的是活跃队列,所以这个时候在过期队列里就有时间处理竞争饥饿的问题了。

这样,我们竞争饥饿,优先级,以及进程效率都解决了。

📒✏️总结

  • 进程切换最重要的部分就是进程上下文的保护和恢复。
  • 进程调度的优先级问题由 活跃进程数组的下标与进程优先级形成一种映射关系 解决。
  • 进程调度的时间复杂度问题由 位图和两个结构体指针 解决,时间复杂度控制在了O(1)。
  • 进程调度的进程饥饿问题由活跃队列和过期队列 解决。

三:🔥 命令行参数

🚀 关于命令行参数,在C/C++中,我们main函数能不能带参数?实际上是可以的:

cpp 复制代码
#include <stdio.h>

int main(int argc, char *argv[])   //main函数的形参
{
	return 0;
}
  • main函数 参数其中两个参数为 int argcchar *argv[] ,其中 argv是指针数组 ,里面存的全是指针变量,这里我告诉你 argcargv 数组的元素个数,那么 argv 数组究竟存着什么东西?我们不妨做个实验:
cpp 复制代码
[lisi@hcss-ecs-a9ee work]$ cat tmp.cpp

#include<stdio.h>
#include<stdlib.h>

int main(int argc, char *argv[])
{
	for(int i = 0 ; i < argc ; ++i)
	{
		printf("argv[%d]:%s\n",i ,argv[i]);
	}
	return 0;
}

[lisi@hcss-ecs-a9ee work]$ g++ tmp.cpp -o tmp -std=c++11
[lisi@hcss-ecs-a9ee work]$ ./tmp
argv[0]:./tmp
  • 我们发现,argv保存的内容恰好是我们向命令行解释器输入的内容,我们不妨在命令后多加几个选项:
cpp 复制代码
[lisi@hcss-ecs-a9ee work]$ ./tmp -a -l
argv[0]:./tmp
argv[1]:-a
argv[2]:-l

这里的结果就很明显了,bash 将我们命令行参数以空格为分隔符转化为一个个的子串,并且 argv里的每一个指针按照顺序指向不同的子串。

说到字符串,我们无论实在Linux还是Windows或者其他系统,都有命令行提示符,他们是怎么构成的?我们输入的命令被转化成了一整个字符串,以空格作为分隔符,将整个字符串转化为一个一个的子串。

所以这样也能获取到我们的命令行参数。现在我们知道了C语言 main函数中两个参数是由bash维护并创建和传参的。但是为什么要这么做?

我们以下面一段代码来帮助理解:

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>


//实现不同的算数功能
int main(int argc, char* argv[])
{
    if (argc != 4)
    {
        printf("Usage:\n\t%s -[add|sub|mul|div] x y\n\n", argv[0]);
    }

    int x = atoi(argv[2]);
    int y = atoi(argv[3]);

    if (strcmp("-add", argv[1]) == 0)
    {
        printf("%d + %d = %d\n", x, y, x + y);
    }
    else if (strcmp("-sub", argv[1]) == 0)
    {
        printf("%d - %d = %d\n", x, y, x - y);
    }
    else if (strcmp("-mul", argv[1]) == 0)
    {
        printf("%d * %d = %d\n", x, y, x * y);
    }
    else if (strcmp("-div", argv[1]) == 0)
    {
        printf("%d / %d = %d\n", x, y, x / y);
    }
    else
    {
        printf("unknown!\n");
    }
    return 0;
}
  • 上面是我们根据输入的命令行参数的选项来做不同功能的函数:

这样我们就可以通过不同的选项,让我们同一个程序执行它内部不同的功能。

这个功能是不是很像我们的指令?(比如:ls 指令)为什么我们指令可以根据不同的选项而做出不同的动作?原因就在于我们的选项传递到main函数中的 argc 和 argv当中,所以能够完成同一个指令根据不同选项做出对应的功能,所以,选项的本质就是命令行参数!

四:🔥 共勉

以上就是我对 【Linux】进程优先级、调度、命令行参数:从理论到实践(二) 的理解,会立刻更新下一篇的,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉

相关推荐
眠修16 分钟前
Kuberrnetes 服务发布
linux·运维·服务器
好奇的菜鸟1 小时前
Docker 配置项详解与示例
运维·docker·容器
xcs194052 小时前
集运维 麒麟桌面版v10 sp1 2403 aarch64 离线java开发环境自动化安装
运维·自动化
BAOYUCompany2 小时前
暴雨服务器成功中标华中科技大学集成电路学院服务器采购项目
运维·服务器
超龄超能程序猿2 小时前
Bitvisse SSH Client 安装配置文档
运维·ssh·github
奈斯ing2 小时前
【Redis篇】数据库架构演进中Redis缓存的技术必然性—高并发场景下穿透、击穿、雪崩的体系化解决方案
运维·redis·缓存·数据库架构
鳄鱼皮坡3 小时前
仿muduo库One Thread One Loop式主从Reactor模型实现高并发服务器
运维·服务器
即将头秃的程序媛3 小时前
centos 7.9安装tomcat,并实现开机自启
linux·运维·centos
fangeqin3 小时前
ubuntu源码安装python3.13遇到Could not build the ssl module!解决方法
linux·python·ubuntu·openssl
小Mie不吃饭4 小时前
FastAPI 小白教程:从入门级到实战(源码教程)
运维·服务器