Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando...

20
Appunti ragionati di Sistemi Operativi Realizzato per l’Università Telematica Internazionale UNINETTUNO Studente: Lorenzo L. Ancora Docente: Prof. Claudio Fornaro VERSIONE WEB

Transcript of Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando...

Page 1: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di

Sistemi Operativi

Realizzato per l’Università Telematica InternazionaleUNINETTUNO

Studente: Lorenzo L. AncoraDocente: Prof. Claudio Fornaro

VERSIONE WEB

Page 2: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Quest'opera è stata rilasciata con licenza Creative Commons Attribuzione - Condividi allo stesso modo 4.0 Internazionale.

Quest’opera è distribuita con una licenza compatibile con l’iniziativa Free Cultural Works, volta a garantire:

• la libertà di usare l’opera;

• la libertà di studiare l’opera ed utilizzare le nozioni apprese;

• la libertà di creare e distribuire copie, totali o parziali, dell’opera;

• la libertà di modifcare, migliorare e ridistribuire l’opera derivata.

Per leggere una copia della licenza visita il sito web:

http://creativecommons.org/licenses/by-sa/4.0/

Tutti i marchi citati nel testo appartengono ai rispettivi proprietari: devono essere utilizzati con il massimo rispetto e solo a fne didattico.

Questa versione è la controparte digitale della release cartacea.A seconda del file scaricato può contenere solo parte del testo.

METADATI

Titolo: Appunti ragionati di Sistemi OperativiAutore: Lorenzo AncoraRelease: 16/05/18Versione: 1.5.7 - WEBFonte: http://ht.ly/MTyk30jPsQK

STATISTICHE

# pagine: 20# paragrafi: 681# parole: 4151# caratteri: 28775

Page 3: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Indice delle appendici

Prefazione............................................................................................................................ 5Appendici............................................................................................................................. 7Appendice A: flesystem scripting.................................................................................8Appendice B: multithreading sincronizzato...............................................................12Appendice C: esperimento combinato.......................................................................16

Page 4: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

4

Page 5: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Prefazione Capitolo

Prefazione

Questo breve compendio raccoglie parte dei miei appunti, scritti per il corso di Sistemi Operativi dell’Università Telematica Internazionale UNINETTUNO. Il testo segue fedelmente le videolezioni dei docenti Maurelio Boari e Paolo Ancilotti, integrandole con esempi, spiegazioni e note ove necessario.

Gli appunti, prima semplici lavagne e fogli, sono stati digitalizzati e rielaborati per consentire sia il ripasso veloce che l’approfondimento da parte di terzi, per cui ho cercato un ragionevole compromesso tra dettaglio e semplicità. Questa trattazione non sostituisce il libro di testo (lettura raccomandata: “Sistemi Operativi” di Ancilotti, Boari, Ciampolini e Lipari) ed è stata concepita come ausilio mnemonico per chi ha già seguito il corso universitario.

Il testo è suddiviso in capitoli, scaricabili e stampabili separatamente: ogni sezione tratta un singolo argomento del corso originale e se serve ricapitola i concetti già visti secondo il punto di vista più opportuno. Il tutor o il docente suggeriranno quali parti studiare ed il metodo di studio più efcace.

Per orientarsi (sono pur sempre gli appunti di uno studente...), il lettore può consultare la legenda e l’indice interattivo dei capitoli. Il testo è dotato anche di alcune appendici, che ho scritto per far risparmiare tempo e fatica a chi desidera approfondire i contenuti.

Spero che questo contributo possa risvegliare la curiosità dello studente verso uno strumento che, troppo spesso, diamo per scontato ma che è indispensabile per l’uso e la programmazione dei moderni computer: il Sistema Operativo.

Lorenzo Ancora

5

Page 6: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Capitolo Prefazione

6

Page 7: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

Appendici

7

Page 8: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

Appendice A: flessstem scripting

GNU/Linux scripting: gestione semplice di gerarchie di fle ed uso dei tool di sistema tramite pipe anonime.

Prerequisiti:

• interprete Python standard – versione 3.5

versione più recente e relativa guida all’uso su “www.python.org”. Dopo l’installazione/aggiornamento dell’interprete è sempre una buona idea riavviare il SO e controllare che le variabili d’ambiente *PATH* contengano il percorso dell’ultima versione dell’interprete Python;

