•cpu资源分配的先后顺序,就是指进程的优先级(priority)。
•优先级高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
•还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能
优先级存在的目的是为了合理地给每一个进程分配CPU资源。
先简单写两个文件:
将代码执行起来,结果如下:
UID代表执行者的身份:
正如我们每个人都有自己的身份证号一样,每一个用户也都有自己的编号。
PRI和NI:
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值
PRI就代表进程的优先级值,或者称其为进程被CPU执行的先后顺序,PRI值越小,优先级就越高,就越早被CPU执行。
而NI表示进程优先级的修正值,PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice。
当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行所以,调整进程优先级,在Linux下,就是调整进程nice值。
nice其取值范围是-20至19,一共40个级别。
要改变进程优先级,直接改变PRI即可,为什么要费力气通过改变NI,进而改变PRI呢?
答:就理论上来说,直接改变PRI是可以的,但是为了体现一个进程优先级调整的幅度大小,就不得不引入NI。
怎么改变进程优先级?
随后执行top命令,进入top之后,输入进程对应的PID,再输入r,再输入要修改的NI值(-20~19)即可:
但改变优先级的方法只需了解即可,因为我们几乎不会用到这个东西。
注意,优先级的修改是不重叠的,也就是说上一次修改后的优先级值并不是下一次修改时的起始值,系统默认的优先级值是80,所以每一次修改的起始值都是80。
需要注意,NI的取值范围并不是没有用的,当我们手动修改的NI值小于-20或大于19时,系统会将修改的NI值认定为-20和19,并不会任由我们“胡来”,也就是说,一个进程的优先级值最小是60,最大时99。
操作系统为什么要限制我们修改NI的范围?
答:调度器的任务是让每一个进程都相对均衡地分配到CPU资源,如果NI值太大,那么优先级就不再是一个相对的概念,而是绝对的概念。这样的后果是很可能会导致一些进程长时间霸占CPU资源,导致其它进程产生“饥饿”问题。
•竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
•独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。
•并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行。
•并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。(这个过程在上一篇文章中已经介绍过了)
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
事实上,我们在操作系统上做出的每一个指令,本质上都是一个可执行文件。
当我们编译一个.c文件形成可执行程序时,都会用./来执行该程序(帮系统找到该程序所在路径),那么为什么我们做出的每一个指令不用加其路径呢?
答:因为环境变量的存在,操作系统会在一连串的路径中依次寻找指令,故而不用我们特意指出指令的具体路径。
如下:
不同的路径之间以分号分隔,操作系统依次在路径中寻找指令。
那如果我们执行编译好的C程序时不使用./,该怎么让操作系统找到它呢?
答:将可执行程序所在路径拷贝到环境变量后即可,如下:
这里对环境变量进行修改不会有太大影响,若需回复原环境变量,只要重启Xshell程序即可。
PATH : 指定命令的搜索路径
HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
SHELL : 当前Shell,它的值通常是/bin/bash。
PATH在前面已经介绍过了,而无论是Linux还是Windows,都存在环境变量PATH。Windows中PATH如下:
此电脑的属性中:
HOME:
先看一个现象:
不同用户登录操作系统时,默认的目录都是不一样的,这是因为它们对应的HOME是不一样的,如下:
- echo: 显示某个环境变量值
- export: 设置一个新的环境变量(将本地变量变为环境变量)
- env: 显示所有环境变量
- unset: 清除环境变量
- set: 显示本地定义的shell变量和环境变量
这里演示一下export:
首先解释一下什么是本地变量:
本地变量只在本次登录时存在,与下一次登录无关,如下:
而export却可以将本地变量变为环境变量:
同样的,要恢复原环境变量,重启Xshell即可(或用unset取消环境变量设置)。
先补充一个概念:命令行参数
众所周知,C语言中的main函数是可以携带参数的,最常见的是以下两个:
下面看一下代码执行结果(注意:该代码只能在C99环境下运行,所以要将Makefile中的代码中添加“-std=C99” )
其实,上面的C代码的作用是将输入的指令都当做命令行字符串,并且数组agrv的大小是可变的(动态增长的),这个数组以NULL结尾。而argc就决定了有几个有效的命令行字符串。
为什么要有命令行参数?有什么用?
答:为了用一个函数实现不同的子功能,命令行选项底层就是采用命令行参数。
举个例子:
除此之外,常见的命令行参数还有char* env[],它用于获取父进程的环境变量。本质上和argv是一样的,只是指向的对象不同,使用的方法也没有什么差别,这里就不进行演示了。
方法①:
通过命令行参数,也就是上面的代码加上char* env[]来实现,将环境变量直接打印出来即可。
方法②:
通过第三方变量environ获取
代码实现:
可是输出的这一堆东西看着就招人烦,所以我们可以使用getenv来让二进制数据转换成我们能看懂的环境变量,如下:
先看一小段代码和执行结果:
可以看到,每次执行命令行的PID都是不同的,但其PPID都是相同的。
下面来查看一下PPID是什么:
可以得到一个结论:命令行上执行的进程,其父进程都是bash
而我们前面说过,父进程的环境变量是可以继承给子进程的,也就是说bash的环境变量可以被每一个命令行上执行的进程继承,而这些子进程也可以有自己的子进程。以此类推,所有的进程都可以继承bash的环境变量,所以环境变量是具有全局属性的。
先看一张C/C++地址空间图:
(该图为博主搜出来的网图,因为懒得自己画了)
下面来用一段代码验证一下图中区域分布的正确性:
相信大家对这张图都不陌生,那么提一个问题:
这张图中的这些个区域是内存吗?
答:不是!!!
这里为了解释这个答案,先用一段代码观察一个现象:
首先,更改子进程中变量值并不会影响父进程中的变量值,是因为写时拷贝,虽然子进程会继承父进程,但是当父子进程中的任意一个数据发生改变时,就会复制一份数据给自己用,以免影响到另一个进程。
其次,两个进程的地址相同,是因为这里的地址空间是虚拟的!!!
第二个结论很容易得出,如果我们在程序中使用的是物理地址,那么同一块地址所代表的值是不可能不同的,所以,在C/C++程序中使用的都是虚拟地址!!!
由于虚拟地址也就是进程地址空间的存在,系统不用担心会暴露自己的物理内存。
事实上,每一个进程都会有一个进程地址空间,而这个进程地址空间实质就是一个用于描述进程使用空间的结构体,而这个结构体又包含于该进程的PCB中,它们和内存中的物理地址存在某种交互关系用于使进程正常执行。
其实描述今后才能使用空间的结构体是"mm_struct",在这个结构体中记录的是每一个变量在内存中使用空间的起始位置(内存空间编号),而系统就是根据这些数据为进程分配空间的。但是,每个进程都会认为自己在独占操作系统的所有空间,并且认为系统空间分配为4GB。
实际上,地址空间上进行区域划分时,对应的线性位置就是虚拟地址。
上文中说了mm_struct中存储的是进程使用物理内存的起始位置,那么这些数据是怎么转换成物理内存的呢?也就是虚拟地址空间到物理内存是怎么转化的呢?
答:是通过页表和MMU实现的。
页表顾名思义就是一张表,在这张表的左侧存放的是虚拟地址的数据,右侧存放的是物理地址的数据。它的作用是将虚拟地址转化为物理地址。因此,也可以说它是一张哈希表。而MMU是帮助查页表的工具(这里不详细解释)。
所以进程实际上是通过页表映射,将虚拟地址转化为物理地址,进而访问进程的代码和数据的。
为什么要这么做?
答:这么做相当于在进程和物理内存之间加了一个中间管理层,可以防止一些进程不合法的访问物理空间,更安全。页表不仅记录了OS为进程分配的物理内存,还记录了OS给进程的某一确定空间的使用权限,这就使得进程只能对这一空间进行OS允许的操作,因此就保证了安全性。
如果进程向OS申请了一块很大的空间,那么这块空间会在刚被申请完成时就被用吗?
答:不一定。
站在OS角度,如果一次分配给某一个进程一块很大的空间,但这块空间并没有被使用,而其他进程却急需使用空间,那么这块空间就相当于被闲置了。
那么OS是怎么判断空间有没有被使用呢?
答:进程使用空间时,一定会对这块空间进行读写操作。换言之,OS只需判断这块空间有没有被进行读写操作即可。
那么话又说回来,OS怎么处理内存被闲置的问题呢?
答:OS给进程分配空间的过程是完全透明的,当空间出现被闲置的情况时,OS就会把一部分空间先分配给其它进程使用,使用结束之后再还给该进程。但是,从表面看,就好像这块空间一直在等待这个进程使用一样。这就叫做基于缺页中断进行物理内存申请,这里不做更细的解释。
如果物理空间已经满了,进程还能申请到空间吗?
答:可以的。OS会将某一进程中闲置的空间先置换到磁盘中,再给另一个进程使用,使用完毕之后,再回复原空间。
CPU怎么知道进程的代码起始行在哪里呢?
答:为了防止CPU每次读取进程数据都要进行全盘的搜索查找代码入口。在页表的左端有一个固定不变的位置标志着代码起始行,CPU每次都从这个固定的入口读取代码。然后在页表的右侧将左侧的入口映射到物理内存中代码的实际位置即可。
同样的,不只是代码的位置,进程的一切数据都采用这样的方式映射,以便大大减少内存管理的负担。
总结一下,为什么要有进程地址空间???
①通过添加一层软件层,完成有效的对进程操作内存进行风险管理(权限管理),本质目的是为例保护物理内存以及各个进程的数据安全。
②将内存申请和内存使用的概念在时间上划分清楚,通过虚拟地址空间,来屏蔽底层申请内存的过程,达到进程读写内存和OS进行内存管理操作,进行软件层面的分离。
③站在CPU和应用层的角度,进程可以看做统一使用4GB空间,而且每个空间区域的相对位置是比较确定的。
OS最终这样设计的目的是为了达成一个目标:每个进程都认为自己是独占系统资源的。
再回到上文中提到的子进程修改值之后,父子进程的值是不一样的,但地址却是一样的问题中去。
当父子进程都没有修改数据时,两者使用的进行读操作的值确实是同一个(同一块物理内存)。
但当自己成改变自己的值时,OS会在这一瞬间进行写时拷贝,使父子进程进行读写操作的数据不再是同一块空间,就出现了打印的值的不同的现象。
而两者的地址是一样的,是因为这个地址只是一个虚拟地址,是页表中未经过映射到物理内存的虚拟地址!
如图:
所以,这也回答了C/C++代码中一个现象的原因:
当两个字符指针指向的字符串内容是一样的时候,这两个指针指向的其实就是同一块空间(在页表中进行映射时,映射到了同一块物理空间上),这么做是因为 OS只维护一块空间是成本最低的。
这里再补充一下,存在栈区上面的空间里存放的是命令行参数,感兴趣的朋友可以打印一下命令行参数的位置验证一下,这里就不示范了。