目录
一、前言
我们在学习C/C++的时候或多或少都会提到关于内存分布的相关知识,例如静态成员变量存放在静态区,局部变量存放在栈区等等,那么这些空间是怎么进行排布的呢?我们在运行程序时是直接根据数据在内存的地址来调用的吗?
二、进程地址空间
程序地址空间也可以叫做进程地址空间,因为程序运行起来就是一个进程。
先来看一张空间布局图
这是我们平时为了更好理解而虚构出来的空间布局,实际上这些数据和代码并不是按照这种排布在内存中的,下面会说明为什么。
我们先来看一段代码,并理解运行结果为什么是这样。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int val = 100;
int main()
{
pid_t id = fork();
if(id==0)
{
//子进程
sleep(1);
val+=5; //修改val
printf("child:pid %d val=%d addr=%p\n",getpid(),val,&val);
}
else
{
//父进程
sleep(1);
printf("father:pid %d val=%d addr=%p\n",getpid(),val,&val);
}
return 0;
}
1.还可以发现他们之间val的地址是相同的,为什么会有这种现象呢?
首先这个地址肯定不是物理地址,如果是物理地址的话,指向相同空间,一方改变另一方也会发生改变。
在linux下这种地址被称为虚拟地址,并且我们写的C/C++程序中的地址也是虚拟地址,也就是说系统会根据虚拟地址进而转换位物理地址对数据进行访问,有一张对应虚拟地址与物理地址一一对应的表,在操作系统中这个表被称为页表。
为什么父进程与子进程的val的地址相同,因为创建的子进程会将父进程的代码和数据进行拷贝,当然也就把地址啥的都拷贝下来了,类似于浅拷贝,所以我们看到的地址是虚拟地址并且相同。
2.通过观察可以发现变量val在子进程中已经被改变了但是父进程中val的值未被改变。
每个进程都会有自己独立的虚拟地址空间,也有自己对应的页表。
我们上面讲的进程地址空间其实就是虚拟地址空间,是操作系统为了更好的管理并排列好每个区域的数据(例如常量在常量区等等)。使用虚拟地址就能够更好的进行区域划分,那么操作系统是怎么进行区域划分的呢?
在操作系统的task_struct中会维护着指向mm_struct的指针,而mm_struct就是进行虚拟地址空间区域划分(管理)的。
在mm_struct中定义的各个区域的范围,通过更改这些变量就可以对区域进行划分和修改。
unsigned long total_vm, locked_vm, shared_vm, exec_vm; unsigned long stack_vm, reserved_vm, def_flags, nr_ptes; unsigned long start_code, end_code, start_data, end_data; unsigned long start_brk, brk, start_stack; unsigned long arg_start, arg_end, env_start, env_end;
在mm_struct中还存在着
![]()
这个是用来管理每个区域的,例如管理栈区的地址范围,相当于是一个别墅的管家。
这个时候我们再来解释为什么子进程与父进程的的val互不影响。
我们知道进程之间是相互独立的,当某个进程对数据进行改变时就会发生写实拷贝,写时拷贝的触发条件就是当某个进程对数据进行修改时。
写实拷贝的过程:
所以我们看到的现象就是这样形成的。
三、扩展
系统怎么对页表进行管理:先描述,再组织
系统会维护一个关于页表信息的结构体,类似于对进程的管理一样的操作。
页表是用来做虚拟地址与物理地址的映射的。
我们可以不先加载代码和数据,只有task_struct,mm_struct等等,因为系统是通过查页表来进行访问数据和代码的,可以先有虚拟地址,然后再将物理地址一个一个填入页表中。
为什么我们对常量进行修改,程序缺崩溃了?因为在页表中还会维护着对每个数据的权限信息,如果我们对该数据进行的非权限访问或修改,系统就会做出相应的操作。
四、总结
为什么系统要使用虚拟地址空间?直接对物理内存的数据进行操作不行吗?
如果直接对内存进行操作,我们创建子进程时,就必须对父进程的数据和代码进行拷贝到内存,这样就会造成空间浪费。
使用虚拟地址空间的话,创建子进程只对会发生修改的数据进行拷贝到内存进行了。
可以让进程管理与内存管理,进行一定的解耦合(使两边的关联变浅)。
地址空间和页表是OS创建并维护的,凡是想使用地址空间和叶表进行映射, 也⼀定要在OS的监管之下来进行访问!!也顺便保护了物理内存中的所有的合法数据 ,包括各个 进程以及内核的相关有效数据!
因为有了虚拟地址空间,那么数据在内存中的存储位置也就没那么重要了。