目录
坚持
📖一、进程优先级
什么是优先级?我们上一篇文章提到过,CPU只有一个,而我们的进程想要运行是需要放在CPU上的,因此进程需要在运行队列上排队,那么这种排队并不是随便排队,而是有顺序的排队,依靠进程的优先级进行排队。
为什么要用优先级进行排队?因为如果没有优先级,那么弱小的进程永远抢不到CPU,就像我们去食堂买饭,如果不排队,那么弱小的人永远抢不到饭
而排队的顺序就需要通过优先级来确定,优先级高的排在前面,优先级低的排在后面。
如果因为优先级设计不合理(调度算法设计不合理)导致我们的进程长时间得不到 CPU 资源,该进程的代码长时间无法得到推进,就会产生该进程的饥饿问题,在用户看来就是应用卡死。
(我们上节文章提到过就绪状态,在进程从就绪状态重新放到运行状态时,它的优先级并不是从最后一个开始的,而是由调度器最合理的重新规划优先级)
总结->:
CPU 资源分配的先后顺序,就是指进程的优先权。
优先级高的进程有优先执行权利。配置进程优先级对多任务环境的 Linux 很有用,可能会改善系统性能(注意:不要随意的修改进程优先级,只有调度器能够最公平的帮我们调度进程)。
还可以把进程运行到指定 CPU 上,这样一来,把不重要的进程安排到某个 CPU,可以大大改善系统整体性能。
📖二、查看进程优先级
想要查看进程优先级,我们可以通过这样的指令:
//通过ps -al 指令可以查看进程的优先级
ps -al | head -1 ; ps -al | grep 可执行程序名称
我们通过上述命令可以对任意进程看到如下图的示例->:
UID:代表执行者的身份。
PRI:代表这个进程可被执行的优先级,其值越小越早被执行,是 task_struct 结构体对象中的一个成员。
NI:代表这个进程的 nice 值,是进程优先级的修正数据。
PRI and NI
1. PRI 即进程的优先级,或者通俗点说就是程序被 CPU 执行的先后顺序,此值越小,进程的优先级别越高。
2. NI 就是我们呢常说的 nice 值,表示进程可被执行的优先级的修正数值。
PRI 值越小越快被执行,那么加入 nice 值后,将会使得 PRI(new) = PRI(old) + nice。
3. 当 nice 值为负数的时候,那么该程序的优先级值将会变小,即优先级会变高,其会越快被 CPU 执行。
4. 所以调整进程优先级,在 Linux 下就是调整进程的 nice 值。
nice 值的取值范围是[-20,19],一共40个级别
修改进程优先级
普通用户是无法修改进程优先级的,因此要修改进程优先级必须切换成 root 用户。
top
//进入top后按“r”->输入进程PID->输入nice值
小Tips:PRI(new) = PRI(old) + nice中的 PRI(old) 永远都是从80开始。
一些名词解释
竞争性:系统进程数目众多,而 CPU 资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰。
并行:多个进程在多个 CPU 下分别同时运行,这称之为并行。
并发:多个进程在一个 CPU 下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。
我们上一篇文章中提到过,运行队列中的进程都是有各自的时间片的,当一个进程的时间片到了,不管有没有执行完,都会被放到就绪队列中,那么,当这个进程从就绪队列中拿回到运行队列中时,CPU是如何知道这个进程接着要从哪里开始继续运行的呢?
答案是,依靠程序计数器(也叫做PC指针),这个PC指针是CPU中的一个寄存器,一般都是epi,它用于存储下一条将要执行的指令的内存地址,这样一来 CPU 下一次再调度该进程的时候就会通过PC指针知道该从哪里继续执行了。
小Tips:CPU 中的寄存器有很多种,例如:通用寄存器eax、ebx、ecx、edx;和栈帧有关的ebp、esp、eip;和状态有关的status。寄存器也有对数据保存的能力,计算机在运行时一些重要的数据一定要保存在 CPU 内部,即寄存器中,因为寄存器离 CPU 很近存储效率高,所以 CPU 内的寄存器保存的是进程相关的数据,可以随时被 CPU 访问或者修改
进程在从 CPU 上离开的时候。要将自己的上下文数据找个地方保存好,或者带走,保存好的目的是为了未来恢复。进程在被切换的时候,要执行两步操作,即保存上下文和恢复上下文,进程的上文数据量并不大,一般可以直接保存到进程的 PCB 对象中。当 CPU 再次调度该进程的时候,将这些上下文数据再恢复到寄存器里面即可。
总结: 进程从就绪状态中重新回到运行状态,CPU知道接着哪里开始运行,是因为程序计数器中存储着下一条将要执行的指令的内存地址,其程序计数器的值会被保存在进程的 PCB 中
CPU 会根据 PCB 中保存的进程上下文信息,将程序计数器等寄存器恢复到上次暂停时的值,从而使进程能够从上次中断的地方继续执行下一条指令。
📖三、环境变量
基本概念:
环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数。
如:我们在编写 C/C++ 代码的时候,在链接的时候,从来不知道我们所链接的动静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
相对来说,环境变量就是帮助可执行程序的一个辅助工具
📖四、环境变量PATH:Linux系统中的指令搜索路径
我们平时在 bash 命令行中执行自己写的可执行程序时需要加 ./,表示当前目录下的可执行程序,告诉操作系统执行的时候在该目录下查找,指令本质上也是可执行程序,但是我们在执行指令的时候却从来没有加任何路径,那操作系统是如何直到指令在哪个目录呢?
之前我们提到过,所有的指令都在/usr/bin/
目录下,而我们自己写的可执行程序是在我们自己当前的工作目录下。都是目录怎么还会有区别呢?
因为 Linux操作系统 会为我们提供一个环境变量 PATH,它是 Linux 操作系统给我们提供的指令搜索路径,这个环境变量是自从我们开机登录上 Xshell 就存在的,可以通过下面这条指令来查看 PATH 变量的值。
echo $PATH
PATH 变量值是用冒号分隔开的一些路径,这些路径就是我们平时执行指令时,系统去查找指令的路径,系统会依次在这些路径中找指令,这就是为什么执行指令的时候不需要加 ./,
因为系统会自动到 PATH 对应的路径下去挨个搜索,这些指令都在/usr/bin/目录下,并且/usr/bin/目录也是 PATH 的变量值,所以指令最终一定会被找到,而我们自己写的可执行程序一般都是在当前的工作目录下,这个目录并不是 PATH 变量的值,如果不加 ./ 操作系统会自动到 PATH 对应的所有路径下挨个去搜索,最终都没有找到该可执行程序,也就无法执行。可以通过下面这条指令将当前的工作目录添加到环境变量里。
PATH=$PATH:/home/yzq/linux-s/lesson13
///home/yzq/linux-s/lesson13是当前工作目录
//只用=是覆盖写的意思,会把原本的PATH 覆盖掉
如果用 += 就不是覆盖的意思
再去查看 PATH 的值,此时我们当前的工作目录也被添加了进去,此时在该目录下的可执行程序在执行的时候就可以不加 ./ 了。
小Tips:我们上面修改的环境变量是一种内存级的环境变量,保存在 shell 中,每次登陆 shell 会从系统的一些配置文件中将环境变量加载到 shell ,所以我们上面这种修改并不会影响到系统的配置文件,因此每次重启登陆环境变量就可以恢复如初。
📖五、环境变量HOME
我们每次登陆 shell 都默认处在家目录下,这是因为我们每次登陆的时候,shell 会识别到当前登陆的用户,自动帮我们填充 $HOME 环境变量,登陆的时候 shell 会为我们分配命令行解释器 bash,bash 本质上也是一个可执行程序,它会帮我们执行 cd $HOME 指令,这就是为什么我们每次登陆,都默认在家目录下。
小Tips:我们可以通过 env 指令查看当前 bash 从操作系统中继承下来的所有环境变量。
env
除了使用 env 指令,我们还可以通过 getenv 这个系统调用接口来获取某个环境变量的值。
这里只是展示一下,各位也可以获取其他环境变量
📖六、环境变量USER
USER环境变量指的就是你现在所处的用户
通过 USER 这个环境变量可以实现权限的认证,不同的用户登录 shell,它的 USER 是自己的用户名。
举个例子->:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char user[32];
strcpy(user, getenv("USER"));
if(strcmp("root",user) == 0)
{
printf("root用户,不受权限约束\n");
}
else
{
printf("普通用户,受权限约束\n");
}
printf("USER:%s\n",getenv("USER"));
return 0;
}
正是因为 USER 这个环境变量的存在,操作系统就具备了识别当前登录用户的能力,进而可以和文件对应的拥有者、所属组以及对应的权限做对比,一次来判断当前用户是否具有某项权限。
📖七、命令行参数
C/C++ 的 main 函数是可以传参的,这两个参数就叫做命令行参数,如下所示:
int main(int argc, char* argv[])
{
return 0;
}
argv 是一个指针数组,里面保存的是字符串地址,这个数组有 argc 个元素。
int main(int argc, char* argv[])
{
int i = 0;
for(; i < argc; i++)
{
printf("argv[%d]->%s\n",i, argv[i]);
}
return 0;
}
main 函数做i为函数也是可以被调用的,它也有参数,调用 main 函数的时候可以传参。我们在 bash 命令行输入的:./mycode -a -b -c会被 bash 当成一个字符串,它会把这整个字符串以空格作为分隔符,分成一个个单独的字符串,然后将它们的地址存入到 argv 数组中。
小Tips:假设 argc == N
,即有 N 个命令行参数,那么 argv[N]
会默认设置成 NULL
。
📖八、命令行参数的作用
有了命令行参数,我们就可以使用如 ls -a,ls -l 这样通过不同的命令行参数做出不同效果的事
代码如下->:
#include <stdio.h>
int main(int argc, char* argv[])
{
if(argc != 2)
{
printf("./mycode [-a|-b|-c|-d]\n");
return 0;
}
if(strcmp(argv[1], "-a") == 0)
{
printf("执行功能1\n");
}
else if(strcmp(argv[1], "-b") == 0)
{
printf("执行功能2\n");
}
else if(strcmp(argv[1], "-c") == 0)
{
printf("执行功能3\n");
}
else if(strcmp(argv[1], "-d") == 0)
{
printf("执行功能4\n");
}
else
{
printf("没有该选项!\n");
}
return 0;
}
main函数的第三个参数
除了上面提到的 argc 和 argv 两个参数,main 函数还有第三个参数 env,它也是一个指针数组,存放的是该进程的环境变量。
#include <stdio.h>
int main(int argc, char* argv[], char* env[])
{
int i = 0;
for(;env[i]; i++)
{
printf("env[%d]->%s\n", i, env[i]);
}
return 0;
}
总结:我们平时写的 C/C++ 代码会有两张核心向量表,一张叫命令行参数表,另一张叫环境变量表。我们平时执行的指令,自己写的可执行程序都是 bash 的子进程,bash 本身在启动的时候,会从操作系统的配置文件中读取环境变量信息,子进程会继承父进程交给我的环境变量。这就是环境变量具有全局特性的本质。
📖九、环境变量可继承的验证
通过下面这条指令在 bash 的上下文数据中添加一个我们自己的环境变量,通过查看子进程中是否有该环境变量来验证环境变量可以被子进程继承。
export MY_VALUE=12345678
下面执行子进程 ./mycode
,可以发现它里面也有 MY_VALUE 这个环境变量,说明子进程 mycode 继承了父进程 bash 的环境变量。
正是因为子进程可以继承父进程的环境变量,所以我们在 bash 输入的所有指令都要遵守权限,因为输入的所有指令都可以看做是 bash 的子进程,都继承了 bash 的环境变量。通过下面这条指令可以删除环境变量。
unset MY_VALUE
📖十、本地变量
本地变量就是在命令行中直接定义的变量
可以通过 set
命令查到系统当中的所有环境变量和本地变量。
其中本地变量是不会被子进程继承的,只会在本 bash 内有效。可以通过 export
指令将一个本地变量变成环境变量。
export MYVALUE
//MYVALUE本来是一个本地变量
//执行完这条指令后MYVALUE就会变成一个环境变量
在bash命令行输入的指令并不一定都要创建子进程
如上图所示,我们可以通过 echo 指令打印出本地变量,之前说过 echo 作为指令,本质上也是一个可执行程序,既然是可执行程序,那就会创建进程,但是我们又说了子进程是无法继承父进程的本地变量,那为什么 echo 可以打印出父进程 bash 的本地变量呢?原因是:并不是所有的指令都会创建子进程。指令可以分为以下两类:
常规命令:通过创建子进程完成的。
内建命令:bash 不创建子进程,而是由自己亲自执行,类似于 bash 调用了自己写的,或者系统提供的函数。
而 echo 就是一个内建命令,执行 echo 命令的时候并不会创建子进程,与此类似的还有 cd 命令,它也是一个内建命令,通过调用 chdir 系统接口可以改变当前进程的工作目录。
我们知道,linux底层是C语言写的,同时main函数的第二个参数是命令行参数
那么在 bash 命令行输入 cd 命令的时候,bash 并不会直接创建子进程,而是去判断命令行参数是否为 cd,如果是就直接去调用 chdir 系统接口切换工作目录。
📖十一、完结
创作不易,留下你的印记!为自己的努力点个赞吧!