「前言」文章的内容大致是进程优先级和环境变量。

七、进程优先级

7.1 基本概念

7.1.1 什么是优先级

优先级实际上就是获取某种资源的先后顺序,而进程优先级实际上就是进程获取CPU资源分配的先后顺序,就是指进程的优先权(priority)

:优先级和权限是不同的概念,权限决定的是一件事情能不能做,优先级是在权限允许的前提下,该事情先做还是后做

7.1.2 为什么存在优先级

优先级存在的主要原因就是资源是有限的,而存在进程优先级的主要原因就是CPU资源是有限

7.1.3 Linux 优先级特点

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

进程优先级本质就是PCB 结构体里面的一个数字(其他OS也可能是几个),比如吃饭排队,给排队的每一个人一个编号,到哪个编号服务员就喊哪个编号

7.2 查看系统进程

在Linux或者Unix操作系统中,用 ps -l 命令会类似输出以下几个内容

ps -l

注意到其中的几个重要信息,有下:

  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行(priority)
  • NI :代表这个进程的nice值 

进程的优先级就与 PRINI 有关,Linux支持进程进行中进行优先级的调整,调整的策略是通过更改 NI(nice值)完成的

最终优先级 = 老的优先级 + nice

7.3 PRI 和 IN

  • 进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
  • nice值其表示进程可被执行的优先级的修正数值
  • PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为: PRI(new)=PRI(old)+nice
  • nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
  • 调整进程优先级,在Linux下,就是调整进程nice
  • nice其取值范围是-2019,一共40个级别
  • 在Linux中,PIR 的默认值是 80

所以,在Linux中,最终优先级PIR = 80 + nice值,PRI(old)默认是 80

注意

  1. 进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化
  2. 可以理解nice值是进程优先级的修正修正数据

7.4 查看进程优先级和更改进程优先级

测试代码 

#include<stdio.h>    
#include<unistd.h>    
    
int main()    
{    
    while(1)    
    {    
        printf("PID:%d\n", getpid());    
        sleep(2);                           
    }    
    return 0;    
}    

运行进程,查看优先级

ps -al

在Linux操作系统中,初始进程一般优先级PRI默认为80NI默认为0

然后通过 top 命令更改进程的nice值,top进入top后按r–> 输入进程PID–> 输入nice

top

top 命令就相当于Windows操作系统中的任务管理器

然后按

输入你要调整进程的优先级的 PID,然后回车

之后输入 nice值后按q即可退出,如果我这里输入的nice值为15,再次查看,发现 6845进程的进程优先级 PIR 发生了改变,IN 也发生了改变

 注意:

普通用户若是想将NI值调为负值,也就是将进程的优先级调高,需要使用sudo命令提升权限

sudo top

有权限之后就可以调高进程的优先级了

注意nice其取值范围是-2019,你写 nice值超过19,默认为19,比如你输入50nice值最高依旧是19-20也是如此。PRI(old)默认值是 80,不会发生改变

7.5 其它概念

  • 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰。就比如你运行的抖音崩溃了,但是不会影响 qq的进程运行,进程运行各不干扰,不会说抖音运行崩溃了会导致你QQ运行崩溃。独立性后面讲到进程地址空间的时候再深入理解
  • 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行
  • 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

解释并发

假设在只有一个 CPU 的情况下,我们发现也能同时运行几个或多个进程,比如你运行了浏览器,但是又可以运行QQ ,也可以打开PDF阅读器...不是说一个CPU只能运行一个进程么?为什么你又可以运行多个进程?

一个运行中的进程,CPU 不会说一直等它运行结束才运行下一个进程。当代计算机采用的是 时间片轮转的策略,规定每个进程在 CPU 上运行是有一定时间限制的。假设每个进程运行时间片是 10毫秒,CPU 一秒钟就能运行 100 个进程。即使进程没有运行完,也要把进程从 CPU 上拿下来,重新放在运行队列尾部继续排队,再次等待 CPU 运行。这就解释了一个CPU 可以运行多个进程的 “现象”,这种现象就叫并发

7.6 进程切换

CPU 内是有一套寄存器硬件的,其中一个 pc 或叫 eip 寄存器的作用是:保存当前正在执行指令的下一条指令的地址

CPU 永远做着三件事:

  1. 取指令
  2. 分析指令
  3. 执行指令

当进程在 CPU 上运行的时候,会产生很多的临时数据,这些数据是属于当前进程的,这些数据并且保留在 CPU 的寄存器上 

注意区分:CPU 内部虽然有一套寄存器硬件,但是,寄存器内保留的数据是属于当前进程的

寄存器硬件 != 寄存器内的数据

上面的并发谈到,进程在 CPU 上运行是,该进程占用 CPU,进程不是一直占用 CPU 到进程结束,而是说,进程运行的时候,有属于自己运行的时间片。当进程运行时间达到时间片后,就会从 CPU上拿下来

进程被拿下来到下一次进入CPU 运行也要被恢复,怎么恢复?

进程被 CPU 拿下来了,该进程运行时产生的临时数据也要保留,这里就暂且说临时数据保留在 PCB 里面吧(这里保留在PCB不太对,这里暂且这么理解)

等到进程下一次运行的时候,之前保留在的数据要被恢复

举个栗子:比如你在大一要去当兵,去当兵你的学校会把你的学籍保留下来,等你从部队回来了,你的学籍就要被恢复才能继续读书。这里的学校就相当于 CPU,你就相当于一个进程,部队就相当于一个等待队列,学籍就相当于在上一次运行是产生的临时数据。你从部队回到学校继续读书就相当于进程再一次进入 CPU 内运行,这一个过程就叫进程切换

进程在切换的时候,要对进程的上下文保护,这里的上下文指的就是寄存器内的临时数据,不是寄存器。当进程在恢复运行的时候,要进行上下文恢复

在任何时刻,CPU 内里面寄存器的数据,看起来是在大家都能看见的寄存器上,但是,寄存器内的数据只属于当前进程。寄存器被所有进程共享,但寄存器内的数据,是每个进程各自私有的,这些私有的数据叫上下文数据

八、环境变量

8.1 环境变量基本概念

我们所写的代码生成的可执行程序,这个可执行程序就是一个命令,不过我们要加 ./才能运行它

我们使用的 Linux命令本质就是一些可执行程序,不过执行它们,不用加 ./就可以直接运行这些可执行程序

我们可以使用 file 命令查看一下

file mytest
//mytest 是你的可执行程序

也可以使用file命令查看一下Linux 的命令

为什么我们写的可执行程序要加 ./才能运行,而Linux的命令却不用加 ./ 就能运行?它们同样是可执行程序

这就与环境变量有关系了,我们也可以把 mytest 可执行程序放入 /usr/bin/ 路径下,不用加 ./ 也能直接运行 mytest,但是这种做法不推荐,你写的程序没有经过测试,可能会污染命令池

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

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

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

8.2 常见环境变量

  • PATH : 指定命令的搜索路径
  • HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
  • SHELL : 当前 Shell,它的值通常是/bin/bash

这些下面解释

windows下也有环境变量,“我的电脑” -> 右键 -> 属性 -> 高级系统设置

8.3 查看环境变量

env 命令 

env		//显示所有环境变量

查一个具体的环境变量路径

echo $NAME //NAME:你的环境变量名称

比如查看,PATHHOME,找到的话会打印出来,以冒号分隔

8.4 和环境变量相关的命令

  • echo:显示某个环境变量值
  • export:设置一个新的环境变量
  • env:显示所有环境变量
  • unset:清除环境变量
  • set:显示本地定义的shell变量和环境变量

8.5 测试PATH

PATH:指定命令的搜索路径

我们所在 Linux 执行的每一条指令,都会通过这个环境变量 PATH 进行检索,找到了指令就运行它,找不到就说没有这个命令

上面也提到,为什么我们写的可执行程序要加 ./才能运行,而Linux的命令却不用加./就能运行?它们同样是可执行程序

系统就是通过环境变量 PATH 来找到相关命令的,比如上面的 lsmytest,环境变量会在系统的路径下查找该命令, 找到就执行,找不到就说没有

echo $ 查看环境变量PATH我们可以看到如下内容

[fy@VM-4-14-centos d1]$ echo $PATH

可以看到环境变量PATH当中有多条路径,这些路径由冒号隔开,比如当你使用 ls命令时,系统就会查看环境变量 PATH,然后默认从左到右依次在各个路径当中进行查找,而 ls 命令实际就位于 PATH当中的某一个路径下,所以就算 ls 命令不带路径执行,系统也是能够找到的,这就是因为 ls 命令处于环境变量 PATH 的路径下

这也说明了为什么我们直接执行我们写的可执行程序 mytest 不可以,因为我们写的可执行程序 mytest 不在环境变量 PATH 的路径下,我们想执行我们写的可执行程序就必须加上 ./,它的作用就是帮我们在当前路径下找到该可执行程序并执行它

那可不可以让我们自己的可执行程序也不用带路径就可以执行呢?

当然可以,下面给出两种方式:

方式一:将可执行程序拷贝到环境变量PATH的某一路径下

上面也说了,这种做法不推荐,你写的程序没有经过测试,可能会污染命令池

方式二:将可执行程序所在的目录导入到环境变量PATH当中

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

使用命令 export PATH=$PATH:当前路径

export PATH=$PATH:/home/fy/CODE_lqh/code_linux/code_12_05/d1

这样就可以直接运行当前路径下的可执行程序了

更改环境变量,只对本次登录有效,只要不动配置文件,咋搞都没问题,下次登录又会重新生成新的环境变量

那问题又来了,系统的环境变量哪里来的呢?

进入系统目录 cd ~

系统定义环境变量就在这两个文件里面定义

我们在登录的时候,当前系统的 bash 进程默认会把这两个文件执行一次,也就是把环境变量导入到你当前的环境当中,你把当前的环境变量覆盖了,重新登录又会生成新的环境变量,这些环境变量都是具有全局属性的,每一个环境变量都有自己的功能

8.6 测试HOME和SHELL

HOME

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

HOME环境变量就是 cd ~ 这个命令

普通用户:

超级用户root:

SHELL

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

我们在Linux操作系统当中所敲的各种命令,实际上需要由命令行解释器进行解释,而在Linux当中有许多种命令行解释器(例如bashsh),我们可以通过查看环境变量SHELL来知道自己当前所用的命令行解释器的种类

注:每一个环境变量都有自己特定的功能

8.7 通过系统调用获取环境变量 

有两种方法:putenvgetenvputenv 后面讲解,这里使用的是 getenv

man 查看一下

man 3 getenv

测试代码

USER:标识当前使用的 Linux用户

#include<stdio.h>    
#include<stdlib.h>    
    
int main()    
{    
    char* who = getenv("USER");    
    printf("%s\n", who);
    
    return 0;    
}    

 运行结果

8.8 环境变量通常是具有全局属性的

我们可以随便定义一个环境变量,比如

但是这个环境变量叫做本地环境变量,它是一个局部变量,在 env 全部环境变量里面查找是没有的

export: 设置一个新的环境变量,可以使用这个指令设置新的环境变量,再次查找就已经在 env 里面有了

export aaa

下面进行代码测试

#include<stdio.h>    
#include<stdlib.h>    
    
int main()    
{    
   char* myval = getenv("myval");    
   if(myval == NULL)//getenv 查询为没有返回 NULL
   {    
       printf("myval no found\n");    
       return 1;    
   }    
    
    printf("%s\n", myval);
    
    return 0;    
}    

没有设置环境变量 myval 之前,进行运行

定义一个新的本地环境变量 myval,并将 myval 设置成一个新的环境变量 

再次运行程序

结果说明了:环境变量是可以被子进程继承下去

bash 是一个系统进程,mytest 运行后也是一个进程,这个进程是 bash 的一个子进程,子进程能找到并打印环境变量 mytest,说明了子进程可以集成环境变量,子进程的环境变量是从 bash 来的,所以说环境变量具有全局属性 

