为什么host_statistics64()会返回不一致的结果?

时间:2013-02-09 15:37:37

标签: macos memory darwin mach

为什么OS X 10.6.8中的host_statistics64()(我不知道其他版本是否存在此问题)返回的计数为免费,活动,非活动和有线内存,但总计不超过内存?为什么它缺少不一致的页数?

以下输出表示十秒钟内未分类为空闲,活动,非活动或有线的页面数量(大约每秒采样一次)。

458
243
153
199
357
140
304
93
181
224

产生上述数字的代码是:

#include <stdio.h>
#include <mach/mach.h>
#include <mach/vm_statistics.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char** argv) {
        struct vm_statistics64 stats;
        mach_port_t host    = mach_host_self();
        natural_t   count   = HOST_VM_INFO64_COUNT;
        natural_t   missing = 0;
        int         debug   = argc == 2 ? !strcmp(argv[1], "-v") : 0;
        kern_return_t ret;
        int           mib[2];
        long          ram;
        natural_t     pages;
        size_t        length;
        int           i;

        mib[0] = CTL_HW;
        mib[1] = HW_MEMSIZE;
        length = sizeof(long);
        sysctl(mib, 2, &ram, &length, NULL, 0);
        pages  = ram / getpagesize();

        for (i = 0; i < 10; i++) {
                if ((ret = host_statistics64(host, HOST_VM_INFO64, (host_info64_t)&stats, &count)) != KERN_SUCCESS) {
                        printf("oops\n");
                        return 1;
                }

                /* updated for 10.9 */
                missing = pages - (
                        stats.free_count     +
                        stats.active_count   +
                        stats.inactive_count +
                        stats.wire_count     +
                        stats.compressor_page_count
                );

                if (debug) {
                        printf(
                                "%11d pages (# of pages)\n"
                                "%11d free_count (# of pages free) \n"
                                "%11d active_count (# of pages active) \n"
                                "%11d inactive_count (# of pages inactive) \n"
                                "%11d wire_count (# of pages wired down) \n"
                                "%11lld zero_fill_count (# of zero fill pages) \n"
                                "%11lld reactivations (# of pages reactivated) \n"
                                "%11lld pageins (# of pageins) \n"
                                "%11lld pageouts (# of pageouts) \n"
                                "%11lld faults (# of faults) \n"
                                "%11lld cow_faults (# of copy-on-writes) \n"
                                "%11lld lookups (object cache lookups) \n"
                                "%11lld hits (object cache hits) \n"
                                "%11lld purges (# of pages purged) \n"
                                "%11d purgeable_count (# of pages purgeable) \n"
                                "%11d speculative_count (# of pages speculative (also counted in free_count)) \n"
                                "%11lld decompressions (# of pages decompressed) \n"
                                "%11lld compressions (# of pages compressed) \n"
                                "%11lld swapins (# of pages swapped in (via compression segments)) \n"
                                "%11lld swapouts (# of pages swapped out (via compression segments)) \n"
                                "%11d compressor_page_count (# of pages used by the compressed pager to hold all the compressed data) \n"
                                "%11d throttled_count (# of pages throttled) \n"
                                "%11d external_page_count (# of pages that are file-backed (non-swap)) \n"
                                "%11d internal_page_count (# of pages that are anonymous) \n"
                                "%11lld total_uncompressed_pages_in_compressor (# of pages (uncompressed) held within the compressor.) \n",
                                pages, stats.free_count, stats.active_count, stats.inactive_count,
                                stats.wire_count, stats.zero_fill_count, stats.reactivations,
                                stats.pageins, stats.pageouts, stats.faults, stats.cow_faults,
                                stats.lookups, stats.hits, stats.purges, stats.purgeable_count,
                                stats.speculative_count, stats.decompressions, stats.compressions,
                                stats.swapins, stats.swapouts, stats.compressor_page_count,
                                stats.throttled_count, stats.external_page_count,
                                stats.internal_page_count, stats.total_uncompressed_pages_in_compressor
                        );
                }

                printf("%i\n", missing);
                sleep(1);
        }

        return 0;
}

2 个答案:

