引言:
北京时间:2023/2/20/22:15,昨天晚上,看了一晚上的cs:go,主要原因是因为我的好舍友,叫我开箱子,然后就不可言语,看了一晚上的开箱子和精彩剪辑,不过这个游戏确实是颠覆了我对游戏的认知,游戏里的装备居然是可以卖钱的,我发现这个游戏机制非常好,但是这个游戏对新手不太友好,不然这个游戏还是挺快乐的,并且该游戏里的装备也颠覆了我对钱的认知,……,今天就让我们来学习一下孤儿进程、环境变量、优先级和地址空间相关的知识。
什么是孤儿进程
在上一篇博客中,我们已经学习了什么是僵尸进程,我相信大家都知道,我们是通过把某一个子进程使用kill -9 PID 的方式,来把该进程给杀掉,之后才导致一个进程从运行状态(R)或者睡眠状态(S),变成了僵尸状态(Z),所以可以想象如果此时我们不是把某个子进程杀掉,而是把某个子进程的父进程杀掉,此时是什么情况呢?如下图:
从上图的代码中我们可以看出,我们使用fork()函数创建了两个一样的进程,此时这两个进程可以同时执行while循环语句,并且判断条件互不干扰,重点:此时还可以看出,下面的循环,也就是父进程的循环不是死循环,而是只循环10次,10次之后,该循环就会停止,也就意味着上面循环进程的父进程停止了,此时该子进程就没有了相应的父进程,但又可以发现,当父进程停止之后,此时该子进程的父进程从原来的22024变成了1,这是为什么呢?并且从右图可以看到,此时当父进程停止之后,父进程直接消失了,按照之前学习的知识,不是说进程停止之后会先变成僵尸状态吗?这又是为什么呢?
原因就是:本质上,一个子进程停止之后,它的父进程就会把它给回收,并且由于bash是所有子进程的父进程,所以此时当该进程的父进程停止之后,该父进程就会直接被bash进程给回收,所以我们看不到该进程变成僵尸状态,并且当该进程的父进程如果先停止,那么此时该进程就会直接默认的被1号进程(操作系统)给领养,使其拥有一个新的父进程,所以此时该子进程就是我们所说的孤儿进程。
为什么会有孤儿进程的概念:
从逆向的角度思考,此时如果该进程没有新的父进程,不是孤儿进程,那么此时会导致什么问题呢?首先明白,只有父进程可以给子进程收尸,其次明白,不收拾会怎样,得出结论:如果一个子进程没有父进程就会导致无法收尸,就会导致该子进程无法管理,使该子进程一直在我们的内存中占空间,最终导致:内存泄露问题,程序崩溃。
并且此时杀掉进程有两种方式,一种是杀掉该进程的PID(kill -9 PID),一种是直接杀掉该目标进程(killall 目标进程),所以你想让那个进程当孤儿进程你就杀掉谁的父进程,哈哈哈哈!
什么是环境变量
搞定了上述的孤儿进程的知识之后,进程状态相关的知识,我们就搞定的差不多了,像进程中比较特殊的僵尸进程和孤儿进程我已经通过Linux下的代码进程看到了相应的场景,当然进程状态中比较特殊的还有守护进程和精灵进程之类,这些我们目前先不做了解,接下来就让我们学习一下什么是环境变量吧!
首先我们先从一个问题出发,就是当我们写法代码,编译之后,想要运行,为什么一定要加 (./) 符号呢?为什么像Linux中的基本指令ls、cd、rm、cp、mv等,不要呢?如图:
只有输入了 (./) 符号才可以执行该程序,(./) 到底是什么意思呢?首先要明白一点,执行一条命令的前提是找到这条命令,所以此时明白 (./) 的意思就是在本地寻找这个文件,如果本地有该文件就执行,否则就报找不到该指令,同理,在我们的Linux系统中也存在一个叫 PATH 的环境变量,在我们输入了ls、cd、rm、cp、mv这些基本指令的时候,系统会自己去PATH 的环境变量中寻找这些指令的,如果环境变量中存在则执行,不存在则报找不到该指令。
所以上述问题的答案就是: 我们生成的可执行文件,并不在系统的PATH 环境变量中,想要执行该文件,就要我们自己指明该文件对应的路径 (./)
搞懂了这些,相信我们就明白,我们是可以自己找到PATH环境变量,然后把自己的可执行程序给加入到PATH环境变量中的,然后在执行该程序的时候,就不需要让我们自己指定路径,而是让系统直接去PATH 环境变量中找该程序,使我们直接输入程序名就可以,不需要指明(./)了
如图:
上图表示的就是,使用 export 指令,向PATH中写入程序路径,但是要注意: 此时不可以直接向PATH中写入,而是要在 $PATH的后面进行插入,不然会把PATH中原有的路径给覆盖掉,导致出现问题(不过重启软件之后可以恢复),所以此时这种行为就相当于是在Linux系统中安装了一个软件!所以无论是Windows还是Linux,只要是安装软件,本质上就是在把该软件对应的可执行文件拷贝到我们电脑相应的路径下。
查看环境变量
输入 env指令 ,查看Linux系统中的环境变量
在高级系统设置中查看Windows系统中的环境变量
上述有非常多的环境变量,每一个都有它们对应的作用,并且这些作用是非常重要的,但是目前对我们来说最重要的就是上述提到的PATH(搜索环境变量)
查看main函数中的环境变量表
int main(int argc,char* argv[],char* envp[])
对于以前学习C语言来说,我们是不需要对main进行传参,也没有函数可以调用main函数进行传参,但本质上,main函数是可以进行传参的,并且此时由于没人可以调用main函数,所以此时参数的数据就是通过命令行解释器获得,就是Linux中的bash获得,并且此时main函数是可以传三个参数的,其中有一个就是指向环境变量表的一个字符指针数组
如图所示:
所以此时main函数中的 char* envp[]
指针数组,就是代表我们的环境变量表;
并且此时根据图中的代码,可以通过循环的方式,直接将该指针数组中的指针所指向的数据进行一一打印,所以如上图,我们就得到了环境变量表中的所有环境变量。
并且如果不使用main函数中的参数 char* envp[] 指针数组,此时也是可以获得环境变量,方法就是使用函数 environ 或者 getenv ,如下代码:
使用该代码,也可以很好的把环境变量表中的所有环境变量给打印出来,跟上图是一样的,此时不多加插入了, 注意:此时因为我们要访问的是一个指针数组,所以此时需要使用二级指针,所以要写成 char** environ
,并且加上extern,表示从外部引用,这样才可以很好的访问环境变量表。
此时我们就可以把环境变量表中的USER环境变量中的数据给打印出来了,该函数的好处就是可以一个一个的获得我们想要的环境变量;重点: 所以无论是我们的user指令,还是pwd指令,还是cd等指令,其实本质上都是通过getenv函数来实现其特定的功能的,比如上述我们就自己实现了一个可以打印user的指令。
并且如果我们想要自己实现pwd和cd等指令,只需要把上图代码中的user换成pwd或者cd就行了,通过getenv函数我们可以直接拿到任何的环境变量,实现任何的环境指令。
使用环境变量实现权限问题
如下代码,充分表明了环境变量对于权限方面的使用方法,可以通过环境变量中的USER,来控制用户权限。
总:环境变量本质就是一个内存级的一张表(内存中的某块地址),这张表由用户在登录系统的时候,系统自动进行给特定用户形成属于自己的环境变量表,并且每一个环境变量都有自己的特定使用场景。
系统如何配置环境变量
通过上述总结可以知道,环境变量是系统自动帮我们配置的,那么就有一个问题,系统是如何进行环境变量的配置呢? 此时就涉及到了两个Linux系统中的两个文件,一个是 .bash_profile
,一个是 .bashrc
如图:
所以在我们登录的时候,系统就会自动帮我们加载上述两个文件, .bash_profile 和 .bashrc 中的配置文件,将环境变量给配置完成,具体原理:就是将上述两个配置文件中的默认程序加载到命令行解释器(bash)中。
浅谈环境变量的继承
搞定了上述加载环境变量相关知识,此时我们来看看环境变量的另一方面的知识,环境变量的继承,在理解环境变量的继承之前,我们写复习一下以前的知识:父进程是如何创建子进程的
如图:
我们可以看到,父进程在创建子进程的时候,会将自己的大部分属性数据拷贝给子进程,所以明白了这个道理,我们就可以谈谈环境变量的继承问题了;谈继承问题的第一个前提:明白环境变量本质上是在命令行解释器(shell、bash)进程中,由命令行解释器(shell、bash)进程控制的, 所以当我们的shell进程在创建子进程的时候,就会像上图一样,将自己属性中的环境变量给拷贝给子进程,所以这就是环境变量的继承现象,并且深入理解,可以发现,当我们在shell中创建一个环境变量时,该环境变量并不会被子进程继承,只有当该环境变量被export指令写入到了环境变量表中时,子进程才可以继承,所以得出结论:shell的本地变量只在shell内部有效,不可以被子进程继承(只有当使用了export把shell的本地变量导入到环境变量表后,子进程才可以继承),进一步说明,环境变量表是在shell进程中被控制。
source(.) 让配置文件立即生效指令
什么是命令行参数
从main函数中看命令函参数:int main(int argc,char* argv[])
所以此时main函数中的int argc
参数代表的就是各种的参数选项,并且第一个下标永远存放的是该可执行程序(myproc).
并且此时char* argv[]
表示的就是:如下图
给int argc;中的参数选项提供存储空间,所以main函数中的两个命令函参数就是用来提供参数选项和存储参数选项的。
注意:该参数选项表是由Linux中的命令行解释器bash生成的,并且目的就是提供给子进程中的环境变量使用。
总:int argc代表的就是命令行参数对应的个数,int argv[]代表的就是各个参数对应的起始地址。
命令行参数有什么用呢?
我相信大家都很好奇,命令行参数到底有什么用,因为平常我们使用C语言写代码的时候,main函数中的参数永远是为空的,表明命令行参数对于我们写C语言代码是起不到作用的,本质原因:也是因为我们无法去调用main函数,但是当我们呢在Linux系统中时,因为有了命令行解释器(bash),并且因为bash是所有进程的父进程,所以此时命令行解释器是可以通过控制子进程的方式去控制main函数,给main中的形参提供实参的,所以我们把main函数中的参数就叫做命令行参数,那么命令行解释器(bash)为什么要给main函数(子进程)提供参数呢?或者可以说,命令行解释器给main函数提供参数有什么用呢?接下来,我们就通过一个代码来具体看一看,命令行参数给main函数传参的用处(用处巨大):
通过上图,可以看出,我们可以根据不同的命令行参数中的不同的选项,让我们的同一个程序,执行不同的不能(本质就是if和else if 的使用),所以例如我们以前使用的(ls)指令,就是通过命令行参数的形式,将自己不同的选项传递给相应的程序使用,然后该程序在其内部对相应选项进行判断(else if),这样就可以让同一个指令(软件)执行不同的功能了,如:(ls -a ,ls -l,ls -t,ls -b)等等!
进程的优先级问题
通过以前知识的学习,我们可以很清楚的知道,在系统当中,所有的资源都是有限的,导致排队是必须的,有排队就会涉及优先级的问题:所以让我们看一下什么是优先级或者说系统内部是怎样排队的,进程的优先级(PRI),也就是被CPU执行的先后顺序,该值越小,进程的优先级越高,优先级的修正数值(NI)表示进程可被执行的优先级的修正数值,PRI值越小越快被执行,加入NI之后,就会变成 PRI(new) = PRI(old)+NI,所以NI可以决定PRI,调整NI就是在调整进程的优先级,注意:NI表示的只是进程的修正数据,不是进程优先级,所以本质上,我们是通过修改NI的值来修改进程的优先级的,如下图:
可以发现,输入ps -al指令
,我们就可以看见刚刚创建的进程的PRI和NI数据,并且此时如果输入top指令
,我们可以看到,如下图的场景:
所以此时我们就可以通过这个场景来修改我们进程的优先级(PRI),当然本质是通过UI来修改;输入r,可以看到一个命令行,此时就可以进行优先级的设置,(通过PID来设置),输入PID之后,你就可以看到如下图的场景:
此时就可以输入你想设置的UI值了(-20~19),例如:此时我输入了一个10,此时我的UI值就变成了10,进程的优先级就变成了90,如下图:
充分表明此时我们是可以通过UI来调整我们的进程优先级(PRI),并且PID的范围也必须符合UI的范围(注意:想要通过UI修改PRI,此时的PRI每一次都是从80开始,修改之后的PRI不会叠加,永远从80开始)
进程的特性:
搞定了上述的进程的优先级的问题,此时我们可以通过进程的优先级和以前学习的有关进程的相关的知识,得出一些有关进程的特性: