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

【Linux】进程优先级 | 进程的切换 | 环境变量详解

  🤣 爆笑教程 👉 《看表情包学Linux》👈 猛戳订阅  🔥

💭 写在前面:我们先讲解进程的优先级,探讨为什么会存在优先级,以及如何查看系统进程、进程优先级的修改。然后讲解进程的切换,首次介绍进程的竞争性、独立性,以及并行和并发的概念,在通过讲解进程抢占引出可见寄存器与不可见寄存器。最后我们讲解环境变量,介绍环境变量 PATH,并且做一个 "让自己的可执行程序不带路径也能执行"的实践,讲解环境变量的到如何删除,最后再讲几个常见的环境变量。


Ⅰ. 进程优先级(Process Priority)

0x00 引入:什么是优先级?

我们先思考思考 权限 是什么?权限的本质是谈论 "能" 还是 "不能" 的问题。

那什么是 优先级 ?优先级是进程获取资源的先后顺序!

CPU 资源分配的先后顺序,就是指进程的优先权(priority)

优先权高的进程有优先执行权利。配置进程优先权对多任务环境的 Linux 很有用,可以改善系统性能,还可以把进程运行到指定的 CPU 上,这样一来就可以把不重要的进程安排到某个 CPU,可以大大改善系统整体性能。

0x02 为什么会存在优先级?

我们不妨先思考下我们日常生活中排队的本质,排队的本质可以说是 "确定优先级" ,

而插队行为就是更改优先级。因为排队造就了优先级,那我们为什么要排队?

​  可以不排队吗?可以,结果就是大家一窝蜂抢呗,全部用抢的。

"无理由暴力抢占式,优胜劣汰,我挤死你"

(前后时间来确认先后顺序) \rightarrow​ (谁能挤谁能撞谁就排到前面)

现实生活中一旦出现了抢,就难免会引发争执。

在比如说食堂买饭如果大家都不排队,如果你就是挤不过别人,就会一直买不到,

你就每次都要饿肚子,这在操作系统中叫做 "饥饿问题" 。

所以,排队主要是换了一种竞争方式,不以那么残酷的方式竞争,让进程都能 井然有序

我们之所以要排队,其实最主要的原因是因为资源不够!

如果资源是无限的,就像希尔伯特旅馆一样,该旅馆拥有无限多的房间,那也不需要排队的了。

200 名学生要去食堂吃饭,但窗口就 20 个,当然需要排队。如果窗口有 200 个,理论上不用排。

因为 系统里面永远都是进程占大多数,资源是少数。 这就导致了进程竞争资源是常态!

排队和进程资源竞争都是一定要确认先后的,它们的本质都是 确认优先级

本章我们要讲的是 Linux 的进程优先级,Linux 下的优先级有很多方式,包括设置和修改。

我们不建议修改优先级,如果你不懂调度器的调度算法,你随便修改优先级其实就是变相地 "插队" 了。你可以让你的进程尽快地得到了某种 CPU 资源或其它资源,凡是可能会打破调度器的平衡。其实你在用户层再怎么设置,也不会对调度器的调度策略产生什么影响。

再加上设置优先级没有什么意义,所以本章我们就不去讲解了。

0x03 查看系统进程:ps -l

🔍 查看系统进程:在 Linux 或者 Unix 系统中,输入  ps -l  命令则会输出内容:

$ ps -l    # 查看进程的优先级

我们写一个简单的 Hello 程序,令其每隔一秒发送一次 Hello

#include <stdio.h>
#include <unistd.h>

int main(void) {
    while (1) {
        sleep(1);
        printf("Hello!\n");
    }
}

我们把它运行起来,此时我们使用  ps -l   查看:

因为  ps -l  只能显示当前终端下进程的相关信息,我们可以使用给它加上 \textrm{-a}​ 选项:

$ ps -la   

 

此时我们的进程 process 就显示出来了,我们重点关注 \textrm{PRI}​ 和 \textrm{NI}​ 列。

 Linux 中的进程优先级由两部分组成:\textrm{PRI\, +\, NI}

  • \textrm{PRI}​:优先级 (priority),默认进程优先级为 80​。
  • \textrm{NI}​:nice 值 (nice value) ,进程优先级的修正属性,取值区间为 [-20, 19]​ ,默认值为 0​ 。