• GNU/Linux – qualsiasi versione compatibile con POSIX;

• IDLE 3 – IDE consigliato e distribuito con l’interprete standard

converte gli spazi in tabulazioni (per evitare errori di nesting), fornisce aiuti contestuali e facilita il debugging;

Per sicurezza creare una nuova directory e salvare lo script al suo interno tramite IDLE 3. Posizionarsi nella nuova directory con il comando di shell “cd nuovadirectory” e verifcare che sia la working directory eseguendo “pwd”. Dopo aver seguito le istruzioni per l’uso all’inizio del listato, lo script può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”.

1 # Esempi in Python 3.5: operazioni di complessità crescente con i file.2 # Scritto per gli appunti del corso di Sistemi Operativi (ITIU).3 # Lo script è ben testato su GNU/Linux Debian 9 con Python 3.5.4 # Lorenzo L. Ancora - 20185 #6 # Istruzioni per l'uso:7 # Porre un piccolo file di test con nome "ex_IN.dat" nella directory di lavoro

8 # dello script e dare valori a piacere ai primi 3 bytes.9 # Creare la gerarchia di directories /copie/(inalterate|modificate)/.10 # Leggere i commenti per comprendere gli esempi, poi seguire le11 # istruzioni a video.12 13 ### Generale ###14

8

Page 9: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

15 # Librerie fondamentali (importazione necessaria per l'esecuzione non interattiva)

16 import os, io, sys17 # Alias delle operazioni di copia, per distinguere bene funzioni quasi

omonime18 from shutil import SameFileError19 from shutil import copy as copiaSemplice, copy2 as copiaConMetadata20 21 fileDiEsempio1 = "ex_IN.dat"22 fileDiEsempio2a = "./copie/inalterate/ex_copia.dat"23 fileDiEsempio2b = "./copie/inalterate/ex_copia_esatta.dat"24 fileDiEsempio3 = "./copie/inalterate/ex_OUT.dat"25 fileDiEsempio4 = "./copie/modificate/ex_OUT_mod.dat"26 fileDiEsempio5 = "./copie/inalterate/../modificate/ex_OUT_mod.dat"27 28 # Programmazione difensiva: chiusura esplicita dei file handle.29 # Sincronizza forzatamente lo stream buffer di fileHandle SSE doFlush è

attiva.30 # flush() si usa dopo molte scritture o in ambiente con buffering

aggressivo.31 def chiudiFile(fileHandle:io.BufferedIOBase, doFlush:bool = False) -> None:32 # Programmazione difensiva: asserzione sull'INPUT.33 assert(hasattr(fileHandle, "read")); # True SSE può essere letta come

file.34 35 if doFlush: fileHandle.flush(); # Flush per i sistemi con buffer pigri36 # Dealloca il descrittore SSE non è in uso da altri processi:37 fileHandle.close(); # Chiusura del file handle38 39 return;40 41 # Richiede che un file sia presente sulla memoria secondaria.42 # Il file può anche essere una directory o totalmente opzionale.43 def richiediFile(filePath:str, directory:bool = False, fatal:bool = True)

-> bool:44 # Programmazione difensiva: assegnazione pedante alla dichiarazione.45 fileExists = False;46 47 if directory is False: # Richiesto un file48 # True SSE l'i-node è un file49 fileExists = os.path.isfile(filePath);50 else: # Richiesta una directory51 # True SSE l'i-node è una directory52 fileExists = os.path.isdir(filePath);53 54 if not fileExists: # File/directory mancante55 print("Serve \"{FILE}\" in \"{CDIR}\".".format(FILE=filePath,\56 CDIR=os.path.abspath(os.getcwd())),\57 file=sys.stderr);58 if fatal is True: # Errore fatale59 print("Errore: INPUT assente.");60 exit(os.EX_IOERR);61 62 return fileExists;63 64 print("Programma di esempio avviato.")65 66 # Programmazione difensiva: imporre dei prerequisiti per l'esecuzione.67 richiediFile("copie", directory=True)68 richiediFile("copie/inalterate", directory=True)69 richiediFile("copie/modificate", directory=True)

9

Page 10: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

