Frage Warum gibt host_statistics64 () inkonsistente Ergebnisse zurück?


Warum gibt host_statistics64 () in OS X 10.6.8 (ich weiß nicht, ob andere Versionen dieses Problem haben) Zähler für freien, aktiven, inaktiven und verdrahteten Speicher zurück, die nicht zur Gesamtmenge des RAMs addiert werden? Und warum fehlt eine inkonsistente Anzahl von Seiten?

Die folgende Ausgabe gibt die Anzahl der Seiten an, die nicht als frei, aktiv, inaktiv oder verdrahtet über zehn Sekunden (ungefähr einmal pro Sekunde) klassifiziert sind.

458
243
153
199
357
140
304
93
181
224

Der Code, der die obigen Zahlen erzeugt, ist:

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

14
2018-02-09 15:37


Ursprung


Antworten:


TL; DR: 

  • host_statistics64() Informieren Sie sich über verschiedene Quellen, die möglicherweise Zeit kosten und zu widersprüchlichen Ergebnissen führen können.
  • host_statistics64() erhält einige Informationen von Variablen mit Namen wie vm_page_foo_count. Aber nicht alle diese Variablen werden berücksichtigt, z. vm_page_stolen_count ist nicht.
  • Die gut bekannten /usr/bin/top fügt hinzu gestohlene Seiten zur Anzahl von verdrahtete Seiten. Dies ist ein Indikator dafür, dass diese Seiten beim Zählen von Seiten berücksichtigt werden sollten.

Anmerkungen

  • Ich arbeite an einem macOS 10.12 mit Darwin Kernel Version 16.5.0 xnu-3789.51.2 ~ 3 / RELEASE_X86_64 x86_64 aber alles Verhalten ist vollständig reproduzierbar.
  • Ich werde einen Quellcode der XNU-Version, die ich auf meinem Rechner verwende, verlinken. Es kann hier gefunden werden: xnu-3789.51.2.
  • Das Programm, das Sie geschrieben haben, ist im Grunde dasselbe /usr/bin/vm_stat Das ist nur ein Wrapper für host_statistics64() (und host_statistics()). Der entsprechende Quellcode kann hier gefunden werden: system_cmds-496 / vm_stat.tproj / vm_stat.c.

Wie geht es? host_statistics64() passen in XNU und wie funktioniert es?

Wie Widley weiß, wird der OS X-Kernel aufgerufen XNU (XNU IST NOT UNIX) und "ist ein Hybrid-Kernel, der den an der Carnegie Mellon University entwickelten Mach-Kernel mit Komponenten von FreeBSD und C ++ API zum Schreiben von Treibern namens IOKit kombiniert." (https://github.com/opensource-apple/xnu/blob/10.12/README.md)

Die virtuelle Speicherverwaltung (VM) ist Teil von Mach deshalb host_statistics64() befindet sich hier. Werfen wir einen genaueren Blick auf die Implementierung, die in. Enthalten ist xnu-3789.51.2 / osfmk / kern / host.c.

Die Funktionssignatur ist

kern_return_t
host_statistics64(host_t host, host_flavor_t flavor, host_info64_t info, mach_msg_type_number_t * count);

Die ersten relevanten Zeilen sind

[...]
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);
}
[...]

Wir bekommen host_vm_stat welches vom Typ ist vm_statistics64_data_t. Dies ist nur ein typedef struct vm_statistics64 wie du sehen kannst xnu-3789.51.2 / osfmk / mach / vm_statistics.h. Und wir bekommen Prozessorinformationen vom Makro PROCESSOR_DATA() definiert in xnu-3789.51.2 / osfmk / kern / prozessor_daten.h. Wir füllen host_vm_stat während wir alle unsere Prozessoren durchlaufen, indem wir einfach die relevanten Zahlen addieren.

Wie Sie sehen können, finden wir einige bekannte Statistiken wie zero_fill_count oder compressions aber nicht alles abgedeckt von host_statistics64().

Die nächsten relevanten Zeilen sind:

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;

Wir verwenden es wieder stat und mach es zu unserer Output-Struktur. Wir füllen dann aus free_count mit der Summe von zwei unsigned long namens vm_page_free_count und vm_page_speculative_count. Wir sammeln die anderen verbleibenden Daten auf die gleiche Weise (unter Verwendung von Variablen mit dem Namen vm_page_foo_count) oder indem man die Statistiken ausnimmt host_vm_stat was wir oben ausgefüllt haben.

1. Schlussfolgerung Wir sammeln Daten aus verschiedenen Quellen. Entweder von Prozessorinformationen oder von Variablen aufgerufen vm_page_foo_count. Dies kostet Zeit und könnte in einigen Ungereimtheiten enden, da VM ein sehr schneller und kontinuierlicher Prozess ist.

