Bootstrap

Linux—进程概念学习-03

Linux—进程学习—3

1.进程优先级

什么是进程优先级:

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

  • 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的Linux很有用,可以改善系统性能。

  • 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能

要注意权限和优先级的区别:

  • 权限:能还是不能

  • 优先级:都能,先做还是后做。

为什么会有进程优先级?

因为资源太少了,需要资源的进程太多了。

就比如大多数计算机的cpu都只有一个,而需要等待cpu的进程数量远远多于cpu这个资源的数量。

1.1Linux中的进程优先级

在Linux中,优先级和进程状态一样,其实就是2个整数。这个整数是放在PCB里面维护的,属于是进程的一个属性

写一个小程序来查看进程优先级:

image-20241115135233623

输入ps -lps -al可以查看到一些重要信息【ps -al可以查看当自己运行的进程信息】

image-20241115135447053

  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号

  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号

  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行

  • NI :代表这个进程的nice值【默认是0】

Linux比较特殊,其最终优先级 = PRI(刚被创建出来就被赋予的优先级) + NI

PRI and NI

  • PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高

  • NI就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值

  • PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice

  • 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行

  • 调整进程优先级,在Linux下,就是调整进程nice值

  • nice其取值范围是-20至19,一共40个级别

这说明:Linux是支持,进程在运行中,调整进程优先级的【更改NI实现】

要注意:

进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化

1.2修改进程优先级—top

修改进程优先级其实没有什么必要,因为会不会修改另说,很多时候修改完的效果并不会说有想象的那么好。除非是追求极致效率,不然没有必要去修改进程优先级。

这里演示一个修改进程优先级的方法——使用top修改进程优先级

top修改优先级的方法:

  • 进入top后按“r”–>输入进程PID–>输入nice值

验证一下:

进场优先级的修改可能会需要root权限,因此调用top的时候,可以使用sudo top

image-20241115145047635

正常输入r弹出来的进程是一个默认进程,如上图所示,默认进程的ipd是9152、

这里的小程序仍然是上面那个。

image-20241115145459103

输入要修改优先级的进程的pid,然后就会对我们输入的pid的进程进行修改,如下图所示

image-20241115145319448

然后输入在对25849的进程优先级输入一个-100

image-20241115145815325

退出top工具,出来查看进程优先级发现NI和PRI都发生了变化【PRI最终是60,变小了,因此该进程的优先级变高了】

image-20241115145708214

但是该进程退出后,在重新运行的话,优先级还是默认的

image-20241115145838931

image-20241115145850527

这里需要注意——Linux是不会让用户过度的修改NI值的。

这是因为,如果用户将自己的进程的优先级调的非常高,那么就会导致这个进程经常需要占用CPU,这样就会导致操作系统的调度失衡,其他进程很可能没办法占用到cpu资源了。因此Linux不会让用户过分的去调低NI值,来让进程的优先级过高

NI的值前面也说了,范围就是-20至19,一共40个级别。

因此Linux中,用户能够修改的最终的优先级范围就是[80 - 20, 80 + 19]。

2.进程的其他概念

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

  • **独立性:**多进程运行,需要独享各种资源,多进程运行期间互不干扰。哪怕其中一个进程挂了,但是不会影响另外一个进程运行。

为了维持这个独立性,需要耗费更多的资源来维护【学习进程地址空间之后,才能理解】

  • **并行:**多个进程在多个CPU下分别,同时进行运行,这称之为并行

  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

对并发需要知道的:

一个cpu,在任意一个时刻下,只能够运行一个进程,也就是只能被一个进程占用。

在cpu资源只有一个或2个的情况下,为了能够在一段时间内,推进多个进程运行的现象叫做并发。

这是如何做到的呢,其实主要靠的是一个策略——时间片

其实就是一个进程,操作系统规定你只能占用cpu资源多少时间。时间到了,就必须将cpu资源让出来,让其他进程占用cpu。这里有个现象是进程切换

image-20241115184610422

3.进程切换

前面也说了,CPU比较蠢,需要分析并接受外部的指令和数据,才能够进行运算。

因此CPU只干三件事情,【1.取指令、2.分析指令、3.执行指令】

在这个前提下,进程再被CPU执行的时候,其实CPU就是不断的在重复这三步,这个过程用到了很多的寄存器,一个CPU内部会有一套寄存器。