70 71 print("ESEMPIO 1")72 ### Esempio 1 ###73 # Lettura selettiva di alcuni dati.74 75 richiediFile(fileDiEsempio1)76 77 fileHandle1 = open(fileDiEsempio1, "rb") # Modalita' "lettura binaria"78 INPUTdata = fileHandle1.read(3) # Lettura di 3 bytes da fileHandle1 in

INPUTdata79 chiudiFile(fileHandle=fileHandle1) # Chiusura del file descriptor80 81 OUTPUTdata = INPUTdata82 83 84 print("ESEMPIO 2")85 ### Esempio 2 ###86 # Copia dei file esplicita ad alto livello.87 88 # Copia semplice di fileDiEsempio1 in fileDiEsempio2a.89 # Preserva dati e permessi di accesso.90 # Il risultato non è equivalente a quello dell'Esempio 3.91 copiaSemplice(fileDiEsempio1, fileDiEsempio2a)92 93 # Copia completa di fileDiEsempio1 in fileDiEsempio2b94 # Preserva i dati, l'ora, i permessi di accesso ed eventuali attributi

estesi.95 # Non tutti i SO la supportano e si rende necessaria quasi solo per i

backup.96 # Fortemente dipendente dal filesystem; verifiche di correttezza sono

fattibili97 # con l'uso dei tool di sistema (GNU stat e derivati).98 copiaConMetadata(fileDiEsempio1, fileDiEsempio2b)99 100 101 print("ESEMPIO 3")102 ### Esempio 3 ###103 # Copia dei file indiretta.104 105 fileHandle3 = open(fileDiEsempio3, "wb") # Modalita' "scrittura binaria"106 fileHandle3.write(OUTPUTdata) # Scrittura di INPUTdata in fileDiEsempio3107 chiudiFile(fileHandle=fileHandle3, doFlush=True)108 109 110 print("ESEMPIO 4")111 ### Esempio 4 ###112 # Scrittura di singoli bytes.113 114 magicNumber = (0xCA, 0xFE, 0xBA, 0xBE, 0x00) # Java "CAFEBABE"

(esadecimale)115 116 fileHandle4 = open(fileDiEsempio4, "wb") # Modalita' "scrittura binaria"117 OUTPUTdata = bytes(magicNumber)118 fileHandle4.write(OUTPUTdata) # Scrittura di INPUTdata in fileDiEsempio4119 fileHandle4.write(b"SO:0") # Scrittura della stringa "SO:0" in

fileDiEsempio4120 chiudiFile(fileHandle=fileHandle4, doFlush=True)121 122 123 print("ESEMPIO 5")124 ### Esempio 5 ###

10

Page 11: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

125 # Filesystem aliases, manipolazione di sequenze in memoria e scrittura126 # con pathname complesso.127 128 if richiediFile(fileDiEsempio4, fatal=False) is False:129 print("Esempio 5 saltato.")130 exit(os.EX_IOERR);131 132 # Copia semplice di fileDiEsempio4 in fileDiEsempio5.133 # L'espansione del pathname mantiene la struttura delle directories

coerente.134 # Sfruttando questo fattore, causeremo un conflitto che solleverà135 # una nuova eccezione.136 try:137 copiaSemplice(fileDiEsempio4, fileDiEsempio5)138 except SameFileError:139 # L'errore indica il successo del test.140 print("fileDiEsempio4 e fileDiEsempio5 sono il medesimo file. Tutto

OK.")141 142 fileHandle5 = open(fileDiEsempio5, "r+b") # Modalita' "R/W binaria

conservativa"143 # Lettura completa fino alla fine del file (EOF: End-Of-File) in INPUTdata144 INPUTdata = fileHandle5.read()145 INPUTdata = INPUTdata.replace(b"SO:0", b"SO:1") # Sostituzione in memoria146 # Troncamento manuale del file, che in modalità r+ non avviene di default147 fileHandle5.truncate(0)148 fileHandle5.write(INPUTdata) # Scrittura di INPUTdata in fileDiEsempio5149 chiudiFile(fileHandle=fileHandle5, doFlush=True)150 151 152 print("ESEMPIO 6")153 ### Esempio 6 ###154 # Uso elementare di pipe per l'interazione con i tool di sistema.155 156 # Esegue il comando di sistema "ls" ed apre una pipe in lettura,