📌 注意:数字越小,表示优先级越高;数字越大,优先级越低。(Linux 下)

优先级的部分我们在 task_struct 中也是可以找到的。
它的优先级和我们上一章讲的进程状态一样,也是个整数,在 task_struct 中表示:

0x04 进程优先级的修改

要更该进程的优先级,需要更改的是 \textrm{NI}​,而非 \textrm{PRI}​ 。

因为 nice 值是进程优先级的修正数据,所以一个进程不管是在启动前还是在运行中,想要修改优先级,都是通过修改它的 nice 值来达到目的。

其实,我们系统中是存在   nice  命令的,对应的还有   renice  。

$ nice  
$ renice

它们可以让我们在启动一个进程时直接指定优先级,或者启动中或启动前设置优先级。

感兴趣可以自行查阅,我们还是主要学习如何使用   top  命令去修改:

$ top

进入 top 后我们键入 \textrm{r}​ ,此时会发出询问:PID to renice [default pid=x]

在后面输入我们要修改的进程的 \textrm{pid}​ 即可,我们刚才进程的 \textrm{pid}​ 是 20332​:

然后会询问:PID to renice [default pid=1] 问你要设置哪个进程的 \textrm{pid}​:

这里居然提示  Failed  修改失败了!Permission denied (么得权限) !

​ 因为我们刚才说过:

"一个进程的优先级不能轻易被修改,因为会打破调度器的平衡"

如果你执意修改,你须具备 👑 超级用户 的权限 —— \textrm{root}​ !这里我们 sudo top 就行:

$ sudo top

 

 ​ 找到了找到了,我们继续,我们继续!

我们刚才将 nice 值修改为 -20,现在 \textrm{PRI}​ 优先级变成 60 了。

值得强调的是,Linux 不允许用户无节制地设置优先级,设置的优先级范围不能逾过下列区间:

\textrm{NICE}:\, [-20,19]

其取值范围是 -20​ 至 19​,一共 40 个可设置级别。

\textrm{PRI}​ 值越小越快被执行,Linux 的优先级是这样设置的:

prio = prio_old + nice;

所以,只需要改 nice 优先级就能变化。

📌 注意:每次设置优先级,这个 old 优先级都会被恢复成为 80 (跟上一次没关系)

Ⅱ. 进程的切换(Process Switch)

0x00 竞争与独立

竞争性:僧多粥少!系统进程数目众多,而 CPU 资源只有少量,甚至一个,所以进程之间是具有竞争属性的。为了高效的完成任务、更合理竞争相关资源,便具有了优先级。

独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰。

进程运行具有独立性,不会因为一个进程挂掉或者异常而导致其它进程出现问题!

内核结构 + 代码和数据

❓ 思考:那么操作系统是如何做到进程具有独立性的呢?

(我们将在后续讲解进程地址空间时揭晓)

0x01 并行和并发

并行:多个进程在多个 CPU 下分割,同时进行运行,我们称之为并行。
并发:多个进程在单个 CPU 下采用进程切换的方式,在一段时间内,让多个进程都得以推进,称之为并发。

下面我们来理解一下并行与并发。

一般服务器都是双 CPU 的,所以双 CPU 的系统是存在的,就会存在多个进程同时在跑的情况。

如果存在多个 CPU 的情况,任何一个时刻,都有可能有两个进程在同时被运行 —— 并行 

但我们大家接触的、用的笔记本电脑基本都是单核的,单 CPU 的任何时刻只允许一个进程运行。

我的电脑是单 CPU 的,但是我的电脑中有各种进程都可以在跑啊?怎么肥事啊?

它是怎么做到的呢?

不要认为进程一旦占有 CPU,就会一直执行到结束,才会释放 CPU 资源。

所以一直让它跑,直到进程执行完,是不存在的,我们遇到的大部分操作系统都是 分时 的!

操作系统会给每一个进程,在一次调度周期中,赋予一个 时间片 的概念。

例:一秒钟之内每一个进程至少要被调度20次,每一次调度就是自己代码得以推进的时候。

在一个时间段内,多个进程都会通过 "切换交叉" 的方式,当多个进程的代码,在一段时间内都得到推进 ——  并发

