Diapositiva 1

Transcript

Diapositiva 1
POSIX Threads
Sistemi Real-Time
Prof. Davide Brugali
Università degli Studi di Bergamo
POSIX
 Acronimo di Portable Operating System Interface
 È una famiglia di standard IEEE atti a mantenere la
compatibilità tra diversi sistemi operativi
(essenzialmente varianti di UNIX ma non solo)
 La prima versione dello standard risale al 1988, da
allora è stato gradualmente esteso fino a comprendere
19 documenti, ciascuno dei quali definisce lo standard
per varie funzionalità del SO:
 Comandi da shell e scripting
 I/O su file, rete, nodo remoto
 Utilities (echo ecc…)
 Libreria standard di thread e relative API
2
POSIX Standard
POSIX is the Portable Operating System Interface, the open operating interface
standard accepted world-wide. It is produced by IEEE and recognized by ISO and
ANSI.
POSIX.1 Core Services
 Process Creation and Control
 Signals
 Floating Point Exceptions
 Segmentation Violations
 Illegal Instructions
 Bus Errors
 Timers
POSIX.1b Real-time
extensions
•
•
•
•
•
•
•
•
Priority Scheduling
Real-Time Signals
Clocks and Timers
Semaphores
Message Passing
Shared Memory
Asynch and Synch I/O
Memory Locking
 File and Directory Operations
POSIX.1c Threads extensions
 Pipes
•
•
•
•
 C Library (Standard C)
 I/O Port Interface and Control
3
Thread Creation, Control, and Cleanup
Thread Scheduling
Thread Synchronization
Signal Handling
POSIX Standard
To be conformant with the POSIX.1 standard, processes must be kept
separate, and this is achieved through the use of memory protection and
name spaces.
4
POSIX Standard
Threads are schedulable entities that run within a process. Each process will
have one main thread, and it may also have several more threads which
share the address space of the process invoked via pthread_create().
5
Posix Threads
 Pthread è implementata dalla libreria libpthread.so. Il
file header pthread.h consente di includere nella
propria applicazione in linguaggio C un set di circa
cento funzioni, costanti, definizioni e strutture dati, tutti
identificati dal prefisso pthread_ e divisi in quattro
gruppi:
 Gestione del thread (creazione, chiusura)
 Variabili Mutex
 Variabili Condizioni
 Sincronizzazione tra i thread
6
Esempio POSIX Threads
7
Esempio POSIX Threads
8
Creazione di un thread
 La funzione pthread_create accetta quattro parametri:
 Il puntatore all’oggetto pthread_t: è la variabile con cui è
possibile fare riferimento al thread.
 Il puntatore all’oggetto pthread_attr_t: contiene gli
attributi di configurazione del thread.
 Il puntatore alla funzione funct: funzione eseguita dal
thread.
 Il puntatore agli argomenti: argomenti da passare alla
funzione, passati come puntatore void.
9
Creazione di un thread
 La funzione restituisce un intero contenente:
 Un valore pari a zero: se la creazione ha avuto successo
 Un valore diverso da zero: se la creazione non ha avuto
successo. Da questo valore è possibile risalire alla causa.
 Dall’istante immediatamente successivo alla chiamata
l’esecuzione del programma si divide:
 Il programma principale continua l’esecuzione,
eventualmente creando altri thread.
 Il thread inizia l’esecuzione della funzione assegnata.
10
Configurazione di un thread
 La configurazione di un thread avviene attraverso una
variabile di tipo pthread_attr_t:
pthread_attr_t attr;
pthread_attr_init(&attr);
//EXAMPLE
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//. . .
pthread_attr_destroy(&attr);
 Tale variabile contiene tutti gli elementi di configurazione del
thread.
 Una volta inizializzata, può essere usata per impostare varie
configurazioni grazie a funzioni specifiche.
 Può essere riutilizzata per la configurazione di altri thread,
eventuali modifiche dopo la creazione del thread verranno
ignorate.
 Viene deallocata quando non più necessaria.
11
Funzione task
 La funzione passata al thread implementa l’attività del
Task.
 Restituisce void* e deve terminare con la chiamata alla
funzione pthread_exit che segnala l’uscita da un thread.
void *task(void *null){
printf("Hi!, i'm a pthread!!!\n");
//Thread exit
pthread_exit((void *) 0);
}
 Il parametro di pthread_exit rappresenta il dato ritornato
al thread «chiamante» e può contenere un error status
(simile alla funzione exit del main)
12
Passaggio di parametri (1)
 È possibile interagire con l’esecuzione di un thread
attraverso:
 I parametri passati alla funzione task
 Variabili globali o comunque visibili nello scope della funzione
 Il primo approccio consiste nel creare una struttura dati
custom contenente :
 tutti i parametri da passare al thread
 eventuali riferimenti a zone di memoria allocate staticamente dal