答案 0 :(得分:9)

TL; DR:

  • host_statistics64()从不同来源获取可能需要花费时间并且可能产生不一致结果的信息。
  • host_statistics64()通过名称为vm_page_foo_count的变量获取一些信息。但并非所有这些变量都被考虑在内,例如vm_page_stolen_count不是。
  • 众所周知的/usr/bin/top被盗网页添加到有线网页的数量中。这表示在计算页数时应考虑这些页面。

备注

  • 我正在使用 Darwin内核版本16.5.0 xnu-3789.51.2~3 / RELEASE_X86_64 x86_64 处理macOS 10.12,但所有行为都是完全可重现的。
  • 我要在我的机器上链接很多我使用的XNU版本的源代码。可在此处找到:xnu-3789.51.2
  • 您编写的程序与/usr/bin/vm_stat基本相同,host_statistics64()只是host_statistics()(和host_statistics64())的包装。可以在此处找到相应的源代码:system_cmds-496/vm_stat.tproj/vm_stat.c

host_statistics64()如何适应XNU以及它如何运作?

正如威德利所知,OS X内核被称为 XNU( X NU IS N OT U NIX)&#34;是一个混合内核,将Carnegie Mellon大学开发的Mach内核与FreeBSD和C ++ API的组件相结合,用于编写名为IOKit的驱动程序。&#34; https://github.com/opensource-apple/xnu/blob/10.12/README.md)< / p>

虚拟内存管理(VM)是 Mach 的一部分,因此kern_return_t host_statistics64(host_t host, host_flavor_t flavor, host_info64_t info, mach_msg_type_number_t * count); 位于此处。让我们仔细看看xnu-3789.51.2/osfmk/kern/host.c中包含的实现。

功能签名是

[...]
processor_t processor;
vm_statistics64_t stat;
vm_statistics64_data_t host_vm_stat;
mach_msg_type_number_t original_count;
unsigned int local_q_internal_count;
unsigned int local_q_external_count;
[...]
processor = processor_list;
stat = &PROCESSOR_DATA(processor, vm_stat);
host_vm_stat = *stat;

if (processor_count > 1) {
    simple_lock(&processor_list_lock);

    while ((processor = processor->processor_list) != NULL) {
        stat = &PROCESSOR_DATA(processor, vm_stat);

        host_vm_stat.zero_fill_count += stat->zero_fill_count;
        host_vm_stat.reactivations += stat->reactivations;
        host_vm_stat.pageins += stat->pageins;
        host_vm_stat.pageouts += stat->pageouts;
        host_vm_stat.faults += stat->faults;
        host_vm_stat.cow_faults += stat->cow_faults;
        host_vm_stat.lookups += stat->lookups;
        host_vm_stat.hits += stat->hits;
        host_vm_stat.compressions += stat->compressions;
        host_vm_stat.decompressions += stat->decompressions;
        host_vm_stat.swapins += stat->swapins;
        host_vm_stat.swapouts += stat->swapouts;
    }

    simple_unlock(&processor_list_lock);
}
[...]

第一个相关的行是

host_vm_stat

我们得到的vm_statistics64_data_t类型为typedef struct vm_statistics64。您可以在xnu-3789.51.2/osfmk/mach/vm_statistics.h中看到这只是PROCESSOR_DATA()。我们从xnu-3789.51.2/osfmk/kern/processor_data.h中定义的makro host_vm_stat获取处理器信息。我们通过简单地添加相关数字来填充zero_fill_count,同时循环遍历所有处理器。

正如您所看到的,我们会找到一些众所周知的统计信息,例如compressionshost_statistics64(),但并非stat = (vm_statistics64_t)info; stat->free_count = vm_page_free_count + vm_page_speculative_count; stat->active_count = vm_page_active_count; [...] stat->inactive_count = vm_page_inactive_count; stat->wire_count = vm_page_wire_count + vm_page_throttled_count + vm_lopage_free_count; stat->zero_fill_count = host_vm_stat.zero_fill_count; stat->reactivations = host_vm_stat.reactivations; stat->pageins = host_vm_stat.pageins; stat->pageouts = host_vm_stat.pageouts; stat->faults = host_vm_stat.faults; stat->cow_faults = host_vm_stat.cow_faults; stat->lookups = host_vm_stat.lookups; stat->hits = host_vm_stat.hits; stat->purgeable_count = vm_page_purgeable_count; stat->purges = vm_page_purged_count; stat->speculative_count = vm_page_speculative_count; 涵盖的所有统计信息。