corrispondente al suo stdout.157 fileListingHandle = os.popen("ls -lhR ./copie/", "r")158 # Lettura della pipe con formattazione avanzata dell'OUTPUT (come printf di

C).159 print("\n\t---\nGerarchia del filesystem:\n{PIPEREAD}\nFine

gerarchia.".format(\160 PIPEREAD=fileListingHandle.read()))161 # Programmazione difensiva: chiusura dei file speciali / in memoria.162 chiudiFile(fileListingHandle)163 print("\n\t---\nEsecuzione terminata.\nControllare la gerarchia usando\164 \"ls -lR\", \"stat\" ed \"od -x\".");

Fine del listato.

11

Page 12: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

Appendice B: multithreading sincronizzato

Programmazione C: sincronizzazione di più threads tramite conditions.

1 /* Esempio in C 2011 (standard ISO 9899:2011): sincronizzazione avanzata.2 * Scritto come esercizio del corso di Sistemi Operativi (ITIU).3 * Lo script è ben testato su GNU/Linux Debian 9 con GCC 6.3.4 * Lorenzo L. Ancora - 20185 *6 * Traccia: simulare un biblioteca con poche copie di un singolo libro contese

7 * fra 3 clienti. Ogni cliente è rappresentato da un singolo thread POSIX.8 * Iniziare dalle variabili “copia_disponibile”, “mutex_prestito” (mutex) e9 * “prestito_disponibile” (condition). I clienti entrano, leggono e poi10 * escono.11 * Compilare con:12 * gcc -Wall -Wextra -x c --std=c11 --pedantic biblioteca.c -pthread13 */14 15 #include <stdio.h> // Libreria standard per l'I/O16 #include <stdlib.h> // Libreria standard GNU C17 #include <stdbool.h> // Supporto ai booleani18 #include <sys/types.h> // Tipi di sistema ausiliari19 #include <sys/wait.h> // Supporto all'attesa dei sottoprocessi20 #include <pthread.h> // Supporto al multithreading POSIX21 #include <assert.h> // Supporto alle asserzioni22 #include <string.h> // Supporto alle operazioni su memoria contigua23 #include <errno.h> // Gestione approfondita degli errori24 25 #define PROG_NAME "Biblioteca virtuale"26 #define PROG_VER "0.1"27 #define PROG_AUT "Lorenzo Ancora (04/2018, ITIU)"28 29 #define CLIENTS_NUM 330 31 // Mutex per la mutua esclusione32 pthread_mutex_t mutex_prestito = PTHREAD_MUTEX_INITIALIZER;33 // Condizione: "prestito abilitato?"34 pthread_cond_t prestito_disponibile = PTHREAD_COND_INITIALIZER;35 // customers della biblioteca36 pthread_t customers[CLIENTS_NUM];37 // Numero di copie del volume archiviate38 unsigned int copia_disponibile = 3;39 // Vera SSE l'esecuzione non è corretta.40 bool errors = false;41 42 int main(void) {43 void* customer(void* arg);44 void wthread(void);45 46 int* c_ID; // ID del cliente.47 int thread_creation_status = 1; // Stato della creazione del nuovo thread48 int i = 0;49 50 // Programmazione difensiva.51 memset(customers, '\0', sizeof(pthread_t)*CLIENTS_NUM);

12

Page 13: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

52 // Attendiamo i thread, se non sono già stati attesi.53 atexit(wthread);54 55 puts(PROG_NAME " ver. " PROG_VER "\n" PROG_AUT ".\n");56 for(i = 0; i < 3; i++) {57 printf("Creazione del thread %i...\n", i);58 c_ID = malloc(sizeof(int));59 *c_ID = i;60 thread_creation_status = pthread_create(&customers[i], NULL, customer,

