Lezione 11 Thread - UNIMORE · 1 Lezione 11 Thread Sistemi Operativi (9 CFU), CdL Informatica, A....

Post on 16-Aug-2020

4 views 0 download

transcript

1

Lezione 11ThreadSistemi Operativi (9 CFU), CdL Informatica, A. A. 2014/2015Dipartimento di Scienze Fisiche, Informatiche e MatematicheUniversità di Modena e Reggio Emiliahttp://weblab.ing.unimo.it/people/andreolini/didattica/sistemi-operativi

2

Quote of the day(http://blog.golang.org/concurrency-is-not-parallelism)

“The speedup gained from running a program on a parallel computer is greatly limited by the fraction of that program that can't be parallelized.”

Gene Amdahl (1922-)Fisico, Computer ArchitectCocreatore degli IBM 704, 790, System/360Ideatore della “Legge di Amdahl”

3

PROBLEMI LEGATI AI PROCESSI

4

Il modello a processo singolo(Un processo Una traccia di codice)↔

Nella sua accezione più classica, il processo è l'astrazione di un programma in esecuzione.

Aree codice, dati, stack.Stato di esecuzione.

Modello a processo singolo: il processo esegue una singola traccia di codice in modalità interlacciata con gli altri processi.

codice dati file

registri stack

Una singolatraccia

5

Problema: bassa reattività(Una singola traccia non può fare più cose contemporaneamente)

Una singola traccia non può fare più cose simultaneamente.

Un Web server non può rispondere a più utenti allostesso istante.Un Web browser non può scaricare una pagina e, allostesso istante, iniziare a disegnarla.

Ciò comporta una riduzione di reattività della applicazione (low responsiveness).

→ Occorre un meccanismo che permetta ad una applicazione di eseguire simultaneamente più tracce.

6

Modello multi-processo(L'applicazione è un insieme di processi distinti)

Nel modello multi-processo, una applicazione è concepita come insieme di processi distinti.I processi possono coordinarsi fra loro in modi diversi.Coordinazione fork: un processo di controllo riceve richieste e crea un figlio per gestire una risposta.Il processo figlio viene terminato dopo la risposta.

Applicazionemulti-processo

fork

richiesta

fork()

risposta

exit()

7

Problema: costo del dispatching(Tanto più marcato all'aumentare del numero richieste al servizio)

Una applicazione multi-processo fork ha, in generale, una reattività migliore rispetto ad una basata su processo singolo. Tuttavia, al crescere delle richieste degli utenti:

il SO spende la maggior parte del tempo in operazioniinterne di gestione (creazione e distruzione processi,cambi di contesto).il SO viene “distolto” dal compito principale, ovverol'esecuzione delle tracce di codice.

Ciò comporta una riduzione di prestazioni dello scheduler dei processi.

→ Occorre un meccanismo che riduca il costo del dispatching.

8

Coordinazione pre-fork(“Pool” di processi riusabili per diverse richieste)

Coordinazione pre-fork: un processo di controllo

crea un insieme di processi (pool)all'inizio della sua esecuzione e lidistrugge tutti alla sua uscita.espande e riduce dinamicamente ladimensione del pool a seconda delcarico di richieste ricevuto.

Una richiesta è servita da un processo del pool.

Applicazionemulti-processo

pre-fork

richiesta

fork()iniziale

risposta

...

Si modificadinamicamente

9

Esempio: Apache ≤v1.3(Pool di processi riusabili per diverse richieste)

Il Web server Apache (fino a v1.3) adotta il modello multi-processo con coordinazione pre-fork.

Pool di processi creati alla partenzadel server.Il pool si adatta esponenzialmenteal carico (raddoppia o si dimezza).Ogni richiesta è passata ad unprocesso del pool.

OpenLogs

PostConfig

ChildInit

Ciclorichiesta

Ciclorichiesta

Ciclorichiesta

Processo dicontrollo

Restart

StartUpConfig

Pool diprocessi

10

Problema: non condivisione dei dati(I processi figli sono isolati; e se volessero aggiornare contatori globali?)

I gestori dei processi e della memoria isolano gli spazi di indirizzamento virtuali. Pertanto due processi non possono (senza ulteriori strumenti) condividere una struttura dati in lettura e scrittura.Gli strumenti di comunicazione fra processi esistono, ma sono più lenti della condivisione della memoria:

segnali, pipe, code di messaggi, memorie condivise,socket, ...

Ciò comporta una riduzione di prestazioni dello scheduler dei processi.

→ Occorre un meccanismo che permetta a basso costo la condivisione di memoria fra processi.

11

RECAP: le limitazioni viste finora(Si possono rimuovere? Che cosa si ottiene?)

Limitazioni viste finora:incapacità di un processo di eseguire più di unatraccia di codice.assenza di un semplice meccanismo di condivisionedella memoria

E se si provasse a rimuovere tali limitazioni?Un processo può eseguire più tracce allo stessoistante. Le tracce eseguono funzioni impostate dalprogrammatore.Le tracce condividono la memoria (codice e dati).

12

MODELLO MULTI-THREADED

13

Il modello multi-threaded(Un processo Più tracce di codice)↔

Modello multi-threaded: il processo esegue più tracce di codice in modalità interlacciata fra loro e con gli altri processi. Le tracce condividono le risorse del processo che le ha create (stack e registri esclusi).Il thread è la rappresentazione interna al SO di una traccia.

codice dati file

registri registri

Tre tracce

registri

stack stackstack

14

Vantaggi(Perché conviene programmare in ambiente multi-threaded)

Reattività.Il blocco di un thread non inficia sugli altri.Maggiori reattività e fluidità di esecuzione.

Condivisione delle risorse.I thread condividono la memoria del processo che liha creati. Si possono avere strutture dati condivise.→

Economia.Si allocano e distruggono le risorse di processo unasola volta.

Scalabilità.I thread eseguono in parallelo su più CPU, a differenzadi un singolo processo.

15

Svantaggi(Perché NON conviene programmare in ambiente multi-threaded)

Paradigma di programmazione complesso.La condivisione delle risorse e della memoria implicaspesso la gestione della concorrenza degli accessi aqueste ultime.Sono necessari ulteriori passi di programmazione, spessocomplessi.

Debugging complesso.È molto difficile riprodurre bug in presenza di accessiconcorrenti alle risorse.È spesso difficile individuare le cause del bug.

16

Sounds difficult, eh?(It is!)

17

Supporto del SO ai thread(User level e/o kernel level)

La rappresentazione dei thread fornita dal SO può essere implementata in tre modi diversi.

Interamente in user space, senza coinvolgimentoalcuno del kernel.Interamente in kernel space, senza altrocoinvolgimento delle librerie che non sia legato allaimplementazione delle relative chiamate di sistema.In maniera ibrida (sia in user space, sia in kernelspace).

18

Modello da molti a uno(Many-to-one: tutto in user space; tanti thread utente un thread nel kernel)→

Nel modello da molti a uno (many-to-one, N:1) la rappresentazione del thread avviene interamente in user space.

Il kernel gestisce una sola struttura dati (processo).Il processo usa delle funzionalità applicative persimulare uno scheduler di processi leggeri.I processi leggeri eseguono singole funzioni definitenel processo generante e non sono noti al kernel.

Implementazioni: Green threads (Solaris), GNU Portable Threads, Java Threads (vecchie release), Ruby Threads.

19

Modello da molti a uno(Uno schema)

User space

Kernel space

Processomain()

f1() f2() f3()

Libreriathread

create()stop()schedule()resume()destroy()

Thread

PCB

20

Modello da molti a uno: vantaggi(Perché conviene implementare il modello da molti a uno)

I thread sono gestiti nel modo più efficiente possibile.

Scheduling cooperativo: un thread decide di passarela palla ad un altro. Non viene coinvolto lo schedulerdel kernel.

→ Nessun aggravio computazionale legato ai cambi dicontesto tradizionali.

Il modello da molti a uno non richiede un kernel multithreaded per poter essere implementato.

Non è più un vero vantaggio. I kernel moderni hannotutti la nozione di “thread”.

21

Modello da molti a uno: svantaggi(Perché NON conviene implementare il modello da molti a uno)

Se un thread effettua una chiamata bloccante, è l'intero processo a bloccarsi, e non solo il thread in questione.

Modello pessimo in caso di applicazioni con I/O.I thread sono legati allo stesso processo, e non possono eseguire su processori fisici distinti.

Modello pessimo per la scalabilità su più CPU.

22

Modello da uno a uno(One-to-one: tutto in kernel space; un thread utente un thread nel kernel)→

Nel modello da uno a uno (one-to-one, 1:1) la rappresentazione del thread avviene interamente in kernel space.

Il kernel gestisce un descrittore per ciascun thread,detto Thread Control Block (TCB). In alcuni casi, PCB eTCB sono rappresentati dalla stessa struttura dati.Il processo crea thread attraverso una duplicazionesimile a fork().I thread vengono schedulati dal kernel come processiqualunque.

Implementazioni: GNU/Linux (LinuxThreads, NPTL), Windows 95/98/2000/XP/NT.

23

Modello da uno a uno(Uno schema)

User space

Kernel space

Processo

main()

f()

PCB

create()destroy()

Thread

f()

TCB

Thread

f()

TCB

24

Modello da uno a uno: vantaggi(Perché conviene implementare il modello da uno a uno)

Se un thread effettua una chiamata bloccante, non blocca gli altri thread.

I thread possono implementare funzionalità di I/Osenza stallare l'intera applicazione.

I thread sono rappresentati da altrettanti TCB, e possono essere eseguiti su processori fisici distinti.

Un'applicazione può scalare con il numero di CPU.

25

Modello da uno a uno: svantaggi(Perché NON conviene implementare il modello da uno a uno)

I thread sono gestiti dallo scheduler dei processi del kernel.

→ Si paga un (piccolo) costo computazionale ad ognicambio di contesto.

Il modello da uno a uno richiede un kernel multithreaded per poter essere implementato.

Non è più un vero svantaggio. I kernel moderni hannotutti la nozione di “thread”.

26

Modello da molti a molti(Many-to-many: un ibrido dei due modelli precedenti)

Nel modello da molti a molti (many-to-many, N:M) la rappresentazione del thread avviene sia in user space, sia in kernel space.

Unione dei due precedenti modelli. L'idea è quella diprendere il meglio da ciascun modello.Il processo crea M thread a livello kernel attraversouna duplicazione.Ciascuno di questi M thread crea N/M threada livello utente.

Implementazioni: IRIX, HP-UX, Tru64 UNIX, Solaris (fino a v9), Windows 7.

27

Modello da molti a molti(Uno schema)

User space

Kernel space

Processomain()

f1() f2() f3()

Libreriathread

PCB

Threadmain()

f1() f2() f3()

Libreriathread

TCB

Threaduser level

Threadkernel level

create()

28

Architetture multi-threaded(Come si possono combinare i thread fra loro?)

Pipeline.I thread eseguono come una linea di assemblaggio.L'output del thread precedente viene dato in pasto althread successivo.

Master-Slave.Un thread “master” coordina l'esecuzione di tantithread “slave” (che effettuano il lavoro vero e proprio).

Worker.Ciascun thread esegue un compito specificodell'applicazione indipendentemente dagli altri.

29

Architetture multi-threaded(Come si possono combinare i thread fra loro?)

Pipeline

Master-Slave

Worker

processo threadinput outputthread thread

processo

thread thread thread

input output

processo

threadinput output

threadinput output

threadinput output

threadinput output

30

Esempio: Apache ≥v2.0(Pool di thread riusabili per diverse richieste)

A partire dalla versione v2.0, il Web server Apache adotta il modello multi-threaded con coordinazione pre-fork o worker.

Pool di thread creati alla partenzadel server.Il pool si adatta esponenzialmenteal carico (raddoppia o si dimezza).Coordinazione pre-fork: un threadsvolge tutte le operazioni.Coordinazione worker: le operazionisono suddivise fra thread diversi.

OpenLogs

PostConfig

ChildInit

Ciclorichiesta

Ciclorichiesta

Ciclorichiesta

Processo dicontrollo

Restart

StartUpConfig

Pool dithread

31

LA LIBRERIA PTHREADS

32

La libreria Pthreads(La libreria che permette di creare e gestire thread)

In GNU/Linux, i thread sono gestiti tramite la libreria Pthreads (parte integrante della libreria del C).Standard ANSI/IEEE POSIX 1003.1c-1995.

Gestione thread: creazione, distruzione.Gestione sincronizzazione: attesa terminazionethread, mutua esclusione, eventi (variabili condizione).

Più di 60 funzioni per gestire thread tramite tipi di dato opachi (ad es., pthread_t).Concepita per i linguaggi C e C++.

33

Il primo programma multi-threaded(Lo costruiremo passo passo, usando la libreria Pthreads)

Il modo più semplice per illustrare la basi della libreria Pthreads è quello di costruire un primo, semplice programma di esempio.Il programma:

stampa un messaggio di benvenuto.genera due thread che stampano un messaggio dibenvenuto.

34

Uso della libreria(Quali file vanno inclusi? Come va compilato un programma multi-threaded?)

Per usare le funzioni della libreria è necessario:includere il file <pthread.h>.compilare il programma con l'opzione -pthread.gcc -pthread -o programma programma.c

Altrimenti, il compilatore non trova le definizioni delle funzioni ed esce con il classico messaggio di errore “undefined reference to ...”.

35

Costruzione di uno scheletro(Un sorgente che include <pthread.h> e definisce un main() vuoto)

Si editi un programma sorgente “scheletro” di nome pthread.c che include <pthread.h> e definisce main().#include <pthread.h>

int main(int argc, char *argv[]) {return 0;

}Si compili il programma:gcc -pthread -o pthread pthread.c

36

Rappresentazione di un thread(Diretta conseguenza del modello 1:1)

Pthreads adotta il modello di multi-threading 1:1.Nel kernel, processi e thread sono rappresentati da task_struct. Il descrittore è unificato.

Un thread una → task_struct.Un processo una → task_struct.

Nella libreria Pthreads, un thread è rappresentato dal tipo di dato opaco pthread_t. Investigando ulteriormente, si scopre che pthread_t è un unsigned long int.

grep -nrHiE 'typedef\s+.*\s+pthread_t' /usr/include → File /usr/include/bits/pthreadtypes.h

37

Thread Identifier(TID, analogo del PID per i thread)

L'intero lungo senza segno in questione è il Thread Identifier (TID), ovvero l'identificatore univoco di un thread.Quando viene creato un nuovo thread, si salva il TID nella variabile corrispondente di tipo pthread_t .Un TID è analogo ad un PID. Un TID identifica in maniera univoca una task_struct di un thread nel kernel.

38

Definizione delle variabili pthread_t(Nel programma di esempio pthread.c)

Si definiscano due variabili di tipo pthread_t nel programma di esempio pthread.c.int main(int argc, char *argv[]) {

pthread_t t1, t2;

return 0;}Si compili il programma:gcc -pthread -o pthread pthread.c

39

Creazione di thread(pthread_create())

La funzione di libreria pthread_create() crea un thread.

$GLIBC/nptl/pthread_create.c.Una volta creato, il thread eseguirà concorrentemente con il processo/thread che l'ha creato.Il thread esegue una funzione impostata dall'utente.

man pthread_createOCCHIO: in Debian dovete installare il pacchetto binariomanpages-dev per avere le pagine di manuale dellefunzioni di libreria e delle chiamate di sistema.

40

Parametri di ingresso(pthread_t, pthread_attr_t, funzione da eseguire, argomento)

Il primo parametro passato in ingresso a pthread_create() è un puntatore alla variabile pthread_t che rappresenta il thread.Tale puntatore sarà utilizzato per scrivere il TID al termine della creazione.

41

Parametri di ingresso(pthread_t, pthread_attr_t, funzione da eseguire, argomento)

Il secondo parametro passato in ingresso a pthread_create() è un puntatore alla struttura pthread_attr_t che rappresenta alcune proprietà aggiuntive del thread.

Ad es., classe di scheduling (RR, PRIO, NORMAL).man pthread_attr_init per tutti i dettagli.

In questa introduzione non si useranno proprietà aggiuntive dei thread. Pertanto il secondo parametro si imposta al valore NULL.

42

Parametri di ingresso(pthread_t, pthread_attr_t, funzione da eseguire, argomento)

Il terzo parametro passato in ingresso a pthread_create() è un puntatore alla funzione generica che deve essere eseguita:

void *(*start_routine)(void *).

43

Perché void *?(Modello generico di funzione; il type casting traduce void nel tipo desiderato)

L'argomento ed il valore di ritorno sono puntatori generici, di tipo void.In tal modo nella funzione eseguita dal thread è possibile:

tramite cast, tradurre il puntatore generico in unpuntatore ad una struttura dati (che contiene tutti iparametri richiesti).ritornare un puntatore generico il quale, tramite cast,sarà convertito in un puntatore ad una struttura dati(che contiene tutti i risultati prodotti).

44

Parametri di ingresso(pthread_t, pthread_attr_t, funzione da eseguire, argomento)

Il quarto parametro passato in ingresso a pthread_create() è un argomento generico per la funzione:

void *arg.

45

Valore di ritorno di pthread_create()(Un intero)

Il valore di ritorno di pthread_create() è un intero.

0: tutto OK. Il TID del thread appena creato è salvatoall'interno di pthread_t.!=0: si è verificato un errore (tipicamente, le risorse delsistema sono insufficienti per la creazione di unthread).

46

Creazione delle funzioni di lavoro(Nel programma di esempio pthread.c)

Si definiscano due funzioni print_t1() e print_t2() nel programma pthread.c.#include <stdio.h>

void *print_t1(void *arg) {printf(“Hello from thread 1!\n”);

}

void *print_t2(void *arg) {printf(“Hello from thread 2!\n”);

}

Si compili il programma:gcc -pthread -o pthread pthread.c

47

Lancio dei thread(Nel programma di esempio pthread.c)

Si invochino le pthread_create() necessarie per l'esecuzione dei due thread.int main(int argc, char *argv[]) {

...pthread_create(&t1, NULL, print_t1, NULL);pthread_create(&t2, NULL, print_t2, NULL);

}

Si compili il programma:gcc -pthread -o pthread pthread.c

48

Esercizi (5 min.)

1. Si compili pthread.c (se non lo si è ancora fatto) e si esegua pthread. Probabilmente pthread esce senza stampare nulla.

Che cosa è successo?

50

Orrore!(Il programma non funziona se non si rallenta main()!)

51

Come ritardare l'uscita di main()?(Beh, non dovrebbe essere poi tanto difficile...)

È possibile ritardare l'uscita di main() facendo svolgere ad essa ulteriore lavoro.

Invocazione di funzioni applicative o di sistema.Introduzione di sleep().

OCCHIO: la sincronizzazione manuale (ad es. tramite sleep()) va calibrata caso per caso e non è detto che funzioni sempre!Nelle prossime lezioni capiremo come sincronizzare per bene processi e thread.

52

Terminazione di un thread(pthread_exit())

La funzione di libreria pthread_exit() permette l'uscita pulita da un thread.Essa riceve in ingresso un puntatore ad una variabile intera in cui sarà salvato lo stato di uscita del thread. Se non si desidera collezionare lo stato di uscita, si passa l'argomento NULL.

$GLIBC/nptl/pthread_exit.c.Sono distrutte le sole risorse relative al thread (stack locale, variabili locali).L'ultimo thread che esce invoca anche exit() che libera le risorse condivise (codice, memoria globale).

53

Terminazione dei thread(Nel programma di esempio pthread.c)

Si inseriscano invocazioni di pthread_exit() nei seguenti punti strategici di pthread.c:

alla fine di print_t1() e print_t2().alla fine di main().

In questa esercitazione non ci interessa il codice di uscita del thread. Pertanto, si passa il valore NULL alla pthread_exit().Si compili il programma:gcc -pthread -o pthread pthread.c

54

Esercizi (5 min.)

2. Si esegua nuovamente pthread. Si controlli se pthread riesce a stampare qualcosa.

Se pthread non stampa ancora nulla, si inserisca una sleep() nel main().

56

L'esempio completo(È nell'archivio degli esempi)

Il sorgente pthread.c completo è contenuto nell'archivio 11-esempi.tar.bz2.

57

Un problema da risolvere(Duplication of code)

Il programma di esempio ora visto presenta un evidente difetto: le due funzioni eseguite dai thread print_t1() e print_t2() sono identiche.

→ Duplication of code! (Da evitare come la peste).Come si risolve questo problema?

58

Rifattorizzazione di pthread(http://en.wikipedia.org/wiki/Code_refactoring)

Si crea un array globale contenente le due stringhe stampate dai thread.Si definisce una unica funzione print_t() che riceve come parametro un indice dell'array e stampa la stringa corrispondente.Si invoca la pthread_create() passando come quarto argomento l'indice dell'array.

59

Esercizi (15 min.)

3. Si implementi la rifattorizzazione del sorgente pthread.c illustrata nella slide precedente.

61

Un altro programma multi-threaded(Un programma amichevole e multi-threaded)

Il sorgente hello_world.c nell'archivio11-esempi.tar.bz2 è un esempio lievemente più complesso di creazione di thread.Il programma stampa un messaggio di benvenuto in otto lingue diverse, facendo uso di otto thread separati.Lo si compili e lo si lanci. Si noti l'assoluta mancanza di coordinazione con cui il primo thread e gli altri stampano i loro messaggi.

62

DIFFERENZE PROCESSI ↔ THREAD:CONDIVISIONE DELLA MEMORIA

63

Esercizi (2 min.)

4. All'interno della funzione print_hello() di hello_world.c si usi exit() al posto di pthread_exit().

Che cosa succede?

65

Thread ed exit()(Perché non si può accoppare un thread con exit()?)

Attenzione!Mai usare la exit() per uscire da un thread. Usare sempre la funzione di libreria pthread_exit().In generale, si cerchi di evitare del tutto l'uso di exit(). La si usi solo in caso di uscita forzata ed immediata (errore irrecuperabile).Nella lezione sulla sincronizzazione indagheremo meglio sul cosiddetto “codice di uscita” del thread.

66

Thread ed execve()(Problema simile a quello introdotto da exit())

Per lo stesso motivo ora visto, è sconsigliabile (anche se non vietato formalmente) eseguire la chiamata di sistema execve() (e simili) all'interno di un thread.

→ execve() provoca il cambio immediato di spazio di indirizzamento in tutti i thread.Si consideri a tal proposito il sorgente di esempio pthread_execve.c contenuto nell'archivio 11-esempi.tar.bz2.

Due thread; uno dorme, l'altro esegue ls -l.

67

Dati specifici dei thread(Thread Local Data)

Nei thread:le variabili globali sono condivise.le variabili automatiche non sono condivise.

È possibile definire anche variabili globali specifiche al thread (Thread Local Data).Tali variabili si comportano come se fossero globali, ma non sono condivise fra thread.Nella libreria Pthreads si usa lo specificatore __thread.

__thread int variable;

68

Un esempio di variabili locali ai thread(thread_local.c)

Il sorgente pthread_local.c in11-esempi.tar.bz2 illustra un esempio di uso di variabili locali ai thread.Si lanciano 2 thread, ciascuno dei quali esegue una operazione su una variabile globale e locale per thread. Infine, si stampano i contatori globali e locali.Come si può vedere, i valori stampati possono differire a seconda del thread considerato.

69

Una pericolosa ottimizzazione(“Premature optimization is the root of all evil”)

Osservando il sorgente di hello_world.c potrebbe venire in mente di:

rendere t una variabile globale.usare l'indirizzo di t come argomento della funzioneprint_hello().

Il sorgente hello_world_wrong.c contenuto nell'archivio 11-esempi.tar.bz2 contiene tale modifica.

70

Esercizi (5 min.)

5. Si compili hello_world_wrong.c e lo si esegua. Si cerchi di spiegare cosa è successo.

72

Uso di contatori globali(Caveat emptor!)

Attenzione!Mai usare variabili globali (tipicamente, contatori) come argomento dei thread, a meno che non si sappia esattamente che cosa si stia facendo.In generale, non assumete che ciascun thread ottenga un valore diverso del contatore.

73

Accesso condiviso ai dati(“Mutua esclusione”)

Finora non è stato accennato nulla riguardo all'accesso a variabili condivise.Cosa succede se due thread distinti leggono e scrivono la stessa variabile condivisa?Tale problema sarà trattato nel prossimo blocco di lezioni riguardanti la sincronizzazione fra processi.

74

DIFFERENZE PROCESSI ↔ THREAD:THREAD E PROCESS IDENTIFIER

75

Gruppo di thread, leader del gruppo(Simile al gruppo di processi ed al leader del gruppo)

Il thread creante ed i thread da esso creati formano un gruppo di thread (thread group).A tale gruppo è associato un numero intero detto identificatore del gruppo di thread (thread group identifier, TGID).Il TGID è il TID del thread che ha generato per primo altri thread (anche detto processo leader o thread group leader).

76

TID e PID(Così simili, eppure così diversi...)

Un singolo thread:può ottenere il proprio TID tramite la chiamata disistema gettid().può ottenere il PID del processo leader tramite lachiamata di sistema getpid().

OCCHIO: gettid() non ha un wrapper glibc e va eseguita direttamente tramite syscall().

77

Rappresentazione di PID, TID, TGID(Gruppo Processo leader con i suoi thread, PID processo, TID thread)→ → →

PPID=1900

TGID=2001

TID=2001

ProcessoPPID=1900

TGID=2001

TID=2002

Thread APPID=1900

TGID=2001

TID=2003

Thread BPPID=1900

TGID=2001

TID=2004

Thread C

Gruppo con TGID=2001

78

Un piccolo esempio(Ma significativo)

Il programma di esempio getids.c nell'archivio 11-esempi.tar.bz2 illustra le differenze fra TID e PID.Sono lanciati due thread che stampano TID e PID insieme al thread principale.Per il thread principale: TID = PID.Per i thread generati: TID ≠ PID.

79

Individuazione dell'ID dei thread(Non così semplice come può sembrare a prima vista)

I comandi esterni pidof e pgrep di default funzionano con i processi, e non con i thread!Piccolo esperimento: si facciano partire diversi terminali grafici.

GNOME: gnome-terminal.KDE: konsole.XFCE4: xfce4-terminal.

Si stampino I PID di tutte le istanze dei terminali.pgrep gnome-terminal

Si ottiene un solo PID. Perché?

80

Motivo del fallimento(pgrep e pidof ritornano PID)

I comandi esterni pidof e pgrep ritornano di default un PID.

Il PID del singolo processo.Il PID del processo che ha generato i thread (ovvero,il Thread Group ID, ovvero il TID del Thread GroupLeader).

Pertanto, occorre imparare ad identificare ed a stampare anche I diversi TID.Come fare?

81

Modalità “visualizzazione thread”(In pidof? In pgrep?)

Domanda: pidof e pgrep hanno una modalità di visualizzazione dei thread?Si provi a spulciare il manuale:

man pidofman pgrep

Si cerchino istanze della parola chiave “thread”.In pidof non ve ne è traccia nessun supporto.→In pgrep esiste l'opzione -w che stampa i TID!

82

Esercizi (1 min.)

6. Si chiudano tutti gli emulatori di terminale aperti. Se ne faccia partire uno nuovo e si aprano due tab. Si individui il numero di thread lanciati dall'emulatore di terminale.

84

Visualizzazione dell'albero dei processi(ASCII-art)

Il comando esterno pstree stampa una rappresentazione compatta dell'albero dei processi.

pstree | less -Mr

systemd-+-NetworkManager---3*[{NetworkManager}] |-2*[dbus-daemon] |...

Il processopadre

Due processifigli

Tre threadfigli

systemd↔

initLe parentesi graffeindicano un thread

85

Esercizi (2 min.)

7. Si faccia stampare a pstree la gerarchia dei processi generata dal proprio emulatore di terminale. Si evidenzino eventuali thread.

87

Visualizzazione dei thread con ps(System V beats BSD big time)

Il comando esterno ps permette di elencare tutti i thread fatti partire da un dato processo e di mostrare il consumo di alcune risorse (ad es., il tempo di esecuzione).Opzioni in stile SysV (le mie preferite in questo caso):

ps -eLF-e: seleziona tutti i processi-L: include i thread nell'elenco. Stampa i TID (campoLWP) ed il numero di thread generati (campo NLWP). -F: extra full listing (stampa diverse informazioni suithread e sui processi, incluso il tempo di esecuzione).

88

Esercizi (1 min.)

8. Si visualizzi l'albero dei processi e dei thread con il comando ps. Si evidenzi l'albero di processi e thread generato da un terminale. Si individui il tempo di esecuzione sulla CPU dei relativi thread.

90

Limitazioni legate ai thread(La visualizzazione per thread, se esiste, va abilitata esplicitamente)

Purtroppo il comando esterno /usr/bin/time non è pensato per la misurazione del consumo di risorse di singoli thread./usr/bin/time misura il consumo cumulativo di risorse del processo e di tutti i suoi thread.I comandi esterni ps, top, pidstat non mostrano, di default, informazioni relative ai thread.

91

Misurazione dei thread con top(Un po' farraginosa)

Il comando esterno top permette di elencare interattivamente i thread.Si lancia top e si attiva la visione per singoli thread premendo il tasto H.Si noti come il consumo di risorse di memoria sia omogeneo fra i thread.

→ La memoria è condivisa.Al contrario, il tempo di esecuzione accumulato sulla CPU differisce in genere.

92

Esercizi (5 min.)

9. Si lanci il comando chromium (Web browser). Si visualizzi l'albero dei processi e dei thread con il comando top. Si abiliti la visualizzazione a foresta (tasto V). Si navighi l'albero dei processi con il cursore.

Come è organizzato chromium?

94

L'architettura di Chromium(Ecco perché un crash di un tab non crasha l'intera applicazione!)

Chrome adotta un modello misto multi-processo e multi-threaded con worker.Una finestra un processo.→

Se crasha il processo, crasha la sola finestra mentre lealtre rimangono attive!

Il processo lancia tanti thread di tipo worker.Rendering.Download della pagina e delle immagini.Gestione dell'I/O (tastiera, disco).Gestione della cache....

95

Misurazione dei thread con pidstat(Più raffinata di top)

Il comando esterno pidstat permette di misurare il consumo di risorse dei singoli thread.Si lancia pidstat con le solite opzioni e si aggiunge l'opzione -t per la visualizzazione del consumo di risorse per thread.pidstat -u -t -p $(pgrep -n gnome-terminal) 1Sono presenti i campi seguenti:

TGID: Thread Group ID (PID del processo creante)TID: Thread ID

96

Il valore del campo “Command”(Processo nome eseguibile; Thread nome eseguibile o altro, configurabile)→ →

Il campo Command fornisce in generale il nome del file eseguibile del processo (privo di percorso).Se volete il percorso, aggiungete l'opzione -l.Per i thread, tale campo può essere modificato arbitrariamente, ad esempio per spiegare che cosa fa la funzione eseguita.

man 5 proc, si cerchi la parola chiave cmdline esi legga la descrizione di /proc/[pid]/comm percapire come.

97

Esercizi (5 min.)

10. Si navighi su una pagina Web con chromium e si analizzi il suo consumo di risorse.

99

DIFFERENZE PROCESSI ↔ THREAD:IL MECCANISMO DI CLONAZIONE

100

Una doverosa premessa(Integrazione dei concetti presentati nella lezione 9)

Nella lezione 9 sui processi è stato presentato in modo sommario il meccanismo di riproduzione di un processo tramite la chiamata di sistema fork().Dopo aver presentato le principali problematiche legate ai thread, è tempo di presentare la “verità vera”.

101

“What generation are you?”(“Nexus six” )

102

Duplicazione di task(Con e senza condivisione di risorse)

Nel kernel Linux, processi e thread sono identici; per tale motivo vengono chiamati task (usando la terminologia Intel).Il kernel mette a disposizione un meccanismo generale di clonazione di un task con copia o condivisione delle risorse (memoria, file aperti, …).

Task di tipo “processo” copia di risorse.→Task di tipo “thread” condivisione di risorse.→

103

La chiamata di sistema clone()(Implementa il meccanismo generale di duplicazione dei task)

Il kernel offre la chiamata di sistema clone() che implementa il meccanismo generico di duplicazione di task.La libreria del C espone alle altre librerie e alle applicazioni un wrapper clone() che invoca la chiamata di sistema clone().OCCHIO: il wrapper richiede alcuni parametri, la chiamata di sistema ne richiede altri.$GLIBC/sysdeps/unix/sysv/linux/x86_64/clone.S

104

fork() via clone()(Come si creano i processi in GNU/Linux)

La chiamata di sistema fork() è implementata tramite il wrapper clone().La fork() è definita qui:$GLIBC/nptl/sysdeps/unix/sysv/linux/fork.cfork() invoca la macro ARCH_FORK che effettua la duplicazione vera e propria:$GLIBC/nptl/sysdeps/unix/sysv/linux/x86_64/fork.c

ARCH_FORK invoca la chiamata di sistema clone().

105

pthread_create() via clone() - 1/2(Come si creano i thread in GNU/Linux)

La funzione di libreria pthread_create() è implementata tramite il wrapper clone(). La pthread_create() è definita qui:$GLIBC/nptl/pthread_create.cpthread_create() usa create_thread() che effettua la duplicazione vera e propria:$GLIBC/nptl/sysdeps/pthread/createthread.c

106

pthread_create() via clone() - 2/2(Come si creano i thread in GNU/Linux)

create_thread() invoca la do_clone(), definita nello stesso file.do_clone() invoca la macro ARCH_CLONE, definita in:$GLIBC/nptl/sysdeps/ptzhread/createthread.ccome un alias del simbolo __clone.La funzione __clone() è la definizione della chiamata di sistema clone() vista due slide fa.

107

La funzione di servizio sys_clone()(Invoca i meccanismi di basso livello per la clonazione di un task)

La funzione di servizio sys_clone() è definita in: $LINUX/kernel/fork.cEssa chiama direttamente la do_fork() che svolge il lavoro vero e proprio di clonazione attraverso la funzione di lavoro copy_process().

Creazione e inizializzazione di una task_struct.Copia o clonazione delle risorse (funzionicopy_XYZ(), ad es. copy_files()).

108

Valori dei clone flag per fork() e clone()(Molto diversi fra loro)

Chiamata di sistema fork().Clone flag: SIGCHLD.Nessuna condivisione, segnale SIGCHLD inviato alpadre quando il figlio muore.

Chiamata di sistema clone().Clone flag (i principali; quelli minori sono omessi):CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNALCompleta condivisione di memoria, gestori segnali edinformazione su file e file system.Nessun segnale è inviato al padre quando il threadmuore.