以下是阿鲤对linux下的程序地址空间的总结,希望对部分同学能起到帮助;若有误请慷慨提出。
在我们电脑上运行的每一个程序都会有一个自己的地址空间,请注意是运行起来的程序;所以真正占地址空间不是程序而是进程。那么进程的地址空间是怎样分配到呢?首先给大家一张图;
上图是我们一个进程运行起来时地址空间的划分,那么这些地址空间都是怎样划分的呢?
要知道每一个进程都会有一个自己的地址空间,但是我们的电脑内存一般就只有那么大,那么它是怎样开辟出这么多地址空间的呢?
请大家看这一段代码;
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int gval = 10;
int main()
{
pid_t pid = fork();//创建一个子进程
if(pid == 0)//只有子进程能访问到
{
gval = 100;
printf("child --gval:%d--%p\n",gval,&gval);
}
else if(pid > 0)//只有父进程能访问到
{
sleep(3);//让子进程先打印
printf("parent --gval:%d--%p\n",gval,&gval);
}
return 0;
}
输出结果:
有没有懵逼呀,明明父子进程的gval的地址空间都一样为什么他们的gval值却不一样呢?
这是因为进程在运行过程中访问到的都是虚拟地址空间而并非真实的物理空间,那么虚拟地址空间是什么呢?
首先为什么不直接存储在物理内存呢?而要使用虚拟地址:因为直接使用物理内存会导致内存利用率低,缺乏内存访问控制。
其实虚拟地址空间实际上就是一个内存描述符,而这个内存描述符就是一个结构体;所以虚拟地址空间就是:通过一个结构体描述出一块连续的线性地址空间。
那么这些结构体都有什么内容呢?
在linux中这是一个mm_struct结构体--内存描述符,里面含有虚拟空间的大小,每个段的起始位置及终止位置;
接下来请看我们上一个程序的可执行程序;
我们可以看到程序在编译完之后它的地址和大小就已经确定了,并且我们可以看到它的虚拟地址(VMA)是连续的;那么它是怎样保证在连续存储时不会干扰到其他已经使用的内存呢?
其实虚拟地址在管理物理内存时,是采用分散式管理物理内存;虽然我们上面看到的地址是连续的,而实际的物理地址是分散存储的。这样就可以将一个个小的物理内存整理成一块大的虚拟内存;这样就可以提高内存的利用率了,而虚拟地址是通过页表和物理内存进行性联系的,即通过页表将虚拟地址和物理地址映射起来的。而且我们前面所看到的地址的分段其实也都是在页表中分开的;所以内存访问控制就是在页表中实现的。
那么具体的虚拟地址和物理地址是怎样通过页表映射的呢?
这里的方法有:分段式内存管理,分页式内存管理,段页式内存管理,接下来就说说这三种方法
分段式:分段式就是我们上图的段表;它通过段号+段内偏移进行管理;分段式管理中存在一个段表,每次可通过短号去段表中找到段的物理地址然后通过段的物理地址+偏移量去访问到具体的物理地址;如下图。
因为分段所以我们发现其并没有真正的提高内存的利用率;但是分段式对程序是非常友好的。
分页式:分页式是通过页号+页内偏移进行实现的;在分页式中,虚拟地址通过页号在页表中找到相应页表快,然后就可以拿到相应的物理内存快地址,再加上页内偏移就可以找到具体的物理地址。
分页式可以真正地提高内存利用率;
段页式:首先对虚拟地址分段,然后对每个段进行分页;所以虚拟地址组成是段号+段内页号+页内偏移。所以在访问时,首先通过段号找到段表,在段表中找到段内页表起始地址,再通过页号在段内页表中找到物理快号,物理快号+页内偏移找到具体物理地址