Concetti di processo e di thread. Caratteristiche principali dei descrittori di un processo e di un thread. Richiami di architetture di Sistemi Operativi
Concetto di Processo
In questa sezione si riprendono alcuni argomenti che dovrebbero essere già noti, dal corso di Sistemi Operativi. Un Sistema Operativo1 esegue una varietà di programmi. Più precisamente un programma in esecuzione si chiama Processo. Un processo è caratterizzato da:
Codice
Dati
Flusso di controllo
Più precisamente le componenti di un Processo sono:
Codice del programma (text o code section)
Dati del programma
Variabili globali in memoria centrale (data section)
Variabili locali e non locali (stack)
Variabili temporanee generate dal compilatore
(registri del processore)
Variabili allocate dinamicamente (heap)
anche se l’allocazione e la de-allocazione della memoria all’interno di questo segmento sono a carico del programmatore
Stato di evoluzione della computazione
Program counter
Valore delle variabili
Informazioni di gestione del contesto della chiamata di procedure
(indirizzo di ritorno, frame pointer, stack pointer)
Si noti che un Processo non è un Programma:
Un Programma è un’entità passiva e corrisponde ad una lista di istruzioni che implementa un dato algoritmo in un dato linguaggio.
Un Processo è un’entità attiva con valori delle variabili e risorse in uso.
Anche se due processi possono essere associati allo stesso programma, essi sono due differenti istanze di esecuzione dello stesso codice.
Lo stato di un Processo
È lo stato di uso del processore da parte di un processo
Possibili stati
Nuovo (new): Il processo è stato creato
In esecuzione (running): le istruzioni vengono eseguite
In attesa (waiting): il processo sta aspettando il verificarsi di qualche
evento
Pronto all’esecuzione (ready): il processo è in attesa di essere
assegnato ad un processore
Terminato (terminated): il processo ha terminato l’esecuzione
Diagramma degli stati di un processo
Supporti per la gestione dei processi - PCB
Elementi presenti nel PCB: Struttura dati del kernel che mantiene le informazioni sul processo Stato del processo Identificatore del processo (Numero) Program counter Registri della CPU Info per la gestione della memoria centrale (limiti di memoria) Info sullo stato dell’I/O (ad es.: file aperti) Info per la schedulazione della CPU, etc.
Le code di schedulazione dei processi
I processi vengono gestiti mediante code:
Coda di lavori (job queue) – contiene tutti i processi nel sistema
Coda dei processi pronti (ready queue) – contiene tutti i processi che risiedono nella memoria centrale, pronti e in attesa di esecuzione
Coda della periferica di I/O (device queues) – contiene i processi in attesa di una particolare periferica di I/O
Il processo si muove fra le varie code
Esistono vari tipi di schedulatori:
Schedulatore a lungo termine (long-term scheduler) – seleziona quale processo (attualmente in memoria di massa) deve essere inserito nella coda dei processi pronti
Controlla il grado di multi-programmazione
Richiamato solo quando un processo abbandona il sistema (termina)
Schedulatore a breve termine (Short-term scheduler) – seleziona quale processo (in memoria) deve essere eseguito e alloca la CPU ad esso
Schedulatore a medio termine (Medium-term scheduler) – SWAPPING rimuove un processo dalla memoria centrale e lo pone in memoria di massa
Supportato solo da alcuni Sistemi Operativi, come i sistemi basati su time-sharing
Nella figura seguente viene mostrato il diagramma delle code con l’aggiunta dello schedulatore a
medio termine
Rimuove processi dalla memoria centrale: swapping out
Reintroduce in memoria centrale i processi: swapping in
Lo schedulatore a breve termine è eseguito molto frequentemente (millisecondi) ⇒ (deve essere veloce)
Lo schedulatore a lungo termine è eseguito molto meno frequentemente (secondi, minuti) ⇒ (può essere lento)
In alcuni SO può essere assente (ad es. in sistemi time-sharing,come Unix e MSWindows, il grado di multi-programmazione è regolato dallo schedulatore a medio termine)
Ci si limita a caricare tutti i nuovi processi in memoria
Context switch
E’ il passaggio della CPU da un processo ad un altro
Context Switch = sospensione del processo in esecuzione + caricamento del nuovo processo da mettere in esecuzione
Il dispatcher è il modulo che passa effettivamente il controllo della CPU ai processi scelti dallo scheduler a breve termine
Il tempo per il cambio di contesto (latenza di dispatch) è puro tempo di gestione del sistema, poiché durante il cambio non vengono compiute operazioni utili per la computazione dei processi
I tempi per i cambi di contesto dipendono sensibilmente dal supporto hardware: tipicamente sono inferiori ai 10 millisecondi
Creazione di un processo
Il processo in esecuzione invoca una chiamata di sistema che crea e attiva un nuovo processo. Ad esempio, in SO Unix-like la chiamata è la fork()
Processo generante ⇒ processo padre
Processo generato ⇒ processo figlio
Il processo padre crea i processi figli, i quali a loro volta creano altri processi, formando un albero di processi
Risorse dei processi
Un nuovo processo può ottenere risorse in diversi modi:
Condivise col padre
Parzialmente condivise col padre
Indipendenti dal padre (ottenute dal sistema)
Limitare le risorse del figlio ad un sottoinsieme di quelle del padre aiuta ad evitare che un processo crei troppi figli
Inoltre, alla creazione, un processo figlio riceve dal padre i dati di inizializzazione
Parametri utili per l’esecuzione del processo
Ad esempio, un processo che permette di visualizzare un’immagine riceverà dal padre il percorso del file
Spazio di indirizzamento
Lo spazio di indirizzamento (memoria) del processo figlio è sempre distinto da quello del processo padre. Ci sono due possibili scenari:
Il figlio è un duplicato del padre
stesso programma
stessi dati all’atto della creazione
Il figlio ha un nuovo programma caricato nel proprio spazio di indirizzamento
In Unix la chiamata fork() crea un duplicato del padre
Lo spazio degli indirizzi può essere successivamente sovrascritto
Esecuzione dei processi
Due scenari possibili:
Il padre continua l’esecuzione in modo concorrente ai figli - modalità asincrona
Il padre attende finché tutti (o alcuni) i suoi figli sono terminati - modalità sincrona In entrambi i casi (sincrono e asincrono) viene invocata la funzione fork(), tuttavia, nel caso di modalità sincrona, la chiamata di sistema exec, usata dopo la fork, carica un nuovo programma nello spazio di memoria del processo che la esegue2.
Concetto di Thread
Anche chiamati lightweight process perché possiedono un contesto più snello rispetto ai processi.
Flusso di esecuzione indipendente
interno ad un processo
condivide lo spazio di indirizzamento con gli altri thread del processo
Rappresentato da un thread control block (TCB) che punta al PCB del processo contenitore
Esempio di applicazione multi-thread: programma di elaborazione
dei testi:
Thread per l’input da tastiera
Thread per la rappresentazione del testo
Thread per la correzione ortografica
Un thread3 è l’unità di base in cui un sistema operativo alloca il tempo del processore. Ogni thread ha una priorità di pianificazione e un insieme di strutture usate dal sistema per salvare il contesto del thread quando viene sospesa l’esecuzione del thread. Nel contesto del thread sono presenti tutte le informazioni necessarie per riprendere senza problemi l’esecuzione, incluso il set di registri della CPU del thread e lo stack. Nel contesto di un processo possono essere eseguiti più thread. Tutti i thread di un processo ne condividono lo spazio degli indirizzi virtuali. Un thread può eseguire qualsiasi parte del codice del programma, comprese le parti attualmente eseguite da un altro thread. Per impostazione predefinita, viene avviato un programma .NET con un thread singolo, spesso chiamato thread primario. Tuttavia, è possibile creare thread aggiuntivi per eseguire il codice in parallelo o contemporaneamente al thread primario. Questi thread sono spesso chiamati thread di lavoro. L’uso di più thread consente di aumentare la velocità di risposta dell’applicazione e di usare un sistema multiprocessore o multicore per aumentare la velocità effettiva dell’applicazione. Si consideri un’applicazione desktop in cui il thread primario è responsabile degli elementi dell’interfaccia utente e risponde alle azioni dell’utente. Usare i thread di lavoro per eseguire operazioni che richiedono molto tempo e che altrimenti occuperebbero il thread primario e impedirebbero all’interfaccia utente di rispondere. È anche possibile usare un thread dedicato per fare in modo che la comunicazione di rete o del dispositivo sia maggiormente reattiva ai messaggi in ingresso o agli eventi.
Se il programma esegue operazioni che possono essere eseguite in parallelo, il tempo di esecuzione totale può essere ridotto eseguendo queste operazioni in thread separati ed eseguendo il programma in un sistema multiprocessore o multicore. In un sistema di questo tipo, l’uso del multithreading potrebbe aumentare la velocità effettiva e la velocità di risposta.
Confronto tra processo e thread:
Rappresentazione di un processo nella memoria centrale:
Rappresentazione di più thread nella memoria centrale:
Contesto: Thread vs Processi
Contesto di un Thread
stato della computazione (registri, stack, PC…)
attributi (schedulazione, priorità)
descrittore di thread (tid, priorità, segnali pendenti, …)
memoria privata (TSD)
Contesto di un processo; tutto quello che è nel contesto di un thread, ed inoltre:
spazio di memoria
risorse private (con le corrispondenti tabelle dei descrittori)
In un programma non-concorrente, o sequenziale. In ogni momento è possibile interrompere il programma e dire esattamente quale task si stava eseguendo, quale era la sequenza di chiamate, ecc. ⇒ Esecuzione deterministica
In un programma concorrente, si individuano un certo numero di task da eseguire come “flussi indipendenti”. Ogni task ha un compito specifico da portare avanti:
I task possono comunicare tra loro: “flussi cooperanti”
Regioni differenti del codice eseguite allo stesso tempo
Lo stato di un programma ha più di una dimensione ⇒ Esecuzione non deterministica
Vantaggi dei Thread
Semplicità di comunicazione inter-thread. Condivisione di risorse più naturale, programmazione più semplice
I thread condividono per default la memoria e le risorse del processo che li genera (cioè, molti thread nello stesso spazio di indirizzi)
I thread quindi comunicano tramite condivisione di informazioni nella memoria del processo contenitore
I processi comunicano invece tramite meccanismi di scambio di messaggi messi in atto esplicitamente (dal programmatore)
Esempio: in un editor di testo almeno 3 thread condividono lo stesso file: il paginatore, il thread che legge i caratteri battuti sulla tastiera e il correttore ortografico. Con 3 processi, l’effetto non sarebbe lo stesso: i processi richiederebbero l’uso esclusivo del file e “cambi di contesto”
Efficienza e scalabilità
Creazione e distruzione di thread in tempi rapidi
Context switch rapido e quindi scheduling più veloce dello scheduling dei processi
La comunicazione inter-thread è il più veloce dei meccanismi di comunicazione tra processi
Un programma multi-thread può continuare la computazione anche se uno dei suoi thread è bloccato (per esempio in attesa di I/O)
Rendere multi-thread un’applicazione reattiva ne migliora il tempo di risposta!
Su architetture multiprocessore i thread possono essere eseguiti in parallelo su distinti core di elaborazione ⇒ Maggior scalabilità
Svantaggi dei Thread
Difficoltà di ottenere risorse private.
Per ottenere memoria privata all’interno di un thread esistono appositi meccanismi
Pericolo di interferenza maggiore
la condivisione delle risorse accentua il pericolo di interferenza
gli accessi concorrenti alle risorse del processo contenitore devono essere sincronizzati per evitare interferenze (thread safety)
Modelli di supporto dei Thread: User vs Kernel
I thread si possono implementare:
User-Level: thread all’interno del processo utente gestiti da una libreria specifica (da un supporto run-time)
il kernel non è a conoscenza dell’esistenza dei thread
lo switch non richiede chiamate al kernel
Kernel-Level: gestione dei thread affidata al sistema operativo
tramite chiamate di sistema
gestione integrata processi e thread dal kernel
lo switch è provocato da chiamate al kernel
In entrambi i casi, deve esistere una relazione tra i thread a livello
user e a livello kernel per permettere l’accesso alle risorse!
Modelli multi-thread
Esistono diversi tipi di relazione tra thread a livello utente e thread a livello kernel:
Molti-a-uno
Uno-a-uno
Molti-a-molti
A due livelli
A titolo di esempio si riporta il caso di mapping uno a uno, utilizzato in Linux e Windows: