Articoli Manifesto Tools Links Canali Libri Contatti ?
BSD / Kernel

kvm: le mani sul kernel

Abstract
In questo articolo tratteremo della libreria libkvm che permette di accedere alla memoria virtuale del kernel dei sistemi *BSD.
La prima parte illustra l'utilizzo di alcune funzioni della libreria e nella seconda parte vedremo l'implementazione di alcune di esse.
Data di stesura: 05/12/2004
Data di pubblicazione: 16/12/2004
Ultima modifica: 04/04/2006
di Gianluigi Spagnuolo Discuti sul forum   Stampa

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() è
  1. kvm_t * 
  2. kvm_open(const char *execfile, const char *corefile,  
  3.   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
  1. #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
  1. #define _PATH_KSYMS     "/dev/ksyms" 
  2. #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"
  1. #define _PATH_MEM       "/dev/mem" 
"swapfile" indica lo swap device. Se posto a NULL assume il valore di _PATH_DRUM in "paths.h"
  1. #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

  1. kvm_t * 
  2. kvm_openfiles(const char *execfile, const char *corefile,  
  3.   char *swapfile, int flags, char *errbuf); 
Entrambe le funzioni restituiscono un valore di tipo "kmv_t", definito in "kvm.h" come
  1. 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

  1. int 
  2. kvm_close(kvm_t *kd); 

kvm_getprocs e kvm_getproc2

kvm_getprocs() serve a ottenere dal kernel informazioni sui processi attivi
  1. struct kinfo_proc * 
  2. 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"

  1. struct kinfo_proc { 
  2.   struct  proc kp_proc; 
  3.   struct  eproc { 
  4.     struct  proc *e_paddr;  
  5.     struct  session *e_sess; 
  6.     struct  pcred e_pcred; 
  7.     struct  ucred e_ucred; 
  8.     struct  vmspace e_vm;  
  9.     pid_t   e_ppid; 
  10.     pid_t   e_pgid;  
  11.     short   e_jobc;  
  12.     dev_t   e_tdev; 
  13.     pid_t   e_tpgid; 
  14.     struct  session *e_tsess;  
  15. #define WMESGLEN        8 
  16.     char    e_wmesg[WMESGLEN];  
  17.     segsz_t e_xsize;  
  18.     short   e_xrssize; 
  19.     short   e_xccount; 
  20.     short   e_xswrss; 
  21.     long    e_flag; 
  22. #define EPROC_CTTY      0x01 
  23. #define EPROC_SLEADER   0x02 
  24.     char    e_login[MAXLOGNAME]; 
  25.     pid_t   e_sid; 
  26.     long    e_spare[3]; 
  27.   } kp_eproc; 
  28. }; 
"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 è

  1. struct kinfo_proc2 * 
  2. 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)

  1. struct kinfo_proc2 *p;   
  2. int arg, what, cnt; 
  3. 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
  1. int 
  2. 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"
  1. struct nlist { 
  2.   __aconst char *n_name;  
  3.   unsigned char n_type;   
  4.   char n_other;           
  5.   short n_desc;           
  6.   unsigned long n_value;  
  7. }; 
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"
  1. #define N_UNDF  0x00 
  2. #define N_ABS   0x02 
  3. #define N_TEXT  0x04 
  4. #define N_DATA  0x06 
  5. #define N_BSS   0x08 
  6. #define N_INDR  0x0a 
  7. #define N_SIZE  0x0c 
  8. #define N_COMM  0x12 
  9. #define N_SETA  0x14 
  10. #define N_SETT  0x16 
  11. #define N_SETD  0x18 
  12. #define N_SETB  0x1a 
  13. #define N_SETV  0x1c 
  14. #define N_FN    0x1e 
  15. #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

  1. struct nlist nl[]={ 
  2. #define IPSTAT 0 
  3.   {"_ipstat"}, 
  4. #define UDPSTAT 1 
  5.   {"_udpstat"},  
  6.   {NULL} 
  7. }; 
  8.  
  9. 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
  1. ssize_t 
  2. kvm_read(kvm_t *kd, u_long addr, void *buf, size_t nbytes) 
  3.  
  4. ssize_t 
  5. 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

  1. struct ipstat ips; 
  2. struct udpstat udps; 
  3.  
  4. kvm_read(kd, nl[IPSTAT].n_value, &ips, sizeof ips); 
  5. 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

  1.   typedef struct __kvm kvm_t; 