0x02 进程抢占

❓ 思考:OS 就是简单的根据队列来进行前后调度的吗?有没有可能突然来了一个优先级更高的进程?

抢占式内核!我们现在的计算机基本都是支持 抢占 的。正在运行的低优先级进程,可能正在享受着它的时间片、推进着代码,但是如果来了优先级更高的进程,我们的调度器会直接把对应的进程从 CPU 上剥离,放上优先级更高的进程,这个操作就叫做 进程抢占

了解

a. 不允许不同优先级的进程存在的 
b. 相同优先级的进程,是可能存在多个的

task_struct* queue[5];

根据不同的优先级,将特定的进程放入不同的队列中!这其实就是一张简单的哈希表,后面列入的都是队列,其原理是通过哈希根据不同的哈希值确定队列的优先级,每一种优先级 Linux 都会维护一个队列。

举个最简单的例子,下面的 z 是如何得到已经释放的临时变量 a 的数据的?

int func() {
    int a = 10 + 20;
    return a;
}

int z = func();

寄存器功不可没,拷贝一份到寄存器里去,然后再 \textrm{mov} 给 z 变量。

CPU 内的寄存器是:可以临时地存储数据

寄存器分为 可见寄存器 不可见寄存器

当进程在被执行的过程中,一定会存在大量的临时数据,会暂存在 CPU 内的寄存器中。

寄存器上数据的重要性:

我们把进程在运行中产生的各种寄存器数据,我们叫进程的硬件上下文数据。

  • 当进程被剥离:需要保存上下文数据
  • 当进程恢复时:需要将曾经保存的上下文数据恢复到寄存器中。

上下文在哪里保存?task_struct !

📌 注意事项:要准确区分,"寄存器" 和 "寄存器里的数据" 的区别。

  • 寄存器只有一套,但是寄存器里的数据有多份。

Ⅲ. 环境变量(Environment Var)

0x00 引入:思考一个问题

❓ 思考:为什么我们的代码运行要带路径,而系统的指令不用带路径?

如果我们直接输入我们的可执行程序,会显示 bash: process: command not found 

我们说过,执行系统的指令实际上也是程序,系统的指令你也是可以带上路径的:

其实,我们可以通过它的报错 "command not found" 发现些什么!

要执行一个可执行程序,前提是要先找到它。

现在我们的问题就可以转化成:为什么系统的命令能找到,而我们自己的程序找不到?

💡 真相

系统中是存在相关的 环境变量,保存了程序的搜索路径的!

为什么我们的代码运行要带路径,而系统的指令不用带?其本质是由环境变量 \textrm{PATH} 引起的!

0x01 环境变量 PATH 

我们可以通过 env 指令查看环境变量:

$ env

这些变量每一个都有它特殊的用途,系统中搜索可执行程序的环境变量叫做 \textrm{PATH}

我们可以通过 grep 去抓一下:

如何查看环境变量的内容?我们可以使用 echo  去显示:

$ echo $PATH

 在 \textrm{PATH} 前加上 $ 符即可打印出环境变量:

环境变量 \textrm{PATH} 中会承载多种路径,中间用冒号 ( : ) 作为分隔符。

我们再执行某一个程序时,比如执行 ls 时,我们的系统识别到 ls 的输入时,会在上面路径中逐个搜索,只要在特定的路径下找到了 ls,就会执行特定路径下的 ls 并停止搜索。

换言之,\textrm{PATH} 就提供了环境变量,可执行程序搜索的路径。

我们的 ls 在  usr/bin  路径下,这说明当前的 ls 在 \textrm{PATH} 中是可以被找到的,

所以执行 ls 的时侯自然可以不带路径,所以我们自己的  process 不带路径自然就不能执行。

因为当前的  process 所在的路径并没有这里的环境变量,程序在搜索的时侯找了路径也没有找到你这个可执行程序,搜索完找不到,自然就报 "command not found" 了。

0x02 实践:让自己的可执行程序不带路径也能执行

那我现在就想让我的可执行程序 process 不带路径直接执行起来,可以吗?

可以!我们先讲述一种简单粗暴的方式,直接把我们的可执行程序 cp 拷贝到系统的路径中:

$ sudo cp process /usr/bin/

