Introduzione
La libreria kvm fornisce un'interfaccia per accedere alla memoria
virtuale del kernel dei sistemi *BSD.
È possibile accedere sia ai sistemi in esecuzione, via /dev/kmem,
sia ai crashdump tramite i file core generati da savecore(8).
L'interfaccia kvm è stata introdotta per la prima volta in SunOS.
La differenza maggiore tra la versione Sun e quella BSD sta nella
gestione degli errori; a tale scopo l'interfaccia BSD introduce la
routine kvm_geterr(3).
Per la stesura di questo documento sono stati usati i sorgenti dei
sistemi NetBSD/i386 1.6.2 e OpenBSD/i386 3.5, ma non dovrebbero esserci
sostanziali differenze con gli altri sistemi *BSD.
PARTE 1: Le funzioni della libreria kvm
Di seguito vedremo le principali funzioni che la libreria libkvm mette
a disposizione dello sviluppatore.
kvm_open e kvm_close
La prima funzione da chiamare per utilizzare le libkvm è kvm_open()
che restituisce un descrittore da usare per accedere alla memoria
virtuale del kernel. Il prototipo di kvm_open() è
kvm_t *
kvm_open(const char *execfile, const char *corefile,
char *swapfile, int flags, const char *errstr)
"execfile" rappresenta l'immagine del kernel da esaminare. Se viene
posto a NULL viene preso in considerazione il sistema attualmente in
esecuzione indicato dal valore di _PATH_UNIX in "paths.h", ad esempio
#define _PATH_UNIX "/netbsd"
OpenBSD si comporta in modo leggermente diverso, se l'argomento è NULL
viene preso in considerazione il sistema in esecuzione definito da
_PATH_KSYMS e se questo non esiste viene usato il valore di _PATH_UNIX
#define _PATH_KSYMS "/dev/ksyms"
#define _PATH_UNIX "/bsd"
"corefile" è il device della memoria del kernel. In genere o è /dev/mem
oppure un core generato da savecore(8). Se viene posto a NULL viene
usato il valore di _PATH_MEM in "paths.h"
#define _PATH_MEM "/dev/mem"
"swapfile" indica lo swap device. Se posto a NULL assume il valore di
_PATH_DRUM in "paths.h"
#define _PATH_DRUM "/dev/drum"
"flags" indica le modalità di accesso al core file. Come per la open(2)
può assumere i valori O_RDONLY, O_WRONLY e O_RDWR che permettono di
accedere al file rispettivamente solo in lettura, solo in scrittura e
in lettura e scrittura.
Oltre ai suddetti valori "flags" può assumere il valore KVM_NO_FILES.
Utilizzando tale valore le kvm verranno inizializzate in modo da usare
solo le sysctl(3) per raccogliere i dati dal kernel ignorando execfile,
corefile e swapfile.
In questa modalità sono disponibili solo alcune funzioni della libreria
kvm, e precisamente kvm_getproc2(3), kvm_getargv2(3) e kvm_getenvv2(3).
"errstr" indica come gestire gli errori, se è impostato a NULL gli
errori non vengono riportati. Se il valore è diverso da NULL i messaggi
di errore sono stampati su stderr preceduti dalla stringa "errstr".
In genere come valore di "errstr" si usa il nome del programma.
Oltre a kvm_open() esiste anche la funzione kvm_openfile(), che
differisce da kvm_open solo nella gestione degli errori. kvm_openfile()
fornisce una gestione degli errori in stile BSD.
In questo modo l'applicazione vede l'errore attraverso la funzione
kvm_geterr(), il prototipo è il seguente
kvm_t *
kvm_openfiles(const char *execfile, const char *corefile,
char *swapfile, int flags, char *errbuf);
Entrambe le funzioni restituiscono un valore di tipo "kmv_t", definito
in "kvm.h" come
typedef struct __kvm kvm_t;
la struttura "__kvm" è definita in "kvm_private.h" e contiene informazioni
sul kernel che si andrà ad utilizzare in tutte le funzioni della libreria,
nella seconda parte la vedremo in dettaglio.
kvm_close(), ovviamente, non fa altro che deallocare tutte le risorse
impegnate da kvm_open().
Restituisce 0 se ha successo, -1 negli altri casi, c'è poco altro da
aggiungere se non il prototipo
int
kvm_close(kvm_t *kd);
kvm_getprocs e kvm_getproc2
kvm_getprocs() serve a ottenere dal kernel informazioni sui processi
attivi
struct kinfo_proc *
kvm_getprocs(kvm_t *kd, int op, int arg, int *cnt)
"kd" è il descrittore restituito da kvm_open() o da kvm_openfiles().
"op" e "arg" servono a restringere il set di processi da prendere in
considerazione. "op" può assumere i seguenti valori
KERN_PROC_ALL tutti i processi
KERN_PROC_PID processi con ID pari al valore di "arg"
KERN_PROC_PGRP processi con "process group" = arg
KERN_PROC_SESSION processi con "session id" = arg
KERN_PROC_TTY processi con "tty device" = arg
KERN_PROC_UID processi con "effective user ID" = arg
KERN_PROC_GID processi con "effective group ID" = arg
KERN_PROC_RUID processi con "real user ID" = arg
KERN_PROC_GUID processi con "real user ID" = arg
se "op" è impostato a KERN_PROC_TTY, "arg" può anche assumere i valori
KERN_PROC_TTY_NODEV e KERN_PROC_TTY_REVOKE per selezionare
rispettivamente i processi senza "controlling tty" e quelli con il
proprio "controlling tty" revocato.
Il set di valori messo a disposizione da OpenBSD è leggermente diverso e
meno permissivo
KERN_PROC_KTHREAD tutti i processi (userlevel + kernel)
KERN_PROC_ALL tutti i processi a livello utente
KERN_PROC_PID questo e i seguenti come sopra
KERN_PROC_PGRP
KERN_PROC_SESSION
KERN_PROC_TTY
KERN_PROC_UID
KERN_PROC_RUID
infine "cnt" indica il numero di processi trovati.
La funzione ritorna i processi come array di strutture kinfo_proc definita
in "/usr/include/sys/sysctl.h"
struct kinfo_proc {
struct proc kp_proc;
struct eproc {
struct proc *e_paddr;
struct session *e_sess;
struct pcred e_pcred;
struct ucred e_ucred;
struct vmspace e_vm;
pid_t e_ppid;
pid_t e_pgid;
short e_jobc;
dev_t e_tdev;
pid_t e_tpgid;
struct session *e_tsess;
#define WMESGLEN 8
char e_wmesg[WMESGLEN];
segsz_t e_xsize;
short e_xrssize;
short e_xccount;
short e_xswrss;
long e_flag;
#define EPROC_CTTY 0x01
#define EPROC_SLEADER 0x02
char e_login[MAXLOGNAME];
pid_t e_sid;
long e_spare[3];
} kp_eproc;
};
"kp_proc" è una struttura di tipo "proc" che contiene le informazioni
(identificatori, indirizzi e timer vari, segnali, etc.) necessarie alla
gestione del processo.
Il resto della struttutra "kinfo_proc" è costituito da "kp_eproc" che è
una struttura "eproc" contenente tra l'altro l'id del padre e del gruppo,
informazioni sulla sessione, etc.
In parole povere si tratta di una struttura "proc" rinforzata.
kvm_getproc2() è simile a kvm_getprocs(), il prototipo è
struct kinfo_proc2 *
kvm_getproc2(kvm_t *kd, int op, int arg, int elemsize, int *cnt)
"elemsize" rappresenta il numero di byte della struttura "kinfo_proc2"
da restituire.
La differenza maggiore con kvm_getprocs() sta nel valore ritornato,
kvm_getproc2() ritorna i processi come array di strutture "kinfo_proc2"
definita in "sysctl.h". A differenza di "kinfo_proc", tale struttura
contiene molte più informazioni, per un elenco completo rimando al file
"sysctl.h".
In caso di errore entrambe le funzioni ritornano il valore NULL.
Concludiamo con del codice d'esempio tratto da fstat(1)
struct kinfo_proc2 *p;
int arg, what, cnt;
p = kvm_getproc2(kd, what, arg, sizeof(*p), &cnt))
"what" e "arg" valgono rispettivamente "KERN_PROC_ALL" e "0" se fstat
viene interrogato senza opzioni, e che diventano "KERN_PROC_PID" e
"PID_passato_come_parametro" se viene utilizzata l'opzione "-p PID"
oppure "KERN_PROC_UID" e "USER_passato_come_parametro" se si usa l'opzione
"-u USER".
kvm_nlist
kvm_nlist() permette di recuperare le voci della tabella dei simboli
relative ai nomi passati come argomento
int
kvm_nlist(kvm_t *kd, struct nlist *nl)
"kd" è il descrittore restituito da kvm_open() o da kvm_openfiles().
Se kvm_open() è chiamata con l'argomento "execfile" pari a NULL,
kvm_nlist() userà il database "/var/db/kvm.db" creato dal programma
kvm_mkdb(8), sotto OpenBSD il nome è "/var/db/kvm_bsd.db".
La lista di nomi "nl" è passata come array di strutture "nlist" terminato
da una struttura con il campo "n_name" pari a NULL.
La struttura "nlist" è definita in "nlist.h"
struct nlist {
__aconst char *n_name;
unsigned char n_type;
char n_other;
short n_desc;
unsigned long n_value;
};
dove in particolare "n_name" rappresenta il nome del simbolo, "n_value" è
il valore (indirizzo) del simbolo e "n_type" definisce uno dei seguenti
tipi, elencati sempre in "nlist.h"
#define N_UNDF 0x00
#define N_ABS 0x02
#define N_TEXT 0x04
#define N_DATA 0x06
#define N_BSS 0x08
#define N_INDR 0x0a
#define N_SIZE 0x0c
#define N_COMM 0x12
#define N_SETA 0x14
#define N_SETT 0x16
#define N_SETD 0x18
#define N_SETB 0x1a
#define N_SETV 0x1c
#define N_FN 0x1e
#define N_WARN 0x1e
kvm_nlist() restituisce il numero di entry non valide trovate, e -1 in
caso di errore.
Un tipico utilizzo di kvm_nlist() è il seguente
struct nlist nl[]={
#define IPSTAT 0
{"_ipstat"},
#define UDPSTAT 1
{"_udpstat"},
{NULL}
};
kvm_nlist(kd, nl);
In questo modo creiamo un array di strutture "nlist" di nome "nl" e ne
impostiamo solo il campo "n_name", gli altri campi verranno riempiti da
kvm_nlist() se nella tabella dei simboli sono presenti le voci "ipstat"
e "udpstat".
kvm_read e kvm_write
kvm_read() e kvm_write(), come si evince dal nome, leggono e scrivono la
memoria virtuale del kernel
ssize_t
kvm_read(kvm_t *kd, u_long addr, void *buf, size_t nbytes)
ssize_t
kvm_write(kvm_t *kd, u_long addr, const void *buf, size_t nbytes)
"kd" è il descrittore restituito da kvm_open() o da kvm_openfiles().
"addr" rappresenta l'indirizzo della memoria dal quale andare a leggere o
sul quale scrivere. "buf" e "nbytes" rappresentano rispettivamente il
buffer di lettura/scrittura e il numero di bytes da trasferire.
Come indirizzo, in genere, viene usato il campo "n_value" di una
struttura nlist restituita da kvm_nlist().
Entrambe le funzioni restituiscono il numero di byte trasferiti oppure
-1 in caso di errore.
Riprendendo l'esempio di prima un possibile utilizzo di kvm_read() è il
seguente
struct ipstat ips;
struct udpstat udps;
kvm_read(kd, nl[IPSTAT].n_value, &ips, sizeof ips);
kvm_read(kd, nl[UDPSTAT].n_value, &udps, sizeof udps);
dove nl[IPSTAT].n_value e nl[UDPSTAT].n_value sono valori restituiti da
kvm_nlist().
Mi fermo qui con la descrizioni delle funzioni poiché quelle rimanenti
sono abbastanza semplici da comprendere, del resto come quelle appena
illustrate, e rimando alle pagine del manuale per approfondimenti.
PARTE 2: Implementazione della libreria kvm
Vediamo adesso come sono implementate alcune delle funzioni viste prima
all'interno della libreria kvm.
I sorgenti della libreria si trovano in "/usr/src/lib/libkvm/".
Cominciamo col descrivere la struttura fondamentale della libreria ovvero
"kvm_t" che è definita in "kvm.h" nel seguente modo
typedef struct __kvm kvm_t;
La struttura "__kvm" è invece definita in "kvm_private.h"
struct __kvm {
const char *program;
char *errp;
char errbuf[_POSIX2_LINE_MAX];
DB *db;
int pmfd;
int vmfd;
int swfd;
int nlfd;
char alive;
struct kinfo_proc *procbase;
struct kinfo_proc2 *procbase2;
u_long usrstack;
u_long min_uva, max_uva;
int nbpg;
char *swapspc;
char *argspc, *argbuf;
int arglen;
char **argv;
int argc;
struct kcore_hdr *kcore_hdr;
size_t cpu_dsize;
void *cpu_data;
off_t dump_off;
struct vmstate *vmst;
struct pglist *vm_page_buckets;
int vm_page_hash_mask;
};
Procedendo, nel seguito dell'articolo, alla descrizione dell'
implementazione delle varie funzioni descriveremo via via i campi di
questa struttura che ci interessano.
kvm_open
La funzione kvm_open() è definita in "kvm.c", ed è costituita
essenzialmente da una chiamata a _kvm_open() definita nello stesso file.
Il prototipo di _kvm_open() è il seguente
static kvm_t *
_kvm_open(kvm_t *kd, const char *uf, const char *mf,
const char *sf, int flag, char *errout)
Per prima cosa _kvm_open() inizializza alcuni campi della struttura "kd"
di tipo "kvm_t"; ad esempio pone
kd->alive = KVM_ALIVE_DEAD
kd->alive identifica il tipo di kernel, i valori possibili, da
"kvm_private.h", sono
#define KVM_ALIVE_DEAD 0 se si sta lavorando su un core file
#define KVM_ALIVE_FILES 1 se il kernel è quello in esecuzione
è si sta quindi usando /dev/kmem
#define KVM_ALIVE_SYSCTL 2 se il kernel è quello in esecuzione
e si vuole usare solo sysctl(3) per
recuperare i dati dal kernel.
kd->alive viene modificato in base agli argomenti passati a kvm_open(),
ad esempio se come "flag" viene passato il valore KVM_NO_FILES
kd->alive varia di conseguenza
if (flag & KVM_NO_FILES) {
kd->alive = KVM_ALIVE_SYSCTL;
return(kd);
}
OpenBSD ha una gestione semplificata del campo "kd->alive" che può
assumere solo i valori 0 e 1, rispettivamente "dead" e "live"; di
conseguenza viene posto a 0 all'inizio e modificato se necessario.
Per il resto vengono riempiti i campi della struttura riguardanti i file
(execfile, corefile e swapfile) da usare. Se questi non sono indicati
dall'utente come argomenti, vengono ricavati dai valori indicati in
"paths.h"
Altro elemento da notare è il valore di "kd->db", che è una struttura di
tipo DB, che rappresenta il database "/var/db/kvm.db" (per OpenBSD
"/var/db/kvm_bsd.db"). All'inizio è posto a zero, e viene modificato con
una chiamata a kvm_dbopen() se come parametro "execfile" della kvm_open()
è stato indicato NULL e se tale database verifica il kernel in esecuzione.
kvm_dbopen() a sua volta esegue una chiamata a dbopen(3) indicando come
file il valore _PATH_KVMDB definito in "paths.h" come "/var/db/kvm.db"
e "/var/db/kvm_bsd.db" per OpenBSD.
kvm_nlist
Il prototipo di kvm_nlist(), definita in "kvm.c", è
int
kvm_nlist(kvm_t *kd, struct nlist *nl)
Viene innanzitutto fatto un controllo su "kd->db" per vedere se si deve
usare il database o fare una chiamata a __fdnlist(3).
"kd->db", come abbiamo visto prima, viene impostato da kvm_open(). Se vale
0 viene usata la funzione __fdnlist() con i parametri "kd->nlfd" che
indica il descrittore dell'execfile indicato all'apertura o in "paths.h",
e "nl" che è l'array di strutture nlist passato come secondo argomento a
kvm_nlist()
if (kd->db == 0) {
rv = __fdnlist(kd->nlfd, nl);
if (rv == -1)
_kvm_err(kd, 0, "bad namelist");
return (rv);
}
__fdnlist(), come nlist(), restituisce il numero di voci di nl non valide.
Se invece bisogna usare il database, le informazioni vengono ottenute
attraverso una chiamata a kd->db->get()
(kd->db->get)(kd->db, (DBT *)&rec, (DBT *)&rec, 0)
dove "rec" è una struttura di tipo DBT. Per maggiori informazioni su tale
funzione si legga la pagina del manuale relativa a "db(3)".
kvm_getprocs
kvm_getprocs(), di gran lunga la funzione più interessante della libreria, è definita nel file "kvm_proc.c"
struct kinfo_proc *
kvm_getprocs(kvm_t *kd, int op, int arg, int *cnt)
kvm_getprocs() usa strade diverse per compiere il suo lavoro in base
al tipo di kernel che si sta utilizzando. Per distinguere i vari casi
vengono usate tre macro, ISALIVE, ISKMEM e ISSYSCTL, definite in
"kvm_private.h" nel seguente modo
#define ISALIVE(kd) ((kd)->alive != KVM_ALIVE_DEAD)
#define ISKMEM(kd) ((kd)->alive == KVM_ALIVE_FILES)
#define ISSYSCTL(kd) ((kd)->alive == KVM_ALIVE_SYSCTL||ISKMEM(kd))
se si verifica il terzo caso, quando a kvm_open() viene passato il flag
KVM_NO_FILES, ovvero se si vogliono ignorare i file execfile, corefile e
swapfile, viene restituito un errore perché questa modalità non è
disponibile per kvm_getprocs().
Se invece "kd->alive" è pari a KVM_ALIVE_FILES le informazioni vengono
ottenute attraverso chiamate a sysctl(), mentre nei restanti casi viene
utilizzata la funzione kvm_deadprocs().
Come abbiamo visto prima OpenBSD ha una gestione semplificata del campo
"alive" e di conseguenza è definita solo la macro ISALIVE
#define ISALIVE(kd) ((kd)->alive)
Vediamo in dettaglio cosa accade (per chiarezza salto tutti i controlli
sui valori restituiti dalle varie funzioni) nel caso ISKMEM, che sotto
OpenBSD corrisponde a ISALIVE
if (ISKMEM(kd)) {
size = 0;
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = op;
mib[3] = arg;
st = sysctl(mib, 4, NULL, &size, NULL, 0);
...
kd->procbase = (struct kinfo_proc *)_kvm_malloc(kd, size);
...
st = sysctl(mib, 4, kd->procbase, &size, NULL, 0);
...
}
Ricordo che il prototipo della sysctl(3) è il seguente
int
sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp,
void *newp, size_t newlen)
"name" è un array di interi detto "MIB Management Information Base" che
indica a sysctl() quali informazioni sono richieste ed è strutturato a
livelli, "namelen" rappresenta il numero di elementi di tale array, il
nostro "mib" ha 4 elementi.
Le informazioni sono copiate nel buffer indicato da "oldp", le dimensioni
del buffer sono date attraverso la locazione specificata da "oldlenp",
che dopo una chiamata contiene la quantità di dati copiati.
Se come valore del parametro "oldp" viene indicato NULL si ottengono le
dimensioni dei dati disponibili.
"newp" e "newlen" sono utilizzate per settare un nuovo valore.
Prima di andare avanti torniamo all'array "mib". CTL_KERN definito in
"sys/sysctl.h" rappresenta il nome del livello più alto e indica che ci
interessano informazioni relative al kernel.
KERN_PROC è il livello successivo e indica a sysctl(3) che vogliamo la
tabella dei processi.
Il terzo e il quarto livello, nel nostro caso "op" e "arg", indicano
quale sottoinsieme di processi ci interessa. Possono assumere i valori
indicati nella prima parte di questo testo nella descrizione della
funzione kvm_getprocs().
Il valore restituito è un array di strutture "kinfo_proc".
La prima chiamata a syctl(3) serve solo a determinare le dimensioni dei
dati richiesti; tale dimensione viene usata sia per allocare la memoria
necessaria, puntata da "kd->procbase", sia per la seconda chiamata a
sysctl(3).
Essendo il primo livello "CTL_KERN" e il secondo "KERN_PROC", sysctl
chiama a sua volta kern_sysctl() e quindi sysctl_doproc().
Sulle sysctl(3) mi fermo qui in quanto è un argomento molto interessante,
almeno per chi scrive, e sicuramente da approfondire in un futuro
articolo.
Le informazioni raccolte le ritroviamo nel buffer indicato da
"kd->procbase".
Se non ci troviamo né nel caso ISKMEM, né in ISSYSCTL (sotto OpenBSD
non ci troviamo nel caso ISALIVE), ovvero abbiamo a che fare con un
corefile, viene usata la funzione kvm_deadprocs()
if (ISKMEM(kd)) {
...
} else if (ISSYSCTL(kd)) {
...
} else {
struct nlist nl[5], *p;
nl[0].n_name = "_nprocs";
nl[1].n_name = "_allproc";
nl[2].n_name = "_deadproc";
nl[3].n_name = "_zombproc";
nl[4].n_name = NULL;
if (kvm_nlist(kd, nl) != 0) {
...
}
if (KREAD(kd, nl[0].n_value, &nprocs)) {
...
}
size = nprocs * sizeof(struct kinfo_proc);
kd->procbase = (struct kinfo_proc *)_kvm_malloc(kd, size);
...
nprocs = kvm_deadprocs(kd, op, arg, nl[1].n_value,
nl[2].n_value, nl[3].n_value, nprocs);
...
}
Con kmv_list() ricaviamo le voci della tabella dei simboli relative ai
nomi indicati nella struttura "nl", quello che ci interessa è il valore
"n_value" ovvero l'indirizzo da usare in seguito con KREAD.
A questo punto è necessaria una breve panoramica sui processi in
NetBSD e OpenBSD. Storicamente il sistema BSD organizza i processi
in due liste, "zombproc" che contiene i processi in stato zombie
(SZOMB), e "allproc" che contiene tutti gli altri processi.
Successivamente gli sviluppatori di NetBSD hanno aggiunto la
lista "deadproc" che contiene i processi morti ma non ancora
zombie, di questi processi se ne occupa exit2() attraverso la
"falciatrice" reaper().
Queste liste di strutture "proc" sono definite in "sys/proc.h"
LIST_HEAD(proclist, proc);
extern struct proclist allproc;
extern struct proclist zombproc;
extern struct proclist deadproc;
Torniamo a kvm_getprocs(). Con la chiamata a KREAD(), con il valore
"_nprocs", ricaviamo il numero di processi attualmente presenti, usiamo
poi tale valore per allocare la memoria necessaria puntata da
"kd->procbase". KREAD è definita così
#define KREAD(kd, addr, obj) \\
(kvm_read(kd, addr, (obj), sizeof(*obj)) != sizeof(*obj))
OpenBSD differisce nelle voci inserite in "nl", non viene preso in
considerazione il valore "_deadproc".
Poi c'è la chiamata a kvm_deadprocs() definita nello stesso file come
static int
kvm_deadprocs(kvm_t *kd, int what, int arg, u_long a_allproc,
u_long a_deadproc, u_long a_zombproc, int maxcnt)
kvm_deadprocs(), per ognuna delle 3 liste di processi, fa una chiamata a
KREAD() e quindi a kvm_proclist()
KREAD(kd, a_allproc, &p)
kvm_proclist(kd, what, arg, p, bp, maxcnt);
kvm_proclist(), in parole povere, si occupa di riempire i campi delle
strutture kinfo_proc dei processi di nostro interesse con una serie di
chiamate a KREAD(). Il prototipo è il seguente
static int
kvm_proclist(kvm_t *kd, int what, int arg, struct proc *p,
struct kinfo_proc *bp, int maxcnt)
La parte principale è costituita da un ciclo for sulla lista di processi
passatagli da kvm_deadprocs(), da rilevare poi uno switch su "what" che
indica quale sotto insieme di processi è di nostro interesse.
Infine le strutture "proc" ed "eproc" vengono copiate rispettivamente in
"bp->kp_proc" e "bp->kp_eproc", dove "bp" è definito in deadprocs() come
struct kinfo_proc *bp = kd->procbase;
Per quel che riguarda kvm_getproc2()
struct kinfo_proc2 *
kvm_getproc2(kvm_t *kd, int op, int arg, size_t esize, int *cnt)
se ci troviamo nella situazione "ISSYSCTL" (ISALIVE sotto OpenBSD) le
informazioni vengono raccolte via sysctl(), altrimenti viene chiamata
kvm_getprocs() e i valori restituiti, sotto forma di strutture kinfo_proc,
vengono adattati ed espansi in strutture kinfo_proc2.
Anche in questo caso mi fermo qui, lasciando al lettore lo studio dell'
implementazione delle altre funzioni che è, in realtà, abbastanza lineare.