为什么 macOS 上的 virtual_size 与 Linux 上的 VmSize 如此不同?

问题描述 投票:0回答:2

我读到,macOS 上的

virtual_size
(来自
MACH_TASK_BASIC_INFO
)大致相当于 Linux 上的
VmSize
(由
/proc/self/status
报道)。但它们之间却相差了1000多倍。

考虑这个示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#ifdef __APPLE__
#include <mach/mach.h>
#include <mach/task_info.h>
#include <mach/mach_error.h>
#endif

#define MEGABYTE 1048576.0

#ifdef __linux__
typedef struct {
    size_t virtual_size;
    size_t resident_size;
    size_t resident_size_max;
} memory_stats_t;
#endif

int main(void) {
#if defined(__linux__)
    memory_stats_t stats = {};
    FILE* file = fopen("/proc/self/status", "r");
    if (file == NULL) return perror("fopen"), EXIT_FAILURE;
    char line[256];
    while (fgets(line, sizeof(line), file)) {
        if (sscanf(line, "VmSize: %lu kB", &stats.virtual_size) == 1) {
            stats.virtual_size *= 1024;
        } else if (sscanf(line, "VmRSS: %lu kB", &stats.resident_size) == 1) {
            stats.resident_size *= 1024;
        } else if (sscanf(line, "VmHWM: %lu kB", &stats.resident_size_max) == 1) {
            stats.resident_size_max *= 1024;
        }
    }
    fclose(file);
#elif defined(__APPLE__)
    task_t task = MACH_PORT_NULL;
    kern_return_t kr = task_for_pid(mach_task_self(), getpid(), &task);
    if (kr != KERN_SUCCESS) {
        return fprintf(stderr, "task_for_pid error: %s\n", mach_error_string(kr)), EXIT_FAILURE;
    }
    mach_task_basic_info_data_t stats;
    mach_msg_type_number_t task_info_count = MACH_TASK_BASIC_INFO_COUNT;
    kr = task_info(task, MACH_TASK_BASIC_INFO, (task_info_t)&stats, &task_info_count);
    if (kr != KERN_SUCCESS) {
        return fprintf(stderr, "task_info error: %s\n", mach_error_string(kr)), EXIT_FAILURE;
    }
#else
#   error "Unsupported OS"
#endif

    // Print memory usage information
    printf("=== Current process memory usage [PID: %d]\n", getpid());
    printf("Virtual:       %.3f MB\n", stats.virtual_size / MEGABYTE);
    printf("Resident:      %.3f MB\n", stats.resident_size / MEGABYTE);
    printf("Resident-Peak: %.3f MB\n", stats.resident_size_max / MEGABYTE);
    return EXIT_SUCCESS;
}

我用

gcc -o memusage memusage.c && ./memusage
编译并运行它。 (当然,在 macOS 上
gcc
实际上是 Apple Clang,但相同的命令行也可以工作。)

这是我在 Linux 上得到的输出:

=== Current process memory usage [PID: 1403418]
Virtual:       2.594 MB
Resident:      1.125 MB
Resident-Peak: 1.125 MB

对于这样一个简单的程序来说,这似乎是合理的内存使用情况。

相比之下,macOS 的报告如下:

=== Current process memory usage [PID: 68603]
Virtual:       4169.246 MB
Resident:      0.699 MB
Resident-Peak: 0.699 MB

驻留内存使用率虽然在 macOS 上较低,但也是完全合理的。

但据报道 macOS 上的虚拟内存使用量超过 4GB。这么小的程序怎么会使用超过4GB的虚拟内存呢?

很明显,macOS 上的

mach_task_basic_info_data_t.virtual_size
的定义或测量必须与 Linux 上的
VmSize
非常不同。但谁能解释一下具体有什么区别吗? macOS 上是否有任何 API 可以使用与 Linux 大致相似的定义来给出进程的虚拟内存使用情况?我并不期待完全相同的东西,但千倍的差异距离“大致相似的定义”还有很长的路要走。

c linux macos procfs mach
2个回答
0
投票

我解决了。我记得 macOS 将一个“共享区域”映射到每个进程的地址空间中。 Linux 没有真正的等价物。好吧,从技术上讲 Linux 确实如此(vDSO、vvar 和 vsyscall),但它们的大小(千字节)可以忽略不计。相比之下,macOS 共享区域包含千兆字节的数据。它包含的主要内容是 dyld 共享缓存,其中包含操作系统附带的所有共享库。 (也许还有其他一些东西——共享区域似乎包含更多的映射??)XNU还将compage映射到每个进程中,这更直接相当于Linux中的vDSO/etc,但同样足够小,您可以忽略它。

因此,Linux 和 macOS 之间“虚拟大小”的定义基本相同——进程中所有虚拟内存映射的大小之和。不同之处在于 macOS 将额外的约 4GB 数据映射到每个进程中。

因此,如果您想要在 macOS(更直接相当于 Linux)上进行虚拟大小测量,您可以考虑不包括共享区域中的映射的虚拟大小。我不知道是否有任何API可以直接给你这个数字,但是你可以通过添加内存区域的大小来计算它。请参阅下面的示例代码:

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <libproc.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <mach/shared_region.h>

