概念
#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)
{
//child
printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
else
{
//parent
printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
sleep(1);
return 0;
}
父子进程输出的地址是一致的,但是变量内容不一样。
变量内容不一样,所以父子进程输出的变量绝对不是同一个变量。但是地址值是一样的,说明该地址绝对不是物理地址。
- 在Linux地址下这种地址叫做虚拟地址。
- 我们在用C、C++语言所看到的地址全部都是虚拟地址。
- 物理地址用户一概看不到,是由操作系统管理。操作系统必须负责将虚拟地址转化为物理地址。
进程地址空间是指进程可用于寻址内存的一套地址集合。它是一个抽象概念,为进程提供了一种独立于物理内存的、连续的内存视图。
组成部分
同一个变量地址相同,其实就是虚拟地址相同,内容不同,其实就是被映射到了不同的物理地址。
- 代码段:存放程序的可执行代码,通常是只读的,以防止程序运行时修改自身代码。
- 数据段:包含全局变量和静态变量。其中,初始化的全局变量和静态变量存放在数据段的初始化部分,未初始化的存放在数据段的未初始化部分(.bss段)。
- 堆:用于动态分配内存,进程可以在运行期间使用函数(如C语言中的malloc)在堆上申请内存空间,其大小可以动态增长,向上扩展。
- 栈:用于存储局部变量、函数参数和函数调用的返回地址等。栈的生长方向是向下的,随着函数调用和局部变量的分配而动态变化。
与物理内存的关系
- 进程地址空间通过页表等机制映射到物理内存。在现代操作系统中,进程地址空间往往比物理内存大,操作系统通过内存管理技术(如虚拟内存技术)实现进程地址空间到物理内存的映射,允许将暂时不使用的页面置换到磁盘等外部存储设备上。
- 子进程在创建的时候通常会继承父进程的地址空间结构,包括代码段,数据段的布局。
- 子进程有自己的独立的虚拟地址空间,其虚拟地址的范围和布局进程,地质空间的结构相对应。
- 但是当子进程发生修数据修改时,子进程使用自己的虚拟地址进行操作。
- 首先通过虚拟地址找到相应的页表项。
- 如果该数据所在的页已经在物理内存中,且有对应的页表,但是与父进程指向同一个物理地址,那么操作系统会发生写时拷贝。
- 子进程重新映射虚拟地址页表。到物理地址之间的关系。
- 在用户层面上,修改前后的子进程数据的虚拟地址是一致的。
- 但是在底层页表映射的物理物理地址是不一致的。