对于进程切换的一些知识和理解,先看下面这个图:

image-20241115205656346

对进程切换的分析:

进程再被执行的时候,肯定会产生非常多的临时数据,这些临时数据在执行的时候都在寄存器当中运算。这些数据都是属于当前进程的,也就是说,当该进程的时间片到了之后,其他进程占用CPU之后,这些临时数据就会被清空。

但是会有一个情况,当进程的时间片到了之后,如果进程还没有执行完,那么就要考虑该进程回到CPU的情况。

因此,操作系统会将该进程退出时,寄存器内的临时数据,在另外一个地方备份起来【上下文保护】,并记住这个进程,然后清空寄存器内的临时数据,来给下一个切换进来的进程使用。然后每次进程切换的时候,都要判断新进来的进程是不是之前有记录的未执行完的进程,如果有记录的话,就将之前备份的临时数据拿出来,继续执行该进程【上下文恢复】。从而实现上下文数据的恢复。

总结:

  • 进程在切换的时候,要进行进程的上下文保护

  • 进程恢复运行的时候,要进行进程的上下文恢复

4.环境变量

4.0环境变量的理解

为了更好的理解环境变量这个概念,下面先讲个帮助理解的例子:

在Linux中,我们输入系统的指令,是直接输入的【比如ls man who等指令】、而我们自己写的可执行程序,也可以理解成指令。

但是我们自己写的可执行程序,却要加一个./

image-20241117142610633

我们查看文件类型会发现,s和自己写的process都是可执行程序

image-20241117142705617

那是为什么呢?这是因为系统找得到系统指令池的指令,找不到我们自己写的指令。

这也就是为什么我们要执行自己的指令,需要加上./的原因,这里的./是当前路径的意思,告诉系统在当前路径找到process这个指令去执行。而系统的指令都放在了usr/bin下

如果我们想要自己的指令也可以像系统指令那样执行,通过root权限将自己的指令移动到usr/bin这个目录下即可。【但是我们不推荐这样做,因为自己写的程序是没有测试的,不稳定也不安全,会污染系统指令池】

但是为什么在usr/bin这个路径下,系统就找得到,而在自己这个路径下系统就找不到呢?

这就跟要谈的环境变量有关系了,系统中有一个环境变量叫做PATH,这个环境变量是系统启动的时候定义的一个全局有效的环境变量

image-20241117143440221

可以看到,每个冒号中间间隔的都是一个路径,那么在执行一个指令的时候,操作系统就会在环境变量PATH提供的这些路径当中去找、找到了就执行

如果这些路径下都没找到该指令的时候,就会报错—找不到该指令

image-20241117143617533

要注意:环境变量不是只有PATH,环境变量是有很多的

4.1环境变量的基本概念

  • 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数

  • 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。

  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

环境变量其实就是操作系统为了满足不同的应用场景而预先在系统内设置很多的全局变量。

而这些变量一直都会被其他进程所访问到。

4.2添加环境变量—export

添加环境变量有个需要注意的问题,下面做个案例:

可不敢想下图一样直接将要添加的路径直接添加到环境变量中

image-20241117150827756

这样会导致环境变量只剩下你所添加的这个路径,之前的路径都会被覆盖的。

但是如果真的覆盖了,其实问题也不大,因为环境变量这个东西,只要关掉这个命令行客户端,然后重新打开,环境变量就会重置了。原因是啥后面才学。这里只需要知道即可

由于之前的环境变量被覆盖,会导致原来系统的指令也用不了的。

正确的添加环境变量的方法:

输入export PATH=%PATH:要添加的路径

这样就可以在原来的环境变量上,添加一个新的路径

这样就可以直接像执行系统指令一样,直接执行自己的程序,并且 which自己的指令,也会直接出现指令所在的路径,因为which能够在环境变量中找到,

4.3Linux中环境变量的由来

在Linux中,用户的根目录下存在两个隐藏文件,如下图所示:

image-20241117161249241

这两个文件的内容如下图所示:

image-20241117161351531

image-20241117161513070

每次用户登录的时候,系统都会执行一次这两个文件,通过这两个文件来构建当前用户的shell环境变量。【这两个文件的内容看不懂很正常】

为什么用户修改环境变量每次登录的时候,环境变量都会被重置的原因就在这这里。

用户每次登录,系统都会登录上面所说的两个文件来加载环境变量。

