2013-02-09 16 views
14

Perché host_statistics64() in OS X 10.6.8 (non so se altre versioni hanno questo problema) restituisce i conteggi per la memoria libera, attiva, inattiva e cablata che non sommano alla quantità totale di ram ? E perché manca un numero inconsistente di pagine?Perché host_statistics64() restituisce risultati incoerenti?

L'output seguente rappresenta il numero di pagine non classificate come libere, attive, inattive o cablate su dieci secondi (campionate approssimativamente una volta al secondo).

458 
243 
153 
199 
357 
140 
304 
93 
181 
224 

Il codice che produce i numeri di cui sopra è:

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

'vm_stat', che utilizza lo stesso codice, ha lo stesso problema di non-aggiunta. Quindi, non è un problema nel tuo codice di per sé. – nneonneo

+0

Con ML e 8 GB di RAM, la parte mancante è più grande (circa 2400). – patrix

+0

E dare un'occhiata a http://opensource.apple.com/source/top/top-73/libtop.c, sembra esserci qualche calcolo speciale in corso lì. – patrix

risposta

6

TL; DR:

  • host_statistics64() ottenere informazioni da diverse fonti che potrebbero costare tempo e potrebbero produrre risultati inconsistenti.
  • host_statistics64() ottiene alcune informazioni da variabili con nomi come vm_page_foo_count. Ma non tutte queste variabili sono prese in considerazione, ad es. vm_page_stolen_count no.
  • Il noto /usr/bin/top aggiunge pagine rubate al numero di pagine cablate. Questo è un indicatore che queste pagine dovrebbero essere prese in considerazione durante il conteggio delle pagine.

Note

  • Sto lavorando su un MacOS 10.12 con Darwin Kernel Version 16.5.0 XNU-3789.51.2 ~ 3/RELEASE_X86_64 x86_64 ma tutto il comportamento è completamente riproducibile.
  • Ho intenzione di collegare molto un codice sorgente della versione XNU che uso sulla mia macchina. Può essere trovato qui: xnu-3789.51.2.
  • Il programma che hai scritto è praticamente lo stesso di /usr/bin/vm_stat che è solo un wrapper per host_statistics64() (e host_statistics()). Il codice sorgente corrispondente può essere trovato qui: system_cmds-496/vm_stat.tproj/vm_stat.c.

In che modo host_statistics64() si inserisce in XNU e come funziona?

