目录
1.进程的回顾
1.1竞争性
系统里面的进程很多,CPU资源有限,所以不同的进程之间具有竞争性;
1.2独立性
CPU上面的资源不允许同时被访问,都是没一个进程独立被使用的,一个进程的运行是不可以影响其他的进程的;
1.3并行和并发
多个进程在多个CPU下面同时运行,这个就叫做并行;
多个进程在一个CPU下面采用进程切换的方式,在一段时间里面,所有的进程都可以被运行,这个就是并发;
1.4进程的切换
函数的返回值是怎么被外部拿到的:通过CPU里面的寄存器被我们的外部得到的;
我们的系统如何知道进程执行到了哪一行代码:通过程序计数器eip来记录当前进程的正在执行的指令的下一行的指令的地址;
通用寄存器:eax,ebx; 状态寄存器:status
在我们的CPU里面,一定会存在很多的寄存器,我们的CPU里面的寄存器扮演的角色是什么:寄存器也具有对于数据的保存能力,把进程的高频的内容数据放到寄存器里面去,方便我们的CPU管理,快速地找到我们想要的数据;
CPU寄存器保存的是进程的相关的信息,官方称呼:上下文数据(实际上就是我们的进程相关的信息数据);
我们的进程从CPU上面离开的时候,这个相关的上下文数据需要被保存甚至带走,进程在被切换的时候,回来的时候,就可以直接把自己的数据放上去,这样就可以直接运行了,总之就是我们的进程切换的时候会执行上下文的切换,方便自己下次被调用的时候可以实现无缝衔接;
2.环境变量
2.1环境变量简介
获取环境变量的指令:
我们自己可以去创建环境变量,但是我们自己创建的环境变量都是在我们的本地,我们使用这个echo指令可以进行查看,可以发现这个创建是成功的,但是我们使用上面的env指令进行查看的时候发现这个里面并没有我们自己创建的环境变量;
这个时候,如果我们想要看到自己创建的环境变量,就只需要创建的时候在前面加上export就可以了,这个时候我们在使用env查看的时候就会发现这个我们自己创建的环境变量了;
我们可以手动的取消对于环境变量的相关设置,我么可以使用unset指令把我们的自己创建的环境变量删除掉,再使用env进行查看就发现没有了;
2.2命令行参数
环境变量为软件,指令,工具提供命令行选项的支持;
进程启动,不仅仅只是把这个进程加载到内存里面去,还需要命令行参数,系统环境变量表都会被传进来给我们的进程;
我们运行的进程都是子进程,bash本身在启动的时候会从操作系统的配置文件里面读取环境变量信息,子进程会继承父进程交给我们的环境变量;
3.程序地址空间
3.1地址空间简介
我们的程序存储区分为代码区,字符常量区,全局数据区,堆区,栈区等等几个部分,地址是有低地址到高地址进行增长的,我们把这个叫做地址空间;
在栈区定义的变量,这个先定义先入栈,后定义的变量后入栈,因此这个后定义的变量的地址会更大,这个栈区是向下增长,同理这个,堆区是向上增长的;堆区和栈区相对而生;
static修饰的局部变量编译的时候,已经被搞到全局数据区里面了,这个局部变量的生命周期就是一个全局变量的,因此随着我们的函数的调用,这个static修饰的局部变量的数值不会发生改变;
3.2虚拟地址
我们可以看到这个上面的实验,刚开始的这个打印的g_val是100,后来这个对应的是200,但是这个变换之后这个地址是不变的,怎么可能一个变量一个地址,但是这个变量的数值却不一样,这个就是因为这个打印的结果不是真实的物理地址,而是我们的虚拟地址;
3.3页表概念的引入
页表是一张显示这个虚拟地址和真真实的物理地址的关系的映射表;
我们的这个父进程和子进程都有自己的进程地址空间,子进程的进程地址空间 是对于这个父进程的一个拷贝,所有的信息都是一样的,相当于就是一份拷贝;
但是当我们对于这个父进程里面的变量进行修改的时候(子进程会和父进程共享代码和数据)因此这个父进程的变量在子进程里面也是存在的,但是因为这个进程具有独立性,因此这个会发生写实拷贝,这个物理地址会发生变化,开辟新的物理地址去存储这个修改后的变量的数值;
因此这个上面的打印结果,打印的是虚拟地址,这个虚拟地址子进程就是拷贝的父进程的,所以这个打印的结果是一样的,但是这个实际上的物理地址不是一样的;
3.4谈谈细节
到底什么是进程地址空间:数据总线排列组合形成的地址的范围[0,2^32);
进程地址空间实际上就是我们的进程的一个可以使用的范围,我们可以在这个区域上面进行区域的划分,存放各种数据;
进程地址空间在内核里面就是一个内核对象结构体,这个结构体里面有地址区域的起始位置的地址start和终止位置的地址end;
3.5进程地址空间管理
对于任何一个进程,都会创建一个task_struct结构体对象,这个指针指向我们的进程地址空间对象,这个里面就有我们的各种区域的划分,方便我们对于这个区域的管理;
3.6进程地址空间的存在意义
让所有的进程以一个统一的视角去看待内存,因为地址空间使得所有的进程都需要虚拟地址,子进程和父进程的关系,调度是一套统一的流程;
当我们访问内存的时候,会增加一个转换的过程,在这个转换的过程中,虚拟地址空间会进行审查,例如我们对于这个只读区域进行修改,显然是不符合要求的,这个时候的地址空间就会进行这个请求的拦截,防止其进入物理内存,在某些程度上面保护了物理内存;
3.7页表
CPU里面的这个cr3寄存器把这个进程的页表的起始地址进行管理和存储,因此我们进行进程切换的时候,不需要担心这个页表找不到的情况,因为我们会通过这个cr3寄存器找到我们的页表;
左侧的叫做进程管理,右侧的叫做内存管理,因为这个页表的存在在,这个虚拟地址空间和页表的存在,使得内存管理和进程管理解耦合(避免强耦合,强耦合的话就是关联性比较强,容易相互牵扯问题);
进程=内核数据结构PCB+进程地址空间+页表+数据和代码,每一个进程都可以有一整套这个东西;
进程具有对立性:首先就是这个不同的进程对应的这个PCB是不一样的,其次就是这个物理地址部分,子进程和父进程开辟空间的地址是不一样的;
我们创建一个进程之后,首先要做的事情就是去创建内核数据结构,说白了就是去把这个虚拟地址空间,页表和物理内存这个框架去搭建起来,然后根据我们的这个程序的运行情况去惰性加载内存,不会一次性全部提供的,而是当我们的程序去执行某一个地方的时候,我们的物理地址空间才会被开辟,经由我们的页表的权限审核,确定这个是否要在我们的物理地址上面开辟空间;
因此这个里面存在缺页中断,就是这个虚拟地址空间没有对应的物理地址空间,就是这个页表上面的虚拟地址数量大于这个物理地址数量,就是因为这个物理空间不会一次性全部开辟,而是进行的惰性加载;实际上,我们之前介绍的这个写实拷贝,就是我们的这个子进程和父进程共享数据和代码,当我们需要对于这个子进程的数据进行修改的时候,这个因为进程的独立性,才会让这个操作系统重新开辟内存空间,方便对于这个修改的数据进行存放,从而不会影响这个父进程的代码和数据的原始值,这个实际上就是缺页中断的原理;
还有一点就是我们之前学习这个C语言的时候说的是这个代码是只读的,常量区是只读的,这个就是因为我们的页表里面有权限标识符,就是rw之类的,当我们的虚拟地址想要去开辟空间的时候,就回去经过这个页表的验证,符合这个权限要求之后才回去到对应的物理内存上面开辟空间;