🎬 个人主页:HABuo

📖 个人专栏:《C++系列》《Linux系列》《数据结构》《C语言系列》《Python系列》《YOLO系列》

⛰️ 如果再也不能见到你,祝你早安,午安,晚安


目录

📚一、进程切换

📚二、环境变量

📖2.1 一些基本环境变量

📖2.2 环境变量与本地变量

📖2.3 配置文件与环境变量的全局性

📖2.4 程序中获取环境变量的方式

📖2.5 main函数的三个参数

📚三、总结


前言

上面两篇博客我们连续的认识了进程状态的相关知识,以及进程优先级的概念,这篇博客我们进入新的知识的学习,包括:进程切换、环境变量,在环境变量章节中我们将认识为什么我们pwd、which、whoami等等一些指令的工作原理,我们的操作系统怎么知道这些指令并且做出相应的输出的,并且在这个章节我们将讲解main函数的参数,这似乎在我们学习C语言或者C++的时候从没有使用过的,今天就让我们来认识一下它们!

本章重点:
本篇文章着重讲解Linux是怎样进行进程切换的。紧接着讲解环境变量的基本概念和查看方式、包括三种代码级别方式获取环境变量,及main函数的参数和含义


📚一、进程切换

前面我们有一个场景并没有解释,就是微信在给女友1开语音、QQ在给女友2发消息、电脑在CF中穿越,这样的一个场景,我们电脑是如何实现的呢?因为CPU只有一个,我们也知道了CPU运行进程的时候只能运行一个进程,那么多个进程怎么在我们人的感觉上是同时运行的呢?那么我们今天的进程切换这个知识就是来解释这样的场景的!

我们先来看进程切换的概念:

进程切换:也称为上下文切换,是指操作系统将CPU从一个进程切换到另一个进程的过程。在这个过程中,操作系统需要保存当前进程的状态(上下文),以便以后可以恢复该进程,并从就绪队列中选择一个新的进程,将其之前保存的状态恢复,并开始执行。

很好理解,那么随之而来有两个问题:

  1. 没运行完的进程,其中的数据何去何从?
  2. 当一个进程被取下时,它在CPU的数据是否会被删除?

问题一:

很明显,当一个进程在没运行完的情况下被取下时,肯定会保存一个信息,那就是当前进程运行到哪儿了?和C语言的代码相似,当前代码运行到哪儿了系统是怎么知道的?请看下图:

所以其实在进程运行时,是会使用到这些寄存器的!
进程产生的各种数据会在寄存器中进行临时储存!(注意不是取下时在寄存器储存)
进程在切换时会不断对自己的数据进行保存(被取下时)和恢复(重新运行时) 
当进程进行保存时是保存寄存器中的数据 而不是寄存器本身
并且这些数据会被保存至当前进程的PCB当中!

问题二:

事实上,一个进程被取下来时CPU并不会删除它的临时数据而是当下一个进程被放入时,用下一个进程的数据将上一个进程的数据覆盖了!

如果还不理解,请看下面具体的进程切换的过程,相信你会更加透彻:

进程切换过程:进程切换通常发生在内核态,由操作系统内核中的调度器和上下文切换机制完成。

过程①:触发切换

  • 内部事件:进程执行系统调用(如I/O请求)或发生异常(如缺页异常)。

  • 外部事件:中断(如时钟中断、I/O中断)发生。

过程②:保存当前进程的上下文

  • 上下文是指进程在CPU中执行时的状态,包括:

    • 程序计数器(PC):下一条要执行的指令地址。

    • 寄存器状态:通用寄存器、栈指针、状态寄存器等。

    • 内存管理信息:页表基地址、段表基地址等(取决于内存管理方式)。

    • I/O状态:打开的文件描述符、未完成的I/O操作等。

  • 这些信息通常保存在进程控制块(PCB)中。

过程③:选择下一个要运行的进程

  • 调度器从就绪队列中根据调度算法(优先级、时间片轮转等)选择一个进程。

过程④:恢复新进程的上下文

  • 将新进程的PCB中保存的上下文加载到CPU中,包括程序计数器、寄存器等。

过程⑤:切换地址空间

  • 如果新进程和当前进程属于不同的地址空间,则需要切换地址空间,例如切换页表基寄存器。

过程⑥:开始执行新进程

  • 跳转到新进程的程序计数器指向的指令,开始执行新进程。

小结一下:进程切换就是进程在自己的时间片运行完被迫退出或者需要等待外设资源等主动退出,CPU将其在寄存器存储的临时数据(上下文)交给该进程的PCB(这个过程成为上下文保护),CPU读取下一个进程来运行,当上一个退出的进程再次准备就绪,CPU会重新读取该进程的PCB中存储的上次运行的临时数据(这个过程称为上下文恢复)进行运行,从而完成进程切换!

