程序地址空间
地址:通常所说的的地址,都是内存的地址,是内存单元的编号。
但是我们所使用的硬件内存中,真的存在这些划分吗? 我们通过代码来探究一下。
#include <stdio.h>
#include <unistd.h>
int g_val = 100; //创建一个全局变量 g_val
int main ()
{
pid_t pid = fork();
if (pid < 0)
{
return -1; //创建子进程失败
}
else if (pid == 0)
{
//创建子进程成功,子进程的返回值为0
printf("child g_val=%d\n",g_val);
}
else
{
//父进程的返回值是子进程的PID,返回值大于0
printf("parent g_val=%d\n",g_val);
}
return 0;
}
运行后我们可以发现父进程、子进程都可以打印出 g_val 的值。
我们将代码进行修改
#include <stdio.h>
#include <unistd.h>
int g_val = 100; //创建一个全局变量 g_val
int main ()
{
pid_t pid = fork();
if (pid < 0)
{
return -1; //创建子进程失败
}
else if (pid == 0)
{
//创建子进程成功,子进程的返回值为0
g_val = 200;
printf("child g_val=%d\n",g_val);
}
else
{
//父进程的返回值是子进程的PID,返回值大于0
sleep(3);
printf("parent g_val=%d\n",g_val);
}
return 0;
}
在运行到子进程的时候,将全局变量 g_val 的值进行修改,然后父进程先进行休眠,等子进程将全局变量的值修改之后再进行输出,按理说,子进程和父进程的打印结果都应该是200,但是运行程序后我们发现
父进程打印的值是200,我们通过打印 g_val 的地址在来观察一下。
#include <stdio.h>
#include <unistd.h>
int g_val = 100; //创建一个全局变量 g_val
int main ()
{
pid_t pid = fork();
if (pid < 0)
{
return -1; //创建子进程失败
}
else if (pid == 0)
{
//创建子进程成功,子进程的返回值为0
g_val = 200;
printf("child g_val=%d---%p\n",g_val,&g_val);
}
else
{
//父进程的返回值是子进程的PID,返回值大于0
sleep(3);
printf("parent g_val=%d---%p\n",g_val,&g_val);
}
return 0;
}
虚拟地址空间
通过观察地址我们发现,父子进程打印的 g_val 的地址也相同,难道值一个地址单元存放了两个数据吗?
当然不是,实际上,在进程中,程序访问的这些地址,都是假的地址,我们称之为虚拟地址空间,虚拟地址空间实际上是系统给进程所描述的的一个假的地址,在Linux下是一个 mm_struct 结构体。
系统会为每一个进程都分配一个假的地址空间,进程访问的都是虚拟地址,访问内存数据的时候,先将虚拟地址转换为物理地址之后,再进行访问。
为什么要用虚拟地址空间
系统会给每一个进程都描述一个完整的、连续的、线性的虚拟地址空间,实际物理内存是当需要的时候再进行分配,这样的话,对于每一个进程来说,都有一块儿自己的完整的、连续的内存可以使用。
在需要用到实际物理内存空间的时候,通过页表映射一块物理内存地址,这时,才是进行访问真正的物理内存。
通过这种映射的方式,实现了数据在物理内存上的离散存储方式,提高了内存利用率。
虚拟地址如何通过页表获取到物理地址
内存管理方式一共分为三种
- 分段式内存管理
- 分页式内存管理
- 段页式内存管理
分段式内存管理
将地址空间分成多段,便于编译器进行地址管理。
分段式虚拟地址组成:段号+段内地址偏移量。
在系统中,有一个段表,段表的内容包括段号,物理内存段起始地址。
图示:
分页式内存管理
将地址分成多个小块儿,实现数据离散式存储,提高内存利用率。
分页式虚拟地址组成:页号+页内偏移量。
在系统中,有一个页表,页表的内容包括页号,物理内存段起始地址,控制权限等。
图示:
段页式内存管理
将虚拟地址空间进行分段,在每个分段内进行分页式管理,集合了分段式和分页式的优点进行内存管理。
写实拷贝技术
通常,父子进程代码共享,父子进程在没有写入、修改等操作的时候,数据也是共享的,当任意一方试图写入的时候,通过写实拷贝技术拷贝一份副本,达到代码共享,数据独有的特性,这就是为什么父子进程的g_val为什么虚拟地址相同,但保存的数据不一样,就是子进程在修改g_val的值的时候,通过写实拷贝技术拷贝了一份副本,让相同的虚拟地址映射到不同的物理地址