地址空间按照我们的现有的理解来说。
这里可以算是我们的现有的对地址空间的认识
内存分区,不同的变量存储在进不同的区中
但是今天要告诉大家这个进程地址空间不是真正的地址空间
接下来就开始进行验证
子进程和父进程的值进行全局变量修改后
值不一样,但是全局变量地址却是一样的
这说明了打印出来的变量的地址不是真正的物理地址
#include
#include
int i=0;
//定义全局变量 i
int main(){
size_t i=fork();
if(i>0)
{
//分别让子进程和父进程打印地址i的值和地址
//并且全都让i重新赋值
i=10;
while(1)
{
sleep(1);
printf("i am father,val=%d ptr=%p\n",i,&i);
}
}
else
{
i=100;
while(1)
{
sleep(1);
printf("i am child,val=%d ptr=%p\n",i,&i);
}
}
}
接下来就用这个代码践行测试一下
这里能惊奇的发现
父进程和子进程中的变量i值全都不同,但是它们的地址全都一样
这个结果就证明了,地址空间指向的肯定不是真实物理内存。
如果是地址指向真实的物理内存,那这样的情况肯定是不存在的,内存中的一个空间内,不可能存在一个变量有两个值的情况。
那真实的情况是什么样的?
记下来就带来物理地址和地址空间之间的大致关系
这里我们就用一幅图来代表这其中的大致关系
(这里只是大致表示它的逻辑关系,其中细节没有补足)
这里是地址空间都是虚拟地址
真实地址是真实的存储区域
页表则是记录了虚拟地址对应的真实物理地址
充当了地址空间和真实内存中间的媒介。
现在我们就可以来思考一下
上面这个代码为什么运行结果是这样
因为地址一样并不指的是真实物理内存的地址
而是地址空间的虚拟地址
上面这个问题就是写实拷贝。
大致过程就是这样了
实际物理内存这两个是不同的
但是虚拟地址是一样的。
大致过程是这样了,但是接下来我们就来补充一些细节
这里有个细节,我们以前都叫做内存地址空间
但是现在改成了进程地址空间
这说明了现在这个地址空间是属于进程的
什么叫属于进程?
在我们之前讲解进程的时候
提到了进程是由
进程=task_struct+代码和数据 组成的
这里提到:进程地址空间同样属于进程
所以我们这里需要更正一下我们对进程的理解了
进程=内核数据结构(task_struct && mm_struct && 页表结构)+代码和数据
那这里也就能确定:
进程地址空间是用来进行内存可视化的数据结构,同样也能被系统进行管理,同样符合和进程PCB一样的先描述再组织
所以每个进程pcb创建时,同样也要创建属于他们的虚拟内存——mm_struct
我们这里讲完了进程地址空间是啥玩意后
接下来就要来讲讲为什么要有它
这里我们来举一个小例子
我们在前面讲过:进程有独立性
每个进程不知道别的进程的存在,它们只管自己的进程申请和使用
每个进程有了进程地址空间后
就能记录下每个进程的空间范围,用来方便申请内存
同时不妨碍
只要每个类中有一个起始和结束的地址
这样就不会互相冲突,更好维护进程的独立性
这里只是个具体的实际应用
接下来就讲宏观上的整体作用
了
1.
进程地址空间相当于进程和物理地址间加了个媒介
可以让进程在进行寻址请求或者申请时进行审查,所以一旦访问异常,直接拦截
使关于进程的内存操作简单方便了很多。
2.
因为有地址空间和页表的存在,将进程管理模板和内存管理模板进行解耦合
这里通俗的讲就是:
当我们用户进行进程的创建和使用的时候。
不再需要关心内存的申请和创建,操作系统自己就会去调用
让使用计算机的门槛和难度降低了很多
这里提个小细节
还记得之前提过在切换进程的时候,cpu中存储的是进程的上下文。
页表就存在寄存器中,算是进程的上下文的一部分
mm_struct中进行了分区。
分为了常量区和代码区。
其中的代码都是不能进行更改的
这里我们能猜到其实这个:
不能进行更改肯定不是真实的物理内存进行的权限划分
而是进程地址空间实施的。
因为如果真实物理地址进行权限划分的话
那在常量区数据最开始怎么进行写入?
所以权限的划分是体现在进程地址空间中的
那代码是如何知道什么权限有没有被划分的?
页表中有一个专门的权限位
用来表示地址指向的空间是否具有对应的权限
还记得我们讲进程的时候提到过进程挂起的这个概念
当进程长时间未使用或者资源没有准备时
系统会将该进程的代码和数据从内存中去掉,让其他进程使用内存资源
那如何知道进程有没有被挂起?(本质是问系统如何知道代码和数据是否在内存中读取)
这里我们就要提一下
系统为了防止空间和时间的浪费,所以页表中使用的是惰性加载
就是说会将进程的代码和数据先读取在进程内存地址空间,而不去使用实际内存的空间
就是说会在地址空间处先占个位置,在实际内存中不会先去开辟空间
所以为了让系统知道什么地址被成功加载到了物理内存中
页表又加了一个标志位:
用来专门标识数据和代码是否被加载到了内存空间当中
这样就可以做到进程的挂起和惰性加载了。
如果当操作系统发现进程的代码和数据没有被加载到实际内存中时,会触发缺页中断,将进程暂停,重新将进程的代码和数据从磁盘加载到内存中,然后再继续执行。
前面我们提到的写实拷贝同样也是缺页中断的功能实现。