📚二、环境变量

可以看到,这些系统自带的一些指令,天生就能知道我当前的路径以及用户名等信息,它是怎么知道的,你是不是要说,自带的嘛当然知道,不知道还能说是自带的吗?你要是在面试时这样说面试官会来句,背后有门,请你出去散散心!所以这其中的原理正是我们这个环境变量这个章节所要讲述的!

如何查看这些所谓的环境变量呢?

在bash中查看所有环境变量:

使用指令: env

可以看见打印出密密麻麻的环境变量,这其中不需要我们全部记住,只需简单的认识几个常见的环境变量即可!

📖2.1 一些基本环境变量

  • 环境变量PATH

先来一个尝尝鲜:保存程序的默认搜索路径的环境变量: PATH

在运行程序时,系统会去PATH中找当前可执行程序在不在这些路径中如果在就直接执行程序,不在就报错

查看环境变量PATH:

使用指令: echo $PATH

这些路径以冒号:为分割,因为我们自己写的程序不在这些路径中,所以前面我们在运行的时候会加上./来运行程序!

修改环境变量PATH

如果我们也想搞一个不需要./来运行程序该怎么办?

我们可以将自己写的程序的路径加入到环境变量PATH中!

使用指令: PATH=$PATH:要添加的路径

此时我已经将我的路径添加到PATH了,现在我直接像系统指令一样运行我的指令

但是请注意,当你将你的路径添加后下次重启时又会恢复为默认路径所以想一劳永逸的话可以将你自己的可执行程序放入默认的路径中!

  • 环境变量PWD

记录当前路径,你以为平时执行的pwd指令是怎么知道当前路径的?稍加思考就能窥探,pwd实际上调用了PWD环境变量!

  • 环境变量HOME

我们平时使用的指令:cd ~进入家目录,哪儿是家目录?环境变量HOME里面的内容就是家目录!

所以现在我们清楚了为什么我们一登上xshell,系统就自动跑到root目录下,就自动的可以执行pwd、whoami等指令正是因为环境变量的作用!所以我们也更加清楚的认识到为什么操作系统是1号进程,为什么它是第一个加载的进程!

📖2.2 环境变量与本地变量

我们可以在bash中直接定义环境变量:

使用指令: 环境变量名=内容

但是用户自己定义的环境变量是本地的,而env并不能查看本地的环境变量
所以使用env查找刚才定义的环境变量时,实际上会找不到!

本地的是什么意思,其实有点C/C++中局部变量的意味!就是该变量只会在当前进程中有效!那环境变量是具有全局性喽,对!为什么要具有全局性?是为了实现不同的应用场景,像刚才的pwd、whoami等这些是随时变动的信息,因此只有全局性才能使这些指令起作用。

如果想要我们定义的环境变量,也放在系统的环境变量表中应该怎么做?

使用指令: export 环境变量名

也可以直接使用export定义环境变量:

怎么取消export声明的环境变量:

使用指令: unset 环境变量名

📖2.3 配置文件与环境变量的全局性

根据上面的一些信息我们可以推论:

在修改/定义环境变量时,无论定义的是本地变量还是export声明的环境变量,实际上修改/定义的都是bash进程内部的环境变量信息,每一次重新登录都会形成新的bash解释器,并且新形成的bash解释器会自动从家目录下的bash_profile文件中读取信息形成一份新的环境变量表!

所以为啥每次重新启动bash后我们自己定义/修改的环境变量就不见了?因为它并没有被保存在配置文件bash_profile中!

查看配置文件bash_profile,我的是.profile,问题不大:

当我们在配置文件中加上自己想要一直存在的环境变量时,此时bash再次启动后,这个环境变量就会被放到环境变量表中!注意不是环境变量不消失,而是这个全局配置文件在bash启动时被加载从而使得不用定义每次都存在

并且在创建子进程时这张表会通过main函数的参数传递给子进程,所以子进程有了和父进程一样的环境变量表,而子进程创建孙子进程时又会将子进程的表给孙子进程所以说,环境变量具有全局性!

在Linux环境下,环境变量的“全局”通常指的是在当前进程及其所有子进程中可见。

所以可以理解,在这个全局配置文件中的环境变量不光在该进程与子进程之间存在,而且跨进程也存在因为它自动加载,而export声明的环境变量也具有全局属性,因为它可以继承给子进程,而本地变量是不具有全局属性的。总结看下表:

