【Linux】探索Linux进程优先级 | 环境变量 |本地变量 | 内建命令

2023-12-13 18:57:06

最近,我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念,而且内容风趣幽默。我觉得它对大家可能会有所帮助,所以我在此分享。点击这里跳转到网站

在这里插入图片描述
在这里插入图片描述

🎉博客主页:小智_x0___0x_

🎉欢迎关注:👍点赞🙌收藏??留言

🎉系列专栏:Linux入门到精通

🎉代码仓库:小智的代码仓库


一、进程优先级

1.1优先级VS权限

优先级(对资源的访问,谁先访问,谁后访问)和权限看起来很像,但实际上它们有一些区别。权限决定的是能否进行某种操作,比如读取、写入或执行某个文件。优先级则是在你已经具备权限的情况下,决定谁先谁后地访问资源。比如在学校食堂排队时,你的优先级决定了你是先吃饭还是后吃饭。如果资源无法提供,或者你没有权限访问,那就是你没有这个权限。而优先级是用来决定当前的某个进程先享受还是后享受某种资源。

1.2为什么要有进程优先级?

在计算机系统中,进城有很多个,而CPU资源是有限的。而进程之间是要互相竞争对应的资源的。操作系统必须保证大家良性竞争,就必须确认优先级,优先级的存在就是为了更好地利用资源,解决不良竞争问题。
如果大家都不排队,都去抢资源,那么就会导致计算机的使用率不高,任务得不到合理分配,排队的本质就是确认优先级,通过设定优先级,让不同的进程能够按照一定的顺序获取资源,避免混乱和无序竞争。如果优先级设计不合理或者调度算法不科学,就可能导致一些进程长时间得不到CPU的资源,该进程的代码长时间得无法推进,就会造成该进程的饥饿问题。

1.3具体Linux中的优先级

1.3.1查看进程优先级

我们先来写一段代码:

#include <iostream>
#include <unistd.h>
using namespace std;

int main()
{
    while(1)
    {
        cout<<"i am a process"<<endl;
        sleep(1);
    }

    return 0;
}

跑起来之后我们可以使用这段指令来查询进程的状态:

ps -al | head -1 && ps -al | grep myproc

在这里插入图片描述

  • PRI :进程可被执行的优先级,其值越小越早被执行
  • NI :进程的nice值(进程优先级的修正数据)

1.3.2 PRI and NI

  • PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
  • NI就是进程的nice值,表示进程可被执行的优先级的修正数值
  • PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
  • 当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
  • 所以,调整进程优先级,在Linux下,就是调整进程nice值
  • nice其取值范围是[-20,19],一共40个级别

1.3.3 PRI VS NI

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

1.3.4 修改进程优先级

我们普通用户是无法给进程提高优先级的,只能减小进程优先级,所以要提升进程优先级必须使用root用户
修改方法:

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

【注意】: 我们每次修改nice值的时候PRI都是从80开始±的。

1.3.5优先级队列实现原理

在这里插入图片描述
我们所对应的优先级[60,99]在运行时会转换成[100,139]。
进程在进程优先级队列当中是从上往下,从左往右进行调度的,当runing中的进程被调度完之后,通过指针交换来继续调度waiting中的进程,以此循环,就构成了我们所看到的优先级队列。

1.4拓展小知识

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

1.4.1并发

我们的CPU在一定的时间内只能运行一个进程(多个CPU除外),因为我们操作系统中的进程不止一个,那么这些进程都想要被CPU调度运行,那就不得不进行进程切换,每个进程在CPU上运行一段时间再切换到下一个进程运行,我们把进程在CPU上运行的时间称为进程的时间片,进程切换是基于时间片轮转的调度算法。因为CPU速度很快,所以我们日常使用上感觉不到进程是被不断切换的。

1.4.2几个小问题

1.4.2.1为什么函数的返回值会被外部拿到呢?

这是因为我们CPU上有很多的寄存器,我们平时函数返回的时候会将返回数据,写入到寄存器中,外部再通过读取寄存器中的值来获取函数返回值。

