I'm using an ARM64 system(M1).
$ uname -m
arm64
This is the C program I'm using to find out the virtual address space range on my system.
#include <stdlib.h>
#include <stdio.h>
#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
int main(void) {
printf("Page size = %d\n", getpagesize());
struct rlimit x;
getrlimit(RLIMIT_AS, &x);
printf("Current maximum size = %llx\n", x.rlim_cur);
printf("Limit on maximum size = %llx\n", x.rlim_max);
unsigned long long int value = 0;
value -= 1;
printf("Maximum float = %llx", value);
return 0;
}
Output:
Current maximum size = 7fffffffffffffff // 9223372036854775807
Limit on maximum size = 7fffffffffffffff // 9223372036854775807
Maximum float = ffffffffffffffff // 18446744073709551615
The maximum size seems to be 2^63-1. The last bit is not being used at all.
Why is this the case? On a 64bit system, the virtual memory address range should be until 2^64-1 right?
I am aware that for addresses only 48 bits are used, but that doesn't seem to be related for the range of address space(Why there are two different lengths for addresses in 32 and 64 bit?)
As far as arm64 hardware goes, EL0 and EL1 share a translation regime, with TTBR0_EL1
and TTBR1_EL1
controlling the lower and upper halves of the address space respectively. Usually the lower half is used for userland, and the upper half for the kernel.
But as a first note, rlimit
has nothing to do with the hardware. This is about the operating system.
XNU has this (which also gets copied into Apple's SDKs):
#define RLIM_INFINITY (((__uint64_t)1 << 63) - 1) /* no limit */
So that's just the "no limit" value.
As a second note, despite the description of RLIMIT_AS
, this has nothing to do with the address space size. This is about the sum of all existing mappings in the process.
The real maximum address at which arm64 XNU will let you map memory is 0x00007ffffe000000. This value is just hardcoded for macOS. There is also a minimum address, which is set at process initialisation to the base address of the main binary in the process (usually 0x100000000) plus ASLR slide.
On Apple OSes other than macOS, the rules for the maximum address are more complicated, but here are the relevant code bits:
#if defined(XNU_PLATFORM_MacOSX) || defined(XNU_PLATFORM_DriverKit)
#define MACH_VM_MAX_ADDRESS_RAW 0x00007FFFFE000000ULL
#else
#define MACH_VM_MAX_ADDRESS_RAW 0x0000000FC0000000ULL
#endif
#define SHARED_REGION_BASE_ARM64 0x180000000ULL
#define SHARED_REGION_SIZE_ARM64 0x100000000ULL
/* end of shared region + 512MB for various purposes */
#define ARM64_MIN_MAX_ADDRESS (SHARED_REGION_BASE_ARM64 + SHARED_REGION_SIZE_ARM64 + 0x20000000)
// Max offset is 13.375GB for devices with "large" memory config
#define ARM64_MAX_OFFSET_DEVICE_LARGE (ARM64_MIN_MAX_ADDRESS + 0x138000000)
// Max offset is 9.375GB for devices with "small" memory config
#define ARM64_MAX_OFFSET_DEVICE_SMALL (ARM64_MIN_MAX_ADDRESS + 0x38000000)
vm_map_offset_t
pmap_max_64bit_offset(
__unused unsigned int option)
{
vm_map_offset_t max_offset_ret = 0;
#if defined(__arm64__)
const vm_map_offset_t min_max_offset = ARM64_MIN_MAX_ADDRESS; // end of shared region + 512MB for various purposes
if (option == ARM_PMAP_MAX_OFFSET_DEFAULT) {
max_offset_ret = arm64_pmap_max_offset_default;
} else if (option == ARM_PMAP_MAX_OFFSET_MIN) {
max_offset_ret = min_max_offset;
} else if (option == ARM_PMAP_MAX_OFFSET_MAX) {
max_offset_ret = MACH_VM_MAX_ADDRESS;
} else if (option == ARM_PMAP_MAX_OFFSET_DEVICE) {
if (arm64_pmap_max_offset_default) {
max_offset_ret = arm64_pmap_max_offset_default;
} else if (max_mem > 0xC0000000) {
// devices with > 3GB of memory
max_offset_ret = ARM64_MAX_OFFSET_DEVICE_LARGE;
} else if (max_mem > 0x40000000) {
// devices with > 1GB and <= 3GB of memory
max_offset_ret = ARM64_MAX_OFFSET_DEVICE_SMALL;
} else {
// devices with <= 1 GB of memory
max_offset_ret = min_max_offset;
}
} else if (option == ARM_PMAP_MAX_OFFSET_JUMBO) {
if (arm64_pmap_max_offset_default) {
// Allow the boot-arg to override jumbo size
max_offset_ret = arm64_pmap_max_offset_default;
} else {
max_offset_ret = MACH_VM_MAX_ADDRESS; // Max offset is 64GB for pmaps with special "jumbo" blessing
}
} else {
panic("pmap_max_64bit_offset illegal option 0x%x", option);
}
assert(max_offset_ret <= MACH_VM_MAX_ADDRESS);
assert(max_offset_ret >= min_max_offset);
#else
panic("Can't run pmap_max_64bit_offset on non-64bit architectures");
#endif
return max_offset_ret;
}
So on iOS and other non-macOS configs, the address size limit is either 0x0000000fc0000000 if you have "jumbo" maps (which you can get with the com.apple.developer.kernel.extended-virtual-addressing
entitlement, or on some devices with com.apple.developer.kernel.increased-memory-limit
), and otherwise either 0x00000002a0000000, 0x00000002b8000000 or 0x00000003b8000000, depending on how much physical memory the device has.
Note that the latter three sizes are subject to change though, because they're calculated from the size of the shared cache region bounds, which is itself subject to change. The "9.375GB" and "13.375GB" comments are also wrong today, because they stem from a time when SHARED_REGION_SIZE_ARM64
was 0xa0000000
.