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