【Linux进程篇】掌握进程优先级与环境变量:提升系统效能的秘诀

W...Y的主页 😊

代码仓库分享💕 


前言:我们已经学习了如何创建子进程,进程状态,知道了什么叫做R状态、S状态、T状态等待,并且了解了僵尸进程与孤儿进程,今天我们继续来学习进程中的优先级与环境变量,话不多说让我们直接进入主题。

目录

进程优先级

基本概念

查看系统进程

PRI and NI

进程饥饿问题

进程的调度与切换

内核进程调度队列

环境变量

基本概念

命令行参数

常见环境变量

和环境变量相关的命令

环境变量的组织方式

通过代码如何获取环境变量

通过系统调用获取或设置环境变量

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

总结


进程优先级

基本概念

1.cpu资源分配的先后顺序,就是指进程的优先权(priority)。
2.优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
3.还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。

优先级与权限的关系一定是已经有享受资源的权限了,操作系统在根据对CPU资源的使用急切程度确定优先级。

查看系统进程

在linux或者unix系统中,用ps –l命令则会类似输出以下几个内容:

此命令会显示当前用户下所有进程的内容。

我们很容易注意到其中的几个重要信息,有下:

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

PRI and NI

PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高。Linux的默认优先级为80。Linux的优先级是可以被修改的,范围在[60,99]。

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

PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
所以,调整进程优先级,在Linux下,就是调整进程nice值
nice其取值范围是-20至19,一共40个级别

 那我们如何修改优先级(也就是ni值呢?):

我们先输入指令top进入任务管理器,在点击键盘上的r键,我们就可以看到让我们指定一个PID:

那我们目前的程序PID为5035,输入后我们就可以改变优先级了: 

如果我们想让优先级增大10,就输入10,反之减小10就输入-10,我们实际修改的是ni的内容,而不是对PRI直接做修改!!! 

我们可以看到我们将优先级改到了99,ni值为19。如果我们输入过大的值或过小的值(即超出NI范围的值),那么Linux会将优先级默认改到最大或最小。

注意:pri(old)的优先级再每一次设置时都是80,而不是前一次的优先级!!!

进程饥饿问题

在Linux中为什么调整优先级是受限制的呢?Linux为什么不能可以将[60,99]调整为[-∞,+∞]。

那是因为如果不加限制,如果恶意将自己的优先级调整的非常高,而给其余人的优先级调整的非常低(优先级较高的进程先享受CPU的资源),那些系统开启自启动的进程也就是正常系统进程很难再享受到CPU的资源,会变得卡顿。这样的情况叫做进程饥饿

任何的分时操作系统在调度上都要保证较为公平的调度。

进程的调度与切换

竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

我们都知道在程序都被task_struct进行管理的,当进程需要运行时,就需要进行排队。我们也知道CPU中有个时间片的东西来控制各个进程一次在CPU中最长占用时间。当某些程序过大导致在规定的时间片内执行不完时,我们就需要切换到下一个队列中的程序。那之前的程序应该怎么办?

CPU中存在大量的寄存器,我们在VS中调用堆栈可以看到,有一些:eax/ebx/ecx/edx,eds/ecs/fg/gs,eip,cr0-cr4等待。这些寄存器可以帮助我们进行对这些代码数据进行记录保存,例如eip(也叫pc指针),这个寄存器可以记录我们的代码执行到了哪一部分。所以说进程在运行过程中要产生大量临时数据放在CPU的寄存器中!!!这些临时数据被我们保存在各个进程的PCB中。

CPU内部的所有临时数据我们叫做进程硬件的上下文。保存我们的进程上下文叫做保护上下文。在首次调度时,我们只需要将代码的起始地址放到eip中,然后逐步进行,进行时生成的临时数据被我们放到寄存器中。而二次调度时,我们只需要将上下文数据恢复到寄存器中即可。

上图是早期进程切换源码 。

总结:所有的保存都是为了最终的恢复,所有的恢复都是为了在上一次保存的位置继续执行!!!

CPU内的寄存器只有一套,而寄存器内部保存的数据可以有多套。所以虽然寄存器数据放在了一个共享的CPU中,但是所有的数据其实都是被私有的!!!

内核进程调度队列

 

上图是Linux2.6内核中进程队列的数据结构,之间关系也已经给大家画出来。在这里我们只看红框和蓝框对应的部分。

首先我们来看queue[140],真正的类型:task_struct* queue[40]。他其实是一个指针数组,里面存放的是task_struct指针,那为什么是140个呢?前0~99我们不用,因为0~99中我们存放的时实时操作,剩下的100~130一共40个刚好对应的是我们上文所提到的优先级的范围差值,正好在每一个数组中可以存放相同优先级的task_struct。这就好比一个C++中的哈希桶结构。

当我们执行进程时,我们就从优先级最高的开始依次往下执行。但是有些队列是为空的,我们需要依次进行扫描判断吗?这就要出现第二个数据int bitmap[5]。 一个int4个字节,32bit,那么这个数组就是32*5 = 160比特位。所以比特位的位置表示哪一个队列,比特位的内容表示这个队列是否为空!就是所谓的位图算法。

我们可以注意到蓝框与红框的内容是一样的,为了避免进程的饥饿问题,Linux操作系统就想出了以对策:

 我们可以看到上图中有一个array结构体数组,他里面存放了蓝框与红框的内容,蓝框中的queue被称为活跃队列,红框中被称为过期队列。当活跃队列中的进程开始被cpu进行调度时,后来的进程就不能在放入到活跃队列中去,而是放到过期队列中。直到活跃队列中的进程全部执行完毕后,再将活跃队列与过期队列进行交换,交换时只需要改变指针变量的内容即可。

环境变量

基本概念

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