既然系统的所有命令都在 usr/bin 路径下,那我们把我们的 process 拷进去就行了。

实际上,刚才那个操作我们可以称之为 "软件被安装到系统上",但是我们不建议你去自己安装。

也更不建议你将你的指令拷贝到 Linux 系统路径下,因为这会污染 Linux 下的命令池。

经常这么干时间久了你可能都忘了这个是干什么的,半年之后:

"诶!我这系统里怎么还有个 process?"

可能就分不清是你写的还是系统的了,所以我们不建议这么做!

更好的方式是将 process 所处的路径也添加到环境变量中。

前置:在 Linux 命令行中,我们也是可以定义变量的,命令行变量分为两种:

  • 普通变量
  • 环境变量(具备全局属性)

命令行上直接写,变量名等于值,你所定义的这个变量 a,就是 本地变量

(我们这里先对本地变量做一个小小的理解,稍后我们还会讲解的)

用系统查看环境变量的命令 env 去查看一下这个本地变量,会发现根本找不到,

因为它不以环境变量的形式存在,但是它是存在的!

如果你想让一个变量变成环境变量,你可以通过 export 导出一个在系统中可以查看的环境变量:

$ export []=[]

通过 env 并 grep 一下这个变量,我们就能找到我们导出的环境变量了:

(至于环境变量和本地变量之间的差别,我们稍后再讲)

现在我们知道该如何导环境变量了,现在我想执行我的程序不想再带路径该怎么办呢?

💭 操作演示:下面我们来做个好玩的:

把我们的环境变量,当前路径导入到 \textrm{PATH} 路径中看看会发生什么:

$ export PATH=[路径]

这么一导之后,我们发现我们的 process 可以跟系统指令一样不带路径直接执行了:

但是好像我们的系统指令全都寄了!!!

 啊这,怎么会这样呢?!

因为你把 \textrm{PATH} 里的环境变量都搞没了,只剩你自己的路径了,所以这些指令自然都找不到了。

出现了你刚才自己可执行程序不带路径后 Enter 的报错 "command not found" 。

 这……难道环境污染了(紧张)?我的要服务器坏掉了(害怕)?

不用担心!在命令行上设置的环境变量是具有临时性的,只在你登陆期间有效。

你刚才的修改只是在内存中的修改,不会修改系统当中的相关配置文件。

所以你只需要关掉重开就行了,随便搞,不会影响。

如果你想让你的环境变量设置永久有效的话,是需要更改配置文件的,该配置文件在系统当中,跟云服务器没有关系。

那我们该怎么做呢?来,这么做:

$ export PATH=$PATH:[路径]

0x03 环境变量的导入和解除

刚才我们通过 export 去导入变量,如果想取消一个变量,就可以使用 unset 来取消变量设置:

此时我们使用 unset 环境变量,就可以解除 foo:

" 这些东西实际上都是 shell 命令,export 是导出,unset 是取消 "

0x04 介绍几个常见的环境变量

刚才我们介绍了环境变量 \textrm{PATH},它是用来指定命令的搜索路径的。 

下面我们来详细介绍一下常见的环境变量,刚才我们就是用 env 指令去查看环境变量的:

我们能看到有很多环境变量,比如下面这个 \textrm{HOSTNAME} 就是表示 "对应这台主机的主机名" 。

我们同样也是可以通过 echo 指令带上 $ 去查看环境变量:

echo $HOSTNAME

再比如 \textrm{SHELL},它可以告诉你你的 shell 在哪里,通常是 /bin/bash 

echo $SHELL

得益于 Linux 存在历史命令的记录功能,我们可以在 Xshell 里 ↑ ↓ 显出历史命令,就像这样: 

"总不能一直记吧?肯定是有个指令记录的阈值的!"

没错!Linux 最多允许你记录的历史命令条数是 3000。

而我们接下来要介绍的 \textrm{HISTSIZE}  (History Size),就是定义一共记录多少历史指令的环境变量:

$ echo $HISTSIZE

顺便一提,我们可以通过 history 命令令去查看我们历史敲过的所有命令:

 再来!我们再说几个 ~

我们一般是通过 whoami 指令去查看当前是谁正在使用系统的,而 \textrm{USER} 就记录了当前谁在用。