c_ID);61 assert(thread_creation_status != EINVAL); // Programmazione difensiva62 // Gestione degli errori preventiva63 switch(thread_creation_status) {64 case EAGAIN: {65 perror("memoria esaurita o raggiunto limite di sistema");66 exit(EXIT_FAILURE);67 }68 case EPERM: {69 perror("permessi insufficienti");70 exit(EXIT_FAILURE);71 }72 case 0: continue; // Tutto ok.73 default: {74 perror("errore ignoto");75 exit(EXIT_FAILURE);76 }77 } // FINE SWITCH78 printf("riuscita.\n");79 } // FINE CICLO ITERATIVO DETERMINATO80 81 wthread(); // Attesa dei thread.82 puts("\nTerminazione.\n");83 if(errors == false) exit(EXIT_SUCCESS);84 else exit(EXIT_FAILURE);85 }86 87 /* La traccia è troppo vaga per gestire coerentemente i thread, ma

possiamo88 * assicurare correttamente il rilascio delle risorse ed il debugging.89 */90 void wthread(void) {91 void* thread_status; // Stato del thread terminato.92 int join_result = 0; // Stato di ritorno del join.93 bool wait_error = false; // True SSE sono avvenuti errori nelle

attese.94 static bool called = false; // True SSE la funzione è già stata invocata.95 int i = 0;96 97 if(called == true) return; // Non consente doppie invocazioni.98 99 for(i = 0; i < CLIENTS_NUM; i++) {100 printf("Attesa della terminazione del thread %i...\n", i);101 join_result = pthread_join(customers[i], &thread_status);102 103 assert(join_result != EDEADLK); // Programmazione difensiva.104 switch(join_result) {105 case EINVAL: perror("join non supportato dal thread");106 case ESRCH: perror("thread inesistente");107 default: puts("completata.");108 }109

13

Page 14: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

110 if(thread_status != PTHREAD_CANCELED) {111 assert(thread_status != NULL); // Programmazione difensiva.112 char t_exit_status = *((char*)thread_status);113 if(t_exit_status == EXIT_SUCCESS) {114 printf("Thread %i terminato con successo.\n", i);115 } else {116 printf("Thread %i terminato con stato %d.\n", i, t_exit_status);117 }118 if(thread_status != NULL) free(thread_status);119 } else { // Il thread è stato cancellato. Non dovrebbe accadere.120 printf("Thread %i cancellato.\n", i);121 perror("cancellazione anomala di un thread");122 }123 }124 125 if(wait_error == false) puts("Tutti i thread sono terminati con

successo.");126 else {127 errors = true;128 puts("Errori nella terminazione dei thread.");129 }130 131 called = true;132 return;133 }134 135 #define P_CSTATUS(ID,STATUS) printf("%i> Il cliente " STATUS ".\n", ID+1)136 void* customer(void* arg) {137 void readbookofIT(int reader_id);138 139 int* tstatus = NULL; // Stato di terminazione.140 int mstatus = 0; // Stato delle operazioni sulle mutex.141 int id_customer = *((int*)arg); // ID del cliente.142 143 //<l’utente entra nella biblioteca per richiedere il prestito>144 P_CSTATUS(id_customer, "è entrato in biblioteca");145 tstatus = malloc(sizeof(__typeof__(EXIT_FAILURE)));146 //<il bibliotecario preleva una copia e la consegna>147 P_CSTATUS(id_customer, "richiede il libro");148 mstatus = pthread_mutex_lock(&mutex_prestito); // Blocco della mutex.149 // Programmazione difensiva (implica: mutex valida, basta un singolo

test)150 assert(mstatus == 0);151 while(copia_disponibile == 0) { // Attesa sulla condition e relativo

blocco.152 // Si sospende fintanto che il predicato simbolico è falso153 // La conseguenza è lo sblocco della mutex e l'attesa di

pthread_cond_signal154 mstatus = pthread_cond_wait(&prestito_disponibile, &mutex_prestito);155 // Programmazione difensiva (sempre 0 nell'implementazione *attuale* di156 // pthread_cond_wait), probabilmente varierà in futuro per segnalare

errori.157 assert(mstatus == 0);158 }159 copia_disponibile--; // Aggiornamento sicuro della risorsa.160 pthread_mutex_unlock(&mutex_prestito); // Sblocco della mutex.161 //<l’utente utilizza il libro>162 readbookofIT(id_customer); // Invocazione di esempio.163 //<l’utente riconsegna il libro>164 pthread_mutex_lock(&mutex_prestito); // Blocco della mutex.165 copia_disponibile++; // Aggiornamento sicuro della risorsa.

14

Page 15: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

166 // Segnala che il predicato simbolico è vero167 // La conseguenza è la riattivazione del prossimo thread in attesa su di

essa168 mstatus = pthread_cond_signal(&prestito_disponibile);169 // Programmazione difensiva (sempre 0 nell'implementazione *attuale* di170 // pthread_cond_signal), probabilmente varierà in futuro per segnalare

errori.171 assert(mstatus == 0);172 pthread_mutex_unlock(&mutex_prestito); // Sblocco della mutex.173 //<l’utente esce dalla biblioteca>174 P_CSTATUS(id_customer, "è uscito dalla biblioteca");175 if(arg != NULL) free(arg); // Il cliente non deve più essere servito.176 *tstatus = EXIT_SUCCESS; // Terminazione corretta, tutto bene.177 pthread_exit(tstatus); // Termina il thread e NON il processo.178 }179 180 void readbookofIT(int reader_id) {181 P_CSTATUS(reader_id, "legge il libro");182 // La traccia non specifica cosa deve fare con il libro. STUB/a piacere.183 return;184 }

Fine del listato.

15

Page 16: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

Appendice C: esperimento combinato

Programmazione C avanzata: esperimento con overfows, threads, parametri e segnali. Salvare il lavoro in corso prima dell’esecuzione.

La documentazione ufciale (GNU/Linux man-page “signal” del 12/2016) aferma che gli unici segnali non gestibili sono SIGKILL e SIGSTOP. Questo programma dimostra, usando le primitive più recenti, che Linux 4 non è in grado di gestire il segnale SIGSEGV (errore di segmentazione) quando esso è causato da uno stack overfow.

1 /* Stack test2 * ISO C99 - 7.143 * Lorenzo Ancora - 20184 *5 * Tentativo di gestire gli stack overflow tramite segnali e threads.6 *7 * Premessa: SIGSEGV è gestito correttamente da Linux SSE non avviene uno8 * stack overflow (senza *garantire* che il processo continui regolarmente).

9 *10 * Tesi: in caso di stack overflow il processo non riesce a gestire SIGSEGV

e11 * vengono ignorati i custom handler. Reazione attualmente non documentata.12 *13 * NOTA: utilità CLI - invocare l'help (-h).14 *15 */16 17 #include <unistd.h> // POSIX - wrappers18 #include <errno.h> // POSIX - error number19 #include <stdlib.h> // POSIX - ausili GNU libc20 #include <stdbool.h> // Definizioni booleane standard21 #include <stdio.h> // Funzioni di I/O standard22 #include <assert.h> // Supporto alla programmazione difensiva23 #include <pthread.h> // Supporto ai thread POSIX24 #include <getopt.h> // Gestione segnali di sistema25 #include <signal.h> // Gestione segnali di sistema26 27 // Test macro per sigaction, getopt, ...28 #ifndef _POSIX_C_SOURCE // Standard POSIX disabilitato29 #warning "Si raccomanda la compilazione standard POSIX"30 #warning "gcc --language=c --std=<c11|gnu11> -pthread stack_test.c"31 #else // Standard POSIX obsoleto?32 #if _POSIX_C_SOURCE < 199309L33 #error "Richiesta compilazione POSIX.1-2008 o superiore per sigaction"34 #elif _POSIX_C_SOURCE < 235 #error "Richiesta compilazione POSIX.1-2008 o superiore per getopt"36 #endif37 #endif38 39 // Costanti precompilatorie

16

Page 17: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

40 #define D_SIGNAL SIGSEGV // Tipo di segnale da gestire (SIGFPE (raro)/SIGSEGV)

41 #define HELP_TEXT "\42 ./stack_test [-v] -s TIPO\n\n\43 -v\tEsecuzione loquace (consigliata);\n\44 -s N\tTipo di test:\n\45 1 - handler+overflow;\n 2 - overflow;\n 3 - handler+overflow a catena.\n\46 "47 48 // Enumerazioni49 typedef enum CLIArgStackTestScope {50 INVALID = -1, // Non valido51 ENTRY_POINT = 0, // Default - riservato52 SIGNAL_HANDLER = 1, // Stack overflow con gestore53 STACK_OVERFLOW = 2, // Stack overflow senza gestore54 HANDLER_STACK_OVERFLOW = 3 // Stack overflow con gestore (a catena)55 } TestScope;56 57 // Variabili globali58 pthread_t checker_thread; // Thread di analisi per la gestione dei segnali59 bool correct_exec = true; // SSE true: esecuzione corretta60 bool signaled = false; // SSE true: handler invocato dal SO61 bool verbose = false; // SSE true: OUTPUT informazioni di debugging62 63 // Entry point64 int main(int argc, char** argv) {65 TestScope detect_test_scope(char testscope);66 void warn_invalid_CLI_argument(bool fatal);67 68 TestScope currentscope = ENTRY_POINT;69 char currentoption = 0;70 71 // Limite di sicurezza sul numero di argomenti72 if(argc > 256) {73 errno = E2BIG;74 perror("CLI - ridurre il numero di parametri");75 exit(EXIT_FAILURE);76 }77 assert(argc < 256); // Controllo difensivo di coerenza78 79 // Parsing argomenti dalla CLI80 while(currentoption != -1) {81 // Parsing argomento successivo82 currentoption = (char) getopt(argc, argv, "vhs:");83 84 // Analisi argomento corrente (appena parsato)85 switch(currentoption) {86 case 'h': // Aiuto87 puts(HELP_TEXT);88 exit(EXIT_SUCCESS);89 case 'v': // OUTPUT loquace90 verbose = true;91 break;92 case 's': // Tipo di test (opzione obbligatoria)93 currentscope = detect_test_scope(optarg[0]);94 if(verbose) printf("Tipo di test: %c (%i)\n", optarg[0],

currentscope);95 if(currentscope == INVALID) warn_invalid_CLI_argument(true);96 else break;97 case -1: break; // Argomenti CLI terminati98 default: // L'utente non ha letto l'help

