我使用的是ARM64系统(M1).

$ uname -m
arm64

这是我用来找出系统上的虚拟地址空间范围的C程序.

#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;
}

输出:

Current maximum size = 7fffffffffffffff // 9223372036854775807
Limit on maximum size = 7fffffffffffffff // 9223372036854775807
Maximum float = ffffffffffffffff // 18446744073709551615

最大大小似乎是2^63-1.最后一位根本没有被使用.

为何会是这样呢?在64位系统上,虚拟内存地址范围应该是到2^64-1,对吗?

我知道地址只使用了48位,但这似乎与地址空间的范围(Why there are two different lengths for addresses in 32 and 64 bit?)无关

推荐答案

就ARM64硬件而言,EL0和EL1共享一种转换机制,TTBR0_EL1TTBR1_EL1分别控制地址空间的下半部分和上半部分.通常,下半部分用于用户空间,上半部分用于内核.

但首先要注意的是,rlimit与硬件无关.这是关于操作系统的.

XNU有这样的功能(它也会被复制到苹果的SDK中):

#define RLIM_INFINITY   (((__uint64_t)1 << 63) - 1)     /* no limit */

所以这只是"无限制"的值.

第二个注意事项是,尽管描述了RLIMIT_AS,但这与地址空间大小无关.这大约是the sum of all existing mappings in the process.

Arm64 XNU允许您映射内存的实际最大地址是0x00007ffffe000000.This value is just hardcoded for macOS.还有一个最小地址,它在进程初始化时设置为进程中主二进制的基地址(通常为0xThis value is just hardcoded for macOS000000)加上ASLR幻灯片.

在MacOS以外的苹果操作系统上,最大地址的规则更为复杂,但以下是相关的代码位:

osfmk/mach/arm/vm_param.h:

#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

osfmk/mach/shared_region.h:

#define SHARED_REGION_BASE_ARM64                0x180000000ULL
#define SHARED_REGION_SIZE_ARM64                0x100000000ULL

osfmk/arm/pmap/pmap.c:

/* 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;
}

因此,在iOS和其他非MacOS配置上,如果您有"巨型"映射(您可以使用com.apple.developer.kernel.extended-virtual-addressing授权获得,或者在some设备上使用com.apple.developer.kernel.increased-memory-limit),则地址大小限制为0x0000000fc0000000;否则,根据设备的物理内存大小,地址大小限制为0x00000002a0000000、0x00000002b8000000或0x00000003b8000000.

请注意,后三个大小可能会发生变化,因为它们是根据共享缓存区域边界的大小计算的,而共享缓存区域边界本身可能会发生变化."9.375 GB"和"13.375 GB"的 comments 在今天也是错误的,因为它们源于SHARED_REGION_SIZE_ARM640xa0000000的时代.

C++相关问答推荐

C中空终止符后面的数字?

为什么PLT表中没有push指令?

在传统操作系统上可以在虚拟0x0写入吗?

Clang:如何强制运行时错误的崩溃/异常由于-fsanitize=undefined

使用双指针动态分配和初始化2D数组

为静态库做准备中的奇怪行为

我的程序在收到SIGUSR1信号以从PAUSE()继续程序时总是崩溃()

轮询libusb_pollfd struct 列表的正确方式是什么?

判断X宏的空性

为什么数组的最后一个元素丢失了?

为什么未初始化的 struct 的数组从另一个数组获取值?

为什么realloc函数在此代码中修改变量?

如何确保我将使用C标准库函数的函数版本,如&getc";,而不是类似函数的宏版本?

我在C中运行和调试时得到了不同的输出

C语言中的数字指针

Printf()在C中打印终止字符之后的字符,我该如何解决这个问题?

Fprintf正在写入多个 struct 成员,并且数据过剩

C 语言中霍尔分区的快速排序算法

在 C 中的 scanf() 格式说明符中使用宏获取字符串长度

比 * 更快的乘法