return a -> mov eax a //返回值a会转化成mov指令,将变量a中的值保存在eax寄存器中
1.4.2.2系统怎么知道我们的进程当前执行到哪行代码了呢

在我们计算机的CPU中有一个程序计数器PC指针,或者eip,这两个都是用来记录当前进程正在执行的下一行指令的地址。
程序计数器通常被存储在寄存器中,这样可以让CPU在读取指令时更快地访问。由于CPU是按照顺序执行指令的,因此程序计数器的主要功能就是告诉CPU下一个要执行的指令在哪里。

1.4.2.3在CPU中寄存器扮演什么角色呢?

在我们CPU中有很多的寄存器例如,

  • 通用寄存器:eax,ebx,ecx,edx等等。通用寄存器从字面意思来理解就是没有什么用的寄存器,只要你需要,我就可以来供你使用,保存一些数据了什么的。
  • 栈帧寄存器:ebp,esp,eip等等。用来维护函数栈帧结构的寄存器。
  • 状态寄存器status。是一个包含有关处理器状态信息的寄存器。它通常包含一些标志位,例如:溢出标志,进位标志,奇偶标志等等。

CPU中有大量的寄存器主要是为了提高效率,进程的高频数据会被放入寄存器中,寄存器中保存了进程的相关数据,以方便进行对数据的访问或者修改。

cpu寄存器中保存的进程的临时数据,也包含进程的上下文数据,进程上下文包括执行该进程有关的各种寄存器(例如通用寄存器、程序计数器PC、程序状态字寄存器PS等)的值。

1.4.2.4为什么要保存进程的上下文数据呢?

所有的保存都是为了恢复,如果我们不保存进程的上下文数据,那么进程再被切换出去之后,再被切换回来的时候,CPU就不知道该从哪里开始运行,所以我们进程在被切换的时候会将自己的上下文保存好甚至带走,等到再次被调度的时候,首先做的第一件事就是恢复上下文,随后再接着执行代码。

1.4.2.5进程数据上下文保存在哪里呢?

一般情况下我们把进程的上下文是以结构体形式保存在进程的PCB数据结构(task_struct)里的。

struct reg_info
{
	int eax;
	int ebc;
	int eip;
	.......
}

二、环境变量

2.1基本概念

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

在Windows中我们可以通过设置->系统设置->高级系统设置来查看我们Windows中定义的环境变量。
在这里插入图片描述

2.2环境变量PATH

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

在我们平时想要运行编译好的可执行程序就必须带上./来表示当前目录下的可执行程序,那么我们平时用的pwd,ls指令为什么不用输入路径呢,这是因为系统中存在环境变量PATHPATH里面保存了我们指令的搜索路径,并且这个环境变量是我们打卡开一个终端以生具来的,默认就有的,那么可以使用下面这段指令来查看系统的PATH环境变量:

echo $PATH

在这里插入图片描述
这里是我自己Linux上面配置的环境变量,里面的内容是通过:来分隔开每一个路径的。上面的每一条路径就是我们每执行一条指令,系统查找指令的路径,比如我们想要执行ls指令,系统就会在PATH里的路径中查找ls指令,找到了就正确执行,没有找到就会给我们报错bash:xxx:command not found,报了这个错误表示没有找到指令,证明了我们在执行我们自己的程序的时候如果没有带路径,系统也会给我们在环境变量中查找,只不过没有找到。

所以我们想要将我们当前所处的路径添加到环境变量里,是不是就可以不用带路径呢?
我们来试一试,使用下面指令添加环境变量:

PATH=$PATH:/home/xz/xz_-linux/csdn_2023_12_10
#/home/xz/xz_-linux/csdn_2023_12_10是我自己的当前目录
# 如果只用PATH=/home/xz/xz_-linux/csdn_2023_12_10的话就会直接覆盖这个整个PATH

在这里插入图片描述
添加成功之后我们再来不带./来执行一下我们自己的可执行程序>
在这里插入图片描述
我们可以看到程序没有./也是可以正确执行的。

