Intel and AMD documentation says that for 64 bit mode only 48 bits are actually available for virtual addresses, and bits from 48 to 63 must replicate bit 47 (sign-extension). As far as I know, all current CPU are implemented this way, but nothing (in theory) forbids to extend the available space in future implementations (and this won't break the binary compatibility).
Is there a standard way to programatically determine the number of meaningful bits? (i.e. some specific CPUID, as happens for physical addresses).
I know that in practice 48 bits are far more than enough for any reasonable application and OS; my question is theoretical.
Yes, you can use CPUID.80000008H:EAX[7:0]
if supported.
The algorithm is as follow:
- Check the maximum extended E value for
cpuid
with CPUID.80000000h.EAX
.
- If E >= 80000008h, use
CPUID.80000008H:EAX[7:0]
for the number of physical address bits.
CPUID.80000008H:EAX[15:8]
for the number of linear address bits.
- Else if
CPUID.1:EDX.PAE
(bit 6) then the CPU has 36 physical address bits and 32 linear address bits.
- Else the CPU has 32 physical and logical address bits.
The notation, from Intel, CPUID.X:R B
means
- X The value to put into
eax
before cpuid
.
- R The output register of interest.
- B A bitfield in the form [upper:lower] inclusive, or a named bit in the form .bitname.
AMD set the maximum limits for virtual:physical addresses at 63:52.
The current implementation is typically 48:40, though the size of the physical address space can be different.
An example code that can be compiled with NASM
BITS 64
GLOBAL max_phy_addr
GLOBAL max_lin_addr
SECTION .text
max_phy_addr:
push rbx
mov eax, 80000000h
cpuid
cmp eax, 80000008h
jae .fromCpuid
mov eax, 1
cpuid
mov eax, 32
shr edx, 4
and edx, 4
add eax, edx
pop rbx
ret
.fromCpuid:
mov eax, 80000008h
cpuid
movzx eax, al
pop rbx
ret
max_lin_addr:
push rbx
mov eax, 80000000h
cpuid
cmp eax, 80000008h
jae .fromCpuid
mov eax, 1
cpuid
mov eax, 32
pop rbx
ret
.fromCpuid:
mov eax, 80000008h
cpuid
movzx eax, ah
pop rbx
ret
And used with a C program such as
#include <stdio.h>
long max_phy_addr();
long max_lin_addr();
int main()
{
printf("Phy: %llu\nLin: %llu\n", max_phy_addr(), max_lin_addr());
return 0;
}
And I've just discovered that my Haswell is a 48:39!