La struttura "__kvm" è invece definita in "kvm_private.h"
  1. struct __kvm { 
  2.   const char *program; 
  3.   char  *errp; 
  4.   char  errbuf[_POSIX2_LINE_MAX]; 
  5.   DB  *db; 
  6.   int pmfd; 
  7.   int vmfd; 
  8.   int swfd; 
  9.   int nlfd; 
  10.   char  alive; 
  11.   struct kinfo_proc *procbase; 
  12.   struct kinfo_proc2 *procbase2; 
  13.   u_long  usrstack; 
  14.   u_long  min_uva, max_uva; 
  15.   int nbpg; 
  16.   char  *swapspc; 
  17.   char  *argspc, *argbuf; 
  18.   int arglen; 
  19.   char  **argv;  
  20.   int argc; 
  21.  
  22.   struct kcore_hdr *kcore_hdr; 
  23.   size_t  cpu_dsize; 
  24.   void  *cpu_data; 
  25.   off_t dump_off; 
  26.  
  27.   struct vmstate *vmst; 
  28.   struct pglist *vm_page_buckets; 
  29.   int vm_page_hash_mask; 
  30. }; 
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
  1. static kvm_t * 
  2. _kvm_open(kvm_t *kd, const char *uf, const char *mf,  
  3.   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
  1. 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
  1. if (flag & KVM_NO_FILES) { 
  2.   kd->alive = KVM_ALIVE_SYSCTL; 
  3.   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", è
  1. int 
  2. 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()
  1. if (kd->db == 0) { 
  2.   rv = __fdnlist(kd->nlfd, nl); 
  3.   if (rv == -1) 
  4.     _kvm_err(kd, 0, "bad namelist"); 
  5.   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()

  1. (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"
  1. struct kinfo_proc * 
  2. 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
  1. #define ISALIVE(kd)  ((kd)->alive != KVM_ALIVE_DEAD) 
  2. #define ISKMEM(kd)   ((kd)->alive == KVM_ALIVE_FILES) 
  3. #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

  1. #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
  1. if (ISKMEM(kd)) { 
  2.   size = 0; 
  3.   mib[0] = CTL_KERN; 
  4.   mib[1] = KERN_PROC; 
  5.   mib[2] = op; 
  6.   mib[3] = arg; 
  7.   st = sysctl(mib, 4, NULL, &size, NULL, 0); 
  8.   ... 
  9.   kd->procbase = (struct kinfo_proc *)_kvm_malloc(kd, size); 
  10.   ... 
  11.   st = sysctl(mib, 4, kd->procbase, &size, NULL, 0); 
  12.   ... 
Ricordo che il prototipo della sysctl(3) è il seguente
  1. int 
  2. sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, 
  3.   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()

  1. if (ISKMEM(kd)) { 
  2.   ... 
  3. } else if (ISSYSCTL(kd)) { 
  4.   ... 
  5. } else { 
  6.  
  7.   struct nlist nl[5], *p; 
  8.   nl[0].n_name = "_nprocs"; 
  9.   nl[1].n_name = "_allproc"; 
  10.   nl[2].n_name = "_deadproc"; 
  11.   nl[3].n_name = "_zombproc"; 
  12.   nl[4].n_name = NULL; 
  13.   if (kvm_nlist(kd, nl) != 0) {  
  14.     ... 
  15.   if (KREAD(kd, nl[0].n_value, &nprocs)) { 
  16.     ... 
  17.   size = nprocs * sizeof(struct kinfo_proc); 
  18.   kd->procbase = (struct kinfo_proc *)_kvm_malloc(kd, size); 
  19.   ... 
  20.   nprocs = kvm_deadprocs(kd, op, arg, nl[1].n_value, 
  21.     nl[2].n_value, nl[3].n_value, nprocs); 
  22.               
  23.   ...  
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"

  1. LIST_HEAD(proclist, proc);   
  2. extern struct proclist  allproc; 
  3. extern struct proclist  zombproc; 
  4. 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ì

  1. #define KREAD(kd, addr, obj) \\ 
  2.   (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

  1. static int 
  2. kvm_deadprocs(kvm_t *kd, int what, int arg, u_long a_allproc, 
  3.   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()
  1. KREAD(kd, a_allproc, &p) 
  2. 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
  1. static int 
  2. kvm_proclist(kvm_t *kd, int what, int arg, struct proc *p, 
  3.   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
  1. struct kinfo_proc *bp = kd->procbase; 
Per quel che riguarda kvm_getproc2()
  1. struct kinfo_proc2 * 
  2. 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.

Informazioni sull'autore

Gianluigi Spagnuolo, studente. Si interessa di programmazione, sicurezza informatica, buddismo zen, reti e sistemi operativi liberi.
È tra i fondatori dell' Italian Ruby User Group e dell'Irpinia Linux User Group e moderatore della mailing list Security4Dummies.
Home page: http://kirash.interfree.it

È possibile consultare l'elenco degli articoli scritti da Gianluigi Spagnuolo.

Altri articoli sul tema BSD / Kernel.

Risorse

  1. man pages: kvm(3), sysctl(3), nlist(3), db(3)
  2. sorgenti: /usr/src/lib/libkvm/, /usr/src/sys/
Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

Questo articolo o l'argomento ti ha interessato? Parliamone.