进程优先级
由于系统内的进程数大于资源数,因此不同的进程之间会竞争同一种资源,而OS为了管理进程使用资源的顺序就产生了优先级。
如上图,是一个while死循环运行起来的进程信息:
- PRI:表示这个进程的优先级,数字越小,级别越高;
- NI:表示这个进程的nice值,其范围在-20~19,表示进程可别执行的优先级修正数值;
- PRI_new = PRI_old + NI;NI值为负数时,此时进程的优先级就会提升;
- nice值不会决定进程的优先级,但会影响进程的优先级。
接下来演示如何修改优先级:
如上图,需要注意的是修改优先级需要root用户的权限:
如上图,是修改完成后的权限数值,但是不难发现,NI的值不是我要修改的数值100;
- 修改前:PRI = 80,NI = 0;
- 修改后:PRI = 99,NI = 19;
正如开头所述:nice的值在-20~19之间,当我们renice值为100时,nice的值就会自动变成19,而不是100,而对应的PRI_new的值就是80+19 = 99,这就是原因所在。
还需要注意一点:
每一次优先级的修改,PRI_old的值都会被默认设置成80;
- 竞争性:进程数量大于CPU资源数量,导致进程之间的竞争,为了调节CPU资源使用的先后顺序便有了进程的优先级;
- 独立性:多个进程之间是独立运行的互不干扰但又共享资源;如果说QQ音乐运行期间闪退,但是不会影响到QQ的运行;
- 并行:多进程在多核CPU下分别运行;
- 并发:多进程在单核CPU下用进程切换的方式,在不同的时间片内让不同的进程运行。
如上图,在并行模式下,在任何时刻都可能有多个进程在不同的CPU上运行;而在并发模式下,OS会给每一个进程分配一个时间片,在时间片内该进程会上CPU运行,当时间片结束后该进程会下CPU并返回到运行队列中等待下一次被调度,因此在一段时间内,并发模式下多个进程会交叉的上CPU上运行。
如果此时有一个优先级更高的进程进入到OS中,此时正在运行的低优先级的进程就会被调度器从CPU上剥离下来,让这个高优先级的进程运行,这就是抢占式内核。
OS是如何归类进程的优先级???
进程是如何切换的???
当进程在被执行的过程中,改进成的大量数据会被临时寄存在CPU内的寄存器中。我们把这种进程在运行过程中产生的各种寄存器数据叫做进程的上下文数据。
如果此时进程被剥离,就需要保存该进程对应的上下文数据保存在task_struct中;而当进程恢复运行的时候就需要将之前保存的上下文数据恢复到CPU的寄存器中。
环境变量
环境变量是指在OS中用来指定OS运行环境的一些参数。
自己编写的程序为何无法通过“ 指令+回车 ”执行???
在我们编写完成代码生成可执行文件的时候,例如源文件test1.c编译生成mytest1可执行文件,此时如果想要运行这个文件需要“.mytest1”才可执行,而对于系统的指令(如ls)直接输入回车即可执行。这是什么原因呢???
如上图,使用env指令查看所有的环境变量,在上图中,USER表示当前用户名,PWD表示当前所在路径,LOGNAME表示登录用户。
如上图,当我像使用系统指令的方式使用生成的可执行文件时显示无法找到该命令,这是因为我们自己编写代码生成的可执行文件必须要加上对应的路径。
使用指令:echo $PATH:查看环境变量对应的路径,环境变量对应多个路径,不同的路径使用:分隔开,而当我们输入系统指令(如ls)时,OS就会从这些路径中找该指令,而这个PATH就提供了相对的搜索路径,因此我自己编写的可执行文件无法像系统指令那样使用就是因为该可执行文件无法在PATH提供的路径中找到。
如何将自己编写的程序添加到PATH中呢???
将程序添加到PATH中:
如上图,使用sudo cp 目标程序 /usr/bin/ 指令将可执行文件添加到PATH中。使用sudo rm 目标程序 /user/bin/目标程序 即可删除。
将程序对应的路径添加到PATH中:
如上图,将可执行文件对应的路径给PATH,注意此时其他的系统指令已无法使用,因为此时的PATH的整体已被修改,无法在当前PATH提供的路径下找到其他指令,此时退出重进即可解决。
如上图,退出重新进入即可解决。
正确的做法是:
使用 export PATH=$PATH:对应的路径 即可将对应的路径添加到PATH中,注意刚才的方式是修改,这里是添加。
什么是export???
如上图,当我们直接定义一个变量num1赋值999,此时这个变量叫做本地变量,该类型变量不会以环境变量的形式存在(env | grep num1无法找到)。使用export定义一个环境变量,环境变量具有全局属性,使用env | grep num2可以找到对应值,这里的export的作用就是导出环境变量值。
env只能查看环境变量,set既能查看环境变量也可查看本地变量。
main函数带参问题
如上图,在main函数中存在两个参数:argc和argv[]数组,其中argv是指针数组,当我们运行对应代码时可以看见指针数组存放的是程序名。
如上图,在给main函数传递的参数argc和argv[]就相当于命令行参数,因此在一个程序中,通过传递不同的参数来执行不同的逻辑,产生不同的结果。
如上图,在系统指令ls中后接不同的指令产生不同的结果。在linuxOS中会根据不同的选项产生不同的结果。
如上图,使用main函数编写计算器,在main函数传参时,第一个数组指针表示程序名,第二个数组指针表示对应的操作,第三、四个数组指针表示要操作的数据参数。
如上图,操作结果,
查看环境变量
- 命令行三个参数:
- 第三方变量environ:需要使用extern声明;
- 系统调用getenv:getenv("环境变量");
如上图,main函数的三个参数;
如上图,使用第三方变量environ获取;
如上图,使用 getenv("环境变量") 来获取特定的环境变量。
环境变量继承问题
如上图,不断的运行同一个程序会发现,父进程不变,而子进程一直变化,而父进程的pid对应的就是BASH,也就是说:命令行启动的进程,其父进程都是BASH。
如上图,asy_001被设置成环境变量后具有全局属性,其对应的值会被子进程继承下去,因此在程序第二处会显现环境变量的值,而在第一处由于asy_001是本地变量,其定义在BASH内部,不会被子进程继承,因此不会显现。
那么还有一类特殊情况:
如上图,既然本地变量不会被子进程继承那为什么local_val的值会被echo打印出来呢???
因为:在linux中大部分命令是通过子进程的方式执行的,但还有部分命令是通过bash自己执行的,bash直接调用自己对应的函数来完成特定的功能,这些命令叫做内建命令。
程序地址空间
程序地址空间不是内存,它是一个进程进程地址空间
如上图,在程序地址空间中分为几大类:从代码结果的显示内容为各个变量对应的地址,可以看见地址大小顺序为:代码内容<已初始化变量<未初始化变量<堆上的变量<共享区<栈上的变量<环境参数变量。
但是要注意的:
- 栈和堆的空间排序:栈是从小到大,堆是从大到小;
- 当我们用static定义一个变量时,这个变量就会放在全局变量的区块上去。
虚拟地址空间
如上图,父子进程在读取同一块没有被修改的地址空间时,读取到的内容是一样的,父子进程共享这一块地址空间;而当子进程修改这块地址空间时,父子进程读取到的内容不一样,子进程读取到修改后的,父进程读取到修改前的。
其实父、子进程读取到的内存地址不是物理地址,而是虚拟地址/线性地址/逻辑地址,在linux中,这几个概念一样。
什么是地址空间
每一个进程在启动的时候,OS都会为这个进程创建一个地址空间,这个地址空间就是进程地址空间,每一个进程都有自己的进程地址空间,而OS通过“先描述,再组织”的方式对这些进程地址空间进行管理,因此进程地址空间本质上就是一个数据结构。
地址空间
如上图,当程序被加载到内存变成进程之后,OS会给每个进程构建一个页表,通过虚拟地址的映射找到各自进程对应的物理地址。
- 什么是区域
struct mm_struct{ long code_start; long cod_end; long init_start; long init_end; long uninit_start; long uninit_end; long heap_start; long heap_end; ... ... }
如上图,通过start和end来区别或更改结构体中各自对应的区域地址。
解释父子进程g_val问题
由于子进程是由父进程fork得到的,因此子进程会继承父进程的模板(包括task_struct,页表),因此在修改g_val之前,父、子进程会通过相同的页表将虚拟地址映射到相同的物理内存中。而当子进程要修改g_val时,OS会重新给子进程开辟一块空间并将g_val的值拷贝到新空间并且建立新的映射关系,(这里的新的映射关系只是修改了子进程页表的右侧部分)因此我们看到的父子进程的虚拟地址是一样的,但其实映射后的物理地址已经不一样了。
写时拷贝:当父子进程中任何一个进程要对数据做修改时,OS就会给修改的进程重新开辟一块空间,并将原来的数据拷贝到这块新空间中,并修改其对应的页表,父子进程的虚拟地址不变。
再谈fork函数
fork函数有两个返回值,pid_t id变量会被return两次,而pid_t id=fork()被执行时,谁先返回,谁就要发生写时拷贝,因同一个变量会有不同的内容。(虚拟地址一样,但是物理地址已经不一样)。
虚拟地址的意义
- 保护内存:通过页表的映射来判断程序是否越界访问其他程序甚至系统数据;
- 进程管理:与linux内存管理对进程通过地址空间进行功能模块的解耦合;
- 各个程序以一种统一的形式进行编译加载,简化进程的设计。