文章目录
进程地址空间
程序地址空间
地址空间一共有如下的几个区域,从下到上地址逐渐增加,其中栈区的空间是从上往下使用,即从高地址往低地址增长;堆区的空间是从下往上使用,即从低地址往高地址增长 ,++需要注意的是,在不同位操作系统下或者不同编译器下,内存的分配规则都可能是不同的,这里以linux为例,也是最经典的一种。++
我们平时敲代码使用程序地址空间的时候,当我们定义一个局部变量,它的空间就是在栈区上开辟的,有临时性;当我们使用malloc申请空间的时候,是在堆区开辟的空间;当我们定义一个全局变量的时候,它的空间就是在全局变量中开辟的,其中也分为未初始化全局变量和已初始化全局变量。在32位系统下的寻址空间是4GB
为了直观地体现出地址分配的规则,我们使用一些例子来做演示:
c
#include<stdio.h>
#include<stdlib.h>
int val1 = 10;
int val2;
int main() {
//以下均为存储在各区地址空间中的实例
printf("代码区: %p\n", main);
const char* str = "helllo linux";
printf("字符常量区: %p\n", str);
printf("已初始化全局变量区: %p\n", &val1);
printf("未初始化全局变量区: %p\n", &val2);
char* a = (char*)malloc(sizeof(char));
printf("堆区: %p\n", a);
printf("栈区: %p\n", &str);
return 0;
}
运行结果如下图所示:
通过运行结果会发现打印出来的地址从代码区到栈区依次递增。
进程地址空间
当我们使用fork()函数生成一个子进程的时候,子进程会对将要访问的父进程的内容进行写时拷贝,但是会发现子进程和父进程对于同一个全局变量进行访问更改等操作的时候,这个变量的地址是不变的,也就是说同一个地址可能会有两个值,因为这里的地址并不是物理地址,而是虚拟地址(我们平时写程序用到的地址相关的内容一般都是虚拟地址)。如果是物理地址,这是绝对不可能的,可以配合下面案例理解:
c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int val = 0;
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 0;
}
else if(id == 0){ //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取
val=100;
printf("child: %d : %p\n", val, &val);
}
else{ //parent
sleep(3);
printf("parent: %d : %p\n", val, &val);
}
sleep(1);
return 0;
}
运行结果如图
会发现前文所说的现象,同一个变量,子进程对其将要访问的变量进行写时拷贝,但是父子进程中的val确是同一个地址,因此这里的地址是虚拟地址而非物理地址。他们地址上的逻辑应该对应下图(简化):
- 当父进程创建出来,系统创建了父进程的PCB和父进程的进程地址空间,PCB指向进程地址空间
- 这里创建的进程地址空间是虚拟地址,虚拟地址和物理内存是通过页表来映射的
- 当访问某个地址时,页表通过映射关系,查找到物理地址,并读取存在当中的数据
- 当父进程创建子进程的时候,系统也根据父进程为模板创建子进程对应的PCB和进程地址空间
- 由于子进程时以父进程为模板创建的,因此他们页表是一样的,因此子进程和父进程能够共享代码
- 对于同一个全局变量,当子进程需要对其进行写入等操作时,由于父子进程的虚拟地址对应同一块物理地址,为保证独立性,系统会在物理内存中额外开辟一块空间
- 至此,父子进程各自页表中对于此全局变量的虚拟地址是相同的,但是对应的物理地址是不同的。
简化):
[外链图片转存中...(img-xn3KL9cr-1724489534997)]
[外链图片转存中...(img-1r95tjqS-1724489534998)]
- 当父进程创建出来,系统创建了父进程的PCB和父进程的进程地址空间,PCB指向进程地址空间
- 这里创建的进程地址空间是虚拟地址,虚拟地址和物理内存是通过页表来映射的
- 当访问某个地址时,页表通过映射关系,查找到物理地址,并读取存在当中的数据
- 当父进程创建子进程的时候,系统也根据父进程为模板创建子进程对应的PCB和进程地址空间
- 由于子进程时以父进程为模板创建的,因此他们页表是一样的,因此子进程和父进程能够共享代码
- 对于同一个全局变量,当子进程需要对其进行写入等操作时,由于父子进程的虚拟地址对应同一块物理地址,为保证独立性,系统会在物理内存中额外开辟一块空间
- 至此,父子进程各自页表中对于此全局变量的虚拟地址是相同的,但是对应的物理地址是不同的。