Sehen wir uns die bereits erwähnten Variablen genauer an vm_page_foo_count. Sie sind definiert in xnu-3789.51.2 / osfmk / vm / vm_page.h wie folgt:

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 ? */

Das ist eine Menge Statistiken, die wir nur mit einer sehr begrenzten Anzahl von Benutzern erhalten host_statistics64(). Die meisten dieser Statistiken werden in aktualisiert xnu-3789.51.2 / osfmk / vm / vm_resident.c. Zum Beispiel gibt diese Funktion Seiten in die Liste der freien Seiten frei:

/*
*   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++;
    [...]
}

Sehr interessant ist extern unsigned int vm_page_stolen_count; /* Count of stolen pages not acccounted in zones */. Was sind gestohlene Seiten? Es scheint, als gäbe es Mechanismen, um eine Seite aus einigen Listen herauszunehmen, obwohl sie normalerweise nicht ausgelagert würde. Einer dieser Mechanismen ist der Alter einer Seite in der spekulativen Seitenliste. xnu-3789.51.2 / osfmk / vm / vm_page.h sagt uns

* 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

Es ist in der Tat void vm_pageout_scan(void) das erhöht sich vm_page_stolen_count. Sie finden den entsprechenden Quellcode in xnu-3789.51.2 / osfmk / vm / vm_pageout.c.

Ich glaube, gestohlene Seiten werden bei der Berechnung von VM-Statistiken nicht berücksichtigt. A host_statistics64() tut.

Beweise, dass ich recht habe

Der beste Weg, dies zu beweisen, wäre die Kompilierung von XNU mit einer angepassten Version von host_statistics64() von Hand. Ich hatte keine Gelegenheit dies zu tun, werde es aber bald versuchen.

Glücklicherweise sind wir nicht die einzigen, die an korrekten VM-Statistiken interessiert sind. Deshalb sollten wir uns die Implementierung von "gut bekannt" ansehen /usr/bin/top (nicht in XNU enthalten), die hier vollständig verfügbar ist: top-108 (Ich habe gerade die ausgewählt macOS 10.12.4 Veröffentlichung).

Werfen wir einen Blick darauf top-108 / libtop.c Wo finden wir folgendes:

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

tsamp ist vom Typ libtop_tsamp_t was ist eine Struktur definiert in top-108 / libtop.h. Es enthält unter anderem vm_statistics64_data_t vm_stat und uint64_t pages_stolen.

Wie du siehst, static int libtop_tsamp_update_vm_stats(libtop_tsamp_t* tsamp) bekommt tsamp->vm_stat befüllt von host_statistics64() wie wir wissen. Danach prüft es, ob tsamp->pages_stolen > 0 und addiert es zu den wire_count Bereich tsamp->vm_stat.

2. Schlussfolgerung Wir werden die Anzahl dieser gestohlenen Seiten nicht erhalten, wenn wir nur verwenden host_statistics64() wie in /usr/bin/vm_stat oder dein Beispielcode!

Warum ist host_statistics64() implementiert wie es ist?

Ehrlich gesagt, ich weiß es nicht. Paging ist ein komplexer Prozess und daher eine Echtzeitbeobachtung eine herausfordernde Aufgabe. Wir müssen feststellen, dass es bei der Implementierung keinen Bug zu geben scheint. Ich denke, wir würden nicht einmal eine 100% genaue Anzahl von Seiten bekommen, wenn wir Zugang dazu hätten vm_page_stolen_count. Die Implementierung von /usr/bin/top zählt gestohlene Seiten nicht, wenn ihre Anzahl nicht sehr groß ist.

Eine weitere interessante Sache ist ein Kommentar über der Funktion static void update_pages_stolen(libtop_tsamp_t *tsamp) welches ist /* This is for <rdar://problem/6410098>. */. Öffne Radar ist eine Fehlermeldeseite für Apple-Software und klassifiziert Fehler normalerweise in dem im Kommentar angegebenen Format. Ich konnte den zugehörigen Fehler nicht finden. Vielleicht ging es um fehlende Seiten.

Ich hoffe, diese Informationen könnten Ihnen ein wenig helfen. Wenn es mir gelingt, die neueste (und angepasste) Version von XNU auf meinem Rechner zu kompilieren, werde ich es Ihnen mitteilen. Vielleicht bringt das interessante Erkenntnisse.


6
2018-04-08 20:40



Habe gerade gemerkt, dass wenn du hinzufügst compressor_page_count in die Mischung kommt man viel näher an die tatsächliche Menge an RAM in der Maschine.

Dies ist eine Beobachtung, keine Erklärung, und Links zu denen, wo dies richtig dokumentiert wurde, wäre schön zu haben!


1
2018-04-01 05:20