下一个相关的行是:

stat

我们重用free_count并将其作为输出结构。然后,我们使用unsigned longvm_page_free_count两个vm_page_speculative_count的总和填充vm_page_foo_count。我们以相同的方式收集其他剩余数据(通过使用名为host_vm_stat的变量)或从上面填写的vm_page_foo_count中获取统计数据。

1。结论我们从不同来源收集数据。来自处理器信息或来自名为vm_page_foo_count的变量。这需要花费时间,并且可能以某种不一致的方式结束,因为VM是一个非常快速且持续的过程。

让我们仔细研究已经提到的变量extern unsigned int vm_page_free_count; /* How many pages are free? (sum of all colors) */ extern unsigned int vm_page_active_count; /* How many pages are active? */ extern unsigned int vm_page_inactive_count; /* How many pages are inactive? */ #if CONFIG_SECLUDED_MEMORY extern unsigned int vm_page_secluded_count; /* How many pages are secluded? */ extern unsigned int vm_page_secluded_count_free; extern unsigned int vm_page_secluded_count_inuse; #endif /* CONFIG_SECLUDED_MEMORY */ extern unsigned int vm_page_cleaned_count; /* How many pages are in the clean queue? */ extern unsigned int vm_page_throttled_count;/* How many inactives are throttled */ extern unsigned int vm_page_speculative_count; /* How many speculative pages are unclaimed? */ extern unsigned int vm_page_pageable_internal_count; extern unsigned int vm_page_pageable_external_count; extern unsigned int vm_page_xpmapped_external_count; /* How many pages are mapped executable? */ extern unsigned int vm_page_external_count; /* How many pages are file-backed? */ extern unsigned int vm_page_internal_count; /* How many pages are anonymous? */ extern unsigned int vm_page_wire_count; /* How many pages are wired? */ extern unsigned int vm_page_wire_count_initial; /* How many pages wired at startup */ extern unsigned int vm_page_free_target; /* How many do we want free? */ extern unsigned int vm_page_free_min; /* When to wakeup pageout */ extern unsigned int vm_page_throttle_limit; /* When to throttle new page creation */ extern uint32_t vm_page_creation_throttle; /* When to throttle new page creation */ extern unsigned int vm_page_inactive_target;/* How many do we want inactive? */ #if CONFIG_SECLUDED_MEMORY extern unsigned int vm_page_secluded_target;/* How many do we want secluded? */ #endif /* CONFIG_SECLUDED_MEMORY */ extern unsigned int vm_page_anonymous_min; /* When it's ok to pre-clean */ extern unsigned int vm_page_inactive_min; /* When to wakeup pageout */ extern unsigned int vm_page_free_reserved; /* How many pages reserved to do pageout */ extern unsigned int vm_page_throttle_count; /* Count of page allocations throttled */ extern unsigned int vm_page_gobble_count; extern unsigned int vm_page_stolen_count; /* Count of stolen pages not acccounted in zones */ [...] extern unsigned int vm_page_purgeable_count;/* How many pages are purgeable now ? */ extern unsigned int vm_page_purgeable_wired_count;/* How many purgeable pages are wired now ? */ extern uint64_t vm_page_purged_count; /* How many pages got purged so far ? */ 。它们在xnu-3789.51.2/osfmk/vm/vm_page.h中定义如下:

host_statistics64()

关于我们只能使用/* * vm_page_release: * * Return a page to the free list. */ void vm_page_release( vm_page_t mem, boolean_t page_queues_locked) { [...] vm_page_free_count++; [...] } 访问非常有限的数字的大量统计数据。这些统计数据中的大多数都在xnu-3789.51.2/osfmk/vm/vm_resident.c中更新。例如,此函数将页面释放到空闲页面列表:

extern unsigned int vm_page_stolen_count;  /* Count of stolen pages not acccounted in zones */

非常有趣的是* VM_PAGE_MAX_SPECULATIVE_AGE_Q * VM_PAGE_SPECULATIVE_Q_AGE_MS * defines the amount of time a speculative page is normally * allowed to live in the 'protected' state (i.e. not available * to be stolen if vm_pageout_scan is running and looking for * pages)... however, if the total number of speculative pages * in the protected state exceeds our limit (defined in vm_pageout.c) * and there are none available in VM_PAGE_SPECULATIVE_AGED_Q, then * vm_pageout_scan is allowed to steal pages from the protected * bucket even if they are underage. * * vm_pageout_scan is also allowed to pull pages from a protected * bin if the bin has reached the "age of consent" we've set 。什么是被盗页面?似乎有一些机制可以从某些列表中删除页面,即使它通常不会被分页。其中一种机制是推测页面列表中页面的 age xnu-3789.51.2/osfmk/vm/vm_page.h告诉我们

void vm_pageout_scan(void)

确实vm_page_stolen_count增加host_statistics64()。您可以在xnu-3789.51.2/osfmk/vm/vm_pageout.c中找到相应的源代码。

我认为计算host_statistics64()的VM统计数据时不会考虑被盗页面。

证明我是对的

证明这一点的最佳方法是手动编译带有/usr/bin/top自定义版本的XNU。我没有机会这样做,但很快就会尝试。

幸运的是,我们并不是唯一对正确的VM统计信息感兴趣的人。因此,我们应该看看知识渊博的static int libtop_tsamp_update_vm_stats(libtop_tsamp_t* tsamp) { kern_return_t kr; tsamp->p_vm_stat = tsamp->vm_stat; mach_msg_type_number_t count = sizeof(tsamp->vm_stat) / sizeof(natural_t); kr = host_statistics64(libtop_port, HOST_VM_INFO64, (host_info64_t)&tsamp->vm_stat, &count); if (kr != KERN_SUCCESS) { return kr; } if (tsamp->pages_stolen > 0) { tsamp->vm_stat.wire_count += tsamp->pages_stolen; } [...] return kr; } (未包含在XNU中)的实现,这里完全可用:top-108(我刚刚选择了macOS 10.12.4 release)。

让我们看一下top-108/libtop.c,我们会在其中找到以下内容:

tsamp

libtop_tsamp_t属于vm_statistics64_data_t vm_stat类型,是top-108/libtop.h中定义的结构。它包含uint64_t pages_stolenstatic int libtop_tsamp_update_vm_stats(libtop_tsamp_t* tsamp)等其他内容。

正如您所看到的,tsamp->vm_stathost_statistics64()tsamp->pages_stolen > 0填充,正如我们所知道的那样。之后,它会检查是否wire_count并将其添加到tsamp->vm_stat的{​​{1}}字段中。

2。结论如果我们只使用host_statistics64()中的/usr/bin/vm_stat或您的示例代码,我们就不会获得这些被盗网页的数量!

为什么host_statistics64()按原样实施?

老实说,我不知道。分页是一个复杂的过程,因此实时观察是一项具有挑战性的任务。我们必须注意到它的实现似乎没有错误。如果我们可以访问vm_page_stolen_count,我认为我们甚至无法获得100%准确的网页数。 /usr/bin/top的实施如果数量不是很大,则不会计算被盗网页的数量。

另一个有趣的事情是函数static void update_pages_stolen(libtop_tsamp_t *tsamp)上方的注释/* This is for <rdar://problem/6410098>. */Open Radar是Apple软件的错误报告网站,通常以评论中给出的格式对错误进行分类。我无法找到相关的bug;也许这是关于缺页。

我希望这些信息对您有所帮助。如果我设法在我的机器上编译最新(和定制)版本的XNU,我会告诉你。也许这会带来有趣的见解。

答案 1 :(得分:1)

注意到如果你将compressor_page_count添加到混音中,你会更接近机器中的实际RAM数量。

这是一个观察,而不是一个解释,并且链接到正确记录的位置会很好!