System call fcntl e record locking Esempio: prenotazione di voli aerei La compagnia ACME Airlines usa un sistema di prenotazione dei voli basato su unix. Possiede due uffici per la prenotazione, A e B, ciascuno con un terminale connesso al computer della compagnia. Da ciascuno dei terminali si può accedere al database delle prenotazioni, implementato come le unix, attraverso il programma acmebook. Questo programma permette di leggere e aggiornare il database. In particolare, è possibile decrementare di uno il numero di posti liberi su un determinato volo. Una situazione critica Supponiamo che sul volo ACM501 per Londra sia rimasto libero esattamente un posto. Il Sig.Jones e il Sig.Smith entrano nell ufficio A e B, rispettivamente, chiedendo un biglietto per quel volo. Consideriamo la seguente sequenza di eventi: 1. Dall ufficio A viene lanciato il programma acmebook. Sia PA il processo risultante. 2. Dall ufficio B viene lanciato il programma acmebook. Sia PB il processo risultante. 3. PA accede al database con una read e scopre che c è un posto libero. 4. PB fa una read sul database e scopre che c è un posto libero. 5. PA azzera il contatore di posti liberi con una write e consegna il biglietto al Sig.Jones. 6. PB, non sapendo che l unico posto rimasto è stato già assegnato, azzera il contatore di posti liberi con una write e consegna il biglietto al Sig.Smith. Come conseguenza è stato assegnato un posto in più rispetto a quelli disponibili. Il problema nasce perchè più processi possono accedere contemporaneamente ad uno stesso file. Inoltre una singola operazione logica (la prenotazione), costituita da diverse chiamate alle system call lseek, read, write, può essere effettuata da più processi concorrenti. Una soluzione consiste nel consentire ad un processo di fare il locking della parte di file sulla quale sta lavorando durante la prenotazione. La sistem call fcntl consente di effettuare il locking di (parte di) un file. Record locking con fcntl La system call fcntl offre due tipi di locking: 1. Read locking: Serve per evitare che i dati vengano modificati da un processo, compreso il processo che ha effettuato il read locking. Tuttavia tutti i processi possono accedere ai dati in lettura. In particolare, il read locking non consente a nessun processo di effettuare un write locking. Più processi possono effettuare un read locking contemporaneamente. 2. Write locking: Il processo che lo ha effettuato può modificare la parte di file su cui ha effettuato il write locking. Ma nessun altro processo può effettuare un read/write locking su quella parte di file. Uso della system call fcntl per il locking
#include <fcntl.h> int fcntl(int filedes, int cmd, struct flock *ldata); filedes è un descrittore di file aperto con il parametro O RDONLY oppure O RDWR, nel caso di un read locking, O WRONLY oppure O RDWR, nel caso di un write locking. cmd specifica l azione da effettuare tramite valori definiti in <fcntl.h>: - F GETLK Fornisce la descrizione dei locking in base al contenuto di ldata. L informazione restituita descrive il primo lock che blocca il lock descritto in ldata. - F SETLK Effettua un locking su un file; termina subito se non è possibile. Serve anche per rimuovere un lock. - F SETLKW Effettua un locking su un file, va in sleep se il lock è bloccato da un lock precedentemente effettuato da un altro processo. La struttura ldata contiene la descrizione del locking. In particolare, comprende i seguenti campi: short l_type; /* descrive il tipo di lock */ short l_whence; /* offset */ off_t l_start ; /* offset in byte */ off_t l_len; /* dimensione del segmento in byte */ pid_t l_pid; /* definito dal comando F_GETLK */ I campi l whence, l start, l len specificano il segmento su cui effettuare, controllare o togliere il locking. l whence determina da dove calcolare l offset e può essere SEEK SET (dall inizio del file), SEEK CUR (dalla posizione corrente), SEEK END (dalla fine del file). l start fornisce la posizione di partenza del segmento relativamente a l whence. l len è la lunghezza del segmento in byte. l type fornisce il tipo di lock: - F RDLCK read locking - F WRLCK write locking - F UNLCK unlocking Il campo l pid è rilevante solo se il parametro cmd vale F GETLK. Se esiste un lock che blocca il lock descritto dagli altri membri della struttura, a l pid viene assegnato l identificatore del processo che ha effettuato il locking. Esercizio 1 Scrivere un programma che simuli il sistema di prenotazione di voli ACME. Il programma acquisisce da std input il numero di posti da riservare rispettivamente dall ufficio A e dall ufficio B. Il main crea due processi figli, PA e PB, a cui passa il numero di posti da riservare rispettivamente dall ufficio A e dall ufficio B. I processi PA e PB chiamano ripetutamente la funzione acmebook per riservare i posti uno alla volta. La funzione acmebook utilizza il meccanismo del locking per effettuare le prenotazioni. Quando non ci sono pi`u posti liberi, il programma termina, altrimenti aspetta da std input un altra coppia di interi, rappresentanti i posti da riservare dagli uffici A e B.
Soluzione: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/wait.h> #define MAXLENGTH 3 char buf[maxlength]; void prenota(int n); int acmebook(); main() { int n,n1,n2,fd,pid1,pid2; while(1) { if((fd=open("prenotazioni.txt",o_rdonly))==-1) { perror("errore in apertura del file delle prenotazioni"); /* Lettura dei posti disponibili. */ if(read(fd,buf,maxlength)>0) { n=atoi(buf); if(n==0) { printf("non ci sono piu` posti disponibili.\n"); exit(0); else break; close(fd); printf("posti disponibili: %d\n",n); /* Viene chiesto il numero di posti da prenotare dall'ufficio A. */ printf("numero di posti da riservare dall'ufficio A: "); scanf("%d",&n1); /* Viene chiesto il numero di posti da prenotare dall'ufficio B. */ printf("numero di posti da riservare dall'ufficio B: "); scanf("%d",&n2); switch(pid1=fork()) { case -1: perror("errore nella creazione del primo figlio"); exit(2); case 0: prenota(n1); default: switch(pid2=fork()) { case -1: perror("errore nella creazione del secondo figlio"); exit(3); case 0:
prenota(n2); default: /* Il padre attende la terminazione dei figli. */ waitpid(pid1,null,0); waitpid(pid2,null,0); void prenota(int n) { int i,error_code; for(i=0;i<n;i++) { /* se acmebook restituisce -1 significa che non vi sono * posti disponibili per soddisfare la prenotazione. */ if((error_code=acmebook())==-1) { printf("numero di posti insufficiente.\n"); exit(error_code); exit(0); int acmebook() { struct flock ldata; ldata.l_type=f_wrlck; ldata.l_whence=seek_set; ldata.l_start=0; ldata.l_len=maxlength; int error_code=0,fd,n,i; /* Apertura in lettura/scrittura del file delle prenotazioni */ if((fd=open("prenotazioni.txt",o_rdwr))==-1) { perror("errore in apertura del file delle prenotazioni"); /* Write locking del file delle prenotazioni con eventuale * sospensione del processo nel caso in cui il file sia * gia` bloccato da un altro processo. */ if(fcntl(fd,f_setlkw,&ldata)==-1) { perror("errore nel blocco del file delle prenotazioni"); exit(4); if(read(fd,buf,maxlength)>0) { n=atoi(buf); if(n>0) { n--; sprintf(buf,"%d",n); /* Riposizionamento all'inizio del file prima della scrittura. */ if(lseek(fd,0,seek_set)==-1) { perror("errore di riposizionamento nel file delle prenotazioni");
exit(5); /* Il buffer viene 'ripulito' da eventuali caratteri spuri. */ for(i=strlen(buf);i<maxlength;i++) buf[i]=' '; /* Scrittura su disco del nuovo numero di posti disponibili. */ if(write(fd,buf,maxlength)==-1) { perror("errore in scrittura nel file delle prenotazioni"); exit(6); else error_code=-1; /* Impostazione del componente l_type della struttura flock * per rimuovere il lock. */ ldata.l_type=f_unlck; /* Rilascio del blocco sul file delle prenotazioni. */ if(fcntl(fd,f_setlkw,&ldata)==-1) { perror("errore nel rilascio del file delle prenotazioni"); exit(5); close(fd); return error_code; Esercizio 2 Siano P1 e P2 due processi che lavorano sullo stesso file. Supponiamo che P1 esegua un lock sulla sezione SX del file e P2 esegua un lock sulla sezione SY dello stesso file. Che cosa succede se poi P1 tenta di fare un lock su SY con F SETLKW e P2 tenta di fare un lock su SX con F SETLKW? Soluzione: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> main() { int fd; struct flock first_lock; struct flock second_lock; first_lock.l_type=f_wrlck; first_lock.l_whence=seek_set; first_lock.l_start=0; first_lock.l_len=10; second_lock.l_type=f_wrlck;
second_lock.l_whence=seek_set; second_lock.l_start=10; second_lock.l_len=5; if((fd=open("file",o_rdwr))==-1) { perror("errore nell'apertura del file"); if(fcntl(fd,f_setlkw,&first_lock)==-1) { perror("errore nell'esecuzione della prima operazione di blocco"); exit(2); printf("a: blocco completato con successo (PID %d)\n",getpid()); switch(fork()) { case -1: perror("errore nell'esecuzione della fork"); exit(3); case 0: if(fcntl(fd,f_setlkw,&second_lock)==-1) { perror("errore nell'esecuzione della seconda operazione di blocco"); exit(4); printf("b: blocco completato con successo (PID %d)\n",getpid()); if(fcntl(fd,f_setlkw,&first_lock)==-1) { perror("errore nell'esecuzione della terza operazione di blocco"); exit(5); printf("c: blocco completato con successo (PID %d)\n",getpid()); exit(0); default: /* Pausa di 10 secondi. */ sleep(10); if(fcntl(fd,f_setlkw,&second_lock)==-1) { perror("errore nell'esecuzione della quarta operazione di blocco"); exit(6); printf("d: blocco completato con successo (PID %d)\n",getpid()); Quello che succede lanciando il programma e riportato qui di seguito: A: blocco completato con successo (PID 3603) B: blocco completato con successo (PID 3604) Errore nell'esecuzione della quarta operazione di blocco: Resource deadlock avoided C: blocco completato con successo (PID 3604) Quindi Unix e in grado di identicare il deadlock che si verica, segnalandolo con un opportuno messaggio d'errore (ed evitando la situazione di stallo dei due processi). In questo caso infatti la chiamata a fcntl ritorna immediatamente restituendo -1 al chiamante ed impostando la variabile speciale errno con il valore EDEADLK. Nel caso in cui il deadlock coinvolga piu di due processi
tuttavia fcntl non e in grado di rilevarlo. Esercizio 3 Modicare il programma dell esercizio 1 in modo da implementare l'accesso esclusivo al le delle prenotazioni tramite regioni critiche gestite da semafori. Soluzione: Il programma riportato di seguito e stato ottenuto da quello dell esercizio 1, sostituendo le chiamate a fcntl con chiamate a p e v (includendo ovviamente le opportune direttive include, define e le dichiarazioni delle funzioni ausiliarie): #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/sem.h> #include <errno.h> #define SEMPERM 0600 #define MAXLENGTH 3 char buf[maxlength]; typedef union _semun { int val; struct semid_ds *buf; ushort *array; semun; int p(int semid); int v(int semid); int initsem(key_t semkey); void prenota(int n,key_t semkey); int acmebook(); main() { key_t semkey=0x200; int n,n1,n2,fd,pid1,pid2,semid; semun ctl_arg; while(1) { if((fd=open("prenotazioni.txt",o_rdonly))==-1) { perror("errore in apertura del file delle prenotazioni"); /* Lettura dei posti disponibili. */ if(read(fd,buf,maxlength)>0) { n=atoi(buf);
if(n==0) { printf("non ci sono piu` posti disponibili.\n"); exit(0); else break; close(fd); printf("posti disponibili: %d\n",n); /* Viene chiesto il numero di posti da prenotare dall'ufficio A. */ printf("numero di posti da riservare dall'ufficio A: "); scanf("%d",&n1); /* Viene chiesto il numero di posti da prenotare dall'ufficio B. */ printf("numero di posti da riservare dall'ufficio B: "); scanf("%d",&n2); switch(pid1=fork()) { case -1: perror("errore nella creazione del primo figlio"); exit(2); case 0: prenota(n1,semkey); default: switch(pid2=fork()) { case -1: perror("errore nella creazione del secondo figlio"); exit(3); case 0: prenota(n2,semkey); default: /* Il padre attende la terminazione dei figli. */ waitpid(pid1,null,0); waitpid(pid2,null,0); semid=initsem(semkey); /* Rimozione del semaforo. */ if(semctl(semid,0,ipc_rmid,ctl_arg)==-1) { perror("errore nella rimozione del semaforo"); exit(7); void prenota(int n,key_t semkey) { int i,error_code; for(i=0;i<n;i++) { /* se acmebook restituisce -1 significa che non vi sono * posti disponibili per soddisfare la prenotazione. */
error_code=acmebook(semkey); if(error_code==-1) { printf("numero di posti insufficiente.\n"); exit(error_code); if(error_code==-2) { printf("errore nell'inizializzazione del semaforo.\n"); exit(error_code); exit(0); int acmebook(key_t semkey) { int error_code=0,fd,n,i,semid; if((semid=initsem(semkey))<0) return -2; /* Apertura in lettura/scrittura del file delle prenotazioni */ if((fd=open("prenotazioni.txt",o_rdwr))==-1) { perror("errore in apertura del file delle prenotazioni"); /* Inizio della regione critica */ p(semid); if(read(fd,buf,maxlength)>0) { n=atoi(buf); if(n>0) { n--; sprintf(buf,"%d",n); /* Riposizionamento all'inizio del file prima della scrittura. */ if(lseek(fd,0,seek_set)==-1) { perror("errore di riposizionamento nel file delle prenotazioni"); exit(5); /* Il buffer viene 'ripulito' da eventuali caratteri spuri. */ for(i=strlen(buf);i<maxlength;i++) buf[i]=' '; /* Scrittura su disco del nuovo numero di posti disponibili. */ if(write(fd,buf,maxlength)==-1) { perror("errore in scrittura nel file delle prenotazioni"); exit(6); else error_code=-1; /* Fine della regione critica */ v(semid); close(fd); return error_code;
int initsem(key_t semkey) { int status=0,semid; if((semid=semget(semkey,1,semperm IPC_CREAT IPC_EXCL))==-1) { if(errno==eexist) semid=semget(semkey,1,0); else { semun arg; arg.val=1; status=semctl(semid,0,setval,arg); if(semid==-1 status==-1) { perror("initsem fallita"); return -1; return semid; int p(int semid) { struct sembuf p_buf; p_buf.sem_num=0; p_buf.sem_op=-1; p_buf.sem_flg=sem_undo; if(semop(semid,&p_buf,1)==-1) { perror("p(semid) fallita"); return 0; int v(int semid) { struct sembuf v_buf; v_buf.sem_num=0; v_buf.sem_op=1; v_buf.sem_flg=sem_undo; if(semop(semid,&v_buf,1)==-1) { perror("v(semid) fallita"); return 0;