实际上,我们刚刚修改的PATH环境变量是一种在内存中的环境变量,所以无需担心。如果不小心改错了,只需关闭Xshell并重新登录即可。这个PATH环境变量是在shell中保存的。然而,当shell尚未存在时,即在系统启动时,环境变量是从哪里来的呢?实际上,这些环境变量预先存储在我们系统的一些配置文件中。当系统启动时,它们会被加载到内存中。因此,现在你不必担心你的环境变量被错误修改,只需重新启动一下,我们的环境就会恢复。

2.3环境变量HOME

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

在我们平是登录xshell的时候普通用户就会直接进入自己的家目录/home/xxx而root用户会进入自己家目录/root ,那么系统是怎么知道我们的工作目录的呢?
主要原因就是因为当我们在登录时,shell就会直接识别到当前登陆账户是谁,然后给当前用户填充$HOME环境变量,当我们登录时,此时默认就直接cd到了$HOME目录下。这就是我们每次登录之后都会处于自己对应的家目录的原因。

2.4获取环境变量env&&getenv

bash中我们可以使用env命令来获取bash从系统中继承下来的所有环境变量。
在这里插入图片描述
也可以使用getenv来获取某一个环境变量。

#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
    cout<<"PATH="<<getenv("PATH")<<endl;
    return 0;  
}

在这里插入图片描述

2.4环境变量USER

USER环境变量用来标识当前登录的用户。在终端中输入命令echo $USER,可以查看当前登录用户的用户名。这个环境变量是bash/shell在启动时自动加载的,它记录了当前登录的用户。
我们再来写一段demo代码:

#include <iostream>
#include <unistd.h>
using namespace std;

int main()
{
    cout<<"USER="<<getenv("USER")<<endl;
    return 0;  
}

我们在普通用户下执行这个程序会打印我们的用户名,用root账户执行则会打印root:
在这里插入图片描述
在这里插入图片描述
了解了上面内容,我们就可以模拟一下系统权限对普通用户和root用户的判定方式:

#include <iostream>
#include <unistd.h>
#include <cstring>
using namespace std;

int main()
{
    char who[32];
    strcpy(who,getenv("USER"));
    if(strcmp(who,"root")==0)
        cout<<"root用户不受条件约束"<<endl;
    else 
        cout<<"普通用户受权限约束"<<endl;
    cout<<"USER:"<<getenv("USER")<<endl;

    
    return 0;  
}

在这里插入图片描述
因为有环境变量的存在,我们的系统就已经具备了能够认识你这个人是谁的能力,只要能认识你是谁,就可以和文件属性当中文件的拥有者所属组和文件所对的权限所对比,进而判定出你有没读写权限。

三、命令行参数

我们C/C++中的main函数其实是可以传参的,这两个参数我们称之为命令行参数:

int main(int argc,char *argv[])
{
	return 0;
}

其中argv是一个指针数组,里面保存了字符串的地址,argc决定了argv中的元素个数,我们来尝试打印一下argv中的内容:

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

在这里插入图片描述
这时候我们发现里面内容只有一个就是./myproc,我们再来给可执行程序后面加一些选项来运行一下看看:
在这里插入图片描述
我们的main函数也是可以被调用的,main函数时程序被执行调用的第一个函数,调用main函数之前,bash会将我们在命令行输入的./myproc -a -b -c -d以空格分隔开分割成argc个字符串。
并保存在argv中其中argv[argc]=NULL,最终再传递给main函数。

3.1命令行参数作用

#include <iostream>   
#include <cstring>  
using namespace std;  
int main(int argc,char *argv[])  
{  
	if(argc!=2)  
	{  
	   printf("Usage:%s -[a|b|c|d]\n",argv[0]);                                           
	   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("defalut功能\n");
	}
	return 0;
}             

在这里插入图片描述
命令行参数可以通过输入不同的选项,来控制程序执行不同的功能代码。
命令行参数为指令,工具,软件等提供命令行选项的支持!

3.3main函数的第三个参数

main函数除了上面那两个参数,还有一个叫做char *env[]的参数;

int main(int argc,char *argv[],char *env[])
{
	return 0;
}

我么也可以来打印一下env中的内容:

