研究背景:kernel 2.6.32 32位
程序地址空间
先看一段代码:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int g_val = 0;
int main(){
pid_t id = fork();
if(id < 0){
perror("fork");
return 0;
}
else if(id == 0){
g_val = 100;
printf("child[%d]: %d : %p \n",getpid(),g_val,&g_val);
}
else{
sleep(3);
printf("parent[%d]: %d : %p \n",getpid(),g_val,&g_val);
}
return 0 ; }
明明子进程时先于父进程运行的,并且对 g_val 的值进行了修改,为什么父进程运行的时候 g_val 的值反而还是 0?并且子进程和父进程中的 g_val 变量所存在的地址都一样的?
得出结论:
变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
地址一样,说明该地址绝对不是物理地址
在Linux环境下,这个地址叫做 虚拟地址,我们在用 C/C++ 看到的地址,全部都是虚拟地址! 物理地址,用户一概看不到,由 OS 统一管理;
进程地址空间
上面的这张图就很好的解释了上面的那段代码:同一个变量,地址相同,起始就是虚拟地址相同,内容不同其实是被映射到了不同的物理地址。
操作系统通过 mm_struct 这个结构体个进程描述了一个虚拟的地址空间;
mm_struct{
ulong size;
ulong code_size;
ulong code_end;
ulong data_start;
ulong data_end;
};
虚拟地址空间又通过 页表 对于到相应的物理内存上,但是实际上在物理内存的管理是通过 页段式内存的管理方式 管理的;
虚拟地址空间 + 页表:提高了内存的利用率、对内存访问进行控制;
虚拟地址空间 + 页表 优点:保持了进程的独立性、充分利用内存、内存访问控制
写时拷贝技术
父进程创建子进程创建了子进程,但是并没有直接给子进程开辟空间,拷贝数据。而是跟父进程映射到同一块内存空间,但是 如果数据发生了变化,那么就需要重新给子进程开辟内存,并且更新页表信息,提高子进程的创建性能。