$ echo $USER

 ​​​​​​

不知道大家有没有关注过,每次登陆服务器默认所处的路径?就是默认所处的工作目录。

root 用户的工作目录和普通用户的工作目录不同,那 Linux 是如何知道的呢? 

\textrm{HOME}:指定用户的主工作目录(即用户登陆到 Linux 系统中时,默认的目录)

当然还有很多,比如 \textrm{SSH-CLIENT} 记录了谁登的服务器、地址、端口号等。

(环境变量实在多,全部讲完不太现实,上面我们讲的都是一些常用的,欢迎补充)

0x05 尾记

命令行中启动的进程,父进程全部都是 bash 。

环境变量具有全局属性,环境变量是会被子进程继承下去的。

所谓的本地变量,本质就是在 bash 内部定义的变量,不会被子进程继承下去。

Linux 下大部分命令都是通过子进程的方式执行的,但是还有一部分命令不通过子进程的方式执行,而是由 bash 自己执行(调用自己的对应的函数来完成特定的功能,比如 cd 命令),我们把这种命令叫做 内建命令

📌 [ 笔者 ]   王亦优
📃 [ 更新 ]   2022.3.
❌ [ 勘误 ]   /* 暂无 */
📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,
              本人也很想知道这些错误,恳望读者批评指正!

📜 参考资料 

C++reference[EB/OL]. []. http://www.cplusplus.com/reference/.

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

百度百科[EB/OL]. []. https://baike.baidu.com/.

比特科技. Linux[EB/OL]. 2021[2021.8.31 

相关文章:

  • 软件测试标准流程
  • 将群晖NAS变为本地盘
  • 网络安全的就业及发展前景如何?
  • Hadoop组件Yarn常见命令
  • 2022年这5款熟悉的软件退出了历史舞台
  • 工程项目管理系统源码+spring cloud 系统管理+java 系统设置+二次开发
  • 修改Vue项目运行的IP和端口
  • 【笔记】移动端自动化:adb调试工具+appium+UIAutomatorViewer
  • 【C→C++】打开C++世界的大门
  • 程序环境--翻译+执行
  • Node=>Express中间件 学习3
  • 软件测试】测试时间不够了,我很慌?项目马上发布了......
  • m序列发生器——Verilog设计
  • 云原生系列之使用 prometheus监控MySQL实战
  • 源码级别的讲解JAVA 中的CAS
  • 「译」Node.js Streams 基础
  • gf框架之分页模块(五) - 自定义分页
  • Java,console输出实时的转向GUI textbox
  • MQ框架的比较
  • node入门
  • QQ浏览器x5内核的兼容性问题
  • SQLServer之创建数据库快照
  • tweak 支持第三方库
  • vue-cli3搭建项目
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 飞驰在Mesos的涡轮引擎上
  • 将回调地狱按在地上摩擦的Promise
  • 聊聊redis的数据结构的应用
  • 人脸识别最新开发经验demo
  • 深入浏览器事件循环的本质
  • 使用Swoole加速Laravel(正式环境中)
  • 收藏好这篇,别再只说“数据劫持”了
  • 我这样减少了26.5M Java内存!
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • Play Store发现SimBad恶意软件,1.5亿Android用户成受害者 ...
  • 进程与线程(三)——进程/线程间通信
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • #Linux(帮助手册)
  • #预处理和函数的对比以及条件编译
  • (Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测
  • (八)Flask之app.route装饰器函数的参数
  • (超简单)构建高可用网络应用:使用Nginx进行负载均衡与健康检查
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (未解决)macOS matplotlib 中文是方框
  • (转)es进行聚合操作时提示Fielddata is disabled on text fields by default
  • (转)http协议
  • (转)一些感悟
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .h头文件 .lib动态链接库文件 .dll 动态链接库
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .NET 使用 ILRepack 合并多个程序集(替代 ILMerge),避免引入额外的依赖
  • .NET 线程 Thread 进程 Process、线程池 pool、Invoke、begininvoke、异步回调
  • .NET版Word处理控件Aspose.words功能演示:在ASP.NET MVC中创建MS Word编辑器
  • .Net中的设计模式——Factory Method模式
  • /bin、/sbin、/usr/bin、/usr/sbin