#include <iostream>
#include <unistd.h>
#include <cstring>
using namespace std;
int main(int argc,char *argv[],char* env[])
{
	for(int i = 0;env[i];i++)
    {
        printf("env[%d]->%s\n",i,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("env[%d]->%s\n",i,environ[i]);
	}
	return 0;
}

在这里插入图片描述

argv和env的结构一模一样,所以我们的C/C++代码一共会有两张核心向量表,一张叫做命令行参数表,一张叫做环境变量表。环境变量表会从父进程中继承下来。
在这里插入图片描述
我们所运行的进程,都是子进程, bash本身在启动的时候,会从操作系统的配置文件中读取环境变量信息,子进程会继承父进程交给我的环境变量!

3.4验证环境变量的继承

我们可以自己定义一个环境变量导入系统中的环境变量表当中,我们想要增加一个环境变量可以使用下面命令:

export MY_VALUE=666666666666

在这里插入图片描述
可以看到我们自己定义的环境变量已经被导入到环境变量表当中。
我们接下来再去之心我们刚刚的代码:
在这里插入图片描述
可以发现它里面也有 MY_VALUE 这个环境变量,说明子进程 myproc 继承了父进程 bash 的环境变量。
删除一个环境变量可以使用unset 环境变量命令。

unset MY_VALUE

四、本地变量&&内建命令

本地变量指的就是我们可以在命运行当中直接定义,比如a=1,b=2,c=3。
在这里插入图片描述
定义的本地变量是不会被写入环境变量表的,所以我们使用env命令也是不会显示我们刚刚定义的那些变量,我们可以使用set命令来查看当前系统所定义的所有环境变量和本地变量。
在这里插入图片描述
此时我们就可以查出来我们刚刚定义的本地变量。
本地变量是不会被子进程进程的,只会在本地bash中有效。
我们再来通过一段代码验证一下:

#include <iostream>
#include <unistd.h>
using namespace std;

int main()
{
    printf("MY_VALUE=%s\n",getenv("MY_VALUE"));  
    return 0;  
}

在这里插入图片描述
我们可以通过export将本地变量写入环境变量中

export MY_VALUE
将本地变量MY_VALUE导入到系统环境变量

在这里插入图片描述
此时就可以打印出来我们刚刚添加的环境变量了。

4.1常规命令&&内建命令

我们再来看这个图中,我们定义了本地变量MY_VALUE,我们的可执行程序(bash的子进程)都读不到这个变量,为什么echo可以读取到呢?
在这里插入图片描述
其实我们命令行上执行的指令不一定都要创建子进程,就好比王婆说媒一样,一些有任务特别难的或者风险的,王婆就会找别人去帮忙说媒。如果是一些很有把握的事情,那王婆还是愿意自己去做的。

由此可以推出我们指令是有区别的:

  • 常规命令:通过创建子进程完成的
  • 内建命令:bash不创建子进程,而是有自己亲自执行,类似于bash调用了自己写,或者系统提供的函数。

所以echo是一个内建命令,它是由bash自己执行的,与此同时我们的cd命令也是一个内建命令,我们可以通过调用chdir来改变当前工作目录。

#include <iostream>
#include <unistd.h>
#include <cstring>
using namespace std;
int main(int argc,char *argv[])
{
    sleep(30);
    printf("change begin\n");
    if(argc==2)
    {
        chdir(argv[1]);
    }
    printf("change end\n");
    sleep(30);
    return 0;
}

在这里插入图片描述
在命令行中输入 cd 命令的时候,bash 并不会创建子进程,而是去判断命令行参数是否为 cd,如果是就直接去调用 chdir 系统接口来切换bash的工作目录。

🍀小结🍀

今天我们学习了"【Linux】探索Linux进程优先级 | 环境变量 |本地变量 | 内建命令"相信大家看完有一定的收获。种一棵树的最好时间是十年前,其次是现在! 把握好当下,合理利用时间努力奋斗,相信大家一定会实现自己的目标!加油!创作不易,辛苦各位小伙伴们动动小手,三连一波💕💕~~~,本文中也有不足之处,欢迎各位随时私信点评指正!
在这里插入图片描述

文章来源:https://blog.csdn.net/xz2935117143/article/details/134908712
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。