17

Page 18: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

99 if(verbose) printf("Opzione %c ignota.\n", currentoption);100 warn_invalid_CLI_argument(false);101 }102 } // ENDWHILE103 104 assert(currentscope != INVALID); // Controllo difensivo di correttezza105 assert(currentscope >= ENTRY_POINT); // Controllo difensivo di coerenza106 {107 int set_signal_handler(int signalid, bool inhandler);108 void stack_overflow(void);109 void* check_signaled(void* arg);110 void manage_exit(void);111 112 int setres = 0; // SSE 0: handler accettato dal SO113 bool overflowinhandler = false; // SSE true: stack overflow aggiuntivo114 115 // Creazione processo leggero (runtime in pseudo-parallelismo)116 if(pthread_create(&checker_thread, NULL, check_signaled, NULL)) {117 perror("Impossibile creare il thread di monitoraggio");118 exit(EXIT_FAILURE);119 } else { // Il thread esiste120 atexit(manage_exit); // Assicuriamoci di deallocare i thread121 }122 123 // Gestione dei test124 switch(currentscope) {125 case HANDLER_STACK_OVERFLOW:126 overflowinhandler = true;127 case SIGNAL_HANDLER:128 setres = set_signal_handler(D_SIGNAL, overflowinhandler);129 130 if(setres == 0) { // Handler accettato dal SO131 if(verbose) {132 printf("Handler per il segnale %i impostato.\n", D_SIGNAL);133 }134 } else { // Handler rifiutato dal SO135 printf("Handler per il segnale %i non impostato.\n", D_SIGNAL);136 assert(setres != 2); // Controllo difensivo di coerenza137 138 break;139 }140 case STACK_OVERFLOW:141 puts("Generazione SIGSEGV tramite stack overflow...");142 stack_overflow();143 default: break;144 }145 }146 147 // Processo sopravvissuto ai test148 if(correct_exec == true) {149 if(verbose) puts("Esecuzione terminata correttamente.");150 exit(EXIT_SUCCESS);151 } else {152 if(verbose) puts("Esecuzione terminata con errori.");153 exit(EXIT_FAILURE);154 }155 }156 157 /* Funzione per migliorare la UX tramite aiuti visuali (help).158 * SSE fatal == true: termina processo.159 */