4.4常见环境变量

  • PATH : 指定命令的搜索路径

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

  • SHELL : 当前Shell,它的值通常是/bin/bash。

除了这些环境变量,还有很多环境变量,比如为什么我们能找到之前所输入的指令,就是因为Linux当中帮我们记住了我们所输入的一些指令记录。比如Linux内核怎么知道我们用的是那个类型的shell呢?也是通过环境变量解决的。

环境变量其实就是操作系统为了满足不同的应用场景而预先在系统内设置很多的全局变量。

输入env指令,可以查看系统所有的环境变量。

image-20241117163429354

比如里面有一个环境变量叫做HISTSIZE,这个环境变量的3000就是Linux系统能帮我们记住3000条历史指令记录

image-20241117163555053

4.5和环境变量相关的命令

  1. echo: 显示某个环境变量值

  2. export: 设置一个新的环境变量

  3. env: 显示所有环境变量

  4. unset: 清除环境变量

  5. set: 显示本地定义的shell变量和环境变量

4.6通过系统调用获得环境变量

下面将通过系统调用获得环境变量的实验来加深对环境变量的理解

4.6.1 getenv

除了通过env来显示环境变量,我们也可以通过系统调用来获得环境变量。

下面做个小实验:

我们通过getenv()这个接口,来获得环境变量

getenv的官方介绍:

image-20241117234249456

因此我们来做个实验:通过系统调用来拿到USER这个环境变量的值

实验的代码如下:

image-20241117234202825

Makefile文件的内容如下:

image-20241117234459024

执行程序的结果如下:

image-20241117234446933

可以看到,getenv拿到了USER这个环境变量的值

image-20241117234717419

因此,如果USER环境变量改变了,这里执行的结果也会进行改变

实验如下:登录root用户,然后再执行此文件

输入su -登录root用户,此时发现USER环境变量已经加载成root了。

image-20241117235014941

那此时在执行我们能写的程序,结果会跟着改变吗?

如下图所示

image-20241117235204134

结果与预期相符。通过系统调用能够获得环境变量

既然系统调用能够获得环境变量,这样就能够判断当前用户的身份了,从而就可以判断当前用户是否有权限去访问某个文件了。

【这也就是为什么用普通用户身份无法访问一些文件,因为系统通过拿到用户的USER环境变量,来判断当前用户这个身份有没有权限访问该文件**(if语句判断),在拿到当前文件的属性是能够被谁访问的(stat拿到文件属性),从而判断当前用户没有权限访问该文件,从而输出Permission denied**】

image-20241117235755849

下面手动实现一下权限是否足够的判断:

image-20241118000829166

实验结果:

image-20241118001057381

还可以再讲一个加深理解环境变量的例子:

ls指令不论什么时候都可以知道用户当前所处的目录,凭什么?为什么执行自己写的程序的时候,需要./mycode,而ls mycode,不需要ls ./mycode?

这是因为ls每次都知道当前的路径,自然也不需要用户手动添加相对路径给shell了。那ls凭什么知道用户所处的路径呢?——凭环境变量PWD

image-20241119121101566

ls也是一个可执行程序,是一个指令,因此每次它执行都相当于bash的子进程,都可以继承到全局属性的环境变量,因此每次执行ls,ls都可以通过环境变量PWD来知道当前路径,自然就不需要用户手动告诉shell相对路径了

而每次变更路径的时候,bash都会及时的更新环境变量PWD,而ls每次执行又可以继承到全局属性的环境变量,因此不管用户在哪里ls,都可以通过环境变量PWD来获取当前路径。

4.6.2 putenv

这个系统调用,目前来说用不到。**功能就是添加或者修改一个环境变量。**后面学习完进程控制,自己写一个简单的命令行解释器的时候会使用它

image-20241124210151280

4.7本地变量

系统的环境变量可以说是一个全局有效的变量,在Linux中除了这种全局变量,还可以定义局部变量

image-20241118113426376

可以看到通过echo是可以获得到该环境变量的,但是这个并不是添加了一个系统的环境变量

image-20241118113545783

如上图所示,env展示的系统环境变量中并没有myval的存在

说明这个myval是一个shell创建的一个局部变量,只在shell有效

但是如果非要查看到myval的存在,可以用set,set展示的变量巨多,包括了环境变量和本地变量

下面做一个实验:通过getenv来看看是否能够获取到变量myval

代码如下:

image-20241118120046130

执行程序的结果如下:

image-20241118115829906

想要myval变成系统的环境变量可以使用export来导入

image-20241118120955827

但是这个导入的环境变量也只是临时的,只需要重启shell就会消失。因为重启shell会重新根据环境变量的配置文件来导入环境变量

总结:
  • 环境变量是全局的,代表它是可以被子进程继承的。因为mycode程序运行时,对于bash来说,就是一个子进程,但是它仍然可以获取环境变量。

为什么子进程需要继承环境变量呢?——因为可以满足不同的应用场景,比如执行命令时候的身份认证,PATH的指令路径

  • 本地变量就无法被继承。它只对当前进程(bash)有效

4.8main函数的参数

4.8.1命令行参数

之前的学习中,又见到过main函数是有3个形参的。这个参数叫做命令行参数

int main(int argc, char *argv[], char *envp[]);

这里的这个argc是决定 argv这个指针数组有多少个元素的。而argv这个指针数组装的都是char*指针,一般来说都是字符串

这里做个实验来看看argv这个指针数组内装的字符串都是什么

自动化构建工具Makefile文件的内容:

image-20241119184758404

用c99是因为代码中出现了for(int i = 0)这个代码

代码如下:

image-20241119184905139

执行结果如下:

image-20241119184924366

可以看到只有1个元素,下标为0的第一个元素是这个可执行文件的名字

如果将在执行的时候加n个选项,那么argc就会是n+1,argv数组就会有n+1个元素,如下图所示:

image-20241122193619698

那这个是怎么实现的呢?

其实就是我们输的指令其实是一个长字符串,然后shell会将我们的指令以空格为分隔符,将这个长字符串切割成一个个小字符串,也就是一个个指令,然后每个指令都是一个字符串类型,然后让根据argc来开辟argv的空间,然后让argv存储着一个个指令。

看下图可以更好的理解:

image-20241122194900464

4.8.1.1命令行参数的意义

可以通过输入分割出来的小字符串来实现不同功能的实现——即argc和argv的应用

下面是便于理解的例子:

image-20241124012851012

下面是实验结果:

image-20241124012728028

经过这个实验结果,可以看到命令行参数存在的意义:利用argc和argv两个命令行参数,执行同一个程序,但是可以通过不同的选项来实现不同的功能

这个非常常见!ls指令就是如此!

image-20241124014049014

在window中也有一个程序可以通过不同选项二实现不同功能的场景,如下图所示:

image-20241124124812278

4.8.2环境变量参数—char* env[]

image-20241124125435581

env这个环境变量参数,也是指针数组类型,装着的就是系统中env所展示的环境变量
他的结构和argv是一样的,只是每个指针变量都指向一个环境变量 。

因此可以看出,环境变量其实是被系统当做一行字符串的。

下面做个实验——通过环境变量参数来获取环境变量

代码如下:

image-20241124130824787

for循环这样写是因为,env[i]的最后一个元素是NULL,因此肯定会停止循环

实验结果:

image-20241124130912383

4.8.3总结

那学习了命令行参数和环境变量参数之后,就知道,实际上在main函数运行的时候,系统是会向main函数这个子进程传两个表的,一个表是命令行参数表,一个表是环境变量表。

这也就是为什么之前说,环境变量是能够继承给子进程的

因此实际上自己的程序在执行之前,系统是会帮我们做非常多事情的。

4.9环境变量的组织方式

前面说了,main函数运行的时候,系统会向其传两个表,而环境变量就是通过传环境变量表传给main函数的。但其实c语言还提供了一个变量来获得环境变量——environ

image-20241124202304520

这个environ是一个二级指针,指向的就是环境变量的表的开头

image-20241124202923300

每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串

因此,可以不通过环境变量参数env来获取环境变量,也可以通过environ变量来获取环境变量。

代码如下:

image-20241124204410135

libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。

执行结果如下:

image-20241124204432277

4.10获取环境变量的3种方式

  1. 通过c语言提供的接口——系统调用来获取环境变量【getenv
  2. 通过命令行参数中的环境变量参数——char *env[]
  3. 通过c语言提供的一个全局变量——char **environ

这三个获取方式更推荐getenv,因为很多场景都不是要获取全部的环境变量,而是按照需要的环境变量去获取。而getenv可以直接通过环境变量的名字来获取到想要的环境变量

;