文章目录
1. 一些进程概念的讲解
竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。
因为进程具有独立性,所以不会因为一个进程挂掉或者异常,而导致其它进程出现问题。如何做到进程具有独立性呢?我们需要到进程地址空间再详谈。
1.1 并行和并发的理解
并行: 多个进程在多个CPU下分别,同时进行运行。
这种存在多个CPU的情况,在任何一个时刻,都有可能有两个进程在同时被运行(在CPU上运行)。这种情况就被称为并行。
那么可能有的同学会问:我的电脑是单CPU的,但是我的电脑中有各种进程都在运行啊,这是为什么呢?
我们要知道:多个进程都在你的系统中运行不等于多个进程都在你的系统中同时运行。我们不要以为进程一旦占有CPU,就会一直执行到结束,才会释放CPU资源。这是不可能的。原因是:我们遇到的大部分操作系统都是分时的。
什么是分时呢?
就是操作系统会给每一个进程,在一次调度周期中,赋予一个时间片的概念。
假设我们让第一个进程被调度,我们给它设10ms,也就是说这第一个进程在CPU上运行的时间只有10ms,然后10ms后,不论这个进程执行到什么程度,这个进程都会停止。在后面的进程被调度时,我们也可以赋予它一个时间片。
这样在一个时间段内,多个进程都会通过切换交叉的方式,让多个进程代码,在一段时间内都得到推进。这种现象叫并发。
现在有这样一个问题:当某一个进程正在CPU上运行时,突然来了一个优先级更高的进程。此时会怎么样呢?
其实在操作系统中,有一个机制叫做抢占式内核。它的意思是:正在运行的低优先级进程,此时来个优先级更高的进程,调度器会直接把进程从CPU上剥离,放上优先级更高的进程。
1.2 Linux2.6内核进程如何调度队列
首先,我们要知道两点:
第一点:OS允许不同优先级进程的存在。
第二点:相同优先级的进程,是可以存在多个。
现在我们假设有5种优先级。
在Linux中,存在一个指针数组,里面指向的是PCB类型的队列。此时当有新的进程进来时,OS根据不同的优先级,将特定的进程放入不同的队列中。
那么现在CPU是如何去调度进程的呢?
第一步:从0下表开始遍历这个指针数组。
第二步:找到第一个非空队列,该队列必定为优先级最高的队列。在上图中,我们看到下标为1的那个队列是第一个非空的。所以我们会从这个队列里拿进程去CPU调度。
其实在操作系统中存在两个一样的结构。
一个叫做活动队列,一个叫做过期队列。
过期队列和活动队列结构一模一样。过期队列上放置的进程,都是时间片耗尽的进程。当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算。
而这两个结构是由两个指针所管理:active指针和expired指针。
active指针永远指向活动队列。expired指针永远指向过期队列。可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的。没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程。
1.3 进程间是如何切换的
在上面我们说正在CPU运行的进程,如果遇到优先级更高的进程,操作系统会把低的进程剥离下来,调度高的进程。这是如何做到的呢?
像我们写的代码i=i+1,其实这个i会放到CPU里的寄存器里加1。
CPU内的寄存器:可以临时的存储数据,但存储的数据非常的少。
所以,当进程被执行的过程中,一定会存在大量的临时数据,暂存在CPU内的寄存器中。
现在就有一个问题:此时一个进程正在CPU上运行,突然来了一个优先级更高的进程,那么此时CPU里的寄存器里的数据该怎么办呢?
就比如说现在这个i=i+1,这个进程刚准备要+1,突然来了一个优先级更高的进程要运行。如果我们直接去CPU上运行,那么原来进程在寄存器里的数据就会被覆盖,那么这个进程的数据就会丢失。
那么操作系统是如何解决的呢?
我们把进程在运行中产生的各种寄存器数据,叫做进程的硬件上下文数据。所以,当进程被剥离时,需要保存上下文数据。当进程恢复的时候,需要将曾经保存的上下文数据恢复到寄存器中。
那么上下文数据在哪里保存呢?
答案就是在进程的PCB里保存。
2. 环境变量
2.1 基本概念
环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数。
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
2.2 添加环境变量
我们知道像系统这些自带的命令,它也是一种可执行程序。
而这个是我们自己写的可执行程序。但是我们却要加上./才能运行。
现在就有一个问题:为什么我们的自己写的代码运行要带路径,而系统的指令不用带路径。
原因是:执行一个可执行程序,前提是要找到它。
为什么系统的命令能找到,而我们自己的程序找不到呢?
原因是:系统中是存在相关的环境变量,保存了程序的搜索路径。
既然是环境变量就有变量名,而系统中搜索可执行程序的环境变量叫做PATH。
我们可以使用命令:env: 显示所有环境变量。然后我们可以过滤一下:
我们想要查看变量的内容,我们可以使用命令:echo $NAME //NAME:你的变量名称
以冒号来做分隔符。当我们在使用系统的命令时,它会在这些路径下一个一个去搜索,找到并执行。
如果我们想把自己的软件不带路径直接运行,我们就需要将当前路径添加到环境变量。那么如何添加呢?
首先,我们要知道不仅仅C/C++可以定义变量,在命令行里也可以定义变量。命令行变量分两种:1.普通变量 2.环境变量(全局)
这是普通变量,我们也可以直接打印普通变量的值。
但是环境变量里它找不到。我们可以用这个命令: set: 显示本地定义的普通变量和环境变量
如果我们想定义环境变量,我们可以用这个命令: export: 设置一个新的环境变量
如果我们想取消用命令:unset:取消环境变量
那么我们也可以自己添加环境变量了,我们直接添加看一下:
但此时你会发现一个问题:我们自己的命令用不了了。
为什么呢?原因是:环境变量的路径只剩下我们自己的了。
解决办法就是重新打开云服务器。因为我们这只是临时的,如果要永久需要在配置文件里设置。那么我们想添加,而不是覆盖呢?我们需要这样写:
我们看到这就可以添加进来了。这样就不需要加路径了。
2.3 环境变量C/C++的获取方式
首先,我们要说一个问题:main函数可以带参数吗?
C语言中main函数的参数有两个,这两个参数写为int argc和char * argv[]。char * argv[]是一个指向字符串的指针数组,int argc的意思是指针数组的个数。
那么这个指针数组里存放的到底是什么呢?
我们打印看一下:
我们来运行:
我们给main函数传递的argc,char*argv[],命令行参数传递的是:命令行中输入的程序名和选项。
也就是这个样子:
最后一个为NULL。那么这样有什么意义呢?
我写了这样的一个代码,我们来看一下如何使用这个命令行计算器。
从上面的使用命令行解释器来看,它的意义就是:同一个程序,根据不同的选项,让不同的命令有不同的执行逻辑,执行结果。
其实main函数可以带三个参数,第三个参数就是 char *env[]。这个参数和第二个参数类似,是一个指向字符串的指针数组。
第一种通过代码获取环境变量:命令行第三个参数
因为数组最后一个是NULL,所以我们可以写成env[i]的形式。然后我们运行一下这个代码:
从这里看出我们从C语言的代码来打印出了环境变量的内容。也得到了一个结论:一个进程是会被传入环境变量参数的。
第二种通过代码获取环境变量:通过environ获取
environ是什么,我们来看一下:
这是C语言给我们提供的全局变量。environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。
我们运行一下:
可以看到也得到了环境变量的内容。
第三种通过代码获取环境变量:getenv获取
getenv函数可以访问特定的环境变量。
通过指定的环境变量名来找到内容。
我们来运行一下:
其实还有一种putenv函数来访问特定的环境变量,我们后面再说。
3. 环境变量具有全局属性理解
环境变量通常是具有全局属性的。这句话怎么理解呢?
我们先看这个代码:
它的运行结果如下:
从上图可知:像命令行启动的程序和一些命令,父进程都是bash。
然后我们再把这个myproc.c文件进行改动:
我们打印一个名叫hello的环境变量内容。这是我们自己写的环境变量名,现在还没有,我们设置一下。
我们先把hello设置为本地变量。
然后我们在把hello改成环境变量。
从这两个运行结果我们能得到什么结论呢?
结论:环境变量是会被子进程继承下去的。而本地变量,本质是在bash内部定义的变量,不会被子进程继承下去。
那么可能有的人又会有一个问题:
这里的set也是一条命令,也是子进程。不是说本地变量不会被继承下去的吗?为什么能找到这里的本地变量呢?
原因是:Linux下大部分命令都是通过子进程的方式执行的。但是,还有一部分命令,不通过子进程的方式执行,而是由bash自己执行(调用自己的对应函数来完成特定的功能),我们把这种命令叫做内建命令。