环境变量具有全局属性,根本原因是环境变量是可以被子进程继承下去

环境变量为什么要被子进程继承下去?

为了适应不同的场景,让 bash 帮找指令路径,进行身份验证

上面的 myval 没有设置成环境变量,当它只是一个本地变量时,它只会在当前进程 bash内有效,它无法被子进程继承,所以它就是一个局部变量

unset 和 set

  • unset: 清除环境变量
  • set: 显示本地定义的shell变量和环境变量

定义一个本地变量 yourval,不设置成环境变量

使用命令 set,里面会显示所有的环境变量并且包括本地变量 

grep 查一下 yourval

如果这个环境变量不想要,使用命令 unset,再次查找这个环境变量或本地变量已经没有了

8.9 命令行参数

你是否知道 main 函数有参数吗?

答案是:有,而且是三个,只是我们平时基本不用它们,所以一般情况下都没有写出来

main 函数的三个参数为:int argcchar* argv[]char* env[]

main 函数也是函数,也是被调用的,这些参数一般是系统或父进程传的

这里我们先测试前面两个参数: int argcchar* argv[]

测试代码:

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

运行结果,指针数组的 0 下标指向的就是 ./mytest

那再给它增加一些参数(程序后面是可以带参数的)

再给它增加一些参数,我们传的参数越多,它的 argv指针数组就越大

先不急解释上面的,我们先来看看一个经常使用的命令 ls

我们都知道命令后面是可以带选项的,ls 就是程序名,后面的 -a -b -l...是这个程序的选项,后面的选项参数有多少,char *argv[] 这个数组就有多大,argc传的就是数组的大小

比如:ls -a -b -c -d -e,命令行解析会把这个长的字符串拆分成一个个子字符串:"ls" "-a" "-b" "-c" "-d" "-e",这些子字符串就存在指针数组 char *arvg[] 里面,"ls" "-a" "-b" "-c" "-d" "-e" 共有 6个,所以 int argc 的大小就为 6

一个命令就被解析成这样,这些工作一般是系统和 shell 做的,argc 代表你的命令行一共有多少个子字符串,argv 就是一张映射表,把每个子字符串一一映射在 argv 里面

为什么要解析成这样子?

为了执行不同的功能,比如ls -a,它就执行 -a 的功能,ls -a -b它就执行 -a -b 的功能,这就是命令行参数最大的意义

上面的 mytest 也是如此

接下来测试第三个参数 char* env[],它是用于获取环境变量的,它也是一个指针数组,以 NULL 结尾,与第二个参数一致

测试代码:

#include<stdio.h>    
      
int main(int argc, char *argv[], char *env[])    
  {    
      int i = 0;    
      //env 也是以 NULL 结尾,NULL 就是 0    
      for(i = 0; env[i]; ++i)    
      {                                        
          printf("env:[%d] -> %s\n", i, env[i]);    
      }    
      
      return 0;    
  }    

运行结果,说明 char *env[] 就是用于获取环境变量的,这也说明我们写的程序为什么能够获取到环境变量

8.10 第三方变量 environ 获取环境变量

除了使用 main 函数的第三个参数和 getenv 来获取环境变量以外,我们还可以通过第三方变量 environ 来获取

man 查看 environ,它是一个二级指针,它其实指向的是 char *env[]

测试代码:

 #include<stdio.h>    
 #include<unistd.h>    
      
 int main()    
 {    
     extern char **environ;                                          
     int i = 0;    
     while(environ[i])    
     {    
         printf("environ:[%d] -> %s\n", i, environ[i]);    
         ++i;    
     }    
      
    return 0;    
 }    

运行结果

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

总结一下:

在进程的上下文中,获取环境变量的三种方式为:

  1. getenv
  2. char *env[]
  3. extern char **environ

--------------- END ---------------

「 作者 」 枫叶先生
「 更新 」 2022.12.10
「 声明 」 余之才疏学浅,故所撰文疏漏难免,
          或有谬误或不准确之处,敬请读者批评指正。