【Linux】进程地址空间
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
目录
引入
我们写一个这样的程序运行并观察其输出结果。
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <cerrno>
#include <cstring>
using namespace std;
int main()
{
int value = 10; //定义一个变量
pid_t id = fork();
if (id == 0) //子进程
{
while(1)
{
value++; //改变value的值
printf("我是子进程,我的pid是: %d,value是: %d,&value是: %p\n",getpid(),value,&value); //输出变量值
sleep(1);
}
}
else if (id > 0) //父进程
{
while(1)
{
printf("我是父进程,我的pid是: %d,value是: %d,&value是: %p\n",getpid(),value,&value); //输出变量值
sleep(1);
}
}
else //fork出错
{
cout << errno << ": " << strerror(errno) << endl;
}
return 0;
}
可以观察到父子进程都有一个 value 值且共用一个地址。而子进程改变 value 时父进程的 value 却不受影响。
假设我们使用的这个地址就是物理地址的话可能会出现读取同一个地址而出现两个不同的值的情况吗
因此我们得出一个结论: 在语言层面用的地址并非物理地址而是虚拟地址。
我们在用C/C++语言所看到的地址全部都是虚拟地址而用户一概看不到物理地址而是由OS统一管理。
我们使用的这个虚拟地址总的叫进程地址空间又叫 mm_struct 是一种线性的结构其通过页表的某种映射关系从而找到物理内存。
进程地址空间
讲个故事吧有个大富翁他有很多的私生子。每个私生子不知道彼此的存在因此都觉得大富翁的所有财产本质上也是自己的。因此需要用钱的之后只要找大富翁要就可以了但也不能一下子要太多否则大富翁会拒绝这种无礼的请求。而大富翁为了避免出现忘记给儿子们画的饼的尴尬情况因此需要将他曾经画过的饼都管理起来。
对号入座之后我们便能够发现大富翁就是OS私生子就是一个个进程。而其画的饼其实就是进程地址空间本质上就是一个内核数据结构struct mm_struct 。
虚拟地址与物理地址
如何理解虚拟地址的不同区域
由于mm_struct本质上是一个线性结构因此只要对线性区域进行指定 start 和 end 即可完成区域的划分因此 mm_struct 内都是一段一段区域的划分区域之间就叫做虚拟地址或线性地址。将来只需要修改区域边界的位置便可以修改区域的大小。
之后 mm_struct 便可以借助页表与MMU(内存管理单元)与物理内存确立映射关系、建立联系。
不仅如此页表之中还存储了对于该空间的权限正如一个常量区之中的字符串我们可以对其读取却无法更改其内容。便是因为在页表之中我们对该空间只有读权限而没有写权限。因此无法进行写入。
写时拷贝
这时我们就可以回过头来讲讲如何做到用一个地址却能得到两个值?
在父进程创建子进程之前申请了一个变量 value因此在进程地址空间中存在一个地址能够通过映射找到物理内存中的 value。
之后父进程创建了子进程子进程会继承父进程的进程地址空间因此二者存储 value 的虚拟地址是相同的并映射到同一块物理内存。若子进程未进行写入则两个进程便会保持原有的映射关系。
若子进程对值进行修改便会触发写时拷贝在物理内存的新地址中拷贝一份新的值进行修改之后修改页表的映射指向这块区域。
值得注意的是哪个进程先进行修改哪个进程就触发写时拷贝。
动态开辟的细节
不知道是否想过一个问题动态开辟的空间是一申请就给我们呢还是使用的时候再给
在动态开辟在申请时到使用前有个空窗期这段时间内空间就被闲置了而 OS 一般不允许任何的浪费和不高效的操作。
因此动态开辟时值分配虚拟内存当要使用该空间时再分配物理内存完善页表之中的映射关系。
为什么存在进程地址空间
避免地址被随意访问
若直接使用物理内存则在访问空间时无法检测野指针问题放任指针随意地修改可能会使数据受损或导致其他进程崩溃出错。
进程管理和内存管理解耦合
如此使用进程地址空间后管理进程时并不关心内存是如何管理的。
同理管理内存是也不关心进程是如何管理的实现了进程管理和内存管理的解耦合。再使用页表将二者关联起来。
使进程用统一的视角看待代码和数据
原代码被编译的时候就是按照虚拟地址空间的方式对代码和数据完成了对应的编制。
这是由于虚拟地址这样的策略并不只影响 OS编译器也要遵守。
因此在内存中打开文件时自然就有了对应的物理地址之后在运行的时候若对函数进行调用通过映射就会找到函数的虚拟地址(当前物理地址中存的是虚拟地址)再通过映射便可以找到函数体(再通过映射找到存函数体的物理地址)。
因此 cpu 中读取的都是虚拟地址。 ---这样便实现使进程用统一的视角看待代码和数据。
且进程的代码和数据并非一直都在内存中而是用多少加载多少不再使用就将其从内存之中移除
好了今天进程地址空间的讲解到这里就结束了如果这篇文章对你有用的话还请留下你的三连加关注