thread principale su cui il thread può fare output.
 Il secondo approccio prevede di leggere e scrivere da/su una
variabile globale.
 In ogni caso, per le operazioni di lettura e scrittura possono
essere necessari meccanismi di mutua esclusione quali
semafori, lock.
13
Passaggio di parametri (2)
#define NUM_THREAD 100
void *task(void *arg){
printf("Hi!, i'm thread number %i\n", *((int*)arg));
}
int main (int argc, char *argv[]) {
pthread_t threads[NUM_THREAD];
// . . .
int *arg[NUM_THREAD];
for(int i = 0; i < NUM_THREAD; i++){
arg[i] = (int *) malloc(sizeof(int));
*arg[i] = i;
pthread_create(&threads[i], &attr, task,
(void *) arg[i]);
}
// . . .
}
14
Chiusura di un thread
 La «strategia» di chiusura di un thread varia a seconda che
si tratti di un thread definito JOINABLE o DETACHED.
 La definizione viene fatta impostando l’attributo del thread
(pthread_attr_t) ad uno dei due valori definiti dalla libreria.
pthread_attr_t attrJoinable, attrDetached;
pthread_attr_init(&attrJoinable);
pthread_attr_init(&attrDetached);
//JOINABLE THREAD
pthread_attr_setdetachstate(&attrDetached,
PTHREAD_CREATE_DETACHED);
//DETACHED Thread
pthread_attr_setdetachstate(&attrJoinable,
PTHREAD_CREATE_JOINABLE);
15
Thread Joinable
 Il thread invocante (es. main) attende la terminazione del
thread invocato chiamando la funzione:
pthread_join (pthread_t thread,
void **value_ptr)
 Questo permette di «catturare» il valore ritornato dalla
funzione di chiusura del thread (salvato in value_ptr).
 Tutte le strutture dati allocate dal thread non verranno
deallocate fino alla chiamata di pthread_join
16
void *task(void *null){
// . . .
int returnValue = 3;
pthread_exit((void *) returnValue);
}
int main (int argc, char *argv[]) {
// . . .
int status;
pthread_join(task, (void **)&status);
printf(“Joined with status %i\n“, status);
//. . .
}
Thread detached
 Al contrario, i thread definiti DETACHED hanno un
esecuzione completamente autonoma rispetto al thread
invocante.
 Le loro strutture dati vengono deallocate
automaticamente al termine della loro esecuzione e,
malgrado venga chiamata la funzione pthread_exit, il
valore ritornato non è leggibile.
 È consigliabile definire thread DETACHED ogni volta
che non si è interessati al valore di ritorno del thread o a
«catturare» il termine dell’esecuzione. Questo snellisce
il codice del thread invocante e migliora le prestazioni
(deallocazione automatica dei dati)
17
Timing handling in POSIX
18
Source : http://feanor.sssup.it/~lipari
timespec structure
 time_t is usually an integer (32 bits) that stores the time
in seconds
 this data type can store both absolute and relative time
values
// defined in <time.h>
struct timespec {
time_t tv_sec; // seconds
long tv_nsec; // nanoseconds
}
19
timespec operations
void timespec_add_us(struct timespec *t, long us) {
t->tv_nsec += us*1000;
if (t->tv_nsec > 1000000000) {
t->tv_nsec = t->tv_nsec - 1000000000;// + ms*1000000;
t->tv_sec += 1;
}
}
int timespec_cmp(struct timespec *a, struct timespec *b) {
if (a->tv_sec > b->tv_sec) return 1;
else if (a->tv_sec < b->tv_sec) return -1;
else if (a->tv_sec == b->tv_sec) {
if (a->tv_nsec > b->tv_nsec) return 1;
else if (a->tv_nsec == b->tv_nsec) return 0;
else return -1;
}
}
20
Getting the time
 To get/set the current time, the following functions are
available.
#include <time.h>
int clock_getres(clockid_t clock_id, struct timespec *res);
int clock_gettime(clockid_t clock_id, struct timespec *tp);
int clock_settime(clockid_t clock_id, const struct
timespec *tp);
 clockid_t is a data type that represents the type of real-
time clock that we want to use
21
Clocks
clock_id can be:
 CLOCK_REALTIME represent the system real-time
clock, it is supported by all implementations. The value
of thic clock can be changed with a call to
clock_settime()
 CLOCK_MONOTONIC represents the system real-time
since startup, but cannot be changed. Not supported in
all implementations
 if _POSIX_THREAD_CPUTIME is defined, then
