一、进程地址空间
1.1 💙理解进程地址空间
在我们学习C/C++的过程中,常常会看到如上图所示的地址空间分布区域,那么它是内存吗?它不是内存,而是虚拟地址空间。我们通过父子进程共存的程序来分析一下。
#include <stdio.h>
#include <unistd.h>
int grobal_val=100;
int main()
{
pid_t id=fork();
if(id==0)
{
int cnt=0;
while(1)
{
printf("子进程:pid=%d,ppid=%d | global_val=%d,&global_val=%p\n",getpid(),getppid(),global_val,&global_val);
sleep(1);
++cnt;
if(cnt==5)
{
global_val=300;
printf("子进程已更改全局变量了——————\n");
}
}
}
else if(id>0)
{
while(1)
{
printf("父进程:pid=%d,ppid=%d | global_val=%d,&global=%p\n",getpid(),getppid(),global_val,&global_val);
sleep(1);
}
}
else
{
printf("fork error\n");
return 1;
}
return 0;
我们可以看到,当子进程修改了全局变量 global_val 的值为300以后,子进程和父进程的 global_val 值不同,因为进程具有独立性,不同的进程之间互不影响;但是这里的global_val的 地址却是相同的!! ;多个进程在读取同一块地址时,怎么可能出现不同的值?所以说明我们得到的地址不是物理地址,而是虚拟地址(线性地址)。
1.2 🧡再次理解
💚 上述现象解释:
🚗 操作系统会为进程创建一个独立的虚拟进程空间,而且它们都有自己的页表结构;子进程由父进程创建,所以子进程的地址空间是从父进程拷贝而来,&global_val 经过页表映射指向同一块物理地址,后来子进程修改了global_val的值,OS为保证进程的独立性,当进程任何一方尝试对共享数据进行写入,会在物理内存上重新开辟一块新的内存空间,将原空间数据拷贝到新空间,修改子进程的页表映射,然后让进程对数据进行修改,这个过程叫做写时拷贝。
在修改的过程中,与父子进程的虚拟地址没有任何关系,只是页表映射到不同的区域。
二、为什么存在进程地址空间
- 1️⃣ 进程地址空间可以保证数据的安全性
我们知道,每一个进程都会创建自己的进程地址空间,通过页表映射到物理内存中。假设进程直接访问物理内存,越界非法操作,对用户的信息是非常的不安全的。通过页表和操作系统可以进行拦截,保护物理内存,从而保证了数据安全。
- 2️⃣ 进程地址空间的存在,可以更方便的进行进程和进程的数据代码的解耦,进程间互相不干扰,保证了进程独立性。
对于互不影响的两个进程来说,各自拥有独立的地址空间以及页表,页表会映射到不同的物理内存上,磁盘代码和数据加载到内存中的位置也不同,一个进程数据的改变不会影响另一个进程;
- 3️⃣ 进程地址空间让进程以统一的视角,来看待进程对应的代码和数据等各个区域,使得编译器也能够以统一的视角来进行编译代码。