Come Widley conosce il kernel OS X si chiama XNU (X ORA È N OT U NIX) e "è un kernel ibrido che combina il kernel Mach sviluppato alla Carnegie Mellon University con i componenti da FreeBSD e C++ API per scrivere driver chiamati IOKit." (https://github.com/opensource-apple/xnu/blob/10.12/README.md)

La gestione della memoria virtuale (VM) fa parte del Mach quindi host_statistics64() si trova qui. Diamo uno sguardo più da vicino la sua attuazione che è contenuta nel xnu-3789.51.2/osfmk/kern/host.c.

La firma della funzione è

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

Le prime linee rilevanti sono

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

Otteniamo host_vm_stat che è di tipo vm_statistics64_data_t. Questo è solo un typedef struct vm_statistics64 come si può vedere in xnu-3789.51.2/osfmk/mach/vm_statistics.h. E otteniamo le informazioni sul processore dal makro PROCESSOR_DATA() definito in xnu-3789.51.2/osfmk/kern/processor_data.h. Compiliamo host_vm_stat mentre eseguiamo il ciclo di tutti i nostri processori semplicemente sommando i numeri rilevanti.

Come potete vedere, troviamo alcune statistiche ben note come zero_fill_count o compressions ma non tutte coperte da host_statistics64().

Le prossime linee in questione sono:

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; 

riutilizziamo stat e rendono la nostra struct uscita. Abbiamo quindi riempire free_count con la somma di due unsigned long chiamato vm_page_free_count e vm_page_speculative_count. Raccogliamo gli altri dati rimanenti nello stesso modo (utilizzando variabili denominate vm_page_foo_count) o prendendo le statistiche da host_vm_stat che abbiamo compilato sopra.

1. Conclusione Raccogliamo dati da diverse fonti. O dalle informazioni del processore o dalle variabili chiamate vm_page_foo_count. Questo costa tempo e potrebbe finire in qualche inconsistenza, il fatto è che VM è un processo molto veloce e continuo.

Diamo uno sguardo più da vicino alle variabili già menzionate vm_page_foo_count. Essi sono definiti in xnu-3789.51.2/osfmk/vm/vm_page.h come segue:

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

Questo è un sacco di statistiche relative abbiamo solo l'accesso ad un numero molto limitato utilizzando host_statistics64(). La maggior parte di queste statistiche viene aggiornata in xnu-3789.51.2/osfmk/vm/vm_resident.c. Per esempio questa funzione rilascia pagine alla lista delle pagine libere:

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

Molto interessante è extern unsigned int vm_page_stolen_count; /* Count of stolen pages not acccounted in zones */. Quali sono le pagine rubate? Sembra che ci siano dei meccanismi per togliere una pagina da alcuni elenchi anche se di solito non viene sfogliata. Uno di questi meccanismi è età di una pagina nell'elenco di pagine speculative. xnu-3789.51.2/osfmk/vm/vm_page.h noi

* 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 

dice che è davvero void vm_pageout_scan(void) che incrementa vm_page_stolen_count. È possibile trovare il codice sorgente corrispondente in xnu-3789.51.2/osfmk/vm/vm_pageout.c.

Penso che le pagine rubate non vengano prese in considerazione durante il calcolo delle statistiche VM a host_statistics64().

La prova che ho ragione

Il modo migliore per dimostrare questo sarebbe compilare XNU con una versione personalizzata di host_statistics64() a mano. Non ho avuto l'opportunità di farlo, ma proverò presto.

Fortunatamente non siamo gli unici interessati alle corrette statistiche VM. Pertanto dovremmo dare un'occhiata all'implementazione del ben noto /usr/bin/top (non contenuto in XNU) che è completamente disponibile qui: top-108 (Ho appena scelto il macOS 10.12.4 release).

Diamo uno sguardo a top-108/libtop.c dove troviamo il seguente:

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 è di tipo libtop_tsamp_t che è una struttura definita in top-108/libtop.h. Contiene tra l'altro vm_statistics64_data_t vm_stat e uint64_t pages_stolen.

Come potete vedere, static int libtop_tsamp_update_vm_stats(libtop_tsamp_t* tsamp) ottiene tsamp->vm_stat compilato da host_statistics64() come lo conosciamo. Successivamente controlla se tsamp->pages_stolen > 0 e lo aggiunge al campo wire_count di tsamp->vm_stat.

2. Conclusione Noi non ottenere il numero di queste pagine rubate se usiamo host_statistics64() come in /usr/bin/vm_stat o il vostro codice di esempio!

Perché lo standard host_statistics64() è implementato così com'è?

Onestamente, non lo so. Il paging è un processo complesso e quindi un'osservazione in tempo reale un compito impegnativo. Dobbiamo notare che non sembra esserci alcun bug nella sua implementazione. Penso che non avremmo nemmeno un numero preciso di pagine al 100% se potessimo avere accesso a vm_page_stolen_count. L'implementazione di /usr/bin/top non conta le pagine rubate se il loro numero non è molto grande.

Un'altra cosa interessante è un commento sopra la funzione static void update_pages_stolen(libtop_tsamp_t *tsamp) che è /* This is for <rdar://problem/6410098>. */. Open Radar è un sito di segnalazione bug per il software Apple e solitamente classifica i bug nel formato indicato nel commento. Non sono riuscito a trovare il bug correlato; forse si trattava di pagine mancanti.

Spero che queste informazioni possano aiutarti un po '. Se riesco a compilare la versione più recente (e personalizzata) di XNU sulla mia macchina, te lo farò sapere. Forse questo porta interessanti intuizioni.

1

appena notato che se si aggiunge compressor_page_count nella miscela che si ottiene molto più vicino alla quantità effettiva di RAM nella macchina.

Questa è un'osservazione, non una spiegazione, e collegamenti a dove questo è stato correttamente documentato sarebbe bello avere!

+0

Le pagine compresse sono uscite in 10.9, come afferma la domanda, era 10.6. Ho aggiornato il codice per 10.9, ma manca ancora approssimativamente la stessa quantità di pagine sotto 10.9 (con il campo aggiunto) come ha fatto sotto 10.6. –

Problemi correlati