#define MEGABYTE 1048576.0

void handle_error(const char* doing, kern_return_t kr) {
    if (kr == KERN_SUCCESS) return;
    fprintf(stderr,"FATAL ERROR: %s: (%d) %s\n", doing, kr, mach_error_string(kr));
    exit(EXIT_FAILURE);
}

void print_memusage_basic_info(void) {
    task_t task = MACH_PORT_NULL;
    handle_error("task_for_pid",task_for_pid(mach_task_self(), getpid(), &task));
    mach_task_basic_info_data_t stats;
    mach_msg_type_number_t task_info_count = MACH_TASK_BASIC_INFO_COUNT;
    handle_error("task_info",task_info(task, MACH_TASK_BASIC_INFO, (task_info_t)&stats, &task_info_count));
    printf("=== Current process [PID: %d] memory usage: per task_info\n", getpid());
    printf("Virtual:       %.3f MB\n", stats.virtual_size / MEGABYTE);
    printf("Resident:      %.3f MB\n", stats.resident_size / MEGABYTE);
    printf("Resident-Peak: %.3f MB\n", stats.resident_size_max / MEGABYTE);
}

bool in_shared_region(mach_vm_address_t address) {
    return address >= SHARED_REGION_BASE && address < (SHARED_REGION_BASE + SHARED_REGION_SIZE);
}

void print_memusage_regions(void) {
    vm_map_t task = mach_task_self();
    mach_vm_address_t address = 0;
    size_t sum = 0, srsum = 0;
    while (true) {
        mach_vm_size_t vmsize = 0;
        vm_region_basic_info_data_64_t info;
        mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
        mach_port_t object_name;
        kern_return_t kr = mach_vm_region(mach_task_self(), &address, &vmsize, VM_REGION_BASIC_INFO_64,
                                          (vm_region_info_t)&info, &info_count, &object_name);
        if (kr == KERN_SUCCESS) {
            if (in_shared_region(address)) {
                srsum += vmsize;
            }
            address += vmsize;
            sum += vmsize;
        } else if (kr == KERN_INVALID_ADDRESS) {
            break;
        } else handle_error("mach_vm_region",kr);
    }
    printf("=== Current process [PID: %d] memory usage: region-based\n", getpid());
    printf("Virtual:       %.3f MB\n", sum / MEGABYTE);
    printf("Shared Region: %.3f MB\n", srsum / MEGABYTE);
    printf("Non-Shared:    %.3f MB\n", (sum-srsum) / MEGABYTE);
}

int main(void) {
    print_memusage_basic_info();
    printf("\n");
    print_memusage_regions();
    return EXIT_SUCCESS;
}

在我的系统上打印:

=== Current process [PID: 77524] memory usage: per task_info
Virtual:       4168.246 MB
Resident:      0.695 MB
Resident-Peak: 0.695 MB

=== Current process [PID: 77524] memory usage: region-based
Virtual:       4168.246 MB
Shared Region: 4158.000 MB
Non-Shared:    10.246 MB

所以我们可以观察到:

  1. 将虚拟内存区域大小相加(通过
    mach_vm_region
    )会产生与
    MACH_TASK_BASIC_INFO
    报告的相同的数字
  2. 排除共享区域中的映射使我们的虚拟内存使用量在 Linux 上更具可比性
    VmSize
    ,并且对于这样一个简单的程序来说更可信。

请注意,您不能只从

SHARED_REGION_SIZE
中减去
virtual_size
,因为大部分共享区域未映射。在我的(旧)x86-64 Mac 上,
SHARED_REGION_SIZE
为 32GB,但实际映射的容量仅略高于 4GB。您需要迭代区域,并分别对属于共享区域的区域进行求和。

我相信共享区域映射是静态的 - 所以您可以做的是在进程启动时计算总共享区域虚拟内存大小,然后从

virtual_size
中减去该大小。对
task_info
的单次调用将比循环对
mach_vm_region
的多次调用要快得多。

可以说,共享区域不应该真正被视为您自己的应用程序虚拟内存消耗的一部分,因为它是操作系统拥有的内存并由所有进程共享。因此,从

virtual_size
中减去总映射共享区域内存确实具有逻辑意义。


0
投票

在您的系统上查找

task_info.h
文件。从这个文件的外观来看,
virtual_size
成员是以字节为单位提供的,而不是MB。

struct task_basic_info_32 {
        integer_t       suspend_count;  /* suspend count for task */
        natural_t       virtual_size;   /* virtual memory size (bytes) */
        natural_t       resident_size;  /* resident memory size (bytes) */
        time_value_t    user_time;      /* total user run time for
                                           terminated threads */
        time_value_t    system_time;    /* total system run time for
                                           terminated threads */
    policy_t    policy;     /* default policy for new threads */
};

同一文件中

_64
变体的定义类似。

© www.soinside.com 2019 - 2024. All rights reserved.