变量类型 声明方式 当前Shell 子进程 全局配置 退出后
本地变量 a=123 ✅ 可见 ❌ 不可见 ❌ 不加载 随Shell结束消失
导出变量 export b=456 ✅ 可见 ✅ 可见(继承) ❌ 不加载 随Shell结束消失
全局配置变量 写在配置文件中 ✅ 可见 ✅ 可见 ✅ 自动加载 永久有效

📖2.4 程序中获取环境变量的方式

所使用的函数:

#include<stdio.h>
#include<stdlib.h>

int main()
{
        printf("当前环境变量PATH内容是:%s\n", getenv("PATH"));
        return 0;

}

所以,大家有没有触动,你在命令行中输入pwd,为什么它就能给你打印出当前路径,事实上pwd就是一个软件就是一个可执行,只不过这个可执行被放置在了系统环境变量PATH路径下,使得它能自己找到,而且文件里面有getenv这样的函数,他就天然的能给你打印出你当前所处的路径!

📖2.5 main函数的三个参数

我们在C/C++写main函数时好像从来没有写过它的参数,事实上main函数其实是有参数的 。

int main(int argc , char* argv[]);
这两个参数又被称为命令行参数
argv是一个数组,指向的元素类型是char*
也就是说argv是一个字符串数组
argc代表这个数组的元素个数!

现在我们并不知道这个数组中存放的是什么字符串,但是可以写个代码验证一下,既然知道数组名和元素个数,那么我们就可以通过打印的方式来查看

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

当我们运行可执行程序mytest时,它会打印0: ./mytest,当我们以空格为分割在./mytest后面继续输入字符串时,并且输入的是随机字符,它会以空格为分割,分别打印出数组中下标为0,1,2的字符串,这些输出的字符串正是我们输入的!所以这两个main函数参数是接收命令行参数的!

注:将命令行输入的字符串放入argv数组是OS干的!

main函数第三个参数char* env[]

第三个参数为env,是不是很熟悉?没错,第三个参数就是环境变量

它和第二个参数一样是指针数组,它指向环境变量表,环境变量表中的内容和在bash中使用env打印的一样!

示例:

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

到目前为止了解到,系统在启动程序时会给main函数提供两张表:

  • 命令行参数表
  • 环境变量表

事实上,这个环境变量表是在我们写了第三个参数才存在的吗?并不是无论写不写第三个参数char *env[],这张表是始终存在的,见下例子:

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

}

通过上述代码我们仍然可以打印出与bash中env指令一样的结果,所以这张环境变量表是始终存在的!

小结一下:

本章节我们知道了环境变量是什么,环境变量是用来指定操作系统运行环境的一些参数,它是在操作系统运行时就加载了的。

见了部分常用的环境变量,如:PATH、PWD、whoami、HOME等等

我们又知道了环境变量的全局性,以及和本地变量的差别

我们又通过以下三种代码级别的方式获取环境变量

  • getenv()(推荐使用)
  • char* env[]
  • char** environ

卖个小关子:

我们知道具有全局属性的环境变量才会继承给子进程,但是一个奇怪的现象发生了,请看下面例子:

echo是不是一个指令,也即是一个可执行,运行起来也即是一个进程,它怎么能打印一个本地变量e的值呢?不是说本地变量不能继承给子进程,这里怎么可以了?欲知故事如何,请看下回!


📚三、总结

本篇博客我们领略了少数的CPU是如何运行多个进程,这个知识是进程切换,紧接着我们又见识了环境变量,知道了环境变量的意义以及它的全局属性,最后我们又展示了三种代码方式获取环境变量。

总结以下:

进程切换:进程在自己的时间片运行完被迫退出或者需要等待外设资源等主动退出,CPU将其在寄存器存储的临时数据(上下文)交给该进程的PCB(这个过程成为上下文保护),CPU读取下一个进程来运行,当上一个退出的进程再次准备就绪,CPU会重新读取该进程的PCB中存储的上次运行的临时数据(这个过程称为上下文恢复)进行运行,从而完成进程切换!

环境变量:

定义:环境变量是用来指定操作系统运行环境的一些参数,它是在操作系统运行时就加载了的。

一些常见环境变量:PATH、PWD、whoami、HOME等

环境变量全局性与本地变量的差别,具体见下表

变量类型 声明方式 当前Shell 子进程 全局配置 退出后
本地变量 a=123 ✅ 可见 ❌ 不可见 ❌ 不加载 随Shell结束消失
导出变量 export b=456 ✅ 可见 ✅ 可见(继承) ❌ 不加载 随Shell结束消失
全局配置变量 写在配置文件中 ✅ 可见 ✅ 可见 ✅ 自动加载 永久有效

三种代码方式获取环境变量

  • getenv()(推荐使用)
  • char* env[]
  • char** environ

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