1. 进程优先级
首先我们要知道,进程优先级是操作系统用来确定多个进程同时运行时,哪个进程会获得更多CPU时间片的相对重要性或优先级的评估。他和权限的区别在于权限决定了能不能访问资源,而优先级是在能访问资源的前提下,决定了资源访问谁先访问,谁后访问。
那么我们为什么需要优先级呢?在去食堂的时候,如果能给我们每一个人配备一个厨师,那么我们就不需要进行排队了,问题是没有那么多的厨师分配给我们,而进程也是同理,因为资源是有限的,而进程有多个,那么注定了进程之间的关系为竞争关系——这就是进程的竞争性。对于操作系统来说,操作系统必须保证良性竞争,为此要确认进程的优先级。如果进程长时间得不到CPU资源的话,那么该进程的代码就会长时间无法得到推进,这样的情况我们将其称为该进程的饥饿问题。
说了这么多,那么在Linux中的优先级是怎么办到的呢?
我们以以下代码为例
运行后查看有
在这里我们可以看到有PRI(优先级)和NI(nice值)两个值,PRI是priority(优先)的缩写,其值越小就越早执行,NI是进程优先级的修正属性,PRI(new) = PRI(old) + nice,我们可以调整nice值来调整进程的优先级,既然优先级可以被调整,那么我们可以大幅更改nice值,从而使该进程一直被调度吗?答案当然是不行的,对于Linux来说,它不想过多的让用户参与优先级,只允许在一定范围内进行优先级调整,Linux给出nice的取值范围为[-20, 19],通过计算我们可以得知PRI的取值范围为[60, 99],一共40个级别。
知道了优先级后,操作系统如何通过其进行的调度呢?
在运行队列中有两个指针数组,优先级为相同的情况下,运行队列采取开散列的方法将其链接起来,在运行队列正在运行时,如果有新的相同优先级的进程产生,那么他会被放入到运行指针数组镜像数组中,在处理完当前运行队列的所有进程后,此时swap(run, wait)这样就将下一批调度队列放入了运行队列中。那么如何判断当前运行队列为空呢?——位图,这样就可以使时间复杂度降低到O(1),这就是Linux内核的O(1)调度算法。
2. 进程概念
竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
对于并发来说,我们之前提到过他是由时间片和进程切换共同做到的,即其是基于进程切换与时间片轮转的调度算法。而我们以前学习语言时,曾经有一个问题就是一个函数的返回值是怎么被外部拿到的呢?
首先,我们知道在CPU中存在很多的寄存器,在函数返回时,如:return a(10); 此时他会被转换成move eax 10,即函数返回值会先由寄存器保存,这之后再通过寄存器返回该数据。此外,操作系统是如何得知我们的进程当前执行到了哪一行呢?——使用程序计数器如:pc, eip等,它们可以记录当前进程正在执行指令的下一行指令的地址。(注:for循环和if条件分支等操作的本质就是修改eip指针)。
那么寄存器有如此之多,那么它们在操作系统之中扮演什么角色呢?答案是由于寄存器贴近CPU,因此它们可以通过将进程中的高频数据存入寄存器中从而提高效率。因此,CPU寄存器中保存的是进程相关的临时数据,我们将这些临时数据称为进程的上下文。在此我们还有一个问题,就是一个进程是有时间片的,在时间片规定时间到达后,其会被进程切换而拿下来,那下次执行该进程的时候,如何得知上次代码执行到哪里了呢?——因此,在进程从CPU上离开时,要将自己的上下文数据保护好或者带走,带走的目的是为了下次运行时恢复从而做到无缝衔接。因此我们可以知道,进程在被切换时需要做两件事:1. 保存上下文;2. 恢复上下文。在这里我们可以认为上下文是可以储存到task_strcut中的。
3. 环境变量
①环境变量概念
环境变量是系统提供的一组name=value形式变量,不同的环境变量有不同的用户,且通常具有全局属性。举几个例子
在我们使用指令时不用加上./但是我们运行自己的程序时,却需要使用,其实这里存在一个环境变量PATH,它是Linux的指令搜索路径,我们可以使用echo $PATH查看
它是各个路径之间使用:连接的字符串,我们输入一个指令时有如下反馈
其本质是在PATH的路径下找指令,找不到就返回command not found。我们可以使用
PATH=$PATH:当前路径
来将当前路径添加到默认路径中,在添加后就可以不用./即可
此外,我们可以使用env指令来查看所有的环境变量
可以看到这里有很多的环境变量,这里拿几个举例
HOME:进入系统时默认使用cd+HOME路径
HISTSIZE:表示history指令中记录的最多条数
PWD:当前所处的工作路径
LOGNAME:登入用户
OLDPWD:上一个路径
除了使用指令外我们还可以使用代码的方式来获取——getenv(),查看手册有
我们以如下代码为例
因此,通过环境变量,系统可以判断当前用户是谁来给予对应权限。
②命令行参数与环境变量
其实main函数可以传入两个参数,我们以以下代码为例
运行可以发现
对于argv我们可以知道它是一个数组指针
他会将输入的命令行以空格为分隔符,分成若干个参数存入argv中。那么为什么需要这么做呢?——其目的是为指令、工具和软件等提供命令行选项的支持。以如下代码为例
此外,除了argc和argv两个参数外,还可以有一个env参数,即
我们打印有如下结果
至此,我们可以知道在调用main函数时,一共会传入两张核心向量表,一张是命令行参数表,另一张是环境变量表。我们所运行的进程都是子进程,bash在本身启动的时候,会从操作系统之中读取环境变量信息,而子进程会继承父进程的环境变量。那么我们如何验证呢?我们可以使用
export MY_VALUE=123456
来向bash中添加这个环境变量,再次运行mycode可以发现
而我们在bash中取消该环境变量
unset MY_VALUE
后,再次运行可以发现
此时MY_VAlUE又消失了。即子进程会继承父进程的环境变量。
③本地变量与内建命令
我们可以直接使用
a=1
b=2
c=3
来直接创建变量,这样创建出来的变量为本地变量,可以使用echo $a来查看变量值,即
也可以使用set来查看所有变量,即
对于本地变量,它只会在本bash内有效,而不会继承给子进程,若是想让它变为环境变量可以使用export,即
由于a是本地变量,对于echo $a来说,echo不应该是bash的子进程吗?既然是的话echo就不应该继承本地变量a,那么如何访问a的值呢?其实对于命令来说,一般分为两大类,一类是常规命令——即通过创建子进程完成的,另一类是内建命令——即bash不创建子进程,而是自己亲自执行,类似与bash调用自己写的或是系统提供的函数,如:echo, cd, end, export等,其实现大概是类似于下面的形式
if (argv[1] == "echo")
{
chdir(argv[1]);
}