18

Page 19: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

160 void warn_invalid_CLI_argument(bool fatal) {161 errno = EINVAL;162 perror("Usa l'opzione 'h' per ricevere supporto");163 164 if(fatal == false) {165 correct_exec = false;166 return;167 }168 else exit(EXIT_FAILURE);169 }170 171 /* Deduce il tipo di test desiderato dall'utente trasformandolo in una

costante172 * e riducendo il dominio di possibili valori restituiti a main.173 */174 TestScope detect_test_scope(char testscope) {175 switch(testscope) {176 case '1': return SIGNAL_HANDLER;177 case '2': return STACK_OVERFLOW;178 case '3': return HANDLER_STACK_OVERFLOW;179 default: return INVALID;180 }181 }182 183 /* Richiede al sistema di impostare un gestore personalizzato (handler) per184 * l'interruzione "signalid".185 * L'argomento "overflowinhandler" attiva lo stack overflow a cascata.186 */187 int set_signal_handler(int signalid, bool overflowinhandler) {188 void handle_signal(int signum);189 void handle_signal_and_stack_overflow(int signum);190 191 int sigactionresult = 0; // SSE 0: handler accettato dal SO192 193 // Metadati dell'handler194 struct sigaction handler_meta = {195 .sa_flags = 0196 };197 198 // Gestione dei test "estremi"199 if(overflowinhandler == false) handler_meta.sa_handler = handle_signal;200 else handler_meta.sa_handler = handle_signal_and_stack_overflow;201 202 // Assegnazione handler personalizzato203 sigactionresult = sigaction(signalid, &handler_meta, NULL);204 205 if(sigactionresult == 0) return 0;206 else if(sigactionresult == -1) return 1;207 else return 2;208 }209 210 /* Gestisce l'interruzione e poi causa ulteriori errori di segmentazione.

*/211 void handle_signal_and_stack_overflow(int signum) {212 void handle_signal(int signum);213 void stack_overflow(void);214 215 handle_signal(signum);216 stack_overflow();217 }218

19

Page 20: Appunti ragionati di Sistemi Operativi · può essere eseguito tramite IDLE 3 o invocando l’interprete direttamente con il comando di shell “python3 script.py”. 1 # Esempi in

Appunti ragionati di Sistemi Operativi

219 /* Gestisce l'interruzione che attiva il flag globale "signaled".220 * L'invocazione di funzioni non pure è delegata a main tramite il flag.221 */222 void handle_signal(int signum) {223 (void) signum; // Per evitare warning, non la usiamo.224 225 signaled = true;226 227 return;228 }229 230 /* Causa un errore di segmentazione tramite ricorsione indeterminata.231 * In questo caso, SIGSEGV da gestibile diventa non gestibile.232 */233 void stack_overflow(void) {stack_overflow();}234 235 /* Codice del thread di analisi, eseguito in pseudo-parallelismo.236 * Segnala l'attivazione del flag globale "signaled".237 */238 void* check_signaled(void* arg) {239 (void) arg; // Solo per evitare warning (non la usiamo).240 241 assert(signaled == false);242 if(verbose) puts("** Monitoraggio handler in corso... **");243 while(signaled == false) {244 pthread_testcancel(); // Punto di cancellazione artificiale245 }246 puts("** Handler invocato. **");247 248 pthread_exit(EXIT_SUCCESS);249 }250 251 /* Gestisce la terminazione, assicurandosi di terminare i thread:252 * i thread non ancora terminati vengono aggiunti alla coda di

cancellazione;253 * i thread "zombie" o cancellati vengono attesi e terminati.254 */255 void manage_exit(void) {256 if(verbose) puts(">> Terminazione stack_test...");257 258 pthread_cancel(checker_thread); // Cancella i thread ancora in loop259 pthread_join(checker_thread, NULL); // Attendi i thread cancellati260 261 if(verbose) puts(">> Thread di analisi terminato.");262 }

Fine del listato.

Al di là degli esperimenti, uno sviluppatore professionale non dovrebbe mai abusare della ricorsione ed in generale delle risorse del sistema.

Il contenuto di questo listato sarebbe più adatto ad un corso di Programmazione di Sistema ma è stato incluso per completezza, al fne di dimostrare che nessun SO è perfetto. In questo campo lo sviluppo non ha mai termine... ma riserva sempre tante sorprese.

20