ELABORATO DI LAUREA - unina.it Berniero.pdf · Anno Accademico 2008/09 Relatore Ch.mo Prof. Stefano...
Transcript of ELABORATO DI LAUREA - unina.it Berniero.pdf · Anno Accademico 2008/09 Relatore Ch.mo Prof. Stefano...
UNIVERSITÀ DEGLI STUDI DI NAPOLI FEDERICO II
FACOLTÀ DI INGEGNERIA
CORSO DI LAUREA SPECIALISTICA IN INGEGNERIA INFORMATICA
(CLASSE DELLE LAUREE SPECIALISTICHE IN INGEGNERIA INFORMATICA N.35/S)
DIPARTIMENTO DI INFORMATICA E SISTEMISTICA
ELABORATO DI LAUREA
Analisi sperimentale di software aging nel kernel Linux
RELATORE CANDIDATO Ch.mo Prof. Stefano Russo Berniero Volzone
Matr.: 885/288 CORRELATORI Ing. Roberto Natella Ing. Roberto Pietrantuono
ANNO ACCADEMICO 2008/2009
Facoltà di Ingegneria Corso di Studi in Ingegneria Informatica Tesi di laurea specialistica
Analisi sperimentale di
software aging nel kernel Linux Anno Accademico 2008/09 Relatore Ch.mo Prof. Stefano Russo Correlatori Ing. Roberto Natella Ing. Roberto Pietrantuono Candidato Berniero Volzone matr.: 885/288
I
Indice
Introduzione 1
Capitolo 1 Dependable Computing e software aging 5 1.1 Dependability dei sistemi informatici 6
1.2 Fallimenti, errori e guasti 10
1.2.1 Fallimenti 12
1.2.2 Errori 16
1.2.3 Guasti 17
1.2.3.1 Guasti software 20
1.3 Metodi per migliorare la dependability del software 23
1.4 Software aging 26
1.5 Metodi per l’analisi dell’aging 28
1.5.1 Analytical modeling 29
1.5.2 Measurement-Based Approach 33
1.6 Software rejuvenation 36
Capitolo 2 Software aging nei sistemi operativi 39 2.1 Sistemi operativi e dependability 40
2.2 Studi correlati 43
Capitolo 3 Instrumentazione del sistema operativo Linux 50 3.1 Struttura del sistema operativo Linux 50
Analisi sperimentale di software aging nel kernel Linux
II
3.1.1 Sottosistemi del kernel 57
3.1.1.1 Process management 58
3.1.1.2 Memory management 59
3.1.1.3 File system e device control 60
3.1.1.4 Networking 62
3.1.2 Process file system 63
3.2 Tracepoint nel kernel Linux 64
3.3 Individuazione delle informazioni di interesse 67
3.4 Instrumentazione dei tracepoint e definizione del tracer 70
3.4.1 Rappresentazione dei dati relativi al tempo di latenza 74
3.5 Scrittura delle informazioni di sistema su file: myTracer-rep 78
Capitolo 4. Procedura sperimentale 81 4.1 Design of Experiments (DoE) 82
4.2 Software aging nel kernel Linux: procedura sperimentale 87
4.3 Progettazione degli esperimenti 89
4.3.1 Analisi di software aging 91
4.3.1.1 Raccolta dei dati e preprocessing 92
4.3.1.2 Test di Mann-Kendall e metodo di Sen 94
4.3.1.3 Principal component analysis 97
Capitolo 5. Sperimentazione ed analisi dei risultati 100 5.1 Esecuzione del test 101
5.1.1 Individuazione dei trend di aging 103
5.1.2 Principal component analysis 108
5.1.3 Valutazione dei risultati 110
Conclusioni e sviluppi futuri 115
Bibliografia 118
III
Indice delle figure
Figura 1.1: diagramma di stato failure-repair. 6
Figura 1.2: diagramma di stato failure-repair con politica di tolleranza ai guasti. 9
Figura 1.3: relazione fra MTTF, MTBF ed MTTR. 10
Figura 1.4: relazione fra fallimenti, errori e guasti. 11
Figura 1.5: tassonomia di fallimenti. 15
Figura 1.6: classi di fault elementari. 17
Figura 1.7: classi di fault combinate. 18
Figura 1.8: metodi di fault tolerance. 24
Figura 1.9: aging-related bugs. 27
Figura 1.10: modello semi-Markoviano. 30
Figura 1.11: modello semi-Markoviano modificato. 31
Figura 1.12: confronto tra i modelli semi-Markoviano e semi-Markoviano modificato. 32
Figura 1.13: modello MRSPN di software rejuvenation. 33
Figura 1.14: esempio di approccio measurement-based. 35
Figura 1.15: approcci di software rejuvenation. 38
Figura 2.1: albero MIB per il modulo PFM del tool di monitoring SNMP. 44
Figura 2.2: monitoring delle informazioni UNIX tramite SNMP. 45
Figura 3.1: architettura fondamentale del SO Linux. 52
Figura 3.2: esempio di system call nel SO Linux. 53
Figura 3.3: transizione tra user e kernel mode per l’esecuzione di due processi. 55
Figura 3.4: accesso alla memoria nella commutazione tra user e kernel mode. 56
Analisi sperimentale di software aging nel kernel Linux
IV
Figura 3.5: sottosistemi del kernel Linux. 57
Figura 3.6 strumento di monitoring. 72
Figura 3.7 istogramma della latenza della system call open catturata in un unico
evento. 75
Figura 3.8 istogramma della latenza della system call open catturata in eventi
successivi. 76
Figura 3.9a formattazione dei dati contenuti nel file mem.txt. 79
Figura 3.9b formattazione dei dati contenuti nel file proc.txt. 79
Figura 3.9c formattazione dei dati contenuti nel file disk.txt. 80
Figura 3.9d formattazione dei dati contenuti nel file net.txt. 80
Figura 3.9e formattazione dei dati contenuti nel file filesys.txt. 80
Figura 4.1 modello procedurale per lo studio del software aging. 88
Figura 4.2 processo di elaborazione delle informazioni di sistema. 91
Figura 5.1 procedura sperimentale. 101
Figura 5.2 procedura di analisi dei dati. 102
Figura 5.3: variazione della memoria fisica libera disponibile. 104
Figura 5.4: trend relativo all’indicatore di memory depletion. 105
Figura 5.5: trend relativo all’indicatore di throughput loss del sottosistema
memory management. 106
Figura 5.6: trend relativo all’indicatore di time loss del sottosistema
memory management. 106
Figura 5.7: composizione della prima e della terza PC relative al
sottosistema file system I/O. 111
Figura 5.8: composizione della prima e della seconda PC relative al
sottosistema disk I/O driver. 111
Figura 5.9: composizione della prima e della seconda PC relative al sottosistema
memory management. 112
Figura 5.10: composizione della prima PC relativa al sottosistema process
management. 112
V
Indice delle tabelle
Tabella 1.1: metriche per la valutazione della dependability. 10
Tabella 3.1: informazioni di tracing per i sottosistemi di memory management e
process management. 69
Tabella 3.2: informazioni di tracing per i sottosistemi di network I/O e file system I/O. 69
Tabella 3.3: informazioni di tracing per il sottosistema disk I/O driver. 70
Tabella 5.1: sintesi dei risultati ottenuti. 107
Tabella 5.2: coefficienti delle componenti principali relative al sottosistema
disk I/O driver. 109
Tabella 5.3: coefficienti delle componenti principali relative al sottosistema
process management. 109
Tabella 5.4: coefficienti delle componenti principali relative al sottosistema
file system I/O. 110
Tabella 5.5: coefficienti delle componenti principali relative al sottosistema
memory management. 110
Tabella 5.6: parametri di workload più significativi per ogni trend
e per ogni sottosistema. 113
1
Introduzione
Il presente lavoro di tesi è frutto di un’attività di ricerca e sperimentazione, svoltasi
presso il laboratorio CINI (Consorzio Interuniversitario Nazionale per l’Informatica) della
Federico II, con lo scopo di analizzare il fenomeno di aging nel sistema operativo Linux.
Il software aging può essere definito come il graduale accumulo di potenziali condizioni
di fault durante l’esecuzione di un programma, che conduce ad una degradazione nelle
prestazioni del sistema ed eventualmente al crash [1]. Per tale ragione, lo studio
approfondito di soluzioni che assicurino elevata affidabilità non può prescindere
dall’analisi dell’invecchiamento software, che è stata così incoraggiata dal diffondersi delle
applicazioni mission/business critical.
Gli studi sul software aging e sulle correlate tecniche di rejuvenation sono relativamente
recenti. Essi risalgono, infatti, alla metà degli anni ’90 e si concentrano, per la maggior
parte, su modelli analitici e statistici atti alla descrizione del fenomeno. Negli ultimi anni,
tuttavia, si sono diffuse una miriade di applicazioni aventi esigenze sempre più critiche in
termini di affidabilità. Mentre le tecniche di fault tolerance hardware appaiono ormai ben
consolidate, i fallimenti software continuano a configurarsi come un problema solo
parzialmente risolto, che costituisce una minaccia per le applicazioni con requisiti di
elevata dependability. Nonostante l’evoluzione delle tecniche di ingegneria del software,
l’eliminazione di tutti i bug durante le fasi di progettazione e di testing resta un concetto
ideale. Basti pensare che taluni fault, per loro stessa natura, non emergono nell’ambiente di
Analisi sperimentale di software aging nel kernel Linux
2
test ma solo in quello di reale funzionamento (ad esempio, perché sono dovuti
all’integrazione con sistemi di notevole dimensione e complessità). Bisogna poi tener
presente che le cause più comuni dell’aging (memory leaking, memory bloating, lock su
file non rilasciati, frammentazione dei dischi, accumulo di errori di round-off, ecc.) non
sono facilmente individuabili con il testing poiché l’applicazione ne esibisce le
conseguenze solo dopo un lungo periodo di funzionamento.
Tali caratteristiche peculiari conferiscono al problema dell’aging una particolare difficoltà,
la quale risulta amplificata nel caso si intenda studiare l’invecchiamento di un sistema
operativo (SO). Infatti, quest’ultimo è un’applicazione di notevole complessità che offre
gli strumenti per la corretta gestione di tutte le risorse di un calcolatore [2]. Tale
definizione implica diversi problemi nello studio dell’aging. In primo luogo, poiché il SO
supervisiona l’utilizzo di tutte le risorse del sistema di calcolo (processi, memoria centrale
e di massa, dispositivi di I/O, ecc.), risulta notevolmente difficile definire un modello
esaustivo, analitico o statistico, che consideri le cause e gli effetti dell’aging nei diversi
sottosistemi. In secondo luogo, l’utilizzo di strumenti di benchmark per la valutazione di
indicatori di aging può influenzare la misura stessa rendendo falsata l’analisi. Ciò è
inevitabile per due ragioni. Innanzitutto, poiché lo strumento di benchmark è esso stesso un
processo che richiede risorse al sistema operativo, un’analisi dell’aging che abbia
l’obiettivo di misurare l’esaurimento delle risorse computazionali dovrebbe effettuare
anche una stima precisa di quelle che lo strumento di benchmark richiede per funzionare.
Inoltre, essendo lo strumento stesso un’applicazione software, non è escluso a priori che
esso possa causare involontariamente una porzione dell’invecchiamento che si ha lo scopo
di misurare.
Questi concetti sono approfonditi nel corso della tesi, che si propone di analizzare il SO
Linux al fine di determinare se esso risulti affetto da aging. Vengono tracciate le linee
guida per l’applicazione di una procedura utile:
- alla rilevazione dell’aging;
- all’individuazione delle porzioni del kernel che risultino maggiormente legate al
Analisi sperimentale di software aging nel kernel Linux
3
fenomeno;
- alla stima del Time To Exhaustion (TTE) delle risorse.
Lo scopo di siffatta analisi consiste, in primo luogo, nel fornire agli sviluppatori del SO un
insieme di valutazioni utili a ridurre i tempi ed i costi per le operazioni di debugging,
mirate al miglioramento generale delle future versioni del kernel. Inoltre, la proposta di
una procedura finalizzata alla stima del TTE delle risorse è rivolta principalmente agli
amministratori dei sistemi Linux long running, i quali possono determinare il periodo di
tempo tra opportuni interventi di ripristino, in considerazione delle particolari condizioni
medie di carico del sistema che gestiscono.
Nello specifico, la tesi si propone di illustrare lo sviluppo ed il funzionamento di uno
strumento in grado di monitorare lo “stato di salute” del SO Linux, attraverso
l’introduzione di sonde per il tracciamento delle informazioni di sistema. Nel lavoro svolto,
tale strumento viene adoperato per lo studio del software aging ma ciò non preclude un suo
utilizzo per fini diversi, che coinvolgano il monitoraggio dello stato delle risorse di
sistema.
La struttura della tesi può essere schematizzata come segue. Il primo capitolo introduce
le definizioni generali inerenti all’affidabilità dei sistemi informatici, per poi focalizzare
l’attenzione sull’aging. Dopo averne descritte le cause, si passano in rassegna le varie
tecniche proposte in letteratura per l’analisi del fenomeno e per la sua prevenzione
(rejuvenation).
Nel secondo capitolo sono invece esposte le problematiche riguardanti lo studio
dell’invecchiamento dei sistemi operativi. L’analisi qui condotta parte della descrizione
delle vulnerabilità dei SO in generale e presenta alcune metodologie proposte in letteratura
per lo studio del software aging in ambienti UNIX e Linux. Lo scopo principale consiste
nel comprendere lo stato dell’arte riguardo agli studi condotti sull’invecchiamento dei SO,
in modo da costituire un background di conoscenza da cui partire nella proposta di una
nuova procedura.
Il terzo capitolo descrive la struttura di Linux di cui viene presentata l’architettura e la
Analisi sperimentale di software aging nel kernel Linux
4
suddivisione in sottosistemi. Tale analisi è mirata alla comprensione delle ragioni che
inducono il kernel ad invecchiare, nonché all’identificazione delle porzioni più vulnerabili
e delle informazioni che possono essere monitorate per controllare lo “stato di salute” del
SO. Viene quindi descritto un meccanismo di tracciamento che consenta di reperire i dati
sensibili riguardo alle singole componenti.
Il quarto ed il quinto capitolo presentano, rispettivamente, la procedura sperimentale
adottata per l’analisi dell’aging nel kernel Linux ed i risultati delle misurazioni effettuate,
relativamente ad un sottoinsieme di indicatori precedentemente individuati. A tale scopo,
viene descritta una serie di strumenti statistici utili all’analisi dei dati che, opportunamente
adattati, consentono di ottenere i risultati illustrati, attraverso grafici e tabelle, nell’ultimo
capitolo.
5
Capitolo 1 Dependable Computing e software aging
Negli ultimi quarant’anni, grazie alla diffusione capillare dell’ICT nei più svariati campi
d’applicazione, le aspettative nei confronti dei sistemi informatici, e particolarmente del
software, sono mutate radicalmente. Internet, il web e la multimedialità appartengono,
ormai, alle abitudini quotidiane di ciascuno e, parallelamente, i sistemi informatici sono
utilizzati in scenari critici sia dal punto di vista economico (business critical) che in termini
di affidabilità e sicurezza (mission critical). Si pensi, ad esempio, ad applicazioni quali il
controllo del traffico ferroviario ed aereo, il controllo remoto di veicoli senza conducente,
le missioni aerospaziali, i software di monitoraggio della apparecchiature mediche, nonché
alle applicazioni bancarie e di e-commerce.
In siffatti scenari viene spontaneo chiedersi se ci si può fidare dei computer, quali siano i
rischi che si corrono e come sia possibile prevenirli o porvi rimedio. L’affidabilità di un
sistema informatico, come si vedrà in seguito, è definita come un macroattributo, cioè un
insieme di indicatori di qualità. Per tale ragione, moltissime sono le cause capaci di
degradare la dependability di un servizio, fino a farla scendere al di sotto di livelli ritenuti
accettabili.
Questo capitolo si propone, allora, di introdurre i concetti generali inerenti
all’affidabilità dei sistemi informatici, per poi focalizzare l’attenzione su un particolare
fenomeno in grado di inficiare la dependability del software: l’aging. Dopo la definizione
dell’invecchiamento del software, ne varranno chiarite le cause e gli effetti e si farà una
Analisi sperimentale di software aging nel kernel Linux
6
breve panoramica sui vari approcci proposti in letteratura per l’analisi e la soluzione del
fenomeno.
1.1 Dependability dei sistemi informatici
Introduciamo di seguito la terminologia ed i concetti di base riguardanti l’affidabilità dei
sistemi informatici [3].
Prima di spiegare cosa si intende per dependability, è necessario fornire alcune
definizioni fondamentali. Un sistema è una qualsiasi entità in grado di interagire con altre
entità (sistemi o utenti umani). Per servizio si intende il comportamento del sistema
percepito dall’utente attraverso una cosiddetta interfaccia che rappresenta, appunto, il
confine fra sistema ed utente.
Un servizio si dice corretto se è conforme alle specifiche, mentre si parla di failure quando
il servizio fornito dal sistema non è corretto. Più precisamente un fallimento è, come
esemplifica la figura 1, una transizione dall’erogazione di un servizio corretto a quella di
un servizio scorretto; la transizione opposta viene detta comunemente restoration (o
riparazione).
Figura 1.1: diagramma di stato failure-repair.
Poiché un servizio è una sequenza di stati esterni del sistema, un service failure consiste
nella deviazione di uno o più stati esterni dalle specifiche. La singola deviazione è indicata
con il termine errore mentre la causa di un errore si dice fault (ossia guasto). È possibile
che un fault generi più di un errore all’interno del sistema: in tal caso si parla di multiple
Analisi sperimentale di software aging nel kernel Linux
7
related error. Un errore è, quindi, quella parte dello stato del sistema che può indurre lo
stesso ad un fallimento. Se il sistema è composto da più componenti, un errore può
condurre ad un fallimento in uno di questi che a sua volta, poiché i componenti
interagiscono, può introdurre uno o più fault nelle altre parti. È importante notare che,
comunque, non tutti gli errori raggiungono lo stato esterno del sistema scatenando un
fallimento, cioè non tutti gli errori esibiscono conseguenze percepibili dall’utente.
A questo punto è possibile fornire due definizioni alternative di dependability:
- la capacità del sistema di erogare un servizio che può essere ritenuto legittimamente
fidato;
- la capacità del sistema di evitare che un servizio fallisca più frequentemente e con
conseguenze più severe rispetto ad un certo grado di accettabilità.
Le due definizioni mettono in evidenza che, per decidere se un servizio può essere ritenuto
affidabile, è sempre necessario fissare prima un concetto di fiducia oppure di accettabilità.
La nozione di dependability è associata a quella di dependence: la dipendenza di un
sistema A da un sistema B rappresenta il punto fin cui la dependability di A è (o potrebbe
essere) influenzata da quella di B. Si può così definire la fiducia come il grado di
dipendenza ammessa.
Più nello specifico, si ritiene che la dependability sia un macroattributo, ossia un
insieme dei seguenti indicatori di qualità:
- availability;
- reliability;
- safety;
- performability;
- maintainability.
L’availability è la disponibilità di un certo servizio, ossia la probabilità che non ci sia un
fallimento al tempo t. In altre parole, si dice che un sistema è available in un certo istante
se in quell’istante è in grado di fornire un servizio corretto. Poiché l’availability è
interpretata come un valore medio, ossia come la probabilità che in un dato istante il
Analisi sperimentale di software aging nel kernel Linux
8
sistema sia disponibile o meno, un sistema indisponibile, ad esempio, 2 secondi al minuto
ed uno indisponibile per un intero giorno ogni anno hanno lo stesso valore di availability.
La reliability è la misura della durata dell’intervallo di tempo durante il quale il sistema
fornisce, in maniera continuativa, un servizio corretto. In termini probabilistici, si può
definire la reliability nell’istante t come la probabilità che non ci sia un fallimento
nell’intervallo di tempo (0, t).
La safety è l’assenza di condizioni di funzionamento del sistema che possano provocare
danni a persone o cose. Essa è cioè la probabilità che non occorra un fallimento cosiddetto
catastrofico nell’intervallo di tempo (0, t). Per dichiarare un failure catastrofico si ricorre
ad una valutazione soggettiva dei rischi.
La performability è una metrica introdotta per valutare le prestazioni del sistema anche in
caso di guasti. In pratica, le definizioni di availability e reliability presuppongono che lo
stato del sistema sia binario, cioè che possa essere erogato al tempo t un servizio corretto
(stato up) o scorretto (stato down). Tale assunzione è vera per quei sistemi che non
implementano politiche di tolleranza ai guasti ma, nei casi fault tolerant, possono essere
definiti più stati. In pratica, quando la specifica funzionale del sistema descrive un insieme
di funzioni da erogare, un fallimento in uno o più servizi può lasciare il sistema in uno
stato degenere (ad esempio servizio lento, servizio limitato, servizio di emergenza, ecc.) in
cui viene fornito all’utente solo un insieme ristretto di funzionalità (figura 1.2): in tal caso
si parla di fallimento parziale. La performability è allora una metrica necessaria a misurare
la degradazione delle performance in un sistema che ammette stati di funzionamento
intermedi fra quello di up e down.
La maintainability, infine, è la capacità di un sistema di essere facilmente sottoposto a
modifiche e riparazioni. In termini probabilistici, essa è definita come la probabilità di
effettuare una riparazione con successo in un certo tempo. La maintainability misura,
quindi, il grado di velocità con cui il sistema può essere ripristinato dopo l’occorrenza di
un fallimento.
Analisi sperimentale di software aging nel kernel Linux
9
Figura 1.2: diagramma di stato failure-repair con politica di tolleranza ai guasti.
Per una stima quantitativa del grado di dependability di un sistema vengono utilizzate,
nella pratica, metriche di tipo statistico. Alcune di esse sono abbastanza generali per essere
applicate a qualsiasi servizio e sono basate sui parametri mostrati in tabella 1.1. Queste
misure sono utili a valutare gli attributi di dependability descritti precedentemente. Ad
esempio, si può definire l’availability come:
MTBFMTTF
MTTRMTTFMTTFA =+
= .
A titolo di esempio, si consideri un sistema che fallisce, in media, una volta all’ora ma si
ripristina automaticamente in 10 ms. In base alla definizione precedente, il sistema in
questione risulta notevolmente available poiché si ha:
Analisi sperimentale di software aging nel kernel Linux
10
99999722,001,03600
3600
01,010
36001=
+=⇒
⎪⎭
⎪⎬⎫
==
==A
smsMTTR
shMTTF
PARAMETRO ACRONIMO DESCRIZIONE
Mean Time To Crash MTTC tempo medio di occorrenza di un crash
Mean Time Between Crashes MTBC tempo medio tra due crash
successivi
Mean Time To Failure MTTF tempo medio di occorrenza di un failure
Mean Time Between Failure MTBF tempo medio tra due failure
successivi Mean Number of
Instruction to Restart MNIR numero medio di istruzioni per il ripristino del sistema
Mean Time To Repair MTTR tempo medio necessario per riparare il sistema
Mean Down Time MDT tempo medio durante il quale il sistema non è funzionante
Mean Time Between Errors MRBE tempo medio fra due errori successivi
Tabella 1.1: metriche per la valutazione della dependability.
La figura 1.3 esemplifica la relazione fra le metriche MTTF, MTBF ed MTTR.
Figura 1.3: relazione fra MTTF, MTBF ed MTTR.
1.2 Fallimenti, errori e guasti
Come accennato precedentemente, un failure può essere definito come l’evento in
corrispondenza del quale il sistema cessa di fornire un servizio corretto. Un servizio può in
generale non essere corretto se non è conforme alle specifiche funzionali oppure se la sua
Analisi sperimentale di software aging nel kernel Linux
11
specifica, in fase di analisi dei requisiti, non ha descritto adeguatamente le funzionalità del
sistema. Un errore è la parte dello stato di un sistema che può indurre lo stesso al
fallimento (unproper service). Se l’errore è opportunamente rilevato esso si dice detected
error, viceversa se l’errore esiste ma non è rilevato si parla di latent error. La causa di un
errore è un fault (guasto), che può derivare dall’avaria di un componente hardware, da
fenomeni di interferenza o da errori di progettazione. La figura 1.4 schematizza le relazioni
fra failure, error e fault. In particolare, l’attivazione di un guasto provoca la transizione del
sistema da uno stato di corretto funzionamento (correct behavior) ad uno stato improprio
(errore). Un errore può poi degenerare in un fallimento mediante propagazione
all’interfaccia utente che rende visibile l’anomalia attraverso la scorrettezza del servizio
fornito. La rilevazione di un errore e le opportune operazioni di ripristino possono in
seguito riportare il sistema ad operare in maniera corretta.
Figura 1.4: relazione fra fallimenti, errori e guasti.
Un sistema può portarsi in uno stato incoerente in ogni fase del suo ciclo di vita a causa
di guasti hardware, errori in fase di progettazione, errati interventi di manutenzione ecc.. Il
ciclo di vita di un sistema può essere suddiviso in due macrofasi: lo sviluppo e l’uso.
La fase di sviluppo include tutte quelle attività necessarie affinché il concetto iniziale del
committente si concretizzi in un sistema finale, in grado di fornire il servizio richiesto
nell’ambiente reale di utilizzo. A partire dallo stato embrionale, il sistema interagisce con
Analisi sperimentale di software aging nel kernel Linux
12
l’ambiente di sviluppo ed è soggetto ai cosiddetti fault di sviluppo che possono essere
causati da uno degli elementi che costituisce l’ambiente:
- il mondo fisico, con i suoi fenomeni naturali;
- gli sviluppatori umani;
- i tool di sviluppo, produzione e testing.
La fase di utilizzo ha inizio quando il sistema viene accettato dall’utente e comincia a
fornire i suoi servizi. L’uso consiste, in particolare, in periodi alternati di corretto
funzionamento, di deviazione dalle specifiche e di spegnimento volontario finalizzato ad
attività di manutenzione. Durante la fase d’utilizzo il sistema interagisce con l’ambiente
d’uso, il quale può introdurre guasti attraverso una delle sue entità:
- il mondo fisico, con i suoi fenomeni naturali;
- gli amministratori, cioè le entità (persone o altri sistemi) che hanno l’autorizzazione
ad usare, gestire, manutenere e riparare il sistema;
- gli utenti, ossia le entità (persone o altri sistemi) che fruiscono dei servizi del sistema
attraverso le sue interfacce;
- i provider (persone o altri sistemi) che forniscono a loro volta servizi al sistema;
- l’infrastruttura, costituita da entità in grado di procurare servizi specializzati come
fonti di informazione (ad esempio, clock, GPS, ecc.), link di comunicazione, sorgenti
di potenza, sistemi di raffreddamento, ecc.;
- gli intrusi, ossia entità (persone o altri sistemi) che cercano di alterare i servizi forniti
dal sistema, di renderlo indisponibile o di degradarne le prestazioni.
Nei tre paragrafi seguenti si espongono alcune tassonomie di failure, error e fault
dettagliate in [3].
1.2.1 Fallimenti
L’occorrenza di un fallimento va definita in relazione alla funzione del sistema
desiderata dall’utente finale e non rispetto alla specifica funzionale fissata in fase di analisi.
Analisi sperimentale di software aging nel kernel Linux
13
Ciò significa che un servizio conforme alle specifiche può essere inaccettabile per un
utilizzatore, ad esempio a causa di omissioni, cattive interpretazioni, assunzioni
indesiderate o inconsistenze. In tal caso, il fatto che un evento è indesiderato può essere
rilevato solo allorché l’evento stesso si presenta, scatenando una serie di conseguenze più o
meno severe. Questa natura soggettiva dei fallimenti fa sì che, generalmente, un sistema
possa fallire in modalità differenti, denominate failure mode, le quali implicano la non
correttezza del servizio secondo quattro diversi punti di vista: dominio, rilevabilità,
percezione e rilevanza del fallimento.
Dal punto di vista del dominio è possibile distinguere:
- timing failure o fallimenti nel tempo, che causano la non conformità di un servizio
alle specifiche in termini di tempo (cioè il verificarsi di un evento nel momento
sbagliato). Fallimenti di questo tipo possono essere poi specializzati in early timing
failure, se il servizio è fornito in anticipo, e late timing failure, se in ritardo;
- content failure o fallimenti nel valore, a seguito dei quali il contenuto informativo del
servizio perviene all’interfaccia in maniera non conforme alle specifiche.
Qualora un sistema fallisca sia nel tempo che nel valore, esso può fornire non
correttamente il servizio in diverse modalità, tra cui:
- halted, quando il sistema permane in uno stato bloccato senza che l’output riesca a
pervenire all’interfaccia. Un particolare fallimento di questo tipo è l’omission failure,
che si ha quando il servizio assume un valore nullo ed un ritardo infinito. Un
omission failure permanente prende il nome di crash;
- erratic, quando il servizio fornisce all’interfaccia informazioni incoerenti.
Dal punto di vista della rilevabilità del fallimento si distinguono:
- signaled failure o fallimenti rilevati, segnalati a livello utente da un opportuno
messaggio di errore;
- unsignaled failure o fallimenti non rilevati, cioè non segnalati a livello utente.
I meccanismi stessi di rilevazione possono fallire in due modi:
Analisi sperimentale di software aging nel kernel Linux
14
- segnalando la perdita di una funzionalità quando in realtà non c’è stato alcun
fallimento (si parla in tal caso di falso allarme);
- omettendo la segnalazione di una mancanza di funzionalità (si ha, cioè, un fallimento
non rilevato).
In taluni sistemi i fallimenti di specifiche funzionalità implicano la riduzione del servizio
ed il sistema stesso è in grado di segnalare agli utenti che la modalità di funzionamento
risulta degradata. Questo approccio può essere utile per ridurre l’emergenza di alcune
situazioni o per consentire, ad esempio, uno spegnimento sicuro degli apparati con finalità
di manutenzione.
Dal punto di vista della percezione del fallimento si distinguono:
- consistent failure o fallimenti consistenti, che inducono tutti gli utenti del sistema ad
avere la stessa percezione del failure;
- unconsistent failure o fallimenti inconsistenti, che possono essere percepiti in
maniera diversa dagli utenti del sistema. Fallimenti di questo tipo sono generalmente
indicati come bizantini.
La stima quantitativa delle conseguenze provocate dal failure sull’ambiente induce alla
definizione di severità del fallimento, legata al costo necessario per il ripristino. I failure
mode possono quindi essere ordinati secondo livelli di severità cui sono associati, di solito,
valori di probabilità di occorrenza massimi accettabili. Il numero e la definizione dei livelli
di severità, così come le probabilità associate, dipendono dall’applicazione considerata
nonché dagli attributi di dependability che risultano più appropriati alla caratterizzazione
del sistema in questione. Ad un alto livello di astrazione, si possono definire due valori
limite di severità, considerando la relazione fra i benefici erogati dal sistema in assenza di
fallimenti e le conseguenze di un singolo failure:
- catastrophic failure o fallimenti catastrofici, che provocano conseguenze al sistema
più gravi di molti ordini di grandezza rispetto al beneficio prodotto dal servizio
fornito in assenza di fallimento;
Analisi sperimentale di software aging nel kernel Linux
15
- minor failure o fallimenti benigni, che ingenerano conseguenze dello stesso ordine di
grandezza (valutato in termini di costo) del beneficio prodotto dal servizio fornito in
assenza di fallimento.
Una schematizzazione di quanto appena esposto è riportata in figura 1.5.
Figura 1.5: tassonomia di fallimenti.
I sistemi che sono progettati ed implementati in modo da fallire solamente in specifiche
modalità ed entro certi limiti accettabili (descritti nelle specifiche sull’affidabilità e la
sicurezza) sono detti fail-controlled. Ad esempio, siffatti componenti possono essere tali da
bloccare l’output invece di presentare valori errati, oppure possono essere in grado di
fallire solo in modo consistente. Si definisce allora sistema fail-halt o fail-stop un
componente in grado di fallire solo in modalità halted. Ciò di solito ha lo scopo di evitare
quanto più possibile qualsiasi conseguenza negativa su altri sistemi o operatori umani. Un
sistema fail-safe, infine, dovrebbe garantire che i fallimenti e le loro conseguenze siano
quanto più ridotti possibile. Fail-safe devono essere, ad esempio, le applicazioni il cui
fallimento implica il sorgere di situazioni pericolose per gli utenti (come applicazioni radar
per il controllo del traffico aereo o software per apparecchiature mediche).
Analisi sperimentale di software aging nel kernel Linux
16
1.2.2 Errori
Un errore è la parte dello stato totale di un sistema che può (o meno) determinare
l’insorgere di un fallimento, nel caso la situazione anomala riesca a propagarsi sino
all’interfaccia utente.
Poiché i sistemi sono costituiti da una serie di componenti interagenti, lo stato totale può
essere definito come la combinazione degli stati delle singole unità. Questa definizione
implica che:
- un guasto all’interno di uno specifico componente può generare errori in più unità del
sistema. In tal caso si parla di multiple related error mentre, se il fault riguarda un
unico componente, gli errori provocati si definiscono single error;
- un fault può originare un errore all’interno dello stato di un componente senza che
occorra un fallimento nel servizio ad alto livello. Ciò avviene quando lo stato esterno
dell’unità compromessa non fa parte dello stato esterno del sistema.
L’occorrenza di un fallimento in presenza di un errore dipende, in particolare, da due
fattori:
- la struttura del sistema e, soprattutto, la natura di ogni forma di ridondanza;
- il comportamento del sistema.
Per quanto concerne il primo punto, si deve tener presente che non esiste soltanto la
cosiddetta ridondanza protettiva (introdotta per fornire una certa tolleranza ai guasti) ma
anche una forma di ridondanza non intenzionale (perché è spesso difficile, se non
impossibile, progettare un sistema senza alcuna ridondanza) che può avere effetti simili
alla prima. Riguardo al secondo punto, invece, può accadere che la parte dello stato che
contiene l’errore non divenga mai necessaria al servizio o che l’errore stesso sia
sovrascritto prima che ingeneri il fallimento.
Una classificazione conveniente degli errori può essere basata sulle categorie di
fallimenti che essi sono in grado di provocare. È cioè possibile distinguere errori:
- di tempo e di valore;
- rilevati e latenti;
Analisi sperimentale di software aging nel kernel Linux
17
- consistenti ed inconsistenti
- catastrofici e benigni.
1.2.3 Guasti
Come mostra la figura 1.6, tutti i guasti che possono interessare un sistema durante il
suo intero ciclo di vita sono classificati in [3] in base ad otto punti di vista, detti classi di
fault elementari.
Figura 1.6: classi di fault elementari.
Se fossero possibili tutte le combinazioni delle 8 classi, potrebbero essere derivate ben 256
differenti classi di fault combinate. Ovviamente, però, non tutti i criteri sono applicabili ad
ognuna delle classi di guasti (ad esempio, i fault naturali non possono essere classificati in
Analisi sperimentale di software aging nel kernel Linux
18
base ad obiettivi, intenti e capacità). È così possibile identificare solo un sottoinsieme di
tutte le combinazioni, come le 31 mostrate in figura 1.7 con i relativi esempi.
Figura 1.7: classi di fault combinate.
Le classi combinate appartengono a tre gruppi parzialmente sovrapposti:
- guasti di sviluppo, che includono tutti quei fault nati in fase di sviluppo del sistema;
- guasti fisici, cioè quelli che riguardano l’hardware;
- guasti di interazione, che includono tutti i fault esterni.
I fault naturali sono guasti fisici (cioè hardware) causati da fenomeni naturali e senza la
partecipazione umana. Ad esempio, i difetti fisici sono fault naturali che si originano
durante lo sviluppo. Durante la fase operativa, i fault naturali possono essere sia interni,
Analisi sperimentale di software aging nel kernel Linux
19
cioè dovuti al deterioramento fisico di alcuni componenti, che esterni, ossia causati da
processi naturali. Questi ultimi si originano al di fuori dei confini del sistema e riescono a
penetrarvi, ad esempio attraverso radiazioni elettromagnetiche, picchi di potenza elettrica,
cambiamenti di temperatura, ecc..
La definizione dei fault dovuti all’uomo (che possono essere sia hardware che software)
include quelli causati dalla mancanza di intervento quando quest’ultimo sarebbe
necessario, cioè i cosiddetti guasti per omissione. Inoltre, i suddetti fault possono essere
classificati in due gruppi, a seconda dello scopo dell’intervento umano:
- guasti maliziosi, introdotti volontariamente durante l’uso del sistema o anche durante
il suo sviluppo (allo scopo di far nascere situazioni di emergenza in fase operativa)
per alterarne lo stato, provocare una sospensione del servizio o per accedere a
informazioni riservate;
- guasti non maliziosi, causati in maniera inconsapevole.
I fault non maliziosi possono poi essere ulteriormente classificati, a seconda della
consapevolezza degli attori, in:
- guasti non deliberati, dovuti ad errori o ad azioni di sviluppatori, utenti e
manutentori di cui questi ultimi ignorano le conseguenze;
- guasti deliberati, che sono causati da decisioni sbagliate ma volontarie.
Un’ulteriore particolarizzazione dei fault non maliziosi può essere fatta considerando le
capacità di sviluppatori ed utenti che possono provocare:
- guasti accidentali, cioè originati da incidenti e senza l’esplicita volontà della persona
coinvolta;
- guasti da incompetenza, dovuti alla poca conoscenza che gli sviluppatori o gli
utilizzatori hanno del dominio del problema e del sistema stesso, oppure alla scarsa
attenzione che questi pongono su taluni fattori (che, ad esempio, conducono a scelte
di progetto infelici).
I fault di interazione sono guasti operativi (cioè che occorrono durante il normale
funzionamento del sistema) che possono essere sia dovuti all’uomo che a cause naturali.
Analisi sperimentale di software aging nel kernel Linux
20
Essi sono comunque ingenerati da alcuni elementi dell’ambiente d’uso che interagiscono
con l’apparato (quindi si tratta di guasti esterni) in maniera scorretta. A volte possono
essere definiti di interazione i cosiddetti errori di configurazione dovuti al settaggio
sbagliato di parametri in grado di influenzare la sicurezza, la gestione della memoria e
delle reti, la comunicazione fra le varie componenti del sistema, ecc.. Una caratteristica
comune dei guasti di interazione è che essi sono favoriti dalla presenza intrinseca di una
qualche vulnerabilità, cioè di un fault interno capace di abilitarne uno esterno in grado di
danneggiare il sistema.
Un’ultima classificazione dei fault riguarda la loro persistenza. Relativamente ai guasti
hardware è possibile distinguere:
- guasti permanenti, stabili e continui nel tempo;
- guasti transienti, legati a momentanee condizioni ambientali e che scompaiono
definitivamente senza la necessità di alcuna operazione di ripristino;
- guasti intermittenti, che si verificano in corrispondenza di particolari condizioni
ambientali e scompaiono senza alcuna azione di riparazione per poi ricomparire.
Per quanto concerne il software, non esiste la distinzione tra guasti intermittenti e transienti
poiché tutti i guasti software si attivano in corrispondenza di precise condizioni, più o
meno complesse. Ne deriva che questi fault sono permanenti, nel senso che ciascun bug
rimane nel codice (finché non è eventualmente corretto) anche se viene attivato (e quindi
percepito dall’utente) solo da precise condizioni.
1.2.3.1 Guasti software
Gray [4], al fine di definire un meccanismo di fault tolerance, presenta preliminarmente
un modello di fallimenti software basato sulla cosiddetta ipotesi Bohrbug/Heisenbug. È
noto che molti guasti hardware sono soft, cioè di natura transitoria, e che ad essi si può (in
qualche misura) porre rimedio con metodi standard basati su checksum, correzione degli
errori di memoria, ritrasmissione ecc.. Tali tecniche riescono ad innalzare il MTBF di una
Analisi sperimentale di software aging nel kernel Linux
21
quantità che si stima vari fra 5 e 100 unità temporali. Gray afferma che, similmente a ciò
che avviene per l’hardware, anche molti fault software sono soft: è infatti esperienza
comune che, riavviando semplicemente un’operazione dopo un crash, questa spesso non
fallisce una seconda volta. Tale ipotesi, apparentemente paradossale, discende dal fatto che
i bug sistematici vengono tipicamente identificati e risolti durante le fasi di debugging e
testing del software.
Guasti di quest’ultimo tipo vengono chiamati Bohrbug. Essi (il cui nome deriva dall’atomo
di Bohr) sono hard, cioè “solidi”: si tratta spesso di fault permanenti di progetto che hanno
natura abbastanza deterministica. Tale caratteristica implica che i Bohrbug sono facili da
identificare con tecniche standard durante le fasi di debugging e collaudo (oppure
addirittura di deployment).
Se si considera un software industriale, dopo il progetto, le revisioni, la fase di quality
assurance, i test di tipo alpha e beta, ecc., si può ritenere che la maggior parte dei Bohrbug
siano stati eliminati. I guasti residui, tipicamente rari se le fasi precedenti sono state
condotte con rigore, saranno allora correlati a condizioni hardware (fault hardware rari o
transienti, problemi di incompatibilità, questioni legate ai driver, ecc.), condizioni limite
(overflow e underflow, carichi eccessivi, ecc.) o race condition1. In siffatte circostanze, il
riavvio del programma ricrea tipicamente un ambiente molto diverso da quello del
fallimento che, in numerosi casi, conduce al normale funzionamento dell’applicazione. Ai
fault che esibiscono il comportamento appena descritto si dà il nome di Heisenbug (dal
principio di indeterminazione omonimo). Essi possono eludere i sistemi di collaudo per
anni poiché, a differenza dei Bohrbug, una stessa operazione di testing può perturbare
l’ambiente operativo al punto da far sparire l’Heisenbug. Le condizioni di attivazione di un
Heisenbug si presentano, infatti, in maniera molto rara e sono difficilmente riproducibili
proprio perché questi fault sono fortemente dipendenti dall’ambiente operativo. Recenti
studi hanno evidenziato che il 70% degli errori sono di tipo transiente e sono causati
1 Si ha una race condition, o corsa critica, quando in un programma, in cui due o più processi condividono
delle risorse comuni, l’esecuzione del programma stesso è influenzata dal modo in cui i processi vengono schedulati. Questo problema viene gestito mediante la mutua esclusione (si permette ad un solo processo alla volta di accedere alla risorsa condivisa).
Analisi sperimentale di software aging nel kernel Linux
22
principalmente da race condition e da problemi di sincronizzazione.
Taluni autori definiscono gli Heisenbug come un sottoinsieme di un classe di fault ben più
vasta, i Mandelbug [5, 6]. Questi sono fault che possono indurre il software ad esibire un
comportamento caotico e non deterministico rispetto all’occorrenza e non occorrenza di
fallimenti. La loro attivazione e le modalità di propagazione seguono dinamiche complesse
in almeno uno dei seguenti modi:
- con lunghi ritardi tra l’attivazione del fault e l’occorrenza finale del fallimento. Se il
sistema attraversa differenti stati di failure durante la propagazione dell’errore,
diventa difficile identificare le azioni dell’utente che realmente hanno attivato il fault
e causato il fallimento. La semplice ripetizione dei passi eseguiti in breve tempo,
prima dell’occorrenza del fallimento, può non condurre alla sua riproduzione;
- con un comportamento che dipende fortemente da alcuni elementi dell’ambiente,
quali il sistema operativo, l’interazione con altre applicazioni, o l’hardware.
Poiché gli Heisenbug sono guasti software che cambiano il proprio comportamento quando
vengono esaminati o isolati (cioè quando muta l’ambiente operativo), essi risultano un
sottotipo di Mandelbug.
Per quel che riguarda le tecniche utili per la correzione dei bug software [5, 6] è stato
già detto che metodi di progettazione, debugging e testing robusti conducono quasi sempre
all’eliminazione dei Bohrbug. Per i Mandelbug si può sfruttare l’assunto che il reboot del
sistema risolve spesso il problema, accoppiando quest’ultimo con approcci di
checkpointing, ossia tecniche che consentono il salvataggio periodico dello stato
dell’applicazione in memoria stabile. Con metodi siffatti, dopo il fallimento è possibile
riavviare l’applicazione, portandola nell’ultimo stato senza errori memorizzato. Un
secondo approccio consiste nell’utilizzare la replicazione, ossia nel ridondare le risorse del
sistema. Due applicazioni identiche che girano su diverse istallazioni dello stesso sistema
operativo possono, infatti, non esibire gli stessi fallimenti. Se si suppone che tutti i
Bohrbug risultano eliminati dopo il testing, allora i guasti dei due programmi replicati
possono ritenersi legati all’ambiente operativo e saranno, dunque, dei Mandelbug. Il
Analisi sperimentale di software aging nel kernel Linux
23
confronto fra le configurazioni hardware e software dei due sistemi, al momento di un
fallimento in uno dei due, può così aiutare a comprendere le cause del problema.
1.3 Metodi per migliorare la dependability del software
Durante le fasi del ciclo di vita del software si può cercare di assicurare una maggiore
affidabilità al prodotto finale, seguendo differenti approcci per la gestione dei guasti [3]:
- fault prevention;
- fault tolerance;
- fault removal;
- fault forecasting.
La prevenzione dei guasti, considerata come un miglioramento del processo di sviluppo
che ha il fine di ridurre il numero di fault introdotti nel sistema prodotto, è una parte
importante dell’ingegneria in generale. La prevenzione dei bug software si attua, ad
esempio, applicando le buone regole imposte dall’ingegneria del software, quali
l’information hiding, la modularità, l’uso di linguaggi di programmazione tipizzati, ecc..
La tolleranza ai guasti (figura 1.8), che ha lo scopo di evitare i fallimenti, si basa sulla
rilevazione degli errori e sul ripristino del sistema. Per quel che riguarda la prima fase,
essa può essere espletata in maniera concorrente (cioè durante la normale erogazione del
servizio) o preventiva (quando il servizio viene sospeso alla ricerca dei guasti latenti).
Il ripristino, invece, si basa sulla gestione degli errori (error handling), che ha lo scopo di
eliminare gli errori dallo stato del sistema, e sulla gestione dei guasti (fault handling), che
serve ad evitare che uno stesso fault sia nuovamente attivato. Questa seconda fase è seguita
di solito dalla cosiddetta manutenzione correttiva, che ha lo scopo di rimuovere i fault
isolati nella fase di handling. La gestione degli errori può essere implementata attraverso
forme di rollback (si porta il sistema all’ultimo checkpoint, ossia all’ultimo stato senza
errori salvato), rollforward (si assegna al nuovo stato del sistema l’ultimo stato senza
errori) o compensazione (ci si serve della ridondanza per mascherare un errore corrente).
Analisi sperimentale di software aging nel kernel Linux
24
Le prime due tecniche vengono invocate on demand, dopo la fase di rilevazione degli
errori. La compensazione, invece, può essere applicata sia on demand che
sistematicamente, ad intervalli di tempo predefiniti o scanditi da eventi, ed
indipendentemente dall’occorrenza di un errore. L’error handling on demand seguita dalla
fault handling formano la strategia di tolleranza ai guasti chiamata brevemente detection
and recovery, mentre la cosiddetta fault masking deriva dall’uso sistematico della
compensazione.
Figura 1.8: metodi di fault tolerance.
Tra le tecniche per la gestione degli errori va annoverata anche la software rejuvenation
(cfr. paragrafo 1.6). Nel caso dell’aging, in particolare, la rilevazione è effettuata
solitamente misurando la disponibilità delle risorse (ad esempio il livello di memoria
libera) e stimando il loro tempo di esaurimento (TTE – cfr. paragrafo 1.5.2). In molti casi,
comunque, la rejuvenation avviene senza la rilevazione degli errori (cioè periodicamente)
al fine di rimuovere questi ultimi, se presenti, soltanto sulla base del TTE stimato.
Analisi sperimentale di software aging nel kernel Linux
25
La gestione dei guasti, infine, si può comporre delle seguenti fasi:
- diagnosi, che consiste nell’identificare e registrare le cause degli errori in termini di
locazione e tipologia;
- isolamento, che implementa l’esclusione logica o fisica del componente difettoso, in
modo da mascherare il guasto;
- riconfigurazione, che si occupa di ridistribuire i compiti dei componenti guasti ad
unità di riserva non fallite;
- reinizializzazione, che consiste nell’aggiornamento e nella registrazione della nuova
configurazione.
La rimozione dei guasti può avvenire durante la fase di sviluppo o durante quella
operativa. Nel primo caso, essa consiste di tre passi: la verifica, la diagnosi e la correzione.
La verifica serve a controllare che il sistema soddisfi alcune proprietà di interesse. In caso
negativo si passa alla diagnosi, per identificare le cause dell’anomalia, e successivamente
alla correzione dell’errore. Dopo quest’ultima fase, dovrebbe essere ripetuto lo step di
verifica, per assicurarsi di non aver introdotto altri fault con la correzione. La validazione
può essere statica o dinamica, a seconda del fatto che avvenga in maniera formale o
coinvolga il funzionamento reale del sistema.
L’eliminazione dei guasti durante l’uso consiste nella cosiddetta manutenzione correttiva o
preventiva. La prima cerca di rimuovere i guasti che hanno condotto ad un errore rilevato,
mentre la seconda ha lo scopo di identificare ed eliminare quei fault non segnalati che
possono risultare in un errore durante il normale funzionamento del sistema.
La fault forecasting, infine, è condotta per implementare una valutazione del
comportamento del sistema per quel che riguarda l’occorrenza dei guasti. Tale stima consta
di due aspetti:
- la valutazione qualitativa o ordinale, che cerca di identificare e classificare i failure
mode oppure le combinazioni di eventi in grado di condurre il sistema al fallimento;
- la valutazione quantitativa o probabilistica, che prova a determinare il punto fin
dove alcune proprietà di interesse (dette misure) sono soddisfatte.
Analisi sperimentale di software aging nel kernel Linux
26
La valutazione quantitativa si può basare su cosiddetti benchmark di affidabilità, ossia su
procedure di misura del comportamento di un sistema in presenza di fault. Il fine di tali
metodologie consiste nella caratterizzazione degli attributi di dependability, nonché nella
scelta comparativa delle possibili soluzioni.
1.4 Software aging
Come precedentemente affermato, non è possibile produrre software bug-free. Ne
deriva che assicurare una certa dependability alle applicazioni implica la necessità di
impiegare tecniche di fault tolerance in fase operazionale. Per sistemi caratterizzati da
requisiti critici di affidabilità, queste possono includere metodologie per l’analisi
dell’aging e per la relativa rejuvenation.
A tal proposito, si definisce software aging [7] quel fenomeno che riduce l’affidabilità
del software, determinandone una lenta e progressiva degradazione delle prestazioni
oppure improvvisi stalli o crash, causato nella maggior parte dei casi da esaurimento delle
risorse del sistema operativo, frammentazione, corruzione di dati ed accumulo di errori
numerici durante un lungo periodo di tempo. La definizione di software aging può
sembrare un’assurdità se si considera il software un prodotto matematico: un teorema,
infatti, se risulta errato oggi, doveva necessariamente esserlo anche al momento della sua
produzione. In realtà moltissimi sono gli aspetti che rendono il comportamento delle
applicazioni più diverse variabile del tempo e, a volte, non predicibile.
I sistemi maggiormente colpiti da tale fenomeno risultano quelli long running, cioè quelli
che restano in esecuzione per lunghi periodi di tempo e tendono a mostrare un tasso di
fallimenti via via crescente. Per tale motivo, il software aging non si osserva quasi mai in
programmi di utilizzo comune ma in applicazioni specializzate, che spesso sono
caratterizzate da requisiti critici di affidabilità e sicurezza.
Bisogna sottolineare che i fault causati dall’aging non riguardano l’obsolescenza dei
sistemi software (dovuta ai cambiamenti apportati nel tempo al sistema stesso oppure ad
Analisi sperimentale di software aging nel kernel Linux
27
interventi errati di manutenzione), bensì fattori come memory leaking, memory bloating,
lock su file non rilasciati, frammentazione dei dischi ed accumulo di errori di round-off.
Questa assunzione risulta relativamente giovane se si pensa che Parnas [8], nel 1998,
attribuiva le cause dell’aging all’incapacità dei gestori del software di implementare
modifiche in grado di sopperire ai cambiamenti richiesti (tecnologici e funzionali) o
all’inadeguatezza della manutenzione.
Se si considera l’invecchiamento del software, la classificazione dei bug presentata nel
paragrafo 1.2.3.1 va modificata includendo i cosiddetti aging-related fault [9,10] (figura
1.9). Questi ultimi possono appartenere sia alla categoria dei Bohrbug sia a quella dei
Mandelbug, a seconda che risultino deterministici, e quindi riproducibili, oppure
transienti.
Figura 1.9: aging-related bugs.
A titolo di esempio, si considerino un bug che causi un progressivo esaurimento delle
risorse in maniera deterministica ed uno che provochi un esaurimento delle risorse con un
andamento difficilmente predicibile e riproducibile. Il primo fault può allora essere
considerato un aging-related Bohrbug mentre il secondo un aging-related Mandelbug.
Per quel che riguarda i metodi utili ad eliminare i bug collegati all’aging, si deve tener
presente che il tasso di fallimenti di questo genere tende ad aumentare al crescere del
tempo in cui il sistema resta running. Per tale ragione, l’approccio più comune, che prende
il nome è di software rejuvenation (cfr. paragrafo 1.6), è di tipo proattivo e consiste nel
ripristinare periodicamente lo stato del sistema.
Analisi sperimentale di software aging nel kernel Linux
28
1.5 Metodi per l’analisi dell’aging
In letteratura sono state introdotte due tipologie di approcci per studio del software
aging: una di tipo analitico e l’altra basata sulla misurazione del degrado delle risorse di
sistema. Il primo criterio è detto analytical modeling e si fonda sull’uso di modelli formali
come, ad esempio, le reti di Petri e le catene Markoviane. Il secondo metodo di studio è
definito measurement-based, in quanto basato su una metodologia empirica ed eseguito
attraverso un’analisi statistica dei dati relativi al sistema sotto osservazione.
L’approccio model based consente di determinare una pianificazione ottimale della fase di
rejuvenation, che però dipende fortemente dai dati che sono stati rilevati nel sistema
stesso. È quindi necessario che il software ed il suo comportamento (anche in caso di
fallimento) siano ben noti a priori, in maniera tale da poter effettuare un’analisi
comparativa tra il normale comportamento del sistema e quello in caso di guasti.
L’approccio measurement based, invece, consente una stima piuttosto affidabile del TTE
(cfr. paragrafo 1.5.2) ma spesso non rende possibile la determinazione di una
schedulazione ottima di rejuvenation.
È immediato quindi osservare che il primo metodo può essere effettivamente applicato
solo se si dispone di una notevole quantità di informazioni sul comportamento del sistema
nei confronti dei guasti. Il secondo, invece, va preso in considerazione quando non si è in
grado di utilizzare uno dei formalismi di cui al paragrafo 1.5.1 per modellare il
comportamento del sistema.
Oltre ai due approcci appena definiti, è talvolta proposto un meccanismo più semplice,
che può essere chiamato time-based. Esso prevede la periodica esecuzione della tecnica di
rejuvenation ad intervalli di tempo regolari. Se il periodo tra due esecuzioni successive è
relativamente breve, il livello di protezione nei confronti dell’aging è sicuramente molto
elevato ma il costo dovuto all’applicazione frequente della tecnica di rejuvenation risulta
troppo oneroso (ed il più delle volte inutile). Se la frequenza di esecuzione della
rejuvenation è al contrario modesta, allora possono verificarsi fallimenti imprevisti, con
conseguenze anche catastrofiche. Ne deriva che tale tecnica non risulta essere molto
Analisi sperimentale di software aging nel kernel Linux
29
adeguata, in quanto prescinde completamente dall’analisi del funzionamento del sistema, a
differenza degli approcci analitici e basati su misure che saranno approfonditi nei paragrafi
successivi. La rejuvenation dovrebbe essere applicata, cioè, solo a valle dell’analisi
dell’aging, condotta al fine di individuare le cause e la gravità dell’invecchiamento, in
modo da massimizzare gli effetti di un eventuale intervento proattivo.
1.5.1 Analytical modeling
Questo primo approccio si basa sull’utilizzo di modelli matematici che consentono di
definire una rappresentazione del degrado del sistema sotto osservazione. In base ad un
insieme di parametri, forniti in input a tali modelli, è possibile ottenere una schedulazione
ottimale degli interventi di rejuvenation.
Il suddetto approccio si basa principalmente sull’utilizzo del modello SMP (semi-
Markovian process), e sul semi-Markov reward process. Tralasciando i mezzi analitici per
la risoluzione dei modelli, lo svantaggio di queste metodologie risiede nel fatto che è
richiesta comunque la conoscenza di un insieme completamente ordinato di osservazioni
sul sistema.
Il modello base di software rejuvenation è stato proposto da Huang et al. in [7].
Sebbene esso preveda una formulazione del problema attraverso una catena di Markov
tempo continua, i risultati possono essere estesi in maniera tale da ottenere una struttura
matematica più generale. In particolare, il modello di software rejuvenation può essere
visto come un processo semi-Markoviano. In figura 1.10 si riporta il diagramma a stati di
tale modello, in cui gli stati sono così definiti:
- stato 0: molto robusto;
- stato 1: con probabilità di failure;
- stato 2: con failure;
- stato 3: software rejuvenation.
Analisi sperimentale di software aging nel kernel Linux
30
Figura 1.10: modello semi-Markoviano.
Lo stato 0 del modello può essere visto come la fase in cui il software si trova
immediatamente dopo il rilascio oppure a valle di eventuali operazioni di ripristino. Dopo
un periodo di tempo casuale, esso comincia ad invecchiare: tale fase corrisponde allo stato
1 e la probabilità di eventuali failure diviene maggiore di zero non appena vi si giunge.
Bisogna quindi prevedere operazioni che siano in grado di prevenire o, eventualmente,
riparare i probabili fallimenti che si verificheranno sul prodotto software. Per questo
motivo, periodicamente, dovrebbero essere effettuate operazioni di ripristino in maniera da
riportare il sistema nello stato 0. Nel caso in cui un fallimento si verifichi in un istante
precedente rispetto alle operazioni di ripristino, bisogna intervenire attraverso una
riparazione immediata del software (stato 2 del diagramma), che sarà completata dopo un
lasso di tempo casuale. In caso contrario, viene attivata la software rejuvenation (ad
intervalli di tempo regolari), corrispondente allo stato 3 del modello.
A questo punto è lecito porsi qualche domanda. Quando la riparazione è completata
successivamente ad un failure, il sistema software è realmente rinnovato? Probabilmente,
in alcuni casi, la risposta è no. Se si distingue la software rejuvenation dalla software
repair, un’addizionale software rejuvenation potrebbe infatti essere necessaria dopo la
riparazione. Ad esempio, il riavvio del sistema successivamente ad una riparazione
potrebbe procurare una certa pulizia ed un ripristino dell’esecuzione del processo dallo
stato di checkpoint. In figura 1.11 è mostrato, allora, il diagramma di transizione
modificato che è stato proposto in [11]. Secondo tale approccio, la software rejuvenation è
Analisi sperimentale di software aging nel kernel Linux
31
eseguita sia dopo il completamento della riparazione che ad intervalli di tempo prestabiliti
(come già previsto dal modello precedente).
A partire dalla catena semi-Markoviana modificata, è stato sviluppato un algoritmo non
parametrico per stimare la schedulazione ottima di software rejuvenation che massimizza
la disponibilità del sistema. Tale algoritmo prevede la massimizzazione della probabilità
relativa al tempo di permanenza nello stato stabile del diagramma (stato 0), assumendo
che il tempo medio per eseguire una riparazione a seguito di un guasto sia strettamente
maggiore del tempo medio di rejuvenation.
Figura 1.11: modello semi-Markoviano modificato.
Uno dei problemi più rilevanti, nelle applicazioni pratiche, è la velocità di convergenza per
la stima del tempo di software rejuvenation ottimale. In particolare, per poter effettuare
una previsione adeguata, bisogna considerare un elevato numero di campioni,
rappresentanti ognuno gli istanti di tempo in cui si è verificato un failure.
Dalla figura 1.12 è possibile evincere come, nel caso del secondo modello proposto, il
periodo di schedulazione ottima per la software rejuvenation in funzione del tempo medio
Analisi sperimentale di software aging nel kernel Linux
32
di failure del sistema sia sempre inferiore a quello del primo modello.
Figura 1.12: confronto tra i modelli semi-Markoviano e semi-Markoviano modificato.
Un secondo approccio analitico si basa sulle reti di Petri ed utilizza le Markov
Regenerative Stochastic Petri Net (MRSPN) [12]. Una difficoltà nella modellazione di un
sistema stocastico deriva dal fatto che l’intervallo di tempo di rejuvenation è deterministico
e ciò rende il sistema non Markoviano. Le reti di Petri (Stochastic Petri Net – SPN), con la
loro notevole flessibilità, vengono utilizzate allora per una modellazione quantitativa di un
sistema.
Il software si trova inizialmente in uno stato “robusto” in cui la probabilità di fallimenti
teoricamente è pari a zero. Durante la sua messa in esercizio, il prodotto comincia ad
invecchiare e transita in un nuovo stato (come visto anche nel precedente modello). A
questo punto, il sistema lavora normalmente ma la probabilità di fallimenti è maggiore di
zero e tende ad aumentare con il passare del tempo.
In figura 1.13, lo stato robusto è raffigurato dal punto Pup. La transizione Tfprob modella
l’aging del software: quando essa ha luogo, il sistema entra nello stato Pfprob in cui c’è
probabilità di failure maggiore di zero. La transizione Tdown modella i crash del software.
Durante il ripristino (cioè finché Tup è abilitata), ogni altra attività è sospesa (nel
diagramma attraverso l’arco inibitore dal punto Pdown a Tclock). La transizione Tclock modella
l’intervallo di rejuvenation ed è abilitata da Tfprobe: una volta entrati in tale fase, il sistema
giunge nello stato di rejuvenation Prej.
Analisi sperimentale di software aging nel kernel Linux
33
Figura 1.13: modello MRSPN di software rejuvenation.
Al termine di questa procedura, la rete deve essere re-inizializzata e riportata negli stati di
Pup e Pclock. Se il software è in uno stato robusto quando scatta Tclock, allora, dopo che la
rejuvenation è completata, scatta Trej1 per riportare la rete nello stato iniziale.
Tale modello rappresenta una rete SPN che ricade all’interno della classe delle MRSPN.
Come nel caso dell’approccio proposto in [11], studiando una rete di questo tipo può essere
definita una schedulazione ottima di software rejuvenation.
La limitazione più evidente del modello MRSPN consiste nel fatto che esso studia l’aging
soltanto in funzione del tempo, ignorando del tutto il carico di lavoro applicato alla
macchina.
1.5.2 Measurement-Based Approach
L’approccio measurement-based è fondato principalmente sul monitoring delle risorse
di sistema, in base alle quali è possibile fornire un insieme di informazioni sotto forma di
indicatori di aging. Questa metodologia si articola in due fasi distinte:
Analisi sperimentale di software aging nel kernel Linux
34
- monitoring e raccolta periodica dei dati relativi agli attributi che possono aiutare a
valutare lo stato di salute del sistema;
- analisi delle informazioni reperite, al fine di quantificare l’effetto dell’aging sul
sistema sotto osservazione, stimando la metrica del Time To Exahustion (TTE),
indicante il tempo in cui una determinata risorsa si esaurisce completamente.
A seconda dei parametri scelti per lo studio, si parla di workload-independent aging
analysis [13, 14] e workload-based aging analysis [13, 15].
Nel primo caso i dati prescelti per l’analisi riguardano esclusivamente lo stato delle risorse
del sistema (memoria libera/occupata, spazio di swap libero/usato, etc.). A partire da tali
informazioni, reperite in un tempo relativamente lungo la cui durata dipende dal contesto
applicativo, si verifica la presenza di un trend di aging attraverso opportune tecniche di
test (tra cui quella di Mann-Kendall). Se la presenza del trend è significativa, con
strumenti atti alla decorrelazione dei dati reperiti e tramite algoritmi di regressione, è
possibile risalire alle probabili cause del software aging all’interno del sistema.
L’analisi di aging di tipo workload-based è impostata, invece, sull’idea secondo la quale il
fenomeno del software aging viene influenzato anche dal carico di lavoro impartito al
sistema. Difatti, il consumo di risorse dipende direttamente dalla quantità di operazioni e
dal carico imposto da ognuna di queste. L’obiettivo è dunque quello di stimare la
correlazione tra workload applicato, availability e perfomability del sistema. Ovviamente,
in tal caso, i risultati possono essere ritenuti validi soltanto se si suppone che il workload
rimanga pressoché costante nel tempo, per tutta la durata dell’esperimento. Questo tipo di
analisi prevede il tracciamento dei parametri che caratterizzano il sistema, in maniera tale
da monitorare le informazioni a livello del SO che descrivono il carico imposto. Ad
esempio, si può scegliere di monitorare le attività di I/O, il numero di system call invocate,
così come la frequenza di allocazione in memoria.
L’approccio workload-based richiede una fase di analisi articolata, in quanto l’esistenza
del trend va spiegata in base ai parametri di workload applicati. Un esempio, a tal
proposito, è presentato in [15], dove è stata definita una fase preliminare in cui vengono
Analisi sperimentale di software aging nel kernel Linux
35
identificati i cosiddetti stati di workload, ossia gli stati in cui il sistema è caratterizzato da
valori dei parametri di carico “simili”. Successivamente si individuano le probabilità di
transizione da uno stato all’altro, modellando il sistema come un processo semi-
Markoviano. Il calcolo del trend di aging può quindi essere eseguito per ogni stato di
workload, consentendo la stima del TTE in funzione di quest’ultimo. Il procedimento può
essere schematizzato come segue (figura 1.14):
- definizione dell’ambiente/sistema che caratterizza l’esperimento;
- reperimento dei dati di interesse del sistema;
- scelta dei parametri di workload più rappresentativi;
- cluster analysis, mirata all’identificazione degli stati di workload;
- definizione della matrice delle probabilità di transizione tra gli stati;
- fitting della Sojourn Time Distribution, ovvero della distribuzione statistica che
caratterizza il tempo speso dal sistema in ogni stato;
Figura 1.14: esempio di approccio measurement-based.
Analisi sperimentale di software aging nel kernel Linux
36
- assegnazione di un costo per ciascuna risorsa monitorata, in ognuno degli stati di
workload individuati. Per ogni stato i e per ogni risorsa j, si definisce il reward rate
rij come la variazione stimata del consumo della risorsa i nello stato j;
- calcolo dell’expected steady reward rate, che può essere considerato come la
frequenza di consumo di ogni risorsa, mediata su tutti gli stati di workload.
1.6 Software rejuvenation
La rejuvenation [5, 6, 7, 10] può essere definita come un periodico e preventivo riavvio
di un’applicazione, che consente di “ripulire” lo stato interno del sistema. L’intervallo di
rejuvenation è basato essenzialmente sulla longevità dell’applicazione in esecuzione. Il
riavvio del sistema comporta il cleaning delle strutture dati in memoria, nonché la
rigenerazione dei processi nello stato iniziale o in uno precedente di checkpoint. Quindi, si
può definire la rejuvenation come un rollback preventivo e periodico di applicazioni long
running, che cerca di evitare eventuali fallimenti futuri.
Dalla definizione si deduce che la periodicità è un requisito essenziale per la corretta
riuscita del ringiovanimento. L’altra caratteristica fondamentale è che tale tecnica deve
essere eseguita in maniera preventiva, ovvero prima che si possano presentare dei fault, o
comunque prima che l’ambiente di sistema degradi a causa del graduale esaurimento delle
risorse.
La rejuvenation può essere espletata a vari livelli di granularità, ad esempio a livello di
sistema o di applicazione. Un esempio del primo caso consiste nel riavvio dell’hardware.
Di solito si utilizzano, comunque, delle tecniche di rejuvenation selettive (a livello di
applicazione), le quali individuano un componente o un sottosistema specifico su cui è
necessario intervenire. In tal modo è possibile, eventualmente, condurre operazioni di
rejuvenation parziali minimizzando i costi relativi a tale fase del ciclo di vita del software.
Un esempio di rejuvenation selettiva è effettuata dal garbage collector della JVM. Tale
componente consente la gestione automatica della memoria, garantendo l’eliminazione
Analisi sperimentale di software aging nel kernel Linux
37
degli oggetti o dei dati che sono stati allocati e non sono più utilizzati da nessun altro
processo/componente all’interno dell’applicazione. Un altro esempio di tecnica selettiva
può essere individuata nell’utilità di deframmentazione dei dischi, in quanto il fenomeno
della frammentazione può essere causa di aging, poiché favorisce il degrado delle
prestazioni dovuto all’aumento dei tempi di ricerca (seek time) e di latenza.
Non sempre, tuttavia, si possono applicare modalità parziali, ad esempio nel caso di
applicazioni software poco modulari oppure quando la rejuvenation selettiva non produce
pienamente gli effetti desiderati sul sistema.
La software rejuvenation può essere espletata seguendo due criteri distinti [10]:
- approccio a ciclo aperto, cioè senza alcun tipo di feedback da parte del sistema dopo
la rejuvenation stessa. In tal caso, la schedulazione degli interventi può essere basata
solo sul tempo trascorso (implementando, cioè, una rejuvenation periodica) e/o sul
numero istantaneo di job in carico al sistema;
- approccio a ciclo chiuso, secondo cui gli interventi sono basati sulle informazioni
inerenti alla salute del software. Quest’ultimo viene così monitorato continuamente
(nella pratica, ad intervalli di tempo deterministici molto brevi), in modo da
collezionare i dati relativi all’uso delle risorse e alle attività del sistema. Come visto
nel precedente paragrafo, tali informazioni sono poi analizzate al fine di stimare il
TTE delle risorse sotto osservazione. La previsione può essere basata sul tempo, sul
tempo e sul workload oppure sul tasso di fallimenti.
L’approccio a ciclo chiuso può essere ulteriormente classificato a seconda che l’analisi
avvenga off-line oppure on-line. Nel primo caso, ci si basa sui dati collezionati in un
periodo di tempo più o meno lungo (ad esempio, in settimane o mesi). Questa metodologia
funziona particolarmente bene se il sistema ha un comportamento che può essere
considerato deterministico. L’approccio a ciclo chiuso on-line, invece, effettua l’analisi dei
dati collezionati ad intervalli di tempo deterministici e, ad ogni nuova disponibilità delle
informazioni del sistema, stima il tempo di rejuvenation. Tale metodologia è più generale
e risulta applicabile anche ai sistemi con dinamiche comportamentali molto complesse,
Analisi sperimentale di software aging nel kernel Linux
38
che non possono essere facilmente determinate. In tal caso, il comportamento futuro del
sistema viene predetto basandosi sui valori correnti degli attributi collezionati e su quelli
passati opportunamente pesati.
La classificazione degli approcci di rejuvenation appena descritta è schematizzata in figura
1.15.
Figura 1.15: approcci di software rejuvenation.
39
Capitolo 2 Software aging nei sistemi operativi
Gli studi riguardanti il fenomeno di aging, come si è già detto, sono relativamente
giovani ed incentrano il proprio interesse su applicazioni disparate (Java Virtual Machine,
application server, librerie, ecc.) trascurando quasi del tutto l’impatto del fenomeno sui
sistemi operativi (SO). Tuttavia, si deve tener presente che il SO si configura come arbitro
di qualsiasi calcolatore, gestendo e distribuendo le risorse computazionali fra i vari
processi in esecuzione. Ne deriva che, se si considera l’invecchiamento come una minaccia
alla dependability del software long running, non si può prescindere dell’analisi dello
stesso nei SO. In sistemi life critical, infatti, un malfunzionamento del sistema operativo
può avere effetti catastrofici, allo stesso modo (o anche con conseguenze peggiori) di un
fallimento del software applicativo. È dunque utile comprendere le cause che possono
indurre un sistema operativo ad invecchiare, nonché le dinamiche ed i tempi che
interessano il fenomeno. Il fine dell’analisi dovrebbe essere quello di prevenire condizioni
di funzionamento limite, che potrebbero compromettere l’affidabilità del sistema.
Nel corso di questo capitolo si presenta, allora, una breve trattazione riguardante le
principali vulnerabilità che possono compromettere la dependability di un sistema
operativo. Inoltre, vengono descritti alcuni studi sul software aging in ambienti
UNIX/Linux, in maniera tale da definire lo stato dell’arte nell’analisi del fenomeno ed una
base solida su cui fondare la procedura presentata nel seguito della tesi.
Analisi sperimentale di software aging nel kernel Linux
40
2.1 Sistemi operativi e dependability
Un SO, come definito in [2], consiste in un insieme di programmi (software) che
gestisce gli elementi fisici (hardware) di un sistema di calcolo. Esso è composto
principalmente da un set di subroutine e strutture dati per il controllo e la gestione:
- dei componenti hardware costituenti un calcolatore;
- dei programmi che su di esso vengono eseguiti.
Molti sistemi operativi sono utilizzati in scenari life critical in cui è necessario
salvaguardare la sicurezza e l’incolumità di enormi masse di persone (basti pensare ai
sistemi radar che governano il traffico aereo). Altri sono alla base di sistemi commerciali e
bancari nei quali è inaccettabile il minimo errore o l’indisponibilità del servizio anche per
periodi brevi. Ne deriva che assicurare l’affidabilità di siffatti sistemi è una questione di
indiscutibile interesse.
I moderni SO hanno due caratteristiche che li rendono fortemente vulnerabili:
- contengono un’enorme quantità di codice;
- hanno uno scarso isolamento rispetto ai guasti.
Per quel che riguarda il primo punto, si stima che il kernel di Linux contenga più di 10
milioni di linee di codice, mentre quello di Windows XP ne abbia circa 23 milioni.
Ovviamente tali dimensioni implicano, per loro stessa natura, l’esistenza di una serie di
bug non rilevati nelle fasi di debugging e testing. A tal proposito, sono stati effettuati molti
studi statistici con lo scopo di stimare la quantità di errori presente nei software di grande
complessità. Ad esempio, in [16] si calcola che i SO possono contenere fra i 6 ed i 16 bug
per 1000 linee di codice eseguibile, mentre in [17] si afferma che la densità varia dai 2 ai
75 bug per 1000 linee di codice, in funzione dalla dimensione del modulo in esame. Stando
al primo approccio, il kernel di Linux potrebbe dunque contenere un insieme di bug
dell’ordine di 75.000 unità. Del resto, la presenza di una mole di errori così consistente è
stata illustrata già nel 2001 nello studio presentato in [18], che ha il fine di analizzare la
localizzazione e la distribuzione dei bug nei vari moduli del kernel Linux. Tale analisi,
basata sulla ricerca automatica degli errori tramite estensioni del compilatore, mostra che il
Analisi sperimentale di software aging nel kernel Linux
41
codice relativo ai driver, il quale rappresenta circa il 70% dell’intero SO, è il più ricco di
bug, sia in termini di numero assoluto di errori (il che si spiega facilmente considerando la
mole di questa tipologia di codice) che per quel che riguarda il rate medio di bug. Tale
caratteristica va spiegata considerando due fattori. Innanzitutto, bisogna tener presente che
i driver di Linux vengono sviluppati da un gran numero di programmatori diversi che
hanno più familiarità con le periferiche che con il sistema operativo e che, quindi,
commettono facilmente errori nell’utilizzare le interfacce messe a disposizione da
quest’ultimo. In secondo luogo, spesso i driver non sono testati a fondo come altre porzioni
del kernel, anche a causa della grande varietà di dispositivi periferici e delle loro
configurazioni.
Indipendentemente dalla precisione delle stime che si trovano in letteratura, quel che è
certo è che la complessità dei SO fa sì che trovare e correggere tutti i bug in fase di
progetto, sviluppo e testing è chiaramente impossibile. Infatti:
- i tempi di individuazione di un solo bug sono generalmente molto lunghi e, di
conseguenza, volendo ipoteticamente rimuovere tutti gli errori dal codice (cosa
praticamente infattibile), tale tempo andrebbe moltiplicato per il numero totale di bug
del sistema;
- il tentativo di riparazione di un bug può ingenerarne di nuovi.
L’analisi di un SO deve anche tener presente che questo tipo di sistemi software,
tipicamente, non vengono sviluppati da zero ma migliorati semplicemente rispetto a
versioni precedenti e resi disponibili per l’utilizzo in un breve lasso di tempo (ciò accade
anche per il sistema operativo Linux). Si parla, in tal caso, di sistemi OTS (Off-The-Shelf) i
quali, in generale, vengono sviluppati per andare incontro alle esigenze di molteplici utenti,
bilanciando costi, tempi e prestazioni. Un siffatto approccio rende elevata la probabilità
che gli utenti, utilizzando il software, causino comportamenti non previsti dagli
sviluppatori (e quindi anche guasti). Tale fenomeno è noto come dependability pitfall: a
fronte di una sostanziosa riduzione dei tempi di sviluppo, della quantità di codice da
scrivere, nonché dei costi, aumenta la probabilità di effetti difficilmente riproducibili,
Analisi sperimentale di software aging nel kernel Linux
42
dovuti principalmente all’integrazione dei vari componenti. Tipicamente, le attività di
testing permettono di individuare solo i guasti permanenti (che si manifestano quando
l’applicazione esibisce comportamenti non conformi alle specifiche di progetto), lasciando
insoluti quelli transienti, che sono il risultato di particolari e temporanee condizioni
ambientali, non prevedibili a priori e spesso difficili da riprodurre.
Oltre alla dimensione, una seconda vulnerabilità dei SO è rappresentata, come si è detto,
dallo scarso isolamento rispetto ai guasti. Un sistema software è costituito da componenti
isolate quando i moduli che ne fanno parte risultano ben incapsulati e comunicano fra loro
solo attraverso specifiche interfacce. La peculiarità delle catene di errori nei sistemi
operativi è rappresentata dal fatto che un guasto non rimane circoscritto alle aree in cui si
presenta ma, il più delle volte, si estende in maniera casuale alle varie componenti della
macchina, con possibilità di crash dell’intero sistema. I singoli moduli software costituenti
il SO non sono, infatti, isolati bensì interconnessi in modo complesso, per cui un
malfunzionamento al loro interno implica spesso comportamenti ed effetti casuali e non
facilmente tracciabili. Ovviamente, la rimozione dei bug, una volta individuati, risulterebbe
notevolmente semplificata se i vari componenti del sistema operativo fossero isolati in
maniera opportuna. In tal modo, sarebbe anche più semplice modificare un singolo
sottosistema, evitare la propagazione di un fault in parte o in tutto il resto del SO e far sì
che la rimozione di un bug non ne provochi altri. Tuttavia è difficile, se non impossibile,
assicurare l’isolamento dei moduli del kernel, non solo a causa della loro complessità ma
anche perché, come si è detto, se ne producono continuamente versioni aggiornate, di
norma aggiungendo o modificando codice esistente.
Concludendo, non si può evitare che un SO abbia dei bug (tantomeno degli aging-
related bug) e la rilevazione di questi è in ogni caso complessa. Si deve tener presente,
infatti, che più un sistema è articolato meno esso risulta tracciabile (diventa cioè sempre
più difficile monitorare lo stato delle sue risorse). Si può pensare, tuttavia, di applicare un
metodo che miri ad osservare lo stato del sistema operativo relativamente alle risorse
“consumate”. In tal modo, infatti, l’analisi degli errori potrebbe essere guidata
Analisi sperimentale di software aging nel kernel Linux
43
dall’individuazione del componente (o dei componenti) che provoca l’esaurimento più o
meno graduale delle risorse computazionali, causando fallimenti (evidenti all’utente) o
ingenerando aging (latente, fino al momento del crash).
2.2 Studi correlati
Uno dei pochi studi condotti sul fenomeno del software aging nei SO è quello descritto
in [14], che fa uso del Simple Network Management Protocol (SNMP). Sulla base di tale
protocollo, gli autori definiscono un tool di monitoring distribuito, impiegato per
collezionare, ad intervalli di tempo regolari, i dati relativi alle risorse utilizzate da
workstation UNIX connesse in rete. Per la rilevazione di eventuali trend di aging, tali
informazioni sono poi sottoposte ad operazioni statistiche e, al fine di quantificare gli
effetti dell’invecchiamento sul SO, viene utilizzata la metrica del TTE.
SNMP, come definito nella RFC 1157 [21], è un protocollo a livello applicativo in grado di
offrire servizi di network management nella suite IP. Esso si basa su tre componenti
principali: il manager, l’agent ed il MIB (Management Information Based). La relazione
client-server tra manager ed agent è definita da SNMP, mentre il MIB descrive le
informazioni che possono essere ottenute o modificate attraverso interazioni tra le entità
precedentemente menzionate.
Il framework SNMP è stato utilizzato per progettare ed implementare un tool di
tracciamento delle risorse distribuito, con l’obiettivo di monitorare da remoto la vita di 3
workstation UNIX, connesse sulla stessa Ethernet LAN in un laboratorio della Duke
University. I 3 moduli chiave del tool sono:
- Pro-active Fault Management MIB (PFM MIB), che definisce gli oggetti utilizzati
per determinare lo stato di salute delle workstation;
- PFM agent, che rappresenta il processo agent eseguito in background su ogni
macchina monitorata;
Analisi sperimentale di software aging nel kernel Linux
44
- PFM manager, la cui funzione primaria consiste nel recuperare i valori degli oggetti
desiderati, attraverso l’invio di richieste get agli agent dislocati nella rete.
In figura 2.1 è mostrato l’albero MIB per il modulo PMF. Poiché gli oggetti per MIB
proprietari vanno definiti in un sottoalbero posizionato sotto il ramo enterprises, il
modulo pfmMIB è stato definito nella gerarchia come {enterprises 2598} (dove il
valore 2598 è arbitrario).
Figura 2.1: albero MIB per il modulo PFM del tool di monitoring SNMP.
Gli oggetti monitorati sono classificati nelle 7 seguenti categorie, rappresentante ciascuna
un oggetto simbolico all’interno del modulo:
- hostID (definito come {pfmMIB 1}), i cui oggetti foglia, nodeName, osName,
osRelease, osVersion e mcHardwareName, definiscono le caratteristiche delle
workstation;
Analisi sperimentale di software aging nel kernel Linux
45
- timeVal (definito come {pfmMIB 7}), che contiene due oggetti foglia,
dataAndTime e hostUpStats, rappresentanti rispettivamente la data e l’ora
corrente e l’istante dell’ultimo reboot della macchina;
- osResource (definito come {pfmMIB 2}), i cui oggetti foglia descrivono lo stato
delle risorse del sistema operativo. Alcuni di questi sono usedSwapSpace,
fileTableSize, realMemoryFree e procsTotal;
- procStats (definito come {pfmMIB 4}), con oggetti foglia che descrivono lo stato
dei processi running all’interno macchina;
- fileSysResource (definito come {pfmMIB 5}), i cui oggetti foglia,
tmpDirSize, tmpDirUsed, tmpDirAvail e tmpDirCapacity, tengono traccia
della directory /tmp dei sistemi UNIX like;
- netResource (definito come {pfmMIB 3}), che viene utilizzato per monitorare la
disponibilità e l’utilizzo delle risorse relative al sottosistema di networking;
- ioResource (definito come {pfmMIB 6}), che contiene informazioni inerenti alle
operazioni effettuate, attraverso i terminali, sull’ I/O da/verso il disco.
Figura 2.2: monitoring delle informazioni UNIX tramite SNMP.
Una singola istanza per ogni oggetto foglia del MIB è inizializzata come una variabile
globale nel programma agent, il quale ascolta passivamente su un numero di porto
prefissato. Nel caso venga ricevuta una richiesta di get dal manager, l’agent esegue un
Analisi sperimentale di software aging nel kernel Linux
46
insieme di istruzioni per ottenere il valore degli oggetti foglia richiesti dal SO, assegna il
valore alla variabile ed invia la risposta al manager. In particolare, i valori degli oggetti
foglia sono ottenuti dall’agent eseguendo un insieme di utility UNIX messe a disposizioni
dal sistema operativo come pstat, iostat, vmstat, nfsstat, top e df. Tale procedura
è schematizzata in figura 2.2.
I valori relativi a tutti gli oggetti sono stati reperiti ogni 15 minuti ed i dati memorizzati in
file aventi formattazione del tipo nome-valore, riportanti il nome dell’oggetto
monitorato ed il suo valore, istante per istante. Le informazioni collezionate sono state
allora osservate in funzione del tempo, al fine di:
- verificare l’esistenza di trend di aging;
- definire la natura delle variazioni;
- quantificare l’aging nel SO UNIX.
La verifica della presenza di un trend sull’utilizzo delle risorse nel sistema operativo è stata
condotta tramite il test di Mann-Kendall (cfr. Capitolo 4). Una volta confermata la
presenza del trend, la sua pendenza è stata calcolata con il metodo dei minimi quadrati
implementato attraverso i test di regressione lineare. È stato allora possibile definire il TTE
relativo alle seguenti risorse:
- memoria reale libera;
- dimensione della tabella dei file;
- dimensione della tabella dei processi;
- spazio di swap utilizzato.
In particolare, detto m il coefficiente angolare stimato del trend e c l’intercetta del punto
iniziale, attraverso l’espressione
y=mx+c,
è possibile individuare dopo quanti giorni le risorse monitorate saranno completamente
esaurite (determinando il valore di x per cui y=0).
In base alla classificazione delle tecniche di analisi dell’aging presentata nel paragrafo 1.5,
lo studio appena esposto si può definire come basato su misure ed indipendente dal
Analisi sperimentale di software aging nel kernel Linux
47
workload. Esso, infatti, colleziona un insieme di informazioni di sistema al fine di
individuare un trend di aging, senza tuttavia tener conto del carico di lavoro imposto alle
workstation.
Un approccio basato sul workload si trova, invece, nella trattazione esposta in [15], che
riprende ed approfondisce lo studio precedentemente descritto ma utilizza una metodologia
per l’analisi dell’aging ibrida, cioè basata sia su un modello analitico che su misure
statistiche. In particolare, la collezione dei dati di sistema è molto simile al caso
precedente: vengono infatti monitorate 9 workstation UNIX eterogenee, connesse ad una
stessa Ethernet LAN, raccogliendo più di 100 parametri ad intervalli regolari di 10 minuti.
Come accennato nel paragrafo 1.5.2, la caratterizzazione del workload è effettuata
attraverso un modello semi-Markoviano, che si fonda proprio sulla stima dell’uso delle
risorse monitorate tramite il tool SNMP. In particolar modo, fra tutte le variabili
controllate, si è scelto di basare l’analisi solo sulle seguenti:
- numero di context switch effettuati in un intervallo di tempo;
- numero di system call invocate in un periodo di tempo predefinito;
- pageIn;
- pageOut.
Tali informazioni definiscono, nello spazio a quattro dimensioni, il workload misurato in
un dato intervallo di tempo. Per individuare una serie di stati differenti di workload
(ciascuno dei quali rappresenti una situazione di carico per il sistema ben definita), viene
utilizzata la cluster analysis. La catena semi-Markoviana, allora, può trovarsi in uno degli
stati determinati e commutare da uno stato i ad uno stato j attraverso un processo che può
essere considerato a due stadi. Nello stadio iniziale, la catena permane in i per un certo
periodo di tempo descritto da una distribuzione Hi(t), denominata sojourn time. Nel
secondo stadio, invece, essa si muove nello stadio j con probabilità pij. Dunque, per
caratterizzare il processo semi-Markoviano (cioè il workload) è necessario definire la
matrice P delle probabilità di transizione da uno stato all’altro ed il vettore H(t) che
descrive la distribuzione statistica del tempo speso dal sistema in ogni stato. Dopo queste
Analisi sperimentale di software aging nel kernel Linux
48
stime, per ciascuna risorsa monitorata e per ogni stato della catena, viene definita una
funzione di costo, detta reward rate rij, che rappresenta la variazione del consumo della
risorsa i nello stato j. Il modello così costituito è stato risolto con un tool automatico allo
scopo di stimare il TTE relativo a ciascuna risorsa monitorata. I risultati del modello semi-
Markoviano vengono infine inseriti in un secondo modello ad alta affidabilità, che tiene
conto sia dei fallimenti seguiti da azioni di ripristino che delle attività di recovery
proattive. Tale modello esaustivo viene utilizzato per derivare lo scheduling ottimo di
rejuvenation che massimizza la disponibilità del sistema o minimizza il costo dovuto ai
fallimenti.
Come si evince, l’approccio descritto è notevolmente più complesso del primo, poiché si
basa su strumenti matematici molto laboriosi (le catene semi-Markoviane, il reward rate, il
modello esaustivo per lo scheduling ottimo di rejuvenation, ecc.).
Un ulteriore interessante studio sul software aging è presentato in [33] e si concentra
sull’analisi dell’utilizzo delle risorse in un web server Apache. In particolare, i dati
vengono collezionati ponendo il sistema in condizioni di sovraccarico tramite Httperf,
un tool per misurazione delle performance di un web server, basato sull’invio periodico di
richieste http. Gli esperimenti hanno mostrato l’esistenza di software aging. Sono state così
applicate sia una tecnica statistica non parametrica che una parametrica basata su serie
temporali per l’analisi del fenomeno e la stima dello scheduling ottimale di rejuvenation. A
valle di tali analisi, effettuando operazioni di kill sui processi Apache (ad intervalli
fissati dalle precedenti stime), è stato osservato un offset nel trend di aging, senza tuttavia
che questo fosse eliminato. Questo “aging residuo” è stato dunque attribuito all’attività del
sistema operativo.
Gli studi descritti presentano alcuni punti di debolezza. Innanzitutto, essi si propongono
di analizzare l’aging dei sistemi UNIX/Linux con appositi tool di monitoring, senza
tuttavia spiegare in maniera chiara come va tenuta in conto l’azione dello strumento nelle
misure effettuate. Risulta infatti ovvio che l’interposizione di un protocollo di rete fra la
macchina da analizzare e la misura collezionata, aggiunge un certo overhead (in tempo e
Analisi sperimentale di software aging nel kernel Linux
49
memoria occupata) al sistema nel suo complesso. Inoltre, nei primi due casi,
l’invecchiamento viene analizzato per un insieme di workstation connesse in rete e,
dunque, si forza il comportamento di ciascuna macchina ad una condizione di
funzionamento che non può essere ritenuta del tutto generale. In altre parole, questi lavori
considerano l’aging complessivo dell’apparato costituito da sistema operativo più
applicazioni.
Per contro, la procedura proposta nei prossimi capitoli si concentra sul software aging
introdotto esclusivamente dal kernel e, a tal fine, fa uso di carichi sintetici all’interno di un
ambiente controllato. Quantificare l’aging del solo SO può allora servire ad isolare il
contributo di quest’ultimo da quello della particolare applicazione in esecuzione. Il
procedimento sperimentale descritto si basa, comunque, su alcuni concetti utilizzati nei due
studi appena illustrati (come la normalizzazione dei dati raccolti, l’utilizzo del process file
system, ecc.) ma propone, come si vedrà, un approccio più semplice per il tracing delle
informazioni di sistema di un singolo kernel Linux. Tale procedura non si affida a
componenti o protocolli esterni al sistema da monitorare ma a sonde posizionate all’interno
del SO.
50
Capitolo 3 Instrumentazione del sistema operativo Linux
Nel corso di questo capitolo si presenta inizialmente una breve descrizione del SO
Linux, per poi entrare nello specifico del kernel e della relativa instrumentazione di codice.
Lo scopo della digressione introduttiva sulla struttura del SO consiste nel mostrare
l’estrema complessità dell’architettura software in esame che si traduce in una pari
difficoltà d’analisi. Inoltre, la descrizione dei sottosistemi del kernel aiuta ad individuare
l’insieme delle aree di interesse da monitorare, al fine di controllare lo stato delle risorse.
Viene così descritto un meccanismo che può essere utilizzato per il tracing delle
informazioni di sistema, su cui si basa la procedura proposta per lo studio del fenomeno di
software aging.
3.1 Struttura del sistema operativo Linux
Il sistema operativo Linux è costituito da tre parti principali di codice (come accade per
la maggioranza delle implementazioni tradizionali di UNIX) [2]:
- il kernel, che è responsabile della gestione di tutte le principali astrazioni del sistema
operativo (tra le quali la memoria virtuale ed i processi) e consente un accesso
protetto e supervisionato alle risorse hardware;
- le librerie di sistema, che definiscono un set di funzioni standard (tramite le quali le
applicazioni possono interagire con il kernel) e realizzano la maggior parte dei
Analisi sperimentale di software aging nel kernel Linux
51
servizi del SO che non necessitano dei pieni privilegi del nucleo. Esse implementano
molte funzionalità, come l’accesso ai file e l’allocazione della memoria;
- le utilità di sistema, cioè programmi che eseguono funzioni di gestione singole e
specializzate. Alcune di queste utility vengono richiamate solo una volta, al fine di
inizializzare o configurare peculiari aspetti del sistema; altre, che nella terminologia
UNIX vengono chiamate demoni, sono in esecuzione in modo permanente, con il
compito di gestire servizi continui, come la risposta alle connessioni di rete in
ingresso, l’accettazione delle richieste di login dai terminali oppure l’aggiornamento
dei file di log.
A differenza della maggioranza dei kernel UNIX, Linux non è monolitico ed offre un
ottimo supporto alla modularità [20]. È cioè possibile caricare o escludere dinamicamente
porzioni del kernel (tipicamente quelle relative ai driver dei dispositivi), che sono appunto
chiamate moduli. I principali vantaggi nell’utilizzo dei moduli sono i seguenti:
- quando viene scritto un nuovo driver, non è necessario ricompilare il nucleo
modificato, collegarlo e ricaricarlo nel sistema. Questa procedura onerosa, infatti,
viene eliminata grazie all’approccio modulare, che consente di compilare
separatamente il driver e caricarlo nel kernel già in uso;
- ciascun modulo non dipende dalla piattaforma hardware;
- un modulo può essere linkato al kernel in esecuzione quando sono necessarie le sue
funzionalità ed eliminato se non ce n’è più bisogno. Ciò rende ottimale l’uso della
memoria;
- una volta caricato, il nuovo modulo è equivalente al codice oggetto staticamente
linkato al kernel. Quindi, esso è eseguito in maniera privilegiata, con pieno accesso
alle risorse computazionali e non c’è dunque bisogno di passare nessun messaggio
particolare per invocare le sue funzioni. Ne deriva che l’utilizzo di un modulo linkato
dinamicamente non implica nessun degrado delle prestazioni rispetto all’uso del
codice statico.
Gli elementi principali della struttura a moduli di Linux sono [2]:
Analisi sperimentale di software aging nel kernel Linux
52
- la gestione dei moduli, che consente il caricamento in memoria dei moduli aggiunti e
la loro comunicazione con il resto del nucleo;
- la registrazione dei moduli, la quale consiste nel comunicare alle altre componenti
l’esistenza di un nuovo modulo;
- la risoluzione dei conflitti, che permette ad un modulo di riservarsi l’uso di certe
risorse fisiche, proteggendole dall’interferenza accidentale di altri moduli.
La figura 3.1 esemplifica la struttura di Linux. Al livello più alto dell’architettura c’è lo
spazio utente (user space), in cui vengono eseguite le applicazioni. Al di sotto di tale strato
vi è il cosiddetto kernel space, contenente il cuore del sistema operativo. Il kernel può
essere suddiviso ulteriormente in tre sottolivelli:
- system call interface, per la gestione delle chiamate di sistema;
- codice del kernel indipendente dall’architettura della macchina;
- codice del kernel dipendente dall’architettura della macchina, il quale costituisce
quello che viene più comunemente denominato BSP (Board Support Package). Esso
varia a seconda del processore e della specifica piattaforma per una data architettura.
Figura 3.1: architettura fondamentale del SO Linux.
Analisi sperimentale di software aging nel kernel Linux
53
Dell’user space fa parte anche la libreria GNU C (glibc) [19] che gestisce l’interfaccia
con le system call ed i meccanismi di transizione tra kernel e user space. Essa si basa sugli
standard ISO C e POSIX, nonché sulle implementazioni System V e Berkeley UNIX. La
libreria contiene facility per la gestione dei caratteri, delle stringhe e degli array, della
memoria virtuale e delle pagine, dei file e degli stream, dei socket, dell’interfaccia
terminale e dell’I/O a basso livello, dei processi, degli utenti e dei gruppi, come pure
funzioni matematiche e per la gestione dei segnali d’interruzione. Le operazioni principali
vengono implementate invocando le system call (in quanto alle applicazioni non è
concesso l’accesso diretto al tali chiamate). Una system call è un servizio che un
programma richiede al kernel poiché solo quest’ultimo possiede i privilegi per eseguirlo. I
programmatori non hanno bisogno di conoscere i dettagli delle chiamate di sistema perché
la libreria GNU C mette a disposizione funzioni che virtualmente offrono tutti i servizi
delle system call, fungendo da wrapper e gestendo il passaggio del controllo alle reali
chiamate di sistema. Ad esempio, per modificare i permessi assegnati ad un file, si utilizza
comunemente la chiamata di libreria chmod e non direttamente la system call che gestisce
il servizio in kernel space. Tuttavia, in problemi di programmazione molto specifici, può
essere necessario dover creare una chiamata di sistema esplicitamente e, a tale scopo,
glibc mette a disposizione la funzione syscall. Nella maggioranza dei casi, comunque,
il ricorso a tale funzione non è necessario e i servizi del kernel vengono invocati come
mostra, in maniera schematica, la figura 3.2 (che presenta un esempio relativo alla system
call read).
Figura 3.2: esempio di system call nel SO Linux.
Analisi sperimentale di software aging nel kernel Linux
54
Il SO Linux mette a disposizione due possibili modalità di esecuzione [20]:
- user mode, per normali applicazioni e librerie d’utente, che non possono accedere
liberamente alle strutture dati ed ai programmi del kernel;
- kernel mode, per il codice appartenente al sistema operativo che viene eseguito in
modalità privilegiata del processore, con accesso totale a tutte le risorse fisiche del
computer. Tale modalità viene utilizzata per il codice del kernel (contenuto in un
eseguibile principale, denominato vmlinuz), per quello relativo alle chiamate di sistema
(le Interrupt Service Routine – ISR), per lo scheduler, per il gestore della memoria, e così
via.
Ogni CPU fornisce istruzioni speciali per commutare dall’user mode al kernel mode e
viceversa. Generalmente un programma viene eseguito in user mode e si passa all’altra
modalità solo quando esso richiede servizi al kernel il quale, dopo averli espletati, riporta il
processo in user mode. Relativamente all’esecuzione in modalità utente, un processo [2]
può essere definito come una entità del sistema operativo che esegue codice appartenete
alle applicazioni, alle quali è offerta l’astrazione di una macchina virtuale a loro esclusiva
disposizione. Tale macchina virtuale comprende un processore, una memoria contenente
codice eseguibile, stack, heap, metodi per invocare chiamate di sistema, ecc.. Quindi, un
processo [20] è un’entità virtuale che solitamente ha tempo di vita limitato all’interno del
sistema. I task di creazione, distruzione e sincronizzazione dei processi esistenti sono
demandati ad un gruppo di routine del kernel. In tal senso, il kernel stesso non va inteso
come un processo speciale bensì come un gestore di processi. Il modello user/kernel mode
assume che i processi richiedano servizi al SO tramite apposite system call in grado di
eseguire, per prima cosa, le istruzioni (dipendenti dall’hardware) per commutare dall’user
al kernel mode.
In un sistema monoprocessore, solo un processo alla volta può essere in esecuzione e
passare da una modalità all’altra come, ad esempio, illustra la figura 3.3. In essa il processo
1 è in user mode e passa al kernel mode dopo aver richiesto un servizio del SO tramite
system call (o meglio tramite un apposito wrapper della libreria GNU C). Dopo
Analisi sperimentale di software aging nel kernel Linux
55
l’espletamento del servizio, il processo torna in modalità utente finché non occorre una
timer interrupt (la quale indica che il tempo a disposizione del processo 1 è finito). Di
conseguenza, viene attivato lo scheduler in modalità supervisore che sceglie di eseguire un
nuovo processo. Ha così luogo un context switch che porta il processo 2 ad essere eseguito
in user mode, sino ad un’interruzione che richiede l’uso di un dispositivo hardware. Il
kernel serve allora l’interruzione in modalità privilegiata, dopodiché commuta di nuovo in
modalità utente per continuare l’esecuzione del processo 2.
Figura 3.3: transizione tra user e kernel mode per l’esecuzione di due processi.
Quest’esempio mostra che le routine del kernel possono essere attivate in vari modi:
- tramite l’invocazione di una system call da parte di un processo (quindi in maniera
sincrona);
- quando un processo in esecuzione solleva un’eccezione, ossia una condizione
inusuale dovuta, ad esempio, ad una istruzione non valida;
- quando un dispositivo periferico alza un segnale di interruzione per notificare alla
CPU l’occorrenza di un evento (come, ad esempio, la fine di un’operazione di I/O).
Ciascuna interruzione viene servita, in tal caso, da un programma del kernel detto
interrupt handler e può presentarsi in qualunque istante poiché le periferiche operano
in maniera asincrona rispetto al processore;
- se viene eseguito un cosiddetto kernel thread, cioè un particolare processo
privilegiato che gira in kernel mode e nello spazio di indirizzamento del kernel e che
Analisi sperimentale di software aging nel kernel Linux
56
non interagisce con gli utenti né richiede l’uso di dispositivi periferici (tali thread
sono creati, di solito, all’avvio del sistema e restano attivi fino allo spegnimento).
Per quanto riguarda la gestione della memoria, un processo utente è costretto a rimanere
entro il suo spazio di indirizzamento (memoria virtuale) in maniera tale da non poter
accedere direttamente a dati o istruzioni di altri processi e del sistema operativo né agli
indirizzi dei dispositivi. In tal modo, il SO rende le risorse di sistema protette facendo sì
che un processo sia in grado di colloquiare con l’esterno soltanto attraverso di esso, tramite
chiamate di sistema (per risorse private) o file memory mapped e shared memory (per
risorse condivise). Una piccola porzione dello spazio di indirizzamento di ogni processo è
riservata ai dati del kernel. In particolare, i primi 3 GB sono disponibili per il processo
mentre l’ultimo GB è riservato al kernel e buona parte di questo è mappato direttamente
con la memoria fisica. La tabella delle pagine relativa a quest’ultima parte di memoria,
definita master kernel page table, è identica per tutti i processi. Il kernel può accedere alle
pagine di tutti i processi, spostarle, swapparle ed utilizzare parte della memoria fisica per le
strutture dati e per l’I/O.
Figura 3.4: accesso alla memoria nella commutazione tra user e kernel mode.
Analisi sperimentale di software aging nel kernel Linux
57
Quando viene invocata una chiamata di sistema, non è dunque necessario un cambio di
contesto totale in quanto il codice del kernel può accedere all’ultimo GB dello spazio di
indirizzamento del processo (in esecuzione in user mode) e commutare, quindi, in kernel
mode. La figura 3.4 esemplifica il passaggio fra le due modalità di esecuzione.
3.1.1 Sottosistemi del kernel
Il kernel Linux può essere visto come un aggregato di sottosistemi tra loro comunicanti,
ciascuno dei quali offre peculiari funzionalità. In particolare, possono essere identificati 5
sottosistemi (figura 3.5), adeguatamente descritti in [2]:
- process management;
- memory management;
- file system;
- networking;
- device control.
Figura 3.5: sottosistemi del kernel Linux.
Analisi sperimentale di software aging nel kernel Linux
58
3.1.1.1 Process management
Al primo sottosistema menzionato è demandata la gestione di ciascun processo, definito
come il contesto di base all’interno del quale ogni attività richiesta da un utente viene
elaborata dal SO. Linux adotta un modello dei processi simile a quello di UNIX per
mantenere una certa compatibilità, pur discostandosi da quest’ultimo per alcuni aspetti. In
particolare, come UNIX, la gestione dei processi è espletata secondo il modello fork-exec:
un nuovo processo viene creato con la chiamata di sistema fork, mentre un nuovo
programma viene eseguito tramite una execve. Le due funzioni sono nettamente
differenti, nel senso che si può creare un processo con la fork senza necessariamente
avviare un nuovo programma e, similmente, l’esecuzione di un nuovo programma non
richiede la creazione preventiva di un processo. Il vantaggio di tale approccio risiede nella
sua estrema semplicità. Piuttosto che dover specificare ogni dettaglio sull’ambiente
relativo ad un nuovo programma, questo viene semplicemente eseguito nell’ambiente
esistente. Se poi un processo padre desidera modificare l’ambiente nel quale dovrà essere
eseguito un processo figlio, può richiamare una fork ed altre system call per modificare il
processo figlio prima di lanciarne l’effettiva esecuzione. Da questo approccio deriva che,
in UNIX, un processo racchiude in sé tutte le informazioni necessarie al SO per gestire il
contesto di una sua singola esecuzione. In Linux, tale contesto può essere suddiviso in
alcune sezioni specifiche che contengono informazioni riguardanti:
- l’identità del processo, la quale consiste principalmente di un identificatore (process
identifier – PID), di eventuali altri identificatori che collegano il processo ad un
gruppo o ad una sessione di lavoro e di credenziali che specificano i diritti di accesso
del processo alle risorse del sistema;
- l’ambiente del processo, che questo eredita dal suo genitore. Esso è composto da un
vettore di argomenti (cioè l’elenco degli argomenti passati dalla riga di comando per
invocare l’esecuzione del programma) e da un vettore d’ambiente (ossia un elenco di
coppie del tipo NOME = VALORE che associano valori arbitrari alle variabili
d’ambiente);
Analisi sperimentale di software aging nel kernel Linux
59
- il contesto del processo, che indica lo stato del programma in ogni istante.
Il kernel Linux prevede anche la gestione dei thread in maniera molto semplice, cioè
adottando una stessa rappresentazione interna per processi e thread. A differenza di un
processo, però, un thread viene creato mediante la system call clone, la quale definisce
una nuova identità per il nuovo processo ma permette a quest’ultimo di condividere lo
spazio di indirizzi con il genitore (a differenza della fork che crea, per il figlio, un
contesto del tutto nuovo).
Per quel che riguarda lo scheduling dei processi, Linux adotta due differenti algoritmi: uno
per l’equa suddivisione del tempo di CPU tra i vari processi e l’altro, basato sulla priorità
assoluta di questi, per le elaborazioni in tempo reale. Il primo usa un sistema a priorità
basato su crediti. Ogni processo possiede, cioè, un certo numero di crediti e, di volta in
volta, viene scelto per l’esecuzione quello con il massimo numero di crediti. Lo scheduling
in tempo reale, invece, può essere sia di tipo First-Come First-Served (FCFS) che Round-
Robin (RR). In entrambi i casi, lo scheduler sceglie il processo a priorità maggiore ma
nell’approccio FCFS il processo prescelto continua l’elaborazione fino alla terminazione o
fino a che esso stesso non si blocca, mentre nel caso RR esso può essere sospeso allo
scadere di un timeout.
3.1.1.2 Memory management
Il sottosistema di memory management consta di due componenti distinte: la prima si
occupa dell’allocazione e della deallocazione della memoria fisica; la seconda, invece,
della memoria virtuale.
Lo strumento principale per la gestione della memoria fisica è l’assegnatore delle pagine
che alloca lo spazio fisico seguendo un algoritmo buddy-heap. Secondo tale approccio,
ogni porzione di memoria assegnabile ha una compagna adiacente (buddy) e, quando
entrambe si liberano, esse vengono combinate per creare una regione più ampia la quale
avrà, a sua volta., una compagna contigua e così di seguito. Se la richiesta di una porzione
Analisi sperimentale di software aging nel kernel Linux
60
piccola di memoria non può essere soddisfatta, allora una regione più ampia viene
suddivisa in due porzioni buddy e così via.
Molti componenti del SO Linux hanno la necessità di allocare intere pagine all’occorrenza
ma possono essere richiesti anche blocchi di memoria molto piccoli. Il kernel mette allora
a disposizione un allocatore addizionale, per la richiesta di porzioni di memoria aventi
dimensioni arbitrarie e non note a priori. Questo servizio, kmalloc (analogo alla funzione
malloc del linguaggio C) alloca intere pagine e, successivamente, le divide in porzioni più
piccole.
Il sistema di memoria virtuale gestisce, invece, il contenuto dello spazio di indirizzi
virtuale di ciascun processo. Come visto nel paragrafo precedente, il kernel riserva, per il
proprio uso interno, una regione (costante e dipendente dall’architettura) di spazio di
indirizzi virtuali per ogni processo. Le voci nella tabella delle pagine che mappano queste
pagine del kernel sono marcate come protette, cosicché le pagine stesse non siano visibili o
modificabili quando il processo è in esecuzione in modalità utente.
3.1.1.3 File system e device control
Per quel che riguarda la gestione del file system, Linux adotta lo stesso modello di
UNIX, secondo cui un file non è necessariamente un insieme di dati memorizzati in un
disco bensì qualsiasi elemento capace di implementare la lettura e la scrittura di un flusso
di informazioni.
Il kernel di Linux gestisce allora tutti i tipi di file, nascondendone i dettagli sulla struttura
interna, tramite uno strato software che costituisce il Virtual File System (VFS). Esso è
progettato secondo principi object oriented ed è composto da due componenti: un set di
definizioni che specificano le caratteristiche di un oggetto file ed un insieme di funzioni
che consentono di manipolare questi oggetti.
I tre tipi di oggetto principali definiti dal VFS sono l’oggetto inode e l’oggetto file, che
rappresentano file individuali, e l’oggetto file system, che rappresenta, appunto, un intero
Analisi sperimentale di software aging nel kernel Linux
61
file system. In particolare, il primo descrive il file nel suo complesso, mentre il secondo
rappresenta un punto di accesso ai dati all’interno del file. L’oggetto file tiene cioè traccia
della posizione in cui il processo sta leggendo o scrivendo attualmente. L’oggetto file
system rappresenta, invece, un insieme connesso di file che costituiscono una gerarchia di
directory. Il kernel gestisce un unico oggetto file system per ciascun disco montato e per
ogni file system di rete accessibile. Quest’oggetto permette di accedere ai singoli inode
poiché il VFS identifica ognuno di essi tramite una coppia univoca <file system,
numero di inode> ed individua il particolare inode chiedendo all’oggetto file system
di riportargli quello con il numero in questione.
Per ognuno dei tre tipi di oggetti, il VFS definisce una serie di operazioni che devono
essere implementate dalla struttura dati. Ne deriva che ogni oggetto contiene un puntatore
ad una tabella di funzioni, la quale elenca gli indirizzi delle routine che espletano le
operazioni necessarie a manipolare l’oggetto. Il VFS non sa quindi se un inode rappresenta
un file di rete o uno memorizzato su un disco, una socket oppure una directory. La
funzione appropriata per l’operazione di lettura di quel file sarà comunque sempre nella
stessa posizione della tabella di funzioni e lo strato software del VFS chiamerà quella
funzione senza preoccuparsi di come i dati verranno realmente letti.
La visione appena descritta del file system rende relativamente semplici le funzionalità
del device control. Un utente può, infatti, aprire un canale di accesso ad una periferica
nello stesso modo in cui può accedere ad un qualunque altro file. Le periferiche appaiono,
cioè, come oggetti all’interno del file system.
Linux divide tutte le periferiche in tre classi:
- periferiche a blocchi (dischi, floppy, dischi ottici, ecc.), che permettono l’accesso a
blocchi di dati di dimensione fissa e completamente indipendenti;
- periferiche a caratteri, che comprendono la maggioranza degli altri dispositivi,
eccezion fatta per le periferiche di rete. I dispositivi a caratteri non devono
necessariamente fornire tutte le funzionalità connesse ai file ordinari. Ad esempio, un
dispositivo relativo ad un altoparlante consente la scrittura dei dati ma non la lettura;
Analisi sperimentale di software aging nel kernel Linux
62
- periferiche di rete, che sono gestite diversamente rispetto alle classi precedenti
poiché gli utenti non possono trasferire dati direttamente da/verso queste ma devono
accedervi tramite il sottosistema di networking.
La distinzione tra dispositivi a blocchi ed a caratteri consiste nel tipo di accesso che nei
primi è casuale e nei secondi sequenziale. Inoltre, tipicamente i device a blocchi
leggono molti byte insieme, mentre quelli a caratteri uno o pochi byte alla volta.
3.1.1.4 Networking
Il sottosistema di rete di Linux risulta notevolmente progredito poiché, non solo
supporta i protocolli Internet standard utilizzati per la maggior parte delle comunicazioni
da UNIX a UNIX, ma implementa anche un numero di protocolli nativi di altri sistemi
operativi.
Internamente, le funzionalità di rete del kernel di Linux sono implementate da tre strati di
software:
- l’interfaccia socket;
- i driver dei protocolli;
- i driver delle periferiche di rete.
Le applicazioni utente eseguono tutte le richieste di rete attraverso l’interfaccia socket, in
modo che ogni programma progettato per fare uso delle socket di Berkley funzioni sotto
Linux senza nessun cambiamento del codice sorgente.
Lo strato successivo del software è lo stack dei protocolli. Ogni volta che arrivano dei dati
a questo strato, sia da una socket dell’applicazione che dal driver delle periferiche di rete,
ci si aspetta che essi siano marcati con un identificatore che specifichi con quale protocollo
devono essere trattati. Lo strato protocollo può riscrivere i pacchetti, crearne di nuovi,
dividere o riassemblare pacchetti in frammenti o, semplicemente, scartare dati in ingresso.
Una volta che ha terminato di processare un insieme di pacchetti, esso decide di inviarli
all’interfaccia socket (se i dati sono destinati a una connessione locale) oppure ai driver
Analisi sperimentale di software aging nel kernel Linux
63
delle periferiche (se i pacchetti devono essere trasmessi verso un dispositivo remoto).
L’insieme dei protocolli supportati dal sistema di rete di Linux è costituito da TCP/IP,
UDP ed eventualmente anche altri come, ad esempio, ATM.
I servizi di rete sono offerti passando singole strutture dati dette skbuff. Esse contengono
un insieme di puntatori ad una porzione di memoria contigua, all’interno della quale è
possibile assemblare i pacchetti.
Nei prossimi paragrafi sarà descritto l’approccio measurement-based proposto per
collezionare informazioni, utili all’analisi dello stato di salute del kernel Linux. Come si
vedrà, sono stati previsti punti di monitoraggio in tutti i vari sottosistemi di cui si è appena
parlato.
3.1.2 Process file system
L’analisi di un SO implica il controllo di alcune informazioni di sistema, quali lo stato
delle periferiche hardware, della memoria, del processore, dei dischi e così via. Per
coadiuvare gli utenti nella raccolta di tali dati, Linux mette a disposizione il process file
system, noto semplicemente come file system proc [2]. In realtà /proc non è una vera e
propria directory ma un file system virtuale, che viene creato dinamicamente dal sistema e
non fa riferimento a nessun dispositivo fisico di memorizzazione. Ciò significa che i
contenuti di /proc non sono memorizzati in nessun luogo ma calcolati su richiesta. Anche
UNIX SVR4 adottava un file system di questo tipo in cui ciascuna directory corrispondeva
ad un processo attivo. Linux ha notevolmente ampliato l’idea originale, aggiungendo nuovi
file di testo contenenti le statistiche relative al kernel e ai driver caricati. Ad esempio, il
comando ps, che elenca lo stato dei processi in esecuzione, è implementato con un
programma ordinario che legge e compone in forma leggibile le informazioni contenute nel
in un file del proc.
Il process file system deve, per prima cosa, costruire la struttura di directory e i dati dei file
in essa contenuti. A tale scopo, esso deve definire in modo univoco un insieme di numeri
Analisi sperimentale di software aging nel kernel Linux
64
di inode per ciascun file, che consentano al sistema di identificare la particolare operazione
richiesta dall’utente e di collocare le informazioni relative nella sezione di memoria di
lettura del processo richiedente. Poiché Linux utilizza PID a 16 bit ed il numero di inode è
invece lungo 32 bit, i primi 16 bit di quest’ultimo sono interpretati come un PID ed i
restanti identificano l’informazione richiesta. Un campo PID pari a zero indica che l’inode
contiene informazioni globali non relative ad uno specifico processo. Il process file system,
infatti, contiene molti file generali che forniscono informazioni sulla versione del nucleo,
sulle prestazioni, sulla memoria libera, ecc..
Il sottoalbero /proc/sys è accessibile tramite la system call sysctl che legge e scrive
i dati in formato binario (anziché in caratteri ASCII), in modo da evitare i ritardi introdotti
dal file system. Per visualizzare il contenuto di /proc è sufficiente digitare il comando ls
/proc che comporrà la lista delle sottodirectory denominate con valori numerici, contenti
informazioni relative ai processi che presentano quel dato PID, più un insieme di file
contenenti informazioni globali. Ad esempio, per ottenere informazioni sul processore,
basta accedere al contenuto del file cpuinfo, mentre per la memoria di sistema i dati di
interesse sono resi disponibili nel file meminfo.
Nel paragrafo 3.5 si illustra il modo in cui il process file system è stato utilizzato nella
procedura proposta, al fine di collezionare i campioni osservati sull’utilizzo della memoria.
3.2 Tracepoint nel kernel Linux
Dalla descrizione dei sottosistemi del kernel Linux, è possibile dedurre che le probabili
cause di aging possono riguardare un vasto insieme di informazioni presenti in ognuno dei
componenti. Al fine di analizzare il fenomeno, si può quindi implementare un’analisi
mesurement-based, tenendo traccia di un insieme di dati atto all’individuazione dei
sottosistemi ed eventualmente dei componenti del SO che influiscono maggiormente sul
trend di aging.
Analisi sperimentale di software aging nel kernel Linux
65
A tal proposito, Linux mette a disposizione un supporto per il tracing delle informazioni di
sistema, costituito dai cosiddetti kernel tracepoint [22]. Tale componente, però, non
soddisfa a pieno le caratteristiche richieste per uno studio approfondito del fenomeno di
software aging. Per poter monitorare lo stato di ben definite variabili attraverso l’utilizzo di
tracepoint, si può comunque predisporre un nuovo tracer capace di acquisire
periodicamente tutti i dati di interesse.
I tracepoint possono essere definiti come degli hook lightweight, cioè dei ganci leggeri che
vanno posizionati in determinati punti nel codice del kernel per registrare l’occorrenza di
taluni eventi. Ciascun tracepoint definisce un’associata probe function per il reperimento
delle informazioni di interesse. Ogni volta che l’esecuzione passa per il tracepoint, la probe
function viene invocata e l’infrastruttura di tracing provvede a salvare il contesto
dell’evento e i dati desiderati all’interno di un tracing buffer. Il tracing buffer è poi
accessibile da user space montando il file system virtuale debugfs (di default su
/sys/kernel/debug) ed accedendo ai file /sys/kernel/debug/tracing/trace e
/sys/kernel/debug/tracing/trace_pipe. Da user space è possibile, inoltre,
attivare o disattivare il tracepoint tramite debugfs, scrivendo rispettivamente 1 o 0 nel file
/sys/kernel/debug/tracing/current_tracer con i comandi:
- echo 1 > tracing_enabled, per l’abilitazione;
- echo 0 > tracing_enabled, per la disattivazione del tracing.
Il modo più rapido per definire un tracepoint nel kernel Linux è quello di usare la event
tracing infrastructure, secondo cui il prototipo di un evento che caratterizza il tracepoint è
formato da:
- una definizione posizionata in un header file;
- uno statement (probe function), aggiunto al codice C del kernel per intercettare
l’evento.
La definizione di un tracepoint viene specificata con una macro del tipo:
Analisi sperimentale di software aging nel kernel Linux
66
TRACE_EVENT(eventname,
TP_PROTO(int first_arg, const char *second_arg),
TP_ARGS(first_arg, second_arg),
TP_STRUCT__entry(
__field(int, num)
__string(str, second_arg)
),
TP_fast_assign(
__entry->num = first_arg;
__assign_str(str, second_arg);
),
TP_printk("%i %s", __entry->num, __get_str(str))
);
dove:
- eventname è un identificatore che indica il particolare evento da tracciare;
- la macro TP_PROTO() è usata per definire il prototipo della probe function invocata
dal tracepoint;
- la macro TP_ARGS() indica i nomi dei parametri dichiarati nel prototipo;
- la macro TP_fast_assign() contiene lo statement C che si occupa di assegnare la
entry ad ogni record del trace buffer (riferito con il nome __entry);
- la macro TP_printk() definisce il formato di ogni record tracciato (secondo lo stile
della printf()).
In questo esempio ogni entry nel trace buffer contiene un attributo numerico (num) ed uno
di tipo stringa (str). Il prototipo della probe function va definito come:
void trace_eventname(int first_arg, const char, *second_arg);
Analisi sperimentale di software aging nel kernel Linux
67
in modo da poter posizionare la funzione in qualsiasi punto all’interno del codice del
kernel per intercettare l’evento eventname. Ogni volta che la probe function viene
invocata, il sistema di tracing provvede a registrare una nuova entry nel trace buffer.
Dopo aver realizzato il nuovo tracer, è poi possibile ricavare tutti i dati di interesse per
lo studio del software aging che riguarderanno, ovviamente, le aree critiche del sistema e,
quindi, i sottosistemi del kernel descritti in precedenza. In particolare, ci si propone di
tenere traccia:
- relativamente al sottosistema di file system, dell’evoluzione di un insieme ben
definito di system call (per quel che riguarda il numero di invocazioni di ognuna ed il
relativo tempo di esecuzione);
- di un set di informazioni relative al sottosistema di memory management, quali
numero totale di allocazioni e deallocazioni, numero di swap-in e swap-out e così
via;
- per quanto concerne il sottosistema di networking, del numero di socket aperte e
chiuse, sia per il protocollo TCP che per UDP;
- per il sottosistema di process management, del numero di context switch, del numero
di processi runnable, stopped, unrannable, ecc.;
- per quanto riguarda il sottosistema di I/O, del numero di interruzioni differenziate per
tipo, gestione di workqueue ecc.,
3.3 Individuazione delle informazioni di interesse
La procedura sperimentale proposta parte da una fase di design che punta alla
sistematizzazione degli scopi dell’analisi. Tale step include:
- la scelta dei sottosistemi da monitorare;
- l’identificazione delle informazioni da sottoporre a tracing per ciascun sottosistema
(informazioni di tracing o parametri di workload);
Analisi sperimentale di software aging nel kernel Linux
68
- l’individuazione di alcuni dati di sistema da monitorare, attraverso i quali sia
possibile sintetizzare lo stato di salute del SO. Tali informazioni possono essere
definite come indicatori di aging.
Per quel che riguarda il primo punto, si è ritenuto opportuno condurre un’analisi quanto più
esaustiva possibile, collezionando dati da tutti i sottosistemi del kernel:
- process management;
- memory management;
- filesystem I/O;
- network I/O;
- driver control.
Per ciascun sottosistema va dunque definito un insieme di informazioni in grado di fornire
dati utili in caso si manifesti il fenomeno di aging durante un esperimento. Queste variabili
dovrebbero consentire di caratterizzare i parametri più significativi del workload, da poter
mettere in relazione con eventuali trend di aging.
Per individuare le informazioni di tracing è utile identificare, in primo luogo, in quali modi
può manifestarsi l’aging secondo il punto di vista dell’utente finale. In pratica, possono
essere individuati 3 indicatori di aging fondamentali:
- memory depletion, che può riguardare la riduzione della porzione di memoria fisica
libera disponibile, la diminuzione della quantità di spazio di swap libero disponibile
e, in generale, l’aumento del consumo totale di memoria;
- throughput loss, ossia la riduzione del numero di operazioni eseguite nell’unità di
tempo, che può manifestarsi a livello applicativo e di sistema operativo;
- time loss, per quel che concerne l’aumento del tempo medio di risposta a livello
applicativo e di SO
Nella procedura descritta l’analisi è stata finalizzata essenzialmente al monitoraggio della
memory depletion, per quanto riguarda la memoria libera disponibile, nonché al thruoghput
loss ed al time loss a livello di kernel.
Analisi sperimentale di software aging nel kernel Linux
69
In base a tali scopi, è stato possibile allora individuare in maniera precisa le informazioni da
tracciare, che sono riportate nelle tabelle 3.1, 3.2 e 3.3 suddivise per sottosistema.
MEMORY MANAGEMENT PROCESS MANAGEMENT
Numero di flush su disco Numero di context switch
Numero di mmap() e munmap() Numero di task creati
Numero di brk() Numero di task distrutti
Numero di page miss Numero di task migrati tra CPU
Numero inserimenti e rimozioni page cache Numero di passaggi allo stato waiting
Numero di byte richiesti Numero di prelazioni
Numero di byte allocati Numero di task “stopped”
Numero di allocazioni Numero di task “unrunnable”
Numero di deallocazioni Numero di task “runnable”
Numero di allocazioni SLAB Numero di system call fork
Numero di deallocazioni SLAB Numero di system call clone
Numero di swap-in Numero di system call exec
Numero di swap-out
Tabella 3.1: informazioni di tracing per i sottosistemi di memory management e process management.
FILESYSTEM I/O NETWORK I/O
Numero di accesi in lettura su disco Numero di accessi a socket
Numero di accessi in scrittura su
disco Numero di socket aperte
Numero di file aperti Numero di socket chiuse
Numero di file chiusi Numero di accept, listen, bind e connect
Numero di access su file Numero di pacchetti TCP trasmessi
Numero di seek su file Numero di pacchetti TCP ricevuti
Numero di pacchetti UDP trasmessi
Tabella 3.2: informazioni di tracing per i sottosistemi di network I/O e file system I/O.
Analisi sperimentale di software aging nel kernel Linux
70
DRIVER CONTROL
Numero di interrupt generati
Numero di tasklet e softirq servite
Numero inserimenti ed esecuzioni da workqueue
Numero workqueue create e distrutte
Tabella 3.3: informazioni di tracing per il sottosistema disk I/O driver.
Oltre ai dati appena riportati si è ritenuto necessario monitorare il tempo di latenza relativo
ad un gruppo critico di system call, così come descritto nel paragrafo 3.4.1.
3.4 Instrumentazione dei tracepoint e definizione del tracer
Una volta individuate le informazioni di tracing, è stato necessario identificare le
funzioni del kernel che le gestiscono ed inserire in ciascuna opportuni tracepoint. Come
visto nel paragrafo 3.2, un tracepoint può essere definito come un gancio all’interno del
codice del SO, cui è associata una determinata probe function, in grado di registrare
l’occorrenza di taluni eventi.
L’architettura event tracing messa a disposizione da Linux, in fase di design è apparsa però
un po’ troppo complessa, soprattutto in considerazione del gran numero di informazioni da
monitorare. Si è pensato, allora, di concepire i tracepoint come sonde definite da
piccolissime porzioni di codice (in particolare da opportune variabili globali di tipo
contatore e dalle operazioni di incremento su di esse), in grado di catturare lo stato
dell’informazione desiderata. Nel sistema così concepito, il valore di ciascun tracepoint
individua, quindi, il numero di un certo parametro, il cui incremento viene effettuato dalla
funzione in cui la sonda è stata instrumentata. Ad esempio, una volta determinata la
funzione all’interno del kernel che effettua il context switch tra processi, per poter tener
traccia del loro numero, è bastato inserire un semplice contatore all’interno della routine.
Tale contatore rappresenta allora il tracepoint per l’indicatore di context switch relativo al
sottosistema di process management. Sono state inoltre predisposte delle opportune
Analisi sperimentale di software aging nel kernel Linux
71
funzioni di get() e set(), rispettivamente allo scopo di restituire ed azzerare lo stato di
ciascun tracepoint. Queste vengono invocate periodicamente dal tracer myTracer.c, al
fine di registrare lo stato corrente dei parametri di workload.
Tutti le informazioni relative ai vari sottosistemi sono state raggruppate all’interno di
un’apposita struttura dati, le cui istanze definiscono i cosiddetti trace event. Tale struttura è
locale al tracer e quest’ultimo, ad intervalli di tempo regolari (di 5 minuti) scanditi
attraverso un timer, provvede a:
- richiamare le funzioni get per reperire lo stato dei tracepoint;
- assegnare tali informazioni ai relativi campi della struttura dati;
- copiare l’intera struttura in un ring buffer, cioè una porzione di memoria, gestita
come una coda circolare, direttamente accessibile dal tracer;
- resettare lo stato dei tracepoint attraverso le funzioni di set.
Il SO provvede poi a rendere disponibile in user space il contenuto del ring buffer,
copiandolo nei file trace e trace_pipe del file system virtuale debugfs. La differenza
sostanziale tra trace e trace_pipe riguarda il fatto che il primo presenta una
formattazione direttamente interpretabile dall’utente ma non fornisce informazioni in
tempo reale, mentre il secondo è un file binario aggiornato ogniqualvolta viene recuperato
un nuovo evento di tracing. In particolare, trace_pipe non ritorna mai EOF (End Of
File), come accade comunemente per le pipe UNIX, in modo da permettere il
monitoraggio continuo di ulteriori dati, qualora appaiano. Ne deriva che, poiché
trace_pipe ha una struttura a pipe real-time, ciascun evento può essere recuperato una
sola volta, perché le informazioni vengono via via sovrascritte. Esso si configura, cioè,
come un file “consumer” in cui i dati , una volta letti (consumati), non sono più reperibili
attraverso una lettura successiva. Tuttavia, per la necessità di acquisizione degli eventi in
tempo reale, si è scelto di utilizzare le informazioni presenti nel file di output
trace_pipe. In particolare, un’apposita funzione definita in user-space, myTracer-
rep.c, recupera in real time gli eventi da tale file, estrae i dati in maniera opportuna, li
memorizza in archivi in formato testuale e li stampa a video.
Analisi sperimentale di software aging nel kernel Linux
72
Analisi sperimentale di software aging nel kernel Linux
73
Ciò permette di acquisire facilmente i dati contenuti negli eventi di tracing, i quali devono
poi essere sottoposti a valutazioni statistiche. Il funzionamento dell’intero sistema di
monitoring è esemplificato in figura 3.6.
Il tracer può essere utilizzato attraverso i seguenti passi:
- mount del file system debugfs attraverso il commando
mount -t debugfs none /debug;
- definizione dell’identificativo del tracer, attraverso il file current_tracer della
directory /debug/tracing tramite il comando
echo myTracer > current_tracer;
- abilitazione del tracing digitando
echo 1 > tracing_enabled.
A questo punto, per reperire le informazioni di tracing, scritte nel file di output
trace_pipe, va attivata la funzione in user space lanciando il comando
./myTracer-rep.
L’implementazione del tracer può essere considerata il contributo più rilevante di questo
lavoro di tesi. Infatti, la sua architettura costituisce un approccio originale al problema del
monitoring delle informazioni di stato delle risorse, le quali vengono reperite dall’interno
del sistema stesso, senza l’ausilio di protocolli o tool esterni. Il controllo di tali dati
avviene così in una maniera notevolmente semplice ed ha comportato la scrittura di una
minima porzione di codice.
L’approccio proposto assume un particolare interesse se si considera che esso può essere
utilizzato anche per studi differenti dall’analisi del software aging che coinvolgano,
comunque, il monitoraggio di qualsivoglia informazione reperibile all’interno del kernel.
Analisi sperimentale di software aging nel kernel Linux
74
3.4.1 Rappresentazione dei dati relativi al tempo di latenza
Si è scelto di tener traccia della latenza di alcune operazioni in modo da stimare la
metrica del time loss utile al profiling del sistema. Per calcolare tale informazione è
necessario predisporre un’apposita coppia di tracepoint che inglobi il codice da monitorare.
Ad esempio, per stimare il tempo di latenza di una system call è necessario posizionare un
tracepoint all’inizio della funzione wrapper ed un altro alla fine della stessa, prima del
return.
Poiché differenti percorsi di esecuzione nel codice producono diversi picchi nella latenza, è
utile impiegare una rappresentazione esponenziale dei tempi, in quanto questi possono
subire variazioni anche diversi ordini di grandezza. Ad esempio, per restituire eventuali
informazioni in cache, il SO impiega un tempo non superiore a qualche nanosecondo;
mentre, per reperire gli stessi dati dall’hard disk o dalla rete, il tempo speso è dell’ordine
delle decine di millisecondi. Si può allora pensare di rappresentare i dati sotto forma di
vettori in cui la posizione i-esima corrisponde ad un intervallo temporale definito in scala
logaritmica e contiene il numero di system call servite in quel periodo. Se ad esempio la
latenza di una chiamata di sistema è pari a latency, allora sarà incrementato di una unità il
contenuto della posizione k-esima del vettore con k tale che:
2k-1 ≤ latency < 2k.
Dal vettore delle latenze finale è possibile determinare le seguenti informazioni:
- numero totale di invocazioni di system call con una data latenza;
- tempo medio di risposta di una determinata operazione;
- latenza totale per ogni intervallo di tempo.
In particolare, nella procedura sperimentale proposta è stato predisposto un vettore distinto
(incluso nella struttura dati del tracer) per monitorare il tempo di servizio di ciascuna delle
seguenti system call:
- open, che consente l’apertura di un file, attraverso il suo descrittore;
- close, che implementa la chiusura di un file, attraverso il suo descrittore;
- access, che controlla i permessi di un utente per un file;
Analisi sperimentale di software aging nel kernel Linux
75
- lseek, che riposiziona l’offset di lettura e scrittura in un file;
- read, che esegue la lettura da una file;
- write, che effettua la scrittura in un file;
- mmap, utilizzato dai processi per creare un nuovo mapping di memoria;
- brk, che modifica la quantità di spazio allocato per una porzione di dati del processo
chiamante;
- munmap, utilizzato dai processi per eliminare o ridurre un mapping di memoria;
- fork, che crea un processo figlio;
- clone, che crea un thread;
- exec, che esegue un programma.
Anche le informazioni di latenza vengono salvate dalla funzione myTracer-rep.c, in
differenti file testuali.
Le figure 3.7 e 3.8 riportano, a titolo di esempio, gli istogrammi della latenza relativa alla
system call open, rispettivamente catturata in un determinato evento ed in una serie
successiva di eventi di un esperimento.
Figure 3.7: istogramma della latenza della system call open catturata in un unico evento.
La cattura del tempo di latenza è stata effettuata con l’ausilio della macro presentata di
seguito (adeguatamente semplificata rispetto a quella definita in [29]), attraverso cui è stato
Analisi sperimentale di software aging nel kernel Linux
76
possibile reperire le informazioni in maniera semplice e con una minima aggiunta di
codice.
Figure 3.8: istogramma della latenza della system call open catturata in eventi successivi.
#ifndef _AGGREGATE_STATS_H_
#define _AGGREGATE_STATS_H_
#define RDTSC(qp) \
do { \
unsigned long lowPart, highPart; \
__asm__ __volatile__("rdtsc" : "=a" (lowPart), "=d" (highPart)); \
qp = (((unsigned long long) highPart) << 32) | lowPart; \
} while (0);
typedef uint32_t aggregate_stats[32];
#define AGGREGATE_STATS_PRE \
Analisi sperimentale di software aging nel kernel Linux
77
unsigned long long l_aggregate_stats; \
RDTSC(l_aggregate_stats);
#define AGGREGATE_STATS_POST(stat) \
do { \
unsigned long long ll; \
unsigned int bucket, delay; \
RDTSC(ll); \
delay = (unsigned int)((ll - l_aggregate_stats) >> 5); \
for (bucket = 0; delay >> 1; bucket += 1) \
delay >>= 1; \
(stat)[bucket]++; \
} while(0);
#define AGGREGATE_STATS_INIT(stat) \
do { \
memset(stat, 0, 32 * sizeof(int)); \
} while (0);
#endif
La prima direttiva define, denominata RDTSC, è utilizzata per contare il numero di cicli
di clock del processore. In particolare, il kernel Linux, per eseguire il conteggio dei cicli di
clock, mette a disposizione un apposito registro contatore TSC (Time Stamp Counter), il cui
valore può essere letto attraverso la chiamata ad rdtsc [20]. Quando viene utilizzato il
registro, il kernel prende in considerazione la frequenza del segnale di clock e, di
conseguenza, decide ogni quanto tempo incrementare il time stamp. Se, ad esempio, la
frequenza di clock del processore è pari ad 1 GHz, il TSC è incrementato di una unità una
volta ogni nanosecondo. Grazie a tale meccanismo Linux è in grado fornire accurate
misurazioni temporali.
Attraverso la parola chiave typedef, è stato definito un nuovo tipo di dato denominato
aggregate_stats, che rappresenta un vettore di 32 elementi a 32 bit. Tale array
Analisi sperimentale di software aging nel kernel Linux
78
contiene, a valle dell’operazione di calcolo, in ogni posizione i-esima il numero di system
call servite nell’intervallo di tempo compreso fra 2i-1 e 2i (con 375 ≤≤ i ) cicli di clock.
AGGREGATE_STATS_PRE, attraverso una opportuna chiamata ad RDTSC, avvia il conteggio
dei cicli di clock. AGGREGATE_STATS_POST permette di definire, a partire dal numero di
cicli di clock conteggiati, in quale degli intervalli ricade il tempo di latenza misurato e di
incrementare di conseguenza l’elemento corrispondente del vettore. Infine,
AGGREGATE_STATS_INIT, è utilizzato per allocare ed inizializzare a zero il vettore
relativo ad un determinato conteggio.
Per monitorare la latenza di una determinata system call, è stata definita all’interno del
codice del suo wrapper un vettore di tipo aggregate_stats e sono state richiamate le
direttive AGGREGATE_STATS_PRE e AGGREGATE_STATS_POST rispettivamente
all’ingresso e all’uscita della routine. In tal modo, è stata predisposta una coppia di punti di
monitoraggio del clock di sistema.
3.5 Scrittura delle informazioni di sistema su file: myTracer-rep
Al verificarsi di ogni nuovo evento di tracing, la funzione scritta in linguaggio C in user
space, myTracer-rep.c, effettua le seguenti operazioni:
- reperisce dal process file system i dati relativi all’indicatore di memory depletion;
- legge le informazioni scritte in trace_pipe tramite l’utilizzo del tracer e le
formatta opportunamente.
Per quel che riguarda il primo punto, la funzione user space legge dal file
/proc/meminfo contenente informazioni sulla memoria e reperisce in particolare la
quantità di memoria libera disponibile, scrivendone il valore nel file freeMem.txt. In
altre parole, il file formattato da myTracer-rep.c presenta, sotto forma di vettore
colonna, la quantità di memoria libera disponibile campionata periodicamente ogni 5
minuti, come avviene per il resto delle informazioni prelevate dal tracer.
Le informazioni lette da trace_pipe vengono invece distribuite fra i seguenti file:
Analisi sperimentale di software aging nel kernel Linux
79
- mem.txt, contenente le informazioni relative al sottosistema di memory
management;
- proc.txt, che include i dati del sottosistema process management;
- disk.txt, che memorizza le informazioni relative al sottosistema disk I/O driver;
- net.txt, contenente i dati relativi al sottosistema di networking;
- filesystem.txt, che include le informazioni riguardanti il sottosistema di
filesystem I/O.
Nelle figure 3.9 si riportano gli schemi di formattazione dei file appena descritti:
Figura 3.9a: formattazione dei dati contenuti nel file mem.txt.
Figura 3.9b: formattazione dei dati contenuti nel file proc.txt.
Analisi sperimentale di software aging nel kernel Linux
80
Figura 3.9c: formattazione dei dati contenuti nel file disk.txt.
Figura 3.9d: formattazione dei dati contenuti nel file net.txt.
Figura 3.9e: formattazione dei dati contenuti nel file filesys.txt.
Per quel che riguarda i tempi di latenza, myTracer-rep.c memorizza i vettori di tipo
aggregate_stats in file di testo denominati come le system call cui si riferiscono
(clone.txt, open.txt, close.txt, ecc.).
81
Capitolo 4 Procedura sperimentale
In questo capitolo viene descritta la procedura sperimentale condotta per lo studio
dell’aging nel kernel Linux. L’approccio seguito è di tipo measurement-based e l’analisi
dei risultati si fonda sulla caratterizzazione preventiva del workload utilizzato per lo stress
del sistema.
Dopo aver introdotto i concetti generali inerenti alla progettazione degli esperimenti,
vengono definiti i passi della procedura prescelta, riguardanti l’identificazione,
l’acquisizione e l’elaborazione dei dati. La sperimentazione pone le sue basi nello studio
dei sottosistemi del kernel Linux, condotta nel capitolo precedente al fine di identificare le
potenziali vulnerabilità del SO nei confronti dell’aging. Lo scopo dell’analisi dei risultati
consiste nel comprendere quali sono i parametri più significativi del workload, da poter
mettere in relazione con eventuali trend di aging.
La procedura seguita consta fondamentalmente di due fasi:
- la raccolta delle informazioni sensibili, reperite tramite un apposito tracer
instrumentato in kernel-space e disponibili all’utente grazie ad un’appropriata
funzione user-space, così come descritto nel precedente capitolo;
- l’analisi dei risultati, che si basa su una serie di strumenti statistici in grado di
rilevare eventuali trend nell’uso delle risorse e di individuare i parametri più
significativi del carico del sistema.
Analisi sperimentale di
software aging nel kernel Linux
82
4.1 Design of Experiments (DoE)
Prima di descrivere il metodo implementato per l’analisi dell’aging nel kernel Linux, si
ritiene opportuno fissare alcune nozioni di base circa il metodo sperimentale [23].
Si può definire statistica la scienza del problem solving in presenza di fattori variabili. Per
variabile si deve intendere una caratteristica, riguardante una peculiare informazione, che
può essere ottenuta tramite un esperimento. In generale, si possono classificare le variabili
in due categorie:
- response variable (variabili di risposta) o variabili dipendenti, che costituiscono il
risultato dell’esperimento stesso;
- fattori o variabili indipendenti, che sono controllabili ed influenzano il valore delle
precedenti.
In tal contesto, un esperimento è un insieme di osservazioni, effettuate per verificare
l’eventuale validità di un’ipotesi riguardante il fenomeno in esame. Per osservazione si
intende, quindi, la collezione di informazioni, ovvero dei valori delle variabili di risposta
ottenuti nell’esperimento.
L’osservazione dei dati costituisce il fulcro del metodo sperimentale. Tutte le informazioni
raccolte sono soggette ad una certa variabilità della propria sorgente che induce variazioni
nelle misure. Le cause di tale variabilità sono disparate e comprendono, in ambito
informatico, lievi differenze fra le macchine, difformità casuali dovute a cambiamenti nelle
condizioni ambientali, errori di misura originati da fattori random o dalla poca sensibilità
degli strumenti utilizzati (ad esempio, per quel che riguarda la misura dei cicli di clock
impiegati per effettuare una certa operazione), ecc.. Ne deriva che la progettazione degli
esperimenti è una fase delicata e complessa che dovrebbe mirare il più possibile a mitigare
le conseguenze degli errori presenti nelle misurazioni.
Una metodologia finalizzata ad un buona progettazione degli esperimenti è stata esposta
già da Fisher nel 1935 [24]. Questi identifica le seguenti caratteristiche come peculiari di
un buon metodo sperimentale:
- comparison (confronto), cioè il confronto tra treatment differenti, dove con il termine
Analisi sperimentale di
software aging nel kernel Linux
83
trattamento si indicano le condizioni in cui viene effettuata una prova sperimentale;
- randomization (casualità), che consiste nel rendere casuali le condizioni di una serie
di esperimenti correlati. Supponendo che il numero di tali prove sia adeguato per
spiegare statisticamente il fenomeno oggetto di studio, la casualità fa sì che gli effetti
di fenomeni di disturbo risultino mediati su tutti gli esperimenti e possano inoltre
essere stimati con metodi probabilistici;
- replication (replicazione), cioè la ripetizione delle prove sperimentali, che
dovrebbero aumentare al crescere delle fonti di variabilità del fenomeno in esame.
Nella pratica non è sempre possibile rispettare il principio di replicazione. Ad
esempio, se il costo legato ad ogni singola prova è proibitivo oppure ciascun
esperimento richiede un tempo molto lungo per essere eseguito, si cerca di ridurre il
più possibile il numero delle prove. Ciò conduce ad un risparmio nei costi che si
ripercuote però inevitabilmente sull’affidabilità dei risultati;
- blocking (clustering), ossia il raggruppamento dei risultati delle misure in gruppi
caratterizzati da specifiche proprietà comuni, con lo scopo di rilevare con più
precisione le sorgenti di variabilità.
Sulla scia degli studi di Fisher, si è sviluppato un vero e proprio filone scientifico
dedicato alla definizione di buone procedure sperimentali. In tale ottica, si può definire
Design of Experiments (DoE) [25], il processo di pianificazione, progetto ed analisi degli
esperimenti condotti per vagliare una certa ipotesi riguardo ad un fenomeno d’interesse. Il
DoE ha lo scopo di guidare il lavoro dello sperimentatore, in modo che questi sia in grado
di trarre conclusioni valide ed oggettive, in maniera efficace ed efficiente.
In contesti industriali di applicazione del DoE, si distinguono due tipologie di variabili
indipendenti:
- fattori quantitativi (ad esempio, velocità, tempo, temperatura, ecc.), di cui va scelto il
range di settaggio e la modalità di misura e controllo durante l’esperimento;
- fattori qualitativi (ad esempio, tipi di materiali, tipi di hardware, tipi di sistemi
operativi da impiegare per testare un software, ecc.), che possono assumere solo
Analisi sperimentale di
software aging nel kernel Linux
84
valori discreti e, quindi, risultano anche più semplici da settare, misurare e
controllare.
A seconda della tipologia della variabili dipendenti, in fase di progettazione degli
esperimenti, è necessario decidere il numero dei cosiddetti livelli da assegnare a ciascun
fattore in ogni prova, dove per livello si intende uno specifico valore o settaggio attribuito
ad un fattore. Nella terminologia DoE, un trial o run (talvolta anche treatment) identifica
una certa combinazione di livelli dei fattori che influenzano l’output della prova. Il numero
di trial richiesti per stimare gli effetti dei fattori sulle variabili di risposta è determinato dal
numero stesso delle variabili indipendenti e da quello dei possibili livelli che ciascuna può
assumere. Se, ad esempio, sono dati k fattori qualitativi ad l livelli, il numero di run
esaustivo è n=lk. Sono state comunque definite moltissime tecniche per rendere più efficaci
le prove e per far sì che non sia sempre necessario un numero troppo elevato di
esperimenti. I tre principi fondamentali del DoE, randomizzazione, replicazione e
clustering, sono appunto applicati per ridurre gli errori sperimentali e per rendere più
significativa ogni prova.
La progettazione degli esperimenti dovrebbe articolarsi, indipendentemente dal contesto
d’impiego, nelle seguenti fasi:
- individuazione dell’obiettivo della sperimentazione attraverso la definizione di una o
più response variable;
- scelta di un set di fattori, che potenzialmente influenzano il risultato;
- pianificazione del numero e della tipologia delle prove da eseguire;
- analisi dei risultati, volta ad individuare la dipendenza delle variabili dipendenti da
quelle indipendenti, separando l’effetto di queste ultime dal cosiddetto residuo,
ovvero dalle conseguenze di elementi non direttamente controllabili.
Le tecniche più largamente utilizzate nella programmazione degli esperimenti sono [26]:
- gli esperimenti fattoriali, in cui ciascuno dei fattori viene considerato a due livelli;
Analisi sperimentale di
software aging nel kernel Linux
85
- la metodologia delle superfici di risposta, usata per la modellazione e l’analisi di
applicazioni il cui obiettivo è l’ottimizzazione di una certa risposta di interesse,
influenzata da un determinato numero di variabili di ingresso;
- il metodo di Taguchi, che ritiene la varianza delle variabili di risposta dipendente da
almeno alcuni dei fattori. Tale approccio si propone, quindi, di individuare per via
sperimentale le condizioni di produzione ottima della variabile dipendente (cioè
quelle in grado di garantire un livello medio della risposta pari ad un valore
prefissato ed una sua varianza che sia insensibile alle diverse fonti di variabilità).
Poiché le tecniche DoE si fondano su metodi statistici, esse sono spesso complesse e
richiedono un certo dispendio di costi sia in fase di progettazione che di attuazione. Per tali
ragioni, a volte, questi strumenti non vengono utilizzati nelle industrie manifatturiere o
magari non se ne sfruttano le piene potenzialità. In ogni caso, bisogna tener presente che il
DoE può condurre, in tempi brevi, a miglioramenti successivi nella qualità di un prodotto o
nell’efficienza di un processo. Per l’utilizzo corretto della metodologia dovrebbero essere
ben chiari 8 concetti fondamentali [27]:
- stabilire dei buoni obiettivi, anche per quel che riguarda la scelta delle variabili da
settare e monitorare;
- cercare di misurare le variabili di risposta in modo quantitativo (evitando ad esempio
di basarsi su giudizi soggettivi della qualità di un prodotto);
- condurre un opportuno numero di repliche per smorzare gli effetti di fattori di
rumore;
- randomizzare il run order, ossia l’ordine in cui si conducono gli esperimenti, al fine
di mitigare l’influenza dalle variabili incontrollate (ad esempio, l’usura
dell’attrezzatura, la temperatura dell’ambiente, le variazioni della materia prima,
ecc.);
- escludere le fonti conosciute di variazione;
- tener presente gli eventuali effetti confusi (una confusione indica che si sono
cambiate due o più cose allo stesso tempo nello stesso modo);
Analisi sperimentale di
software aging nel kernel Linux
86
- condurre una serie sequenziale di esperimenti in modo ripetitivo e standardizzato;
- confermare sempre i risultati critici.
Per quanto concerne l’applicazione del DoE nell’analisi di tipo mesurement-based
dell’aging in un software, si deve innanzitutto stabilire quali fattori (parametri di workload)
a livello applicativo possono essere controllati dall’utente che esegue l’esperimento. Una
volta definiti i parametri controllabili, è necessario identificare le variabili da monitorare a
livello target (nel nostro caso, a livello di SO).
L’esecuzione degli esperimenti può, dunque, essere così definita:
- scelta dei fattori controllabili riguardanti il workload imposto al sistema per ogni
esperimento;
- definizione del numero n di livelli da assegnare ai fattori controllabili;
- scelta di un intervallo di tempo per il campionamento di informazioni relative
all’utilizzo delle risorse di sistema e dei parametri di workload durante ogni
esperimento;
- attuazione di un definito numero di prove, condotte collezionando le informazioni
suddette.
Nei paragrafi che seguono viene descritta la procedura sperimentale utilizzata per lo
studio del software aging nel kernel Linux. Come si vedrà, sebbene si sia cercato di
attenersi ai buoni principi del DoE per rendere sistematica e disciplinata la
sperimentazione, non è stato possibile rispettarne tutti i dettami. In particolar modo, poiché
l’analisi deve riguardare SO long running, non si può effettuare, con costi e tempi
contenuti, un gran numero di test. Si è cercato allora di preservare la validità dei risultati
ottenuti garantendo una certa randomizzazione a livello applicativo. In pratica, il tool
utilizzato per lo stress del sistema (cfr. paragrafo 4.3) fa in modo che le operazioni
richieste al kernel siano casualmente variabili entro certi parametri stabiliti dallo
sperimentatore. Questa variabilità riguarda il numero, il tipo e la tempificazione delle
operazioni effettuate in ciascuna unità temporale.
Analisi sperimentale di
software aging nel kernel Linux
87
Relativamente all’approccio sperimentale adottato, i parametri di workload di alto livello
(cioè quelli settati tramite il tool di stress) coincidono con i fattori controllabili della
terminologia DoE. Tuttavia, l’obiettivo è quello di studiare l’aging nel SO
indipendentemente dalle caratteristiche di alto livello del workload. Ciò può avere lo
scopo, ad esempio, di applicare tecniche di TTE estimation delle risorse senza conoscere
ed instrumentare applicazioni. Per tali motivi, occorre caratterizzare il carico del sistema
raccogliendo delle misure a basso livello, attraverso un opportuno strumento di monitoring
(il tracer, appunto). Questo procedimento consente di associare ad una ben definita
esecuzione di un esperimento le misurazioni che la caratterizzano. Tale caratterizzazione
può poi essere messa in corrispondenza con il trend di aging rilevato nel test. A valle di
queste operazioni, si potrebbe infine effettuare un’analisi di sensitività del trend di aging
verso i parametri di basso livello.
4.2 Software aging nel kernel Linux: procedura sperimentale
La procedura sperimentale per l’analisi del fenomeno di aging nel kernel Linux è stata
suddivisa nelle seguenti macrofasi (riportate in figura 4.1):
- individuazione delle aree da monitorare, che consiste nell’identificare i sottosistemi
del kernel di interesse e le informazioni ad essi relative;
- realizzazione di un tracer, myTracer.c, per il tracciamento delle informazioni di
sistema. Tale componente ha il compito di reperire periodicamente i dati definiti al
passo precedente e di scriverli in un apposito buffer circolare (ring buffer) in kernel
space (quindi non direttamente accessibile da applicazioni utente);
- realizzazione di una funzione in user space, myTracer-rep.c, la quale ha il
compito di rendere disponibili allo sperimentatore le informazioni collezionate dal
tracer. In particolar modo, questa funzione formatta in maniera più leggibile i dati,
distribuendoli ordinatamente in un insieme di file;
- progettazione e realizzazione degli esperimenti;
Analisi sperimentale di
software aging nel kernel Linux
88
- software aging analysis, che ha lo scopo di valutare eventuali trend di aging
relativamente ai vari sottosistemi nonché l’influenza dei parametri di workload sui
trend individuati.
Figura 4.1: modello procedurale per lo studio del software aging.
I primi tre step sono stati illustrati nel capitolo precedente. Nei paragrafi che seguono ci si
propone, allora, di descrivere nei dettagli la procedura sperimentale proposta con la relativa
analisi dei risultati raccolti.
Analisi sperimentale di
software aging nel kernel Linux
89
4.3 Progettazione degli esperimenti
Come si è detto, l’aging si manifesta in software long running con la progressiva
diminuzione delle risorse computazionali disponibili, dovuta all’accumulo in un lungo
periodo di tempo di potenziali condizioni di errore. Ne deriva che l’analisi del fenomeno
dovrebbe basarsi su osservazioni lunghe, di durata dipendente dalla vulnerabilità del
sistema in esame rispetto all’aging.
Per evitare, allora, di condurre esperimenti eccessivamente onerosi, che osservino il kernel
in situazioni di lavoro ordinarie per settimane o anche mesi, si può pensare di imporre un
carico elevato al sistema in modo da forzarne un più veloce degrado, che risulti osservabile
in pochi giorni. In particolare, la scelta puntuale del workload può essere effettuata con un
apposito capacity test, mirato a determinare il valore limite del carico per cui si osservi un
progressivo esaurimento delle risorse che sia imputabile al solo fenomeno dell’aging (e
non alla condizione di lavoro). In altri termini, vanno eseguite prove successive che varino
il workload (facendolo diminuire a partire da un valore massimo che conduca presto il
sistema al crash non attribuibile all’aging) fino ad un livello che possa essere considerato
critico (cioè che permetta di osservare un degrado in pochi giorni) ma non eccessivo (ossia
tale da provocare immediatamente fallimenti non imputabili all’aging).
Per impostare un adeguato carico di lavoro, è stato così utilizzato il tool open source
stress fornito come pacchetto aggiuntivo per Linux. Tale componente consente di
configurare il workload relativo alla CPU, alla memoria, all’I/O, al disco rigido e così via.
Stress non è un vero e proprio strumento di benchmark, poiché non effettua misurazioni
e statistiche ma si limita ad inviare al SO definite quantità di carico, in modo da consentire
agli utenti di analizzare caratteristiche di performance dell’intero sistema o di specifiche
componenti.
Come è possibile leggere digitando il comando man stress, Il tool va attivato attraverso
la seguente istruzione:
stress [OPZIONE [ARGOMENTO]] .
Le opzioni più importanti sono:
Analisi sperimentale di
software aging nel kernel Linux
90
- –version, che indica la versione del tool;
- -v (verbose), che consente di visualizzare passo dopo passo le operazioni eseguite
durante lo stress;
- -t N (timeout), che permette di terminare lo stress dopo la scadenza di un timeout
impostato ad N secondi;
- --backoff N, che consente di impostare un timer ad N microsecondi, alla scadenza
del quale parte l’attività di stress del sistema;
- -c N, che imposta N worker per lo stress della CPU richiamando a ripetizione la
funzione per il calcolo della radice quadrata sqrt();
- -i N, che imposta N worker per sforzare il sottosistema di I/O attraverso
l’operazione sync();
- -m N, che definisce N worker per l’esecuzione delle operazioni di allocazione e
deallocazione della memoria, utilizzando le funzioni malloc() e free();
- --vm-bytes B, che alloca B byte per gli m worker (di default è B=256 MB);
- --vm-stride B, che accede ad un byte di dati ogni B byte (di default è B=4096
MB);
- --vm-hang N, che addormenta per N secondi il processo prima di liberare la
memoria allocata;
- --vm-keep, che reindirizza la memoria invece di liberarla e riallocarla;
- -d N, che definisce N worker per le operazioni di write() ed unlink();
- --hdd-bytes B, che scrive B byte per i worker definiti al punto precedente ( di
default è B=1GB).
Attraverso un opportuno settaggio delle opzioni disponibili (cfr. paragrafo 5.1), è stato così
possibile impostare il carico per l’esperimento condotto in maniera estremamente
semplice. Contestualmente, tramite la sequenza di comandi presentata nel paragrafo 3.4,
sono stati attivati il tracer e la funzione in user space per il reperimento delle informazioni
di sistema. Questa organizzazione fa sì che, anche attraverso un unico esperimento, sia
Analisi sperimentale di
software aging nel kernel Linux
91
possibile ottenere misure valide, pur se non del tutto generali, riguardo ad eventuali trend
di aging.
4.3.1 Analisi di software aging
L’ultimo step della procedura seguita consiste nella rilevazione e stima di eventuali
trend di aging, relativamente all’utilizzo delle risorse, e nell’analisi della relazione tra
questi ed il workload, caratterizzato nello step precedente. Il procedimento che è stato
impiegato per eseguire tale valutazione è rappresentato in figura 4.2.
Figure 4.2: processo di elaborazione delle informazioni di sistema.
La prima fase consiste nella raccolta dei dati a partire dai file resi disponibili dalla funzione
myTracer-rep.c. Le informazioni collezionate sono state sottoposte ad un preventivo
preprocessing (cfr. paragrafo 4.3.1.1) al fine di renderle fra loro omogenee e di ricavare le
Analisi sperimentale di
software aging nel kernel Linux
92
stime di throughput loss e time loss a partire dai vettori di latenza relativi alle system call
monitorate.
A questo punto è stato possibile procedere alla vera e propria analisi dei dati che è stata
espletata in 3 fasi successive:
- la verifica della presenza di trend di aging tramite test di Mann-Kendall e
determinazione dello slope attraverso la tecnica proposta da Sen;
- la Principal Component Analysis (PCA) per decorrelare i parametri di tracing ed
esprimere in maniera più compatta (con meno variabili) le informazioni in essi
contenute;
- individuazione dei coefficienti più rilevanti delle componenti principali più
significative, in modo da definire una relazione tra i parametri di workload e il trend
di aging per il particolare esperimento condotto.
4.3.1.1 Raccolta dei dati e preprocessing
Tramite apposite funzioni Matlab, sono stati letti i dati di sistema che risultano suddivisi
in 3 grupi:
- freeMem.txt, che contiene il vettore FM relativo ai valori campionati della
memoria libera disponibile;
- 5 file che tengono traccia dei parametri di tracing per sottosistema (mem.txt,
proc.txt, disk.txt, net.txt e filesystem.txt);
- 12 file che contengono i tempi di latenza relativi ad altrettante system call
(access.txt, brk.txt, clone.txt, close.txt, exec.txt, fork.txt,
lseek.txt, mmap.txt, munmap.txt, open.txt, read.txt, write.txt).
Questi ultimi sono stati distribuiti in 3 sottogruppi, contenenti ciascuno le informazioni
relative alle system call specifiche di uno dei 3 sottosistemi del kernel di memory, process
e file system management. Per il sottosistema driver control non è stato previsto il
monitoring di alcuna system call, mentre il sottosistema di networking è stato escluso
Analisi sperimentale di
software aging nel kernel Linux
93
dall’esperimento condotto per le motivazioni indicate nell’introduzione al prossimo
capitolo. A partire da ogni sottogruppo sono stati poi definiti i seguenti vettori:
- TLM, TLP e TLF, contenenti i dati di throughput loss per i 3 sottosistemi del kernel;
- MTM, MTP e MTF, rappresentanti le informazioni di time loss per i sottosistemi.
In particolar modo, il calcolo dei primi 3 vettori consiste semplicemente nella somma, riga
per riga, dei valori di latenza ricavati dal sottogruppo relativo di file. Si tenga presente che
ogni file originale contiene, su ciascuna riga, il numero di chiamate di sistema di un unico
tipo servite in uno specifico evento (della durata di 5 minuti) entro un tempo determinato
dalla posizione considerata. Ad esempio, la prima riga del file mmap.txt contiene, nella
prima posizione, il numero di system call mmap con latenza compresa fra 25 e 26 cicli di
clock, nella seconda quelle servite fra 26 e 27 cicli, ecc.. Ne deriva che, sommando gli
elementi di ogni riga, si determina il numero totale di chiamate di sistema mmap eseguite in
ogni specifico evento. Quindi, per calcolare l’indicatore di throughput loss riguardante, ad
esempio, il sottosistema di memory management, è sufficiente sommare i valori di ogni
riga dei file relativi alle system call mmap.txt, munmap.txt e brk.txt e poi
addizionare le somme ottenute. Si avrà dunque:
.num_eventi1per ,),(),(),(32
1
32
1
32
1≤≤++= ∑∑∑
===
ijibrkjimunmapjimmapTLMjjj
i
Con la stessa procedura sono stati calcolati i vettori TLP e TLF.
Per quanto riguarda, invece, gli array relativi al time loss, ciascuno di essi rappresenta il
tempo medio di servizio delle system call appartenenti ad un dato sottosistema (nell’evento
di tracing considerato). Quindi, le righe i-esime di ogni file originale vanno innanzitutto
sommate ed il vettore ottenuto deve essere moltiplicato scalarmente con un array di pesi
esponenziali. Il valore risultante va infine diviso per la somma delle operazioni eseguite in
quell’evento, cioè per iTLM . Ad esempio, relativamente al sottosistema di memory
management, è stato calcolato:
Analisi sperimentale di
software aging nel kernel Linux
94
[ ]
.num_eventi1per ,),(),(),(
2
2
2
:),(:),(:),(
32
1
32
1
32
1
37
6
5
≤≤++
⎥⎥⎥⎥⎥⎥
⎦
⎤
⎢⎢⎢⎢⎢⎢
⎣
⎡
×++
=
∑∑∑===
ijibrkjimunmapjimmap
ibrkimunmapimmap
MTM
jjj
i
Le operazioni appena descritte hanno permesso di ricavare gli indicatori di aging che si
aggiungono a quello di memory depletion.
A questo punto tutti i dati raccolti (parametri di tracing ed indicatori di aging), per
essere espressi in unità omogenee, sono stati sottoposti ad una normalizzazione preventiva
con la formula:
{ }{ } { }xx
xxx
evento evento
evento
minmax
min
∀∀
∀
−
−=′
dove x′ rappresenta il valore normalizzato di x e { }xevento
min∀
(max) indica il minimo
(massimo) valore che x in un determinato evento. Tale trasformazione permette di far
ricadere tutte le variabili nel range di valori compreso tra 0 e 1.
In ultimo, si è ritenuto opportuno eliminare (ossia sostituire con la media della serie) i
campioni a grande distanza dal valor medio (outliers) poiché questi rappresentano
osservazioni rare ed isolate, dovute a fattori non controllabili, che non sono rappresentative
del problema.
4.3.1.2 Test di Mann-Kendall e metodo di Sen
Il primo passo dell’analisi dei risultati sperimentali consiste nel verificare l’esistenza di
trend di aging a partire dai 7 indicatori precedentemente decritti, FM, MTM, MTP, MTF, TLM,
TLP e TLF, contenenti informazioni di sistema campionate periodicamente ogni 5 minuti.
Analisi sperimentale di
software aging nel kernel Linux
95
A tale scopo è stato utilizzato il test non parametrico di Mann-Kendall [30, 31], che
consente di ricercare la presenza di trend di lungo periodo, senza la necessità di fare
assunzioni sulla forma della distribuzione dei dati. In generale, i test non parametrici
costituiscono uno strumento statistico potente in quanto il risultato è minimamente
influenzato dalla presenza di eventuali outlier nelle serie di dati.
L’ipotesi nulla del test di Mann-Kendall suppone l’assenza di trend nell’insieme di dati da
cui è estratto il dataset oggetto di indagine. Secondo l’ipotesi alternativa, invece, è corretto
affermare che nella serie analizzata è presente un trend crescente o descrescente. La
statistica S di Mann-Kendall viene calcolata attraverso la seguente espressione:
∑ ∑−
= +=
−=1
1 1)(
n
i
n
ijij yysignS
dove yi ed yj rappresentano i valori definiti nella serie alle posizioni i e j rispettivamente, n
indica la lunghezza del dataset e
( )⎪⎪⎩
⎪⎪⎨
⎧
<−
=
>
=
0,1
0,0
0,1
ϑ
ϑ
ϑ
ϑ
se
se
se
sign .
Sotto l’ipotesi che le y siano indipendenti ed identicamente distribuite, per n≥10, la
statistica è ben interpretata da una distribuzione normale, con media nulla e varianza pari a:
( )( )18
5212 +−=
nnnσ .
Pertanto, definita la statistica test standardizzata Z come
⎪⎪⎪
⎩
⎪⎪⎪
⎨
⎧
<+
=
>−
=
0 se ,1
0 se ,0
0 se ,1
SS
S
SS
Z
σ
σ
,
Analisi sperimentale di
software aging nel kernel Linux
96
la significatività del trend viene testata tramite il confronto di Z con una distribuzione
guassiana normalizzata, ad un desiderato livello di confidenza (nel caso implementato
impostato al 95%).
Per tutte le serie relative agli indicatori di aging, può essere inoltre calcolata una grandezza
rappresentativa della confidenza del trend. Tale quantità, indicata con il termine di p-value,
diminuisce all’aumentare del livello di confidenza. Quindi, se p-value è prossima a zero,
viene indicata una forte evidenza contro l’ipotesi nulla (ciò significa che, con grande
probabilità, c’è un trend di aging), mentre valori elevati forniscono un’evidenza a favore di
quest’ultima (cioè non è presente alcun trend).
Inoltre, una stima robusta del coefficiente angolare β della retta interpolante i dati può
essere effettuata attraverso il metodo di Sen che utilizza la seguente espressione:
( )ji
ijyy
Mediana ij <∀⎥⎦
⎤⎢⎣
⎡−
−= β .
Nel caso di studio, il test di Mann-Kendall è stato effettuato sui 7 indicatori di aging
attraverso la funzione Matlab denominata ktaub1 che prende in input:
- il vettore bidimensionale contenente la serie dei dati su cui effettuare il test;
- l’indice da cui si intende partire per l’analisi della serie (1 se si vuole considerare
l’intero dataset, 2 se si intende cominciare dal secondo campione, ecc.);
- il coefficiente di confidenza;
- un flag per indicare se si desidera visualizzare il plot del trend.
In output, ktaub fornisce una serie di informazioni tra cui:
- i valori delle statistiche S e Z;
- la deviazione standard dei dati;
- un flag h che indica l’ipotesi verificata nel test (se h=0 allora risulta verificata
l’ipotesi nulla; se, invece, h=1 il trend è significativo);
- il p-value;
1 http://www.mathworks.com/matlabcentral/fileexchange/22389-seasonal-kendall-test-with-slope-for-serial-
dependent-data
Analisi sperimentale di
software aging nel kernel Linux
97
- la stima del coefficiente angolare delle retta interpolante il dataset (slope), calcolato
tramite metodo di Sen. Se tale valore è positivo, allora il trend individuato è
crescente; in caso contrario esso è decrescente.
Quest’ultimo valore fornisce informazioni interessanti circa l’evoluzione futura del
sistema. In particolare, valutare lo slope in caso di aging serve:
- per l’indicatore di memory depletion, a stimare il periodo in cui non si avrà più a
disposizione alcuna locazione di memoria libera (cioè a valutare il TTE relativo alla
memoria);
- per il throughput loss e per il time loss, a quantificare l’andamento del livello di
prestazione del sistema e, quindi, a valutare il tempo impiegato affinché le
performance scendano sotto un livello accettabile.
4.3.1.3 Principal component analysis
In generale, non è ragionevole assumere che i dati restituiti dall’architettura di tracing
siano incorrelati, poiché le informazioni di sistema dipendono nel loro complesso dal
carico applicato e dalla configurazione hardware/software e sono, perciò, connesse. Per
rimuovere la correlazione intrinseca nei dati collezionati, è stata allora utilizzata la
Principal Component Analysis (PCA) [32], che trasforma le variabili originali in
componenti incorrelate, dette appunto Componenti Principali (PC).
L’analisi delle componenti principali o trasformazione di Karhunen-Loeve è una tecnica di
estrazione di feature basata sul criterio dell’errore quadratico medio, che costituisce un
approccio classico alla riduzione della dimensionalità di un problema ed alla separazione
delle sorgenti. A partire da m feature generiche X1, X2, …, Xm, la PCA origina (attraverso
procedimenti algebrici che trascureremo per brevità) un insieme di (al più) m feature
incorrelate PC1, PC2, …, PCm (che costituiscono altrettante stime delle sorgenti), cioè
aventi la matrice di covarianza diagonale. Tali componenti principali possono essere
espresse come combinazioni lineari delle variabili iniziali, cioè:
Analisi sperimentale di
software aging nel kernel Linux
98
∑=
=m
jjiji XcoefPC
1,
dove i coefficienti coefij appartengono al range [-1,1]. Se coefij, in modulo, è molto vicino
ad 1, allora la PC i-esima rappresenta buona parte del contenuto informativo di Xj;
viceversa, se coefij è prossimo a zero, significa che PCi non dipende in maniera rilevante
dalla variabile originale j-esima.
La trasformazione gode delle seguenti proprietà:
- Var[PC1] > Var[PC2] > . . . > Var[PCm], cioè le PC risultano ordinate nel senso
decrescente della varianza. Ciò significa, ad esempio, che PC1 contiene più
informazione utile rispetto a PCm;
- Cov(PCi, PCj) = 0 per ogni i ≠ j, cioè le componenti principali sono incorrelate.
Questo implica che ciascuna rappresenta informazioni differenti rispetto alle altre;
- [ ] [ ]∑∑==
=m
ii
m
ii XVarPCVar
11, cioè non c’è perdita di informazione nel passaggio dalle
variabili iniziali alle PC. Tuttavia, poiché la varianza delle componenti principali
risulta concentrata fortemente nelle prime PC, si può utilizzare un numero ridotto di
variabili per rappresentare gran parte del contenuto informativo del dataset iniziale.
La principal component analysis sui parametri di aging è stata effettuata utilizzando la
funzione di libreria Matlab princomp avente prototipo:
[COEFF, SCORE, LATENT] = princomp(X)
Il parametro di input X rappresenta una matrice N×P le cui colonne corrispondono alle
variabili originali e le cui righe alle osservazioni di queste. La matrice d’uscita COEFF,
invece, contiene i coefficienti delle combinazioni lineari coefij, tramite cui è possibile
ricavare le PC a partire dalle variabili iniziali. Le colonne di tale matrice sono ordinate per
valori decrescenti della varianza delle PC cui si riferiscono. La tabella SCORE restituisce le
rappresentazioni delle variabili X come combinazioni lineari delle componenti principali,
mentre LATENT è un vettore contenente i valori di varianza delle PC.
Analisi sperimentale di
software aging nel kernel Linux
99
I parametri di tracing, opportunamente suddivisi per sottosistemi, sono stati sottoposti a
PCA con princomp. Le PC così ottenute rappresentano variabili non ridondanti (poiché
esse sono incorrelate). È stato poi selezionato solo un sottoinsieme delle componenti
principali { }qiiPC 1= , in grado di rappresentare la maggior parte della varianza dei dati
iniziali (98%). A partire da tali PC (che rappresentano la quasi totalità del contenuto
informativo dei dati iniziali ma non hanno significato fisico), sono stati quindi individuati i
coefficienti di combinazione lineare più significativi, al fine di determinare quali parametri
di workload hanno pesato maggiormente sulla composizione di ciascuna PC e risultano,
quindi, più rilevanti per la caratterizzazione del workload.
100
Capitolo 5 Sperimentazione ed analisi dei risultati
Nel presente capitolo si illustra un esempio della procedura proposta che consente di
analizzare e valutare i dati restituiti dallo strumento di tracing durante un particolare
esperimento. Una volta definito l’ambiente di sviluppo, quello di esecuzione dei test ed i
tool utilizzati, si spiega il modo in cui è possibile valutare eventuali trend di aging, stimare
il TTE di alcune risorse e determinare i parametri di workload più significativi.
L’esecuzione di un esperimento viene condotta essenzialmente in tre fasi:
- abilitando lo strumento di monitoring;
- impostando il workload tramite il tool stress ed attivando i meccanismi di tracing
per la raccolta delle informazioni di sistema;
- analizzando i risultati con l’ausilio di apposite procedure definite in ambiente
Matlab.
È necessario sottolineare che, poiché stress non consente di stabilire il carico del
sottosistema di networking, sarebbe necessario eseguire l’esperimento tramite l’attivazione
simultanea di un secondo strumento di benchmark. Tuttavia, per limitare l’overhead e gli
effetti di aging dovuti a cause esterne al SO, nell’esempio mostrato si è scelto di non
utilizzare un ulteriore tool e di tralasciare quindi lo studio del sottosistema di network I/O.
Inoltre, relativamente al sottosistema di device control, è stato stressata solo la parte
relativa all’I/O da/verso il disco. Pertanto, nei prossimi paragrafi, ci si riferisce a tale
porzione monitorata con la locuzione “sottosistema disk I/O driver”.
Analisi sperimentale di
software aging nel kernel Linux
101
5.1 Esecuzione del test
I test per lo studio del software aging sono stati eseguiti utilizzando la distribuzione
Linux Reh Hat 4, con versione del kernel 2.6.30.1.
Figura 5.1: procedura sperimentale.
Dalla figura 5.1 è possibile evincere, in maniera schematica, l’interazione tra lo
sperimentatore e gli strumenti utilizzati per la cattura e l’elaborazione delle informazioni.
In particolare, l’utente, una volta attivato il tool stress, deve semplicemente abilitare il
tracing del kernel e la funzione per il reperimento periodico dello stato del SO. Una volta
terminato il test, lo sperimentatore è poi in grado di effettuare elaborazioni sui dati raccolti
avviando uno script Matlab opportunamente programmato. Esso consente di eseguire le
operazioni statistiche illustrate nel capitolo 4, al fine di permettere opportune valutazioni
sul fenomeno di aging. Lo script richiama apposite procedure che consentono di ottenere i
risultati parziali dell’analisi.
Analisi sperimentale di
software aging nel kernel Linux
102
In particolare, in figura 5.2 vengono esemplificati i passi seguiti per l’elaborazione delle
informazioni raccolte, ciascuno corrispondente ad una specifica funzione Matlab e
descritto nel capitolo 4. La prima fase consiste nel recuperare le informazioni sui parametri
di workload, salvate nei file relativi dalla funzione myTracer-rep.c. Una volta acquisiti,
questi dati vengono normalizzati e da essi sono scartati i valori outlier. Nello step
successivo si provvede alla costruzione delle serie di dati, corrispondenti agli indicatori di
aging, da sottoporre al test di Mann Kendall (al fine di rilevare eventuali trend di aging e di
stimare lo slope del trend che consente poi di effettuare le TTE estimation) e alla PCA. I
risultati di quest’ultimo passo consentono di determinare i parametri di workload più
rilevanti per i trend di aging individuati.
Figura 5.2: procedura di analisi dei dati.
L’esperimento d’esempio è stato condotto eseguendo in maniera sequenziale un ben
definito insieme di operazioni:
- settaggio del carico di lavoro da imporre alla macchina e scelta del tempo totale di
esecuzione;
Analisi sperimentale di
software aging nel kernel Linux
103
- abilitazione del tracer myTracer;
- attivazione della funzione myTracer-rep per il reperimento delle informazioni in
user space;
- esecuzione del test per un tempo minimo di 5 giorni;
- elaborazione dei dati attraverso lo script Matlab;
- valutazione dei risultati.
Il workload applicato è stato stabilito attraverso la seguente istruzione:
stress -c 50 -i 50 -m 100 --vm-bytes 4M --hdd 100 --hdd-bytes 4M –t 7d .
I valori assegnati ai parametri configurabili del tool sono stati scelti in modo tale da
stressare in maniera abbastanza significativa il sistema. Tale scelta è stata fatta al fine di
individuare più velocemente possibile eventuali trend, cercando però di non indurre un
prematuro esaurimento delle risorse. In particolare, attraverso la precedente istruzione si è
stabilito di utilizzare:
- 50 processi per lo stress relativo alla CPU;
- 50 processi per le operazioni di I/O;
- 100 processi per le operazioni di malloc e free sulla memoria, ognuno dei quali
alloca e libera blocchi di 4 MB;
- 100 processi per l’esecuzione di operazioni di write ed unlink, ognuno dei quali
scrive una quantità di informazioni pari a 4 MB.
Inoltre, la durata complessiva dell’esperimento è stata impostata a 7 giorni in via
precauzionale ma i trend di aging si sono manifestati in anticipo, cosicché è stato possibile
interrompere il test dopo 5 giorni 6 ore e qualche minuto.
5.1.1 Individuazione dei trend di aging
I trend di aging, come spiegato in precedenza, sono stati individuati attraverso la
funzione Matlab ktaub. In particolare, a partire dalle informazioni riguardanti la memoria
libera disponibile, la latenza ed il numero di system call servite durante il periodo di
Analisi sperimentale di
software aging nel kernel Linux
104
osservazione, è stato possibile analizzare i trend relativi ai 7 indicatori di aging considerati,
ottenendo i risultati che seguono.
Memory depletion
Analizzando il contenuto del file freeMem.txt è stato possibile osservare, come
mostrato in figura 5.3, la variazione di memoria libera disponibile durante l’intera
esecuzione dell’esperimento.
Figura 5.3: variazione memoria fisica libera disponibile.
In figura 5.4 si riporta il grafico del trend relativo all’indicatore di memory depletion (linea
in verde), ottenuto utilizzando la funzione Matlab ktaub. Esso risulta significativo al 95%
con coefficiente angolare della retta interpolante pari a -0.5267×10-5 kB/min.
Analisi sperimentale di
software aging nel kernel Linux
105
Figura 5.4: trend relativo all’indicatore di memory depletion.
Throughput loss
Tale indicatore di aging è stato calcolato basandosi sul numero di system call (sotto
osservazione) servite al minuto, così come spiegato nel paragrafo 4.3.1.1. Dei 3 indicatori
di throughput loss, l’unico che presenta un trend significativo è quello relativo al
sottosistema di memory management, così come riporta la figura 5.5.
Time loss
In maniera analoga al caso precedente, tale indicatore di aging è stato ottenuto in base al
calcolo del tempo medio impiegato dal sistema per servire un insieme di system call (cfr.
paragrafo 4.3.1.1). Anche in questo caso, risulta significativo esclusivamente il trend
relativo al sottosistema di memory management (figura 5.6). Questi risultati indicano che,
molto probabilmente, il consumo crescente di memoria rende più lente le operazioni
successive di gestione della memoria stessa.
Analisi sperimentale di
software aging nel kernel Linux
106
Figura 5.5: trend relativo all’indicatore di throughput loss
del sottosistema memory management.
Figura 5.6: trend relativo all’indicatore di time loss del sottosistema memory management.
Analisi sperimentale di
software aging nel kernel Linux
107
Nella tabella 5.1 si riportano, in maniera sintetica, i risultati ottenuti dalle precedenti
analisi.
Parametri T.S. (95%)
Slope (x10-5)
V.P. (in 5 giorni)
TTE (giorni)
Memory depletion [kB/min]
FreeMem SI -0.5267 ≈-7.5% ≈64
Throughput loss filesystem I/O
[op/min]
numero totale di open, close, access,
lseek, read, write
NO - - -
Throughput loss memory
management [op/min]
numero totale di mmap,
munmap, brk SI -0.0063 ≈-11% ≈49
Throughput loss process
management [op/min]
numero totale di exec,
fork, clone NO - - -
Time loss file system I/O
[cicli clock/min]
latenza totale di
open, close, access,
lseek, read, write
NO - - -
Time loss memory management
[cicli clock/min]
latenza totale di
mmap, munmap, brk
SI 0.3537 ≈4.8% ≈100
Time loss process management
[cicli clock/min]
latenza totale di exec, fork,
clone
NO - - -
Tabella 5.1: sintesi risultati ottenuti.
T.S.=trend significativo, V.P.= variazioni percentuali, TTE=Time To Extausion
Per i trend significativi sono stati stimati i TTE delle risorse relative (memoria e numero di
operazioni eseguite). Tale calcolo si basa sulla conoscenza dello slope del trend, il quale
viene restituito da ktaub. Poiché l’equazione della retta interpolante può essere espressa
come
)1(serietemposlopetrend +×= ,
Analisi sperimentale di
software aging nel kernel Linux
108
a partire dal valore della pendenza del trend, il TTE (cioè il valore di tempo in
corrispondenza del quale il trend si annulla) è stato calcolato con la seguente formula:
slopeserieTTE )1(−
= ,
dove )1(serie rappresenta il valore dell’intercetta della retta interpolante con l’asse delle
ordinate.
Per quanto riguarda la memory depletion, è stato stimato che il completo esaurimento dello
spazio di memoria, con il workload utilizzato, dovrebbe verificarsi dopo circa 64 giorni,
ipotizzando un trend decrescente in maniera lineare. Per quel che concerne, invece, il
throughput loss si stima un TTE pari a circa 49 giorni. Relativamente, infine, al time loss si
è stimato che in circa 100 giorni il tempo medio impiegato per eseguire le system call
considerate raddoppia.
Nell’arco di tempo d’esecuzione dell’esperimento, sono state infine individuate le seguenti
manifestazioni:
- riduzione della quantità di memoria libera di circa il 7,5%;
- aumento del tempo di esecuzione (indicato in numero di cicli di clock) delle system
call sottoposte a tracing dell’ordine di circa il 4,8%;
- riduzione del throughput di circa l’11%.
5.1.2 Principal component analysis
I parametri di tracing, opportunamente raggruppati per sottosistema, sono stati
sottoposti a PCA in modo da decorrelare e rappresentare con un minor numero di variabili
le informazioni in essi contenute.
Come spiegato nel paragrafo 4.3.1.3, ciò è stato ottenuto con l’ausilio della funzione di
libreria Matlab princomp. Utilizzando l’output relativo al vettore LATENT, fornito in
uscita da tale funzione, è stato possibile selezionare un sottogruppo delle componenti
principali aventi varianza maggiore (cioè le prime). In particolare, sono state scelte le
componenti principali sufficienti a rappresentare almeno il 98% della varianza delle
Analisi sperimentale di
software aging nel kernel Linux
109
informazioni originali. Nelle seguenti tabelle vengono riportati, per ogni sottosistema, i
coefficienti di combinazione lineare che legano le componenti principali più significative
ai parametri di workload. A partire da questi, si possono calcolare le PC attraverso il
prodotto scalare con le osservazioni dei parametri di workload X, secondo l’espressione:
∑=
=m
jjiji XcoefPC
1.
Tabella 5.2: coefficienti delle componenti principali relative al sottosistema disk I/O driver.
PC1 PC2 PC3
0.0012 0.0011 -0.4165
-0.0009 0.0007 -0.4183
-0.0010 0.0009 -0.4183
-0.1746 0.4599 0.0188
0.9348 0.3550 0.0042
-0.0646 0.1646 -0.0727
0.0107 0.0024 -0.3582
-0.0010 0.0007 -0.4184
0.0025 0.0042 -0.4084
-0.1747 0.4606 0.0113
-0.1746 0.4599 0.0187
-0.1743 0.4601 -0.0175
Tabella 5.3: coefficienti delle componenti principali relative al sottosistema process management.
PC1 PC2
0.4982 0.0028
0.4982 0.0028
-0.0004 1.0000
0.5059 -0.0026
0.4977 -0.0019
Analisi sperimentale di
software aging nel kernel Linux
110
PC1 PC2 PC3
-0.0151 0.4594 -0.1844
-0.0212 0.4579 -0.1833
-0.9992 -0.0377 -0.0121
-0.0094 0.4580 -0.2167
-0.0141 0.4595 -0.2088
-0.0258 0.3961 0.9174
Tabella 5.4: coefficienti delle componenti principali relative al sottosistema file system I/O.
PC1 PC2
-0.0077 -0.1126
-0.0009 -0.0159
-0.0562 -0.6387
-0.0088 -0.0933
-0.0089 -0.0962
-0.0090 -0.0964
-0.0021 -0.0443
-0.0115 -0.1293
-0.0601 -0.7109
-0.0091 -0.1016
-0.0091 -0.1005
-0.7045 0.0607
-0.7045 0.0607
Tabella 5.5: coefficienti delle componenti principali relative al sottosistema memory management.
5.1.3 Valutazione dei risultati
Nelle tabelle precedenti sono stati evidenziati i coefficienti di combinazione lineare
delle PC più significative che superano un preciso valore di soglia. Più precisamente sono
stati selezionati i coefficienti che soddisfano la seguente relazione:
|coeff| ≥ soglia
Analisi sperimentale di
software aging nel kernel Linux
111
dove il valore di soglia è stato scelto pari a 0.5 (poiché i coefficienti delle PC variano fra -1
ed 1).
Figura 5.7: composizione della prima e della terza PC relative al sottosistema file system I/O.
Figura 5.8: composizione della prima e dalla seconda PC relative al sottosistema disk I/O driver.
Analisi sperimentale di
software aging nel kernel Linux
112
Figura 5.9: composizione della prima e della seconda PC relative al sottosistema memory management.
Figura 5.10: composizione della prima PC relativa al sottosistema process management.
Analisi sperimentale di
software aging nel kernel Linux
113
Nelle figure 5.7, 5.8, 5.9 e 5.10 sono raffigurati i coefficienti di combinazione lineare
relativi alle PC maggiormente significative, suddivise per sottosistema. In rosso sono stati
evidenziati i coefficienti che superano (in modulo) il valore di soglia prescelto e che,
quindi, corrispondono ai parametri di workload che più pesano sulla composizione delle
PC considerate.
Dalle figure precedenti si ricavano, in maniera immediata, i parametri di workload più
rilevanti, riportati in tabella 5.6.
File system
I/O
Disk I/O
driver
Memory
management
Process
management
byte_request PC1 nr_access nr_softirq
byte_alloc nr_task_runnable
nr_flush PC2 - nr_tasklet
nr_prelazioni-
PC3 nr_write - - -
Tabella 5.6: parametri di workload più significativi per ogni trend e per ogni sottosistema.
I parametri selezionati influiscono in maniera più significativa rispetto agli altri sui trend di
aging individuati nel test. Da tale assunzione è possibile definire una caratterizzazione del
workload la quale potrebbe consentire, attraverso un elevato numero di esperimenti, di
determinare la variazione dei trend di aging al variare del workload applicato.
Inoltre, l’individuazione di tali parametri permette di riconoscere le aree del kernel che
probabilmente inducono il sistema all’aging e può dunque indirizzare gli sviluppatori del
SO Linux nelle operazioni di debugging (consentendo una sostanziale riduzione dei tempi
e dei costi).
È interessante notare che il fenomeno di aging è stato individuato solo per il sottosistema
di memory management. Ciò indica una probabile vulnerabilità di quest’ultimo. Bisogna
comunque tener presente che tale valutazione non esclude che anche altri sottosistemi
Analisi sperimentale di
software aging nel kernel Linux
114
siano affetti da aging, il quale potrebbe manifestarsi con tempi di sperimentazione più
lunghi e/o carichi di lavoro differenti.
In ultimo è necessario far notare che i TTE dipendono strettamente dal workload e, quindi,
non vanno intesi come metriche generalmente valide. Sicuramente, in condizioni di lavoro
meno pesanti (che sono poi quelle in cui il SO Linux viene utilizzato dagli utenti comuni)
sono necessari tempi consistentemente maggiori per l’eventuale esaurimento delle risorse.
Comunque, l’interesse per il software aging è volto principalmente allo studio del
fenomeno in sistemi long running (che lavorano in situazioni particolarmente critiche sia
per i carichi imposti che per i vincoli di dependability) e, in tali contesti, le valutazioni
effettuate assumono quindi una particolare rilevanza.
115
Conclusioni e sviluppi futuri
Nel presente lavoro è stata descritta una procedura sperimentale utilizzata per lo studio
del software aging nel kernel Linux. Per attuare una soluzione generale e “leggera”, in
grado di monitorare lo stato del SO senza introdurre consistente overhead, è stato
necessario lo studio attento di molteplici argomenti, tra i quali:
- le definizioni inerenti alla dependability del software e all’aging, al fine di
comprendere a pieno il dominio del problema;
- i lavori già effettuati riguardo alla valutazione dell’invecchiamento nei SO, in modo
da definire lo stato dell’arte ed una base solida da cui partire;
- l’architettura del kernel Linux, per determinarne punti di forza e di vulnerabilità e per
acquisire la giusta competenza necessaria alla programmazione di opportuni
componenti aggiuntivi;
- gli strumenti statistici di uso più comune, necessari per una sperimentazione
disciplinata e valida.
Tramite questi studi è stato possibile acquisire un certo background di conoscenza, che è
stato finalizzato alla progettazione e messa in opera dello strumento di tracing
(implementato in linguaggio C) e del procedimento di sperimentazione ed analisi dei
risultati (realizzato in ambiente Matlab).
Relativamente ad uno specifico carico imposto al sistema, sono stati ottenuti i seguenti
risultati:
Analisi sperimentale di software aging nel kernel Linux
116
- presenza di un trend di aging nel sottosistema di memory management, riguardante
gli indicatori di memory depletion, time loss e throughput loss;
- TTE relativo all’esaurimento della memoria disponibile di circa 64 giorni;
- numero di operazioni eseguite dal SO ridotto a zero in circa 49 giorni;
- tempo di esecuzione delle system call monitorate raddoppiato in circa 100 giorni;
- influenza sui trend da parte dei seguenti parametri di workload
- numero di system call access e write eseguite;
- numero di tasklet e softirq;
- numero di flush e di prelazioni, nonché di byte richiesti ed allocati;
- numero di task “runnable”.
A valle di tali risultati si può concludere che, in condizioni di lavoro particolarmente
stressanti, il kernel Linux dovrebbe essere sottoposto ad interventi di rejuvenation (che
potrebbero concretizzarsi in semplici riavvii) almeno una volta ogni 2 mesi.
Come si è detto, i risultati ottenuti dipendono strettamente dal tipo di carico imposto al
sistema. L’approccio proposto si definisce infatti come mesurement-based e workload-
dependent. Ciononostante, la procedura implementata può essere considerata come il punto
di partenza per studi più approfonditi o rivolti a tipologie differenti di software. In primo
luogo, per esaminare più nel dettaglio l’argomento trattato, andrebbero opportunamente
progettati altri esperimenti, in modo da monitorare le stesse informazioni in condizioni di
carico differenti ed in tempi di testing più lunghi. Una caratterizzazione del workload inter-
esperimento, basata ad esempio su tecniche di clustering, potrebbe in tal caso servire a
comprendere e predire il comportamento di sistema in presenza di condizioni di lavoro
svariate.
Sarebbe inoltre interessante applicare la procedura sperimentale ad altre distribuzioni di
Linux in modo da comprendere se talune vulnerabilità sono state ridotte in release più
recenti e se sussiste una qualche differenza fra le varie versioni.
Inoltre, potrebbero essere completati alcuni studi già effettuati sul fenomeno (come quelli
descritti nel paragrafo 2.2) stimando l’entità dell’aging imputabile a ciascuna applicazione
Analisi sperimentale di software aging nel kernel Linux
117
in esecuzione sul SO, al netto del contributo dovuto al solo kernel (calcolato con la
procedura presentata).
Si deve tener presente, infine, che l’approccio presentato in questo lavoro può essere esteso
al controllo delle prestazioni di altre tipologie di applicazioni, che consentano di
implementare meccanismi di tracing. In tal senso, Linux è apparso notevolmente
“predisposto” al monitoraggio, poiché offre una serie di strumenti per un reperimento
abbastanza semplice delle informazioni di sistema (file system virtuali, tracepoint, accesso
a tutto il codice del kernel, ecc.).
118
Bibliografia
[1] Y. Dai, Y. Pan, R. Raje, Advanced Parallel and Distributed Computing, 2006;
[2] A. Silberschatz, P. B. Galvin, G. Gagne, Sistemi operativi, concetti ed esempi, sesta
edizione, 2002;
[3] A. Avižienis, J. C. Laprie, B. Randell, C. Landwehr, Basic Concepts and Taxonomy
of Dependable and Secure Computing, 2004;
[4] J. Gray, Why Do Computers Stop And What Can Be Done About It?, 1985;
[5] M. Grottke, K. S. Trivedi, Fighting bugs: remove, retry, replicate and rejuvenate,
2007;
[6] K. Trivedi, G. Ciardo, B. Dasarathy, M. Grottke, A. Rindos, B. Vashaw, Achieving
and Assuring High Availability, 2008;
[7] Y. Huang, C. Kintala, N. Kolettis e N. D. Fulton, Software Rejuvenation: Analysis,
Module and Applications, 1995;
[8] D. L. Parnas, Software Aging, 1998;
[9] R Reis, Information Technology: Selected Tutorials, 2004;
[10] K. Vaidyanathan, K. S. Trivedi, Extended Classification of Software Faults Based on
Aging, 2001;
[11] T. Dohi, K. Goševa-Popstojanova, K. S. Trivedi, Statistical Non-Parametric
Algorithms to Estimate the Optimal Software Rejuvenation Schedule, 2000;
[12] S. Garg, A. Puliafito, M. Telek, K. S. Trivedi, Analysis of Software Rejuvenation
using Markov Regenerative Stochastic Petri Net, 1995;
Analisi sperimentale di software aging nel kernel Linux
119
[13] K. Vaidyanathan, K. S. Trivedi, A Measurement Based Model for Estimation of
Resource Exhaustion in Operational Software System, 1999;
[14] S. Garg, K. Vaidyanathan, K. S. Trivedi, A Methodology for Detection and
Estimation of Software Aging, 1999;
[15] K. Vaidyanathan, K. S. Trivedi, A Comprehensive Model for Software Rejuvenation,
2005;
[16] V.R. Basili and B.T. Perricone, Software Errors and Complexity: an Empirical
Investigation, 1984;
[17] T. J. Ostrand, E. J. Weyuker, The Distribution of Faults in a Large Industrial
Software System, 2002;
[18] A. Chou, J. Yang, B. Chelf, S. Hallem, D. Engler, An Empirical Study of Operating
System Errors, 2001;
[19] S. Loosemore, R. M. Stallman, R. McGrath, A. Oram, U. Drepper, The GNU C
Library Reference Manual, 2007;
[20] D. P. Bovet, M. Cesati, Understanding the Linux Kernel, 2005;
[21] J. D. Case, M. F., M.L. Schoffstall, C. Davin, M. T. Rose, K. McCloghrie, Simple
Network Management Protocol, 1990;
[22] M. Desnoyers, Using the Linux Kernel Tracepoints, 2009;
[23] R. L. Mason, R. F. Gunst, J. L. Hess, Statistical Design and Analysis of Experiments
With Applications to Engineering and Science, Second Edition, 2003;
[24] R. A. Fisher, The Design of Experiments, 1935;
[25] J. Antony, Design of Experiments for Engineers and Scientists, 2003;
[26] Design of Experiment, dalla rivista on line Orizzonte Scientifico Magazine,
http://www.gmsl.it/magazine/index.asp;
[27] Mark J. Anderson, Shari L. Kraber, Soluzioni per un Doe corretto, 2004;
[28] N. Joukov, A. Traeger, R. Iyer, C. P. Wright, E. Zadok, Operating System Profiling
via Latency Analysis, 2006;
[29] N. Joukov, E. Zadok, Aggregate_stats macros and globals, 2006,
http://ftp.fsl.cs.sunysb.edu/osprof/profilers/POSIX/aggregate_stats.h;
Analisi sperimentale di software aging nel kernel Linux
120
[30] H. B. Mann, Non parametric tests again trend, 1945;
[31] M. G. Kendall, Rank correlation methods, 1962;
[32] J. Shlens, A Tutorial on Principal Component Analysis, 2009;
[33] M. Grottke, K. Vaidyanathan, K. S. Trivedi, Analysis of Software Aging in a Web
Server, 2006.