clock_id can have a value of
CLOCK_THREAD_CPUTIME_ID, which represents a
special clock that measures execution time of the calling
thread (i.e. it is increased only when a thread executes)
by calling pthread_getcpuclockid()
22
Sleep functions
23
Exemple I
void *thread(void *arg) {
struct timespec interval;
interval.tv_sec = 0;
interval.tv_nsec = 500 * 1000000; // 500 msec
while(1) {
// perform computation
nanosleep(&interval, 0);
}
}
24
Exemple II
The previous example does not work!
void *thread(void *arg) {
struct timespec interval;
struct timespec next;
struct timespec rem;
struct timespec now;
interval.tv_sec = 0;
interval.tv_nsec = 500 * 1000000; // 500 msec
clock_gettime(&next);
while(1) {
// perform computation
timespec_add(&next, &interval); // compute next arrival
clock_gettime(&now); // get time
timespec_sub(&rem, &next, &now); // compute sleep interval
nanosleep(&rem, 0); // sleep
}
}
25
Problems with Example II
 Once again, it does not work!
 It could happen that the thread is preempted between calls
to clock_gettime and !nanosleep!,
 in this case the interval is not correctly computed
 The only “clean” solution is to use a system call that
performs the above operations atomically
26
Correct implementation
#include <time.h>
int clock_nanosleep(clockid_t clock_id, int flags,
const struct timespec *rqtp, struct timespec *rmtp);
 This is the most flexible and complete function for




27
suspending a thread (only available in the POSIX RT profile)
clock_id is the clock id, usually CLOCK_REALTIME
flags is used to decided if we want to suspend for a relative
amount of time, or until an absolute point in time. It can be
TIMER_ABSTIME or 0 to mean relative interval
rqtp is a pointer to a timespec value that contain either the
interval of time or the absolute point in time until which the
thread is suspended (depending on the flag value)
rmtp only makes sense if the flag is 0, in which case if the
function is interrupted by a signal it contains the remaining
interval of sleeping time
Example
28
Deadline miss detection
 The following code is used to detect a deadline miss (in
this case, the behavior is to abort the thread)
29
Concurrency: mutual exclusion
and synchronisation
Corso di Sistemi RT
Prof. Davide Brugali
Università degli Studi di Bergamo
Slides adapted from :
http://retis.sssup.it/~lipari/courses/str06/rtcs.html
and
http://retis.sssup.it/~giorgio/slides/rts/
Interaction model
 Activities can interact according to two fundamental
models
 Shared memory
 All activities access the same memory space
 Message passing
 All activities communicate each other by sending messages through
OS primitives
31
UNIBG - Sistemi Real Time - Brugali
Cooperative vs Competitive
 The interaction between concurrent activities (threads
or processes) can be classified into:
 Competitive concurrency
 Different activities compete for the resources
 One activity does not know anything about the other
 The OS must manage the resources so to
 Avoid conflicts
 Be fair
 Cooperative concurrency
 Many activities cooperate to perform an operation
 Every activity knows about the others
 They must synchronize on particular events
32
UNIBG - Sistemi Real Time - Brugali
Competing activities
 Competing activities need to be “protected” from each
other
 Separate memory spaces
 The process model is the best
 The allocation of the resource and the synchronization
must be centralized
 Competitive activities requests for services to a
central manager (the OS or some dedicated process)
which allocates the resources in a fair way
 Client/Server model
 Communication is usually done through messages
33
UNIBG - Sistemi Real Time - Brugali
Message passing
 In a client/server system
 A server manages the resource exclusively
 For example, the printer
 If a process needs to access the resource, it sends a
request to the server
 For example, printing a file, or asking for the status
 The server can send back the responses
 The server can also be on a remote system
Client 1
Server
Client 2
34
UNIBG - Sistemi Real Time - Brugali
Cooperative model
 Cooperative activities know about each other
 They do not need protection
 Not using protection, we have less overhead
 They need to access the same data structures
 Allocation of the resource is de-centralized
 Shared memory is the best model
 The thread model of execution is the best one
35
UNIBG - Sistemi Real Time - Brugali
Shared memory
 Shared memory communication
 It was the first one to be supported in old OS
 It is the simplest one and the closest to the machine
 All threads can access the same memory locations
Thread 1
Thread 2
Shared memory
36
UNIBG - Sistemi Real Time - Brugali
Thread 3
Mutua esclusione e sezioni critiche
 Due o più thread possono fare accesso ad una risorsa





37
condivisa (es: una variabile globale) solo in modo
mutuamente esclusivo.
Per garantire che, in ogni istante, solo un thread possa
accedere alla risorsa occorre definire delle sezioni critiche.
La libreria pthread gestisce l’accesso alle sezioni critiche
attraverso degli oggetti detti mutex (mutual exclusive).
Ogni thread può acquisire e rilasciare il controllo di un mutex
attraverso due funzioni definite dalla libreria stessa.
Fino a quando un thread mantiene il controllo su un certo
mutex, nessun altro thread può acquisirne il controllo.
Quando un thread rilascia il controllo di un mutex, un altro
può prenderne possesso.
Synchronization in POSIX
38
UNIBG - Sistemi Real Time - Brugali
Mutex (1)
pthread_mutex_t mutexOnSharedResource; //Mutex declaration
int sharedResource = 0; //Shared variable to be protected
void *producerTask(void *null){
for(int i = 0; i < ITERATIONS; i++){
pthread_mutex_lock(&mutexOnSharedResource);//IN CRIT. SEC.
sharedResource++;
pthread_mutex_unlock(&mutexOnSharedResource);//OUT CRIT. SEC.
}
pthread_exit((void *) 0);
}
void *consumerTask(void *null){
for(int i = 0; i < ITERATIONS; i++){
pthread_mutex_lock (&mutexOnSharedResource);//IN CRIT. SEC.
if(sharedResource >0 )
sharedResource--;
pthread_mutex_unlock (&mutexOnSharedResource);//OUT CRIT. SEC.
}
pthread_exit((void *) 0);
}
39
Mutex (2)
 Definite le sezioni critiche e il mutex (che deve essere
visibile nello scope di tutti i thread che ne fanno uso) è
sufficiente inizializzarlo e creare i thread
int main (int argc, char *argv[]){
//Mutex init
pthread_mutex_init(&mutexOnSharedResource, NULL);
//Create attribute
pthread_attr_t attr;
//Init attribute
pthread_attr_init(&attr);
pthread_t threads[2];
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
//Create threads
pthread_create(&threads[0], &attr, producerJob, NULL);
pthread_create(&threads[1], &attr, consumerJob, NULL)
/ . . .
pthread_mutex_destroy(&mutexOnSharedRes);
}
40
Notifiche e condizioni (1)
 La libreria pthread consente di notificare il verificarsi di
particolari eventi:
 Un thread resta in attesa del verificarsi di un particolare
evento
 Un altro thread esegue una notifica «sbloccando» il thread
in attesa.
 Il meccanismo richiede l’uso dei mutex e di un’altra
particolare variabile detta «condizione».
 La libreria mette a disposizione funzioni per l’attesa, la
notifica e la notifica broadcast di «condizioni»
 Il meccanismo è simile al metodo notify() di Java
41
Notifiche e condizioni (2)
pthread_mutex_t mutexOnSharedRes; //Create a mutex variable
pthread_cond_t condOnSharedRes; //Create a condition variable
int sharedRes = 0; //Create a shared variable to be protected
void *workTask(void *null){
for(int i = 0; i < ITERATION; i++){
pthread_mutex_lock (&mutexOnSharedRes);//ENTER CRIT. SEC.
sharedRes++;
pthread_mutex_unlock (&mutexOnSharedRes);//EXIT CRIT. SEC.
if(sharedRes == 10)
pthread_cond_signal(&condOnSharedRes);//NOTIFY
}
} pthread_exit((void *) 0);
}
void *watchTask(void *null){
while(isAlive){//WAIT
pthread_cond_wait(&condOnSharedRes, &mutexOnSharedRes);
pthread_mutex_lock (&mutexOnSharedRes); //ENTER CRIT. SEC.
sharedRes = 0;
pthread_mutex_unlock (&mutexOnSharedRes); //EXIT CRIT. SEC.
} pthread_exit((void *) 0);}
42
Notifiche e condizioni (3)
int main (int argc, char *argv[]) {
//Create threads
pthread_t workThread, watchThread;
//Init mutex var
pthread_mutex_init(&mutexOnSharedRes, NULL);
//Init condition var
pthread_cond_init(&condOnSharedRes, NULL);
// . . .
pthread_create(&watchThread, &attr, watchTask, NULL);
pthread_create(&workThread, &attr, workTask, NULL);
// . . .
pthread_mutex_destroy(&mutexOnSharedRes);
pthread_cond_destroy(&condOnSharedRes);
}
 L’operazione di notifica è associata ad un mutex e deve
essere eseguita solo quando si è in possesso del
mutex, in caso contrario il comportamento risulta
impredicibile.
43
Notifiche e condizioni (4)
 Al momento della chiamata a pthread_cond_signal, il
thread chiamante rilascia il mutex e l’esecuzione passa
al thread in attesa.
 In caso non ci siano thread in attesa il chiamante
riacquisisce il mutex e riprende l’esecuzione
 La funzione pthread_cond_signal può
notificare soltanto
un singolo thread in attesa. Nel caso si vogliano
notificare più thread è possibile usare
pthread_cond_broadcast:
 Il chiamante rilascia il lock sul mutex.
 Tutti i thread in attesa vengono notificati e concorrono per
l’acquisizione del mutex.
 Solo uno lo acquisisce entrando nella sezione critica.
44