这2个概念在计算机体系结构、计算机原理与接口技术、汇编语言等课程中常常出现,却又无明确定义和讲解,常常混淆不清,今天我就来彻底地拔个明白。
从运行实体(指进程、线程、中断处理器、内核组件,等)的角度来讲,有效地址就是一个可以用于指定哪个内存位置可以用来执行存取操作的值。例如:
从一个稍微不同的角度来看:
注意最后一组3个例子中的前2位中的“从发起xx操作的运行实体的角度看”这个短语,当从运行实体的角度来考虑的话,多个运行实体可访问的内存位置的有效地址并非都是相同。例如,两个进程可能共享一块内存区域,尽管尽量的避免这种情况发生,仍有可能出现参与访问的各运行实体具有不同的有效地址。在这情况,共享区域的有效地址的异同取决于正在访问的运行实体。因此,通常来讲,一个内存位置的有效地址仅在从一个具体的运行实体对内存位置的访问能力的角度来讲才有具体的意义。
另一个特别容易引起混淆的地方是,在Intel处理器中,为了区分跨段访问这种情况,把不跨段访问时计算的这个地址称为偏移,又称为有效地址。而将跨段访问时计算的这个地址称为逻辑地址。如下图所示:
而有实际应用开发中,我们不区分你是如何构成的地址,将计算出来的地址统称为有效地址。
如果一个发起内存访问操作的处理器不支持虚拟地址或者将虚拟地址功能关闭,那么这个有效地址就相当于是处理器访问的内存的物理地址(即, DRAM, core, SRAM,各种)。
另一方面,如果一个发起内存访问操作的处理器支持虚拟内存且虚拟内存功能打开,这个操作的有效地址就被处理器的内存映射单元转换成了这个操作访问的具体的内存的物理地址。
“有效地址”是一个创造出来的术语, 用于无歧义的描述前面例子中要表达的这种概念。
“有效地址”的术语创建出来几乎可以替换掉更常用但容易混淆的术语“虚拟地址”。尽管“虚拟地址”术语就它自身“创建”而言可能不会混淆,而对它的使用已经变得混淆,至少在过去的三十年硬件工程师和软件开发人员都混淆的使用这个术语(不确定有效地址和虚拟地址谁先定义)。
例如,看看一个相对常见的对“虚拟地址”的定义:
可以被虚拟地址映射设备转换成物理地址的地址。
当系统的虚拟地址映射功能被打开的时候,虚拟地址“虚拟地址”和“有效地址”的定义本质是是等效的。然而,有效地址的定义其实包含了虚拟地址的定义。
另一个关于“虚拟地址”的定义出现在IBM的官方文档中,描述了PowerPC处理器如何将一个有效地址映射到物理地址。简言之,在一个32位的PowerPC处理器上(或者一个64位的PowerPC在32位模式下操作),32位的有效地址首先转换为一个52位的地址,然后这个52位的地址再转换成物理地址(这个物理地址的大小取决于采用的PowerPC处理器的版本)。
IBM的描述这种转换是如何工作的官方文档使用了术语“虚拟地址”来表示这个中间的52位地址。也就是说,在PowerPC处理器的上下文中,IBM定义的“虚拟地址”与他们定义的“有效地址”的定义本质上是不兼容的(IBM对“有效地址”的定义与其它许多硬件厂商对“有效地址”的定义,即使不相同也高度类似)。另一方面,IBM在PowerPC上下文中定义的“有效”地址与上面文章开头定义的“有效地址”等效。
原则很简单:不管过去这个概念有没有混淆,这个“虚拟地址”在现在的虚拟内存上下文中已经混淆了。
应当指出,即使术语“虚拟地址”不混淆,也仍然需要术语“有效地址”,因为在处理器不支持虚拟内存或者虚拟内存特殊征禁用的上下文环境中,“虚拟地址”变得没有意义。
在很多软硬件实现中,一块内存在单个进程的进程内存空间的多个地方“可见”,情况就变得更为复杂。
通过下面的例子来解释这种情况:
#include
#include
#include
main()
{
int shmid;
void *addr1, *addr2;
/* Allocate a shared memory segment of at least 100 bytes */
shmid = shmget( IPC_PRIVATE, 100, IPC_CREAT | 0600 );
if ( shmid == -1 ) {
perror("shmget failed");
exit(1);
}
/*
* Attach the shared memory segment at an arbitrary location
* in our address space
*/
addr1 = shmat( shmid, NULL, 0 );
if ( ((long)addr1) == -1 ) {
perror("shmat 1 failed");
exit(1);
}
/*
* Attach the same shared memory segment again at an arbitrary
* location in our address space
*/
addr2 = shmat( shmid, NULL, 0 );
if ( ((long)addr2) == -1 ) {
perror("shmat 2 failed");
exit(1);
}
/*
* Did we get two different locations?
*/
if ( addr1 == addr2 ) {
printf("addr1 is equal to addr2 (0x%p)\n",addr1);
} else {
printf("addr1 is 0x%p and addr2 is 0x%p\n",addr1,addr2);
strcpy((char *)addr1,"one");
strcpy((char *)addr2,"two");
printf("value at addr1 is \"%s\"\n",addr1);
}
exit(0);
}
程序的输出形如:
addr1 is 0x0x40017000 and addr2 is 0x0x40018000
value at addr1 is "two"
addr2所引用的内存地址的值被addr1所引用的内存地址的值覆盖,这个事实表明,从进程的角度来讲,与shmid 关联的共享内存段有两个不同的有效地址。
如果这个输了形如以下:
addr1 is equal to addr2 (0x40017000)
那么两次系统调用shmat 底层操作系统应该返回相同的地址。
如果这个输出形如以下:
addr1 is 0x0x40017000 and addr2 is 0x0x40018000
value at addr1 is "one"
同样也很奇怪。
从以上两点讲解我们可以大致地得出结论:有效地址的术语比虚拟地址术语的应用更广泛,更少的歧义,甚至可以代替虚拟地址使用。我们在应用程序开发中进程所见地址,我们都可以认为是有效地址。
这个概念的出现,也很容易引起混淆。下面来看看它是如何定义的。
常见的定义:
逻辑地址是程序运行时由CPU生成的地址,它是一个虚拟地址,之所以说是虚拟地址,是因为它不是物理上真实存在的地址。
看看这个定义,又扯上虚拟地址了,这就是多种概念容易引起混淆的地方。从定义来看,通常情况下我们说的逻辑地址,就是相对于物理地址而言的由处理器生成的地址,由前面对有效地址的定义,我们可以认为,这个逻辑地址,可以认为就是有效地址。
同样,这里需要特别注意,在Intel处理器中指的逻辑地址,是指跨段访问时段选择子加上段偏移构成的这个地址,见上面图中所示。