一. 概念
我们平时在写C/C++程序时,所看到的地址其实不是实际的物理内存,而是进程(虚拟)地址空间。每一个进程在创建的时候,系统都会给一个独立的进程地址空间,和一个页表,这个页表负责把虚拟地址映射到物理内存地址。
二. 代码验证
如图所示,父子进程都各自有自己的进程地址空间和页表。子进程创建时,子进程的进程地址空间和页表会以父进程为模板初始化,默认跟父进程共享数据和代码(如代码1)。
// 代码1
#include<stdio.h>
#include<unistd.h>
int main()
{
int a = 0;
pid_t ret = fork();
if (ret < 0)
{
perror("fork");
}
else if (ret)
{
printf("parent[%d] --> a[%p] --> %d\n", getpid(), &a, a);
}
else
{
printf("child[%d] --> a[%p] --> %d\n", getpid(), &a, a);
}
return 0;
}
当父进程或者子进程的数据发生改变时,子进程会发生写时拷贝,程序地址不变,会改变对应进程地址页表的映射关系(如代码2)。 从运行结果我们可以看出,a的值不一样,证明两个a不是同一个变量(物理内存地址不一样)。我们打印的地址是一样的,实际上那是进程地址空间的地址,不是物理内存的地址。
// 代码2
#include<stdio.h>
#include<unistd.h>
int main()
{
int a = 0;
pid_t ret = fork();
if (ret < 0)
{
perror("fork");
}
else if (ret)
{
a = 10; // 这里父进程改变了a,发生了写时拷贝
printf("parent[%d] --> a[%p] --> %d\n", getpid(), &a, a);
}
else
{
printf("child[%d] --> a[%p] --> %d\n", getpid(), &a, a);
}
return 0;
}
三. 进程地址空间的意义
- 对进程操作内存进行风险(权限)管理,起到保护物理内存以及各个进程数据的安全。
- 有时候我们申请空间的时候不一定是马上就用,有了进程地址空间,可以将内存申请和内存使用在时间上划分清楚,这样可以减少内存空间的闲置。
- 每一个进程都有自己独立的进程地址空间,以确保进程具有独立性。
用在时间上划分清楚,这样可以减少内存空间的闲置。- 每一个进程都有自己独立的进程地址空间,以确保进程具有独立性。