Sistemi operativi, A.A. 2011/2012 (M. Cesati) Scaletta esercitazione #14, 21.06.2012 1 Memoria condivisa 1.1 La chiamata di sistema shmget() +-----------------------------------------------+ |int shmget(key_t key, size_t size, int shmflg);| +-----------------------------------------------+ 1.1.1 Restituisce un identificatore della regione di memoria 1.1.2 key: IPC_PRIVATE (nuova risorsa) oppure chiave 1.1.3 size: dimensione della regione di memoria 1.1.4 shmflg: IPC_CREAT, IPC_EXCL, diritti d'accesso (come open()) 1.2 La chiamata di sistema shmat() mappa la regione nello spazio di indirizzamento virtuale del processo +---------------------------------------------------------+ |void *shmat(int shmid, const void *shmaddr, int shmflg); | +---------------------------------------------------------+ 1.2.1 Restituisce l'indirizzo virtuale associato 1.2.2 shmid: identificatore della regione di memoria 1.2.3 shmaddr: generalmente NULL, suggerimento sull'indirizzo virtuale 1.2.4 shmflg: SHM_RDONLY (sola lettura, default e' lettura e scrittura) 1.3 La chiamata di sistema shmdt() +-------------------------------+ |int shmdt(const void *shmaddr);| +-------------------------------+ 1.3.1 shmaddr: indirizzo virtuale della regione di memoria da rimuovere dallo spazio di indirizzamento del processo 1.4 Per manipolare la regione di memoria: chiamata di sistema shmctl() +-----------------------------------------------------+ |int shmctl(int shmid, int cmd, struct shmid_ds *buf);| +-----------------------------------------------------+ 1.5 Esercizio: scrivere due programmi, "produttore" e "consumatore" che scambiano dati per mezzo di una regione di memoria condivisa lunga 32 byte. Il produttore deve riempire la regione con caratteri letti dallo standard input. Il consumatore deve stampare i caratteri con frequenza non maggiore di 100usec. La regione di memoria e' un buffer circolare, ed i processi debbono bloccare se la regione e' piena (produttore) oppure vuota (consumatore). [produttore.c,consumatore.c] 1.5.1 Come tenere traccia del buffer circolare: 1.5.1.1 Indice E della prima posizione libera (modificato dal produttore) 1.5.1.2 Indice S del primo elemento da leggere (modificato dal consumatore) 1.5.1.3 Gli indici stessi devono essere condivisi tra i processi (per controllare se il buffer e' pieno oppure vuoto) +---+---+---+---+---+---+---+---+ | | # | # | # | # | | | | +---+---+---+---+---+---+---+---+ S E +---+---+---+---+---+---+---+---+ | # | | | | | # | # | # | +---+---+---+---+---+---+---+---+ E S +---+---+---+---+---+---+---+---+ | # | # | # | # | | # | # | # | +---+---+---+---+---+---+---+---+ E S 1.5.1.4 Ambiguita' nel caso gli indici sono uguali: puo' essere sia pieno che vuoto; si puo' risolvere ad esempio sprecando uno slot in modo da non consentire mai sovrapposizione +---+---+---+---+---+---+---+---+ | # | # | # | # | # | # | # | # | +---+---+---+---+---+---+---+---+ ES +---+---+---+---+---+---+---+---+ | | | | | | | | | +---+---+---+---+---+---+---+---+ ES 2 Semafori 2.1 La chiamata di sistema semget() +---------------------------------------------+ |int semget(key_t key, int nsems, int semflg);| +---------------------------------------------+ 2.1.1 Restituisce un identificatore dell'insieme di semafori 2.1.2 key: IPC_PRIVATE (nuova risorsa) oppure chiave 2.1.3 nsems: numero di semafori nell'insieme 2.1.4 semflg: IPC_CREAT (crea se necessario) | IPC_EXCL (errore se risorsa gia' esiste) 2.2 La chiamata di sistema semop() +----------------------------------------------------------+ |int semop(int semid, struct sembuf *sops, unsigned nsops);| +----------------------------------------------------------+ 2.2.1 Identificatore dell'insieme di semafori 2.2.2 sops: vettore di strutture che descrivono le operazioni su ciascun semaforo: 2.2.2.1 sem_num: indice del semaforo nell'insieme 2.2.2.2 sem_op: operazione (valore positivo o negativo da sommare) 2.2.2.3 sem_flg: flag: IPC_NOWAIT (non bloccare), SEM_UNDO (annulla l'operazione se il processo termina) 2.3 La chiamata di sistema semctl() +-------------------------------------------------------------+ |int semctl(int semid, int semnum, int cmd, union semun arg); | +-------------------------------------------------------------+ 2.3.1 La union semun deve essere definita esplicitamente nel programma: +----------------------------------------------------------------+ |union semun { | | int val; /* Value for SETVAL */ | | struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ | | unsigned short *array; /* Array for GETALL, SETALL */ | | struct seminfo *__buf; /* Buffer for IPC_INFO | | (Linux-specific) */ | |}; | +----------------------------------------------------------------+ 2.3.1 Per inizializzare i valori dei semafori: 2.3.1.1 Singolo: semctl(semid, semnum, SETVAL, arg); /* arg.val */ 2.3.1.2 Tutti: semctl(semid, 0, SETALL, arg); /* arg.array */ 2.3.2 Per leggere i valori: GETVAL, GETALL 2.4 Esercizio: implementare un semaforo binario (mutex) sulla base di un semaforo System V IPC specificato per mezzo di un identificatore (int). Si debbono definire 4 primitive: bsem_init_busy(), bsem_init_free(), bsem_get(), bsem_put(). +--------------------------------------------------------------+ |#include | |#include | |#include | |#include | |union semun { | | int val; | | struct semid_ds *buf; | | unsigned short *array; | |}; | |int bsem_init_busy(int bsemID) | |{ | | union semun arg; | | arg.val = 0; /* in use */ | | return semctl(bsemID, 0, SETVAL, arg); | |} | |int bsem_init_free(int bsemID) | |{ | | union semun arg; | | arg.val = 1; /* free */ | | return semctl(bsemID, 0, SETVAL, arg); | |} | |int bsem_get(int bsemID) | |{ | | struct sembuf sops; | | sops.sem_num = 0; | | sops.sem_op = -1; | | sops.sem_flg = 0; | | while (semop(bsemID, &sops, 1) == -1) | | if (errno != EINTR) /* ! interrupted by a signal */ | | return -1; | | return 0; | |} | |int bsem_put(int bsemID) | |{ | | struct sembuf sops; | | sops.sem_num = 0; | | sops.sem_op = 1; | | sops.sem_flg = 0; | | return semop(bsemID, &sops, 1); | |} | +--------------------------------------------------------------+ 3 Esercizio: aggiungere un semaforo binario ai programmi "consumatore" e "produttore" 3.1 Verificare che lanciando due processi consumatore insieme si ha corruzione dei puntatori del buffer circolare $ ./consumatore & $ ./consumatore & $ echo .O.O.O.O.O.O.O.O.O.O.O.O.O | produttore .O.O.O.O.O.O.O...O.O.O.O.O 3.2 Aggiungere il semaforo binario condiviso tra produttore e consumatore [produttore_bsem.c, consumatore_bsem.c] 3.2.1 Allocazione del semaforo: +--------------------------------------------+ |int get_ipc_semaphore(void) | |{ | | int sid; | | key_t key = ftok(".", 'b'); | | sid = semget(key, 1, IPC_CREAT | 0666); | | if (sid == -1) { | | perror("semget"); | | exit(EXIT_FAILURE); | | } | | bsem_init_free(sid); /* UNSAFE */ | | return sid; | |} | +--------------------------------------------+ 3.2.2 Ciclo del consumatore: +------------------------------------+ |for (;;) { | | bsem_get(bsemID); | | while (cb->S == cb->E) { | | /* circular buffer empty */ ;| | bsem_put(bsemID); | | usleep(100000); | | bsem_get(bsemID); | | } | | putchar(cb->buf[cb->S]); | | fflush(stdout); | | cb->S = (cb->S + 1) % cbuf_size;| | bsem_put(bsemID); | | usleep(100000); | |} | +------------------------------------+ 3.2.3 Ciclo del produttore: +-------------------------------------+ |for (;;) { | | [...] | | bsem_get(bsemID); | | nE = (cb->E +1) % cbuf_size; | | while (nE == cb->S) { | | /* circular buffer full */ ; | | bsem_put(bsemID); | | usleep(100000); | | bsem_get(bsemID); | | nE = (cb->E +1) % cbuf_size; | | } | | cb->buf[cb->E] = c; | | cb->E = nE; | | bsem_put(bsemID); | |} | +-------------------------------------+ ==========