在前面我们已经介绍了进程相关的基本概念,错过的童鞋们可以戳这里:
https://blog.csdn.net/Sun_Life_/article/details/88580785
这篇文章为大家总结一下Linux中进程地址空间相关知识:
进程地址空间
在这之前大家得先明白一个概念:
地址:指向内存区域的一个编号
假设我们有4GB的内存空间,我们不可能为每一个进程都分配满满4个GB的内存空间,所以我们很有必要了解一下内存的基本布局
那么系统到底是如何分配内存的呢?内存分配的方式其实经过了几次演变:
一、早期的内存分派机制
程序运行时之间装入内存,内存空间不够时需要将已运行程序的数据拷贝出来给新的程序,直接在物理内存上操作,不安全,如图:
二、分段管理机制
代码段和数据段地址从0开始,统一由OS映射管理,CPU将内存分段管理,使用虚拟地址,以偏移地址来计算,安全性高。程序运行地址确定,越界易判断,但没有解决性能问题,如图:
三、分页管理+虚拟地址空间
在之前的基础上采用页表进行分页管理,以及写实拷贝技术,大大提高了安全性和性能,如图:
虚拟地址空间mm_struct结构体中的结构及关系:
如上的分页管理+虚拟地址空间我们可以用如下代码来验证:
父进程创建一个子进程,父子进程分别打印自己的pid、value值、value地址
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int value = 0;
int main()
{
pid_t pid = fork();
if(pid < 0){
perror("fork");
exit(1);
}
else if(pid == 0){//child
printf("I am child: %d, value = %d, %p\n", getpid(), value, &value);
}
else{ //parent
printf("I am parent: %d, value = %d, %p\n", getpid(), value, &value);
}
sleep(1);
return 0;
}
结果发现父子进程打印的变量值和地址都相同,因为子进程并未对变量进行任何修改,我们在修改上述代码,在子进程中将value值改为10,再次运行:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int value = 0;
int main()
{
pid_t pid = fork();
if(pid < 0){
perror("fork");
exit(1);
}
else if(pid == 0){//child
value = 10;
printf("I am child: %d, value = %d, %p\n", getpid(), value, &value);
}
else{ //parent
printf("I am parent: %d, value = %d, %p\n", getpid(), value, &value);
}
sleep(1);
return 0;
}
子进程中的value值已经被修改了,但是父子进程中value的地址却相同,所以:父子进程中的value不是同一个变量,但是地址一样,所以这个地址肯定不是物理地址,而是我们上面讲到的虚拟地址。