在内存管理中,逻辑地址是一个比较难以理解的概念,因为逻辑地址是逻辑上(思想上的,空间上)的概念,并非物理存在,很难想象出逻辑地址到底是什么样的。
在理解逻辑地址的时候,我曾有一个疑问:假如一个操作系统最多支持64个进程,每个进程的线性地址空间为4G(32位CPU的寻址范围),那么总共需要的的内存是4G*64,这么大的内存存储在哪儿呢?
现在,我对这个困惑的解释是:CPU是分时处理的,当时间片被分配到某一进程时,此进程的4G线性内存空间才被“激活”,CPU读取程序的逻辑地址,逻辑地址通过分段处理转化为线性地址(4G空间),线性地址再经过分页处理转化为物理地址。当时间片分配给下一进程时(进程切换),前一进程的运行环境会被保存。
这样的处理,缓解了多任务系统环境中物理内存短缺的问题,物理内存中只会存储当前需要或经常使用的数据,其它数据保存在硬盘中,需要时再进行加载。
程序运行的过程,就是CPU取指,取数据,执行指令,存储结果的过程;在此过程中,CPU读取的地址都是逻辑地址。
那么程序的逻辑地址是哪里来的呢?编译时?加载时?运行时?
在编译完成后,动态库、全局变量、静态变量、函数的逻辑地址已经“基本”确定。(编译时是不分配内存的,只是根据声明时的类型进行占位。)
查看*.map文件和解析a.out文件得到逻辑地址,再和gdb调试时比较发现:编译出的逻辑地址和运行时的逻辑地址是一致的。
*.map文件编译得到:g++ *.cpp -Wl,-Map,*.map
a.out文件:objdump -D a.out | less
实际上编译出来的逻辑地址由段选择符和段偏移地址两部分组成;可以通过段选择符在段描述符表中找到段基地址,段基地址+段偏移地址=线性地址。
而在Linux中,段基地址是都是0,所以得到结论:逻辑地址=段偏移地址=线性地址。
下图摘抄至《Linux内核完全注释》,可以看出Linux内存地址转换过程。
局部变量、堆内存的逻辑地址在运行时确定。
由下面这个例子,我们可以看出局部变量内存逻辑地址在运行时分配。
#include <stdio.h>
#include <stdlib.h>
void f1()
{
static int i = 0;
++i;
if(10==i)
return;
int *p = (int *)malloc(sizeof(int));
printf("p===%x\n",p); //p是堆内存地址,同时又是一个局部变量,由于没有释放内存,每次运行都会分配新的内存,所以p在这里每次输出都不一样
f1();
}
void f2()
{
static int i = 0;
++i;
if(10==i)
return;
int *p2 = (int *)malloc(sizeof(int));
printf("p===%x\n",p2); //p输出值是一样的
free(p2);
f2();
}
int main()
{
f1();
//f2();
return 1;
}