我们再Windows中是否也见过环境变量,当我们需要进行配置Java环境或python环境时就会需要在电脑中安装环境变量:

我们先从命令行参数在到环境变量。

命令行参数

在我们学习C语言时,一定会写main函数,这是我们经常写的几个形式:

int main ()

int main(void)

int main(int argc, char* argv[ ])
而第三个main函数中的参数我们将他叫做命令行参数。

命令行参数有什么用呢? 

argc是我们命令行参数的个数,而argv是一个指针数组,他打印的就是我们在命令行输入的内容,下面代码我们可以测试一下:

#include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 
  5 int main(int argc, char* argv[])
  6 {
  7   for(int i = 0; i < argc; i++)
  8   {
  9     printf("argv[%d] == %s\n", i, argv[i]);                                                                                                          
 10   }
 11   return 0;
 12 }

我们在命令行输入几个数后,打印出来就有几个。

 

那命令行参数有什么用呢?可以通过不同的选项,让我们在同一个程序中执行他内部不同的功能。看demo:

#include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<string.h>
  5 int main(int argc, char* argv[])
  6 {
  7   if(argc != 4)
  8   {
  9     printf("Usage: \n\t%s -[add|sub|mul|div] x y \n\n", argv[0]);
 10   }
 11   int x = atoi(argv[2]);
 12   int y = atoi(argv[3]);
 13 
 14   if(strcmp("-add", argv[1]) == 0)
 15   {
 16     printf("%d + %d = %d\n", x, y, x + y);
 17   }
 18   else if(strcmp("-sub", argv[1]) == 0)
 19   {
 20     printf("%d - %d = %d\n", x, y, x - y);
 21   }
 22 
 23   else if(strcmp("-mul", argv[1]) == 0)                                                                                                              
 24   {
 25     printf("%d * %d = %d\n", x, y, x * y);
 26   }
 27   else
 28   {
 29     printf("%d / %d = %d\n", x, y, x / y);
 30   }
 31   return 0;
 32 }

 

命令行参数是Linux指令的基础,从上面的程序我们可以联想出我们在Linux中使用的指令后面跟的就是命令行参数:

我们使用的ls -al 、ll -al等等都是命令行参数。

我们使用的环境变量不是一两个而是一堆,并且彼此之间没有关系,一般是系统内置的具有特殊用途的变量。而定义变量的本质就是开辟空间,操作系统就是在运行中开辟空间的。

常见环境变量

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

查看环境变量指令: 

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

上图是我Linux用户下的PATH变量,这些路径都由:进行分隔。当我们输入我平时的ls、pwd等等指令时直接就可以使用,就是因为他们都是在/user/bin目录下,操作系统可以自动寻找。但是我们运行一个陌生路径下的可执行程序时就得声明为当前路径下,这就是因为我们在PATH环境变量中没有定义此路径。操作系统不能自动寻找。

我们可以将一个程序放到PATH有的路径下,或者将当前的绝对路径加入到PATH变量中去,我们就可以直接使用此指令。

和环境变量相关的命令

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

注意:环境变量就是key = value的格式,所以我们设置环境变量时应该去以这样的格式进行。

其实我们刚才说的命令行参数后面还有一个参数,完整版:

int main(int argc, char* argv[], char* env[]),这个env与argv差不多一样,都是指针数组,而这个里面存放的都是环境变量:

int main(int argc, char *argv[], char *env[])
   40 {
   41     printf("I am a process, pid: %d, ppid: %d\n", getpid(), getppid());
   42     for(int i = 0; env[i]; i++)
   43     {
   44         printf("-----------env[%d] -> %s\n", i, env[i]);
   45     }
}

这样就可以打印出所有的环境变量。

环境变量的组织方式

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

通过代码如何获取环境变量

命令行第三个参数 

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

通过第三方变量environ获取

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

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

通过系统调用获取或设置环境变量

putenv

getenv 

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

常用getenv和putenv函数来访问特定的环境变量。

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

环境变量通常具有全局属性,可以被子进程继承下去

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

 直接查看,发现没有结果,说明该环境变量根本不存在

导出环境变量
export MYENV="hello world"
再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!

总结

环境变量是一种存储计算机操作系统中配置信息的机制,它们在软件开发和系统管理中扮演着重要角色。

1. **配置灵活性**:环境变量允许开发者和系统管理员在不同的环境(如开发、测试和生产环境)之间切换配置,而无需修改代码或脚本。

2. **安全性**:通过环境变量存储敏感信息(如数据库密码、API密钥等),可以避免将这些信息硬编码在应用程序中,从而提高安全性。

3. **可移植性**:使用环境变量可以使得应用程序更加可移植,因为它们可以在不同系统或不同配置之间轻松地调整设置。

4. **依赖管理**:环境变量可以用来指定应用程序运行所需的依赖项,如库文件的路径。

5. **系统信息**:环境变量可以用来存储有关操作系统的信息,如PATH变量,它定义了操作系统搜索可执行文件的目录。

6. **用户偏好**:用户可以通过设置环境变量来定制他们的系统行为,如设置语言偏好或时区。

7. **简化配置**:对于复杂的系统配置,使用环境变量可以简化配置过程,使得配置更加集中和易于管理。

8. **版本控制**:环境变量不包含在版本控制系统中,因此它们不会与代码一起被提交,这有助于保护敏感信息。

9. **自动化**:在自动化脚本和持续集成/持续部署(CI/CD)流程中,环境变量可以被用来动态设置运行时参数。

10. **跨平台**:环境变量的概念在不同的操作系统中广泛支持,使得跨平台开发更加容易。

总之,环境变量是一种强大的工具,它提供了一种灵活、安全且有效的方式来管理应用程序和系统的配置。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

W…Y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值