Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In...

132
Programmazione su sistemi UNIX Sistemi Operativi Mario Di Raimondo C.d.L. in Informatica (laurea triennale) Dipartimento di Matematica e Informatica – Catania A.A. 2009-2010 Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 1 / 132

Transcript of Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In...

Page 1: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Programmazione su sistemi UNIXSistemi Operativi

Mario Di Raimondo

C.d.L. in Informatica (laurea triennale)Dipartimento di Matematica e Informatica – Catania

A.A. 2009-2010

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 1 / 132

Page 2: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Introduzione

Chiamate di sistema

In questa parte ci occuperemo di alcuni aspetti fondamentali inerenti ilproblema della progettazione e realizzazione di programmi nei sistemiUNIX (in particolare su Linux).In particolare, ci si occupera dell’interfaccia tra il programmatore ed ilsistema operativo.In genere i programmi, per svolgere il loro compito, hanno bisognodell’esecuzione di alcune mansioni ausiliarie strettamente legate allagestione delle risorse del sistema.Il meccanismo che permette ad un’applicazione di invocare una mansioneausiliaria e il meccanismo delle system calls (chiamate di sistema).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 2 / 132

Page 3: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Introduzione

Tipo di chiamate di sistema

Nei sistemi UNIX le chiamate di sistema sono in genere suddivise incategorie a secondo del tipo di risorse da gestire:

gestione dei file;

gestione di processi;

comunicazione e sincronizzazione tra processi.

Qui vedremo solo alcune (una trattazione esaustiva sarebbe fuori dagliscopi del corso). Il linguaggio di riferimento sara il Linguaggio C, leinterfacce di sistema lo utilizzano come riferimento poiche il kernel stessodei sistemi UNIX in genere (ed e il caso di Linux) e scritto in C (conl’ausilio di codice scritto in assembly).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 3 / 132

Page 4: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Introduzione

Serve aiuto?

Cosı come avete visto nella parte dedicata alla shell, anche qui le manpages dei sistemi UNIX ci vengono in aiuto. Sono disponibili delle paginedi manuale per ognuno delle principali chiamate di sistema.Il modo in cui richiamare la pagina e il solito: man chiamata

A volte, ci sono chiamate di sistema che sono omonime a comandi. Leman page sono organizzate in sezioni. Alcuni esempi di sezioni:

1: programmi eseguibili e comandi di shell;

2: chiamate al sistema (funzioni fornite dal kernel);

3: chiamate alle librerie (funzioni all’interno delle librerie di sistema).

In caso di piu pagine con lo stesso nome, si puo specificare la sezioneusando la sintassi: man numero_sezione nome_pagina

Esempio: man 2 chown

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 4 / 132

Page 5: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Strutture

Descrittori di file (1)

Ogni processo puo effettuare operazione di lettura/scrittura su uno o piufile. Ogni operazione fa riferimento ad un determinato file.Il kernel associa ad ogni processo una tabella dei file aperti dal processostesso. All’atto della creazione di un file, o se e gia esistente e si apre, ilsistema associa ad esso un intero non negativo, denominato descrittore difile, che identifica il canale di input/output.Esso rappresenta un collegamento tra il processo ed il file ad essoassociato. Non e altro che l’indice nella tabella dei file aperti dal processo.All’atto della creazione di un processo, le prime tre allocazioni di taletabella sono riservate nel seguente modo:

0: standard input

1: standard output

2: standard error

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 5 / 132

Page 6: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Strutture

Descrittori di file (2)

Ogni elemento della tabella dei file aperti ha un puntatore dilettura/scrittura chiamato file pointer. Tale puntatore e inizialmente (inapertura o creazione) pari a 0, per indicare che punta l’inizio del file. Adogni operazione di lettura/scrittura, il puntatore viene spostato dialtrettanti byte. Indica il punto in cui avverra la prossima operazione dilettura/scrittura.E’ possibile spostare tale puntatore in una posizione arbitraria attraversoapposite chiamate di sistema.Il numero di file che ogni processo puo tenere aperti contemporaneamentee limitato (che dipende dalla dimensione della tabella dei file). E’ quindibuona regola mantenere a minimo il numero di file aperti e chiudere ognifile a cui non si ha l’intenzione di accedere nell’immediato futuro.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 6 / 132

Page 7: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Creazione di un file (1)

Per creare un nuovo file in un sistema UNIX viene utilizzata la chiamata disistema creat(). La sintassi e la seguente:int creat(char *file_name, int mode)

file_name: puntatore alla stringa di caratteri che contiene il nomedel file da creare (pathname assoluto o relativo);

mode: specifica i permessi di accesso al file da creare.

Se il file esiste gia, l’effetto della chiamata e quello di azzerare il contenutodello stesso (portando la dimensione a zero) lasciando inalterati i diritti diaccesso precedenti (quelli specificati con mode vengono ignorati).La chiamata a creat() ritorna un valore per determinare l’esitodell’operazione. Se il valore e non negativo, l’operazione di creazione eandata a buon fine ed il valore riportato e esattamente il suodescrittore di file. Tale numero puo essere usato come riferimentoper le successive operazioni sul file da parte del processo.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 7 / 132

Page 8: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Creazione di un file (2)

Se il valore riportato e pari a -1, significa che l’operazione e fallita.Possibili motivi possono essere:

parte del prefisso della stringa *file_name o non esiste o non e unadirectory, oppure la stringa corrisponde ad una directory esistente;

non si ha i diritti di scrittura nella directory contenente il file dacreare, oppure si tratta di un filesystem montato in sola lettura;

file_name e un puntatore nullo o punta ad un’area di memoria nonvalido per quel processo;

il file esiste gia e: o il file e gia in utilizzo da parte di un altro processoo e negato l’accesso;

si e gia raggiunto il numero massimo di file che il processo puomantenere aperto.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 8 / 132

Page 9: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Creazione di un file: esempio

creat.c

#include "stdio.h"#include "stdlib.h"

int main() {int fd;if ( (fd=creat(" pippo.txt " ,600)) == −1) {

printf (" Errore nella chiamata creat \n");exit (1);

}printf ("File creato con successo con descrittore di file %d \n",fd);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 9 / 132

Page 10: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Circa i permessi di accesso

Le chiamate di sistema che fanno uso di un parametro che specifica i dirittidi accesso per i file possono fare uso di alcune costanti predefinite chepossono essere combinati con l’operatore |:

S_IRUSR: permesso di lettura, proprietario;

S_IWUSR: permesso di scrittura, proprietario;

S_IXUSR: permesso di esecuzione, proprietario;

S_IRGRP: permesso di lettura, gruppo;

S_IWGRP: permesso di scrittura, gruppo;

S_IXGRP: permesso di esecuzione, gruppo;

S_IROTH: permesso di lettura, altri;

S_IWOTH: permesso di scrittura, altri;

S_IXOTH: permesso di esecuzione, altri.

Tali costanti si trovano nel file sys/stat.h che si deve quindi includere.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 10 / 132

Page 11: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Aprire un file (1)

Nel caso in cui il file da gestire esiste gia, per fare delle operazioni su diesso e necessario comunque associare un descrittore di file ad esso(nell’ambito del processo corrente).Per fare cio viene usata la chiamata di sistema open() che ha la seguentesintassi:int open(char *file_name, int option_flags[, int mode])

file_name: puntatore alla stringa di caratteri che contiene il nomedel file da aprire;

option_flags: specifica la modalita di apertura;

mode: specifica dei permessi nel caso in cui sia necessario creare il file.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 11 / 132

Page 12: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Aprire un file (2)

La modalita di apertura option_flags e un intero i cui bit rappresentanoalcune opzioni da usare nell’apertura del file. I bit desiderati sono attivatiutilizzando apposite costanti definite nell’header /usr/include/fcntl.hcombinandoli con l’operatore | (OR bit-a-bit):

O_RDONLY: apre il file in sola lettura;

O_WRONLY: apre il file in sola scrittura;

O_RDWR: apre il file in lettura e scrittura;

O_APPEND: apre il file e posiziona il puntatore di posizione alla fine,cosicche una eventuale operazione di scrittura accodi i blocchi scrittia quelli gia esistenti;

O_CREAT: se il file non esiste, il file viene creato con i diritti di accessospecificati da mode;

O_TRUNC: se il file esiste lo tronca a dimensione zero.

In effetti si deve scegliere uno tra solo-lettura, solo-scrittura elettura-scrittura e poi eventualmente combinare le altre modalita con l’OR.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 12 / 132

Page 13: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Aprire un file (3)

La funzione open() apre un file esistente e riporta un descrittore di file peresso relativamente al processo chiamante, dopo aver creato una appositavoce nella tabella dei file aperti. Se il file non esiste ed e stata specifical’opzione O_CREAT, allora il file viene creato con gli eventuali permessispecificati con il parametro opzionale mode.Se l’operazione di apertura/creazione fallisce, la funzione riporta il valore-1.Una chiamata del tipo open(filename, O_RDWR|O_TRUNC|O_CREAT,

0660) e del tutto equivalente a creat(filename, 0660), infatti se il fileesiste gia lo apre e lo tronca portando il puntatore all’inizio del file, il tuttomantenendo i diritti precedenti. Se il file non esiste, entrambe le chiamatecreano il file in lettura-scrittura con gli stessi diritti di accesso.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 13 / 132

Page 14: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Chiudere un file aperto

Per chiudere un file che si e precedentemente creato/aperto e che non sipensa piu di usare nel breve tempo, si puo usare la chiamata close() perliberare la relativa voce nella tabella dei file aperti ed il relativo descrittoredi file.La sintassi e la seguente:int close(int ds_file)

ds_file: il descrittore del file da chiudere.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 14 / 132

Page 15: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Esempio di apertura e chiusura

openclose.c

#include "stdio.h"#include "stdlib.h"#include "fcntl.h"

int main() {int fd;char ∗name="pippo.txt";

fd=open(name ,O_RDWR );if ( fd == −1) {

printf (" Errore nell 'apertura del file %s. \n",name);exit (1);

}printf ("Il file %s e' stato aperto con descrittore di file %d. \n",name ,fd);close(fd);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 15 / 132

Page 16: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Lettura da un file (1)

Per leggere dei dati da un file precedentemente aperto si puo utilizzare lachiamata di sistema read(). La sintassi completa e:int read(int ds_file, char *buffer_pointer,

unsigned transfer_size)

ds_file: un descrittore di un file valido da cui leggere i dati;

buffer_pointer: un puntatore ad una area di memoria temporaneasu cui copiare i dati letti dal file (un buffer);

transfer_size: specifica il numero di byte (caratteri) che sidesidera leggere dal file.

In caso di successo, il numero di byte effettivamente letti viene riportatocome valore di ritorno (quindi positivo).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 16 / 132

Page 17: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Lettura da un file (2)

In caso di fallimento viene riportato -1, possibili cause:

il descrittore passato non corrisponde ad un file effettivamente aperto;

il canale identificato da ds_file non permette la lettura;

il buffer puntato da buffer_pointer punta ad un’area di memoriache non e interamente contenuta nello spazio di indirizzamentopermesso al processo.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 17 / 132

Page 18: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Lettura da un file (3)

Se il numero di byte che e possibile leggere e X, dove X<=transfer_size,allora X verra ritornato dalla chiamata a read().Nel caso in cui il numero di byte letti e superiore alle dimensioni del bufferdi memoria indirizzato da buffer_pointer, allora possono succedere unodei due casi:

la chiamata tenta di scrivere in una area fuori dall’indirizzamento(vedi prima);

l’area e dentro l’indirizzamento e la chiamata sembra andare a buonfine (come riportato dal valore di ritorno); gli effetti pero sonoimprevedibili e potenzialmente disastrosi (ma solo per il processochiamante): altre variabili o aree di memoria possono esseresovrascritte.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 18 / 132

Page 19: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Esempio di lettura da file

read.c

#include "stdio.h"#include "stdlib.h"#include "fcntl.h"

int main() {int fd,n;char ∗name="pippo.txt";char buffer [1024];

if ( (fd=open(name ,O_RDONLY )) == −1 ) {printf (" Errore nell 'apertura del file %s. \n",name);exit (1);

}n=read(fd ,buffer ,10);if ( n==−1 ) {

printf (" Errore durante la lettura. \n");exit (1);

}buffer[n]='\0';printf ("ho letto: %s \n",buffer );

close(fd);}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 19 / 132

Page 20: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Esempio: conteggia la dimensione di un file

count.c

#include "stdio.h"#include "stdlib.h"#include "fcntl.h"#define BUFSIZE 1024int main(int argc , char ∗argv []) {

int fd,size;int total =0;char buffer[BUFSIZE ];if (argc != 2) { printf (" utilizzo: %s file\n",argv [0]); exit (1); }// apre il file sorgente in sola letturafd=open(argv[1], O_RDONLY );if (fd == −1) {

perror(argv [1]);exit (1);

}// copia tutti i dati in memoria per conteggiare la dimensionedo {

size=read(fd ,buffer ,BUFSIZE );if (size == −1 ) { perror(argv [1]); exit (1); }total += size;printf (" DEBUG: ho letto %d byte\n",size);

} while (size > 0);printf ("La dimensione totale e' di %d byte\n",total);close(fd);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 20 / 132

Page 21: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Scrittura su un file (1)

Per scrivere dei dati su un file usiamo la chiamata di sistema write(). Lasintassi completa e:int write(int ds_file, char *buffer_pointer,

unsigned transfer_size)

ds_file: un descrittore di un file valido su cui si vuole scrivere i dati;

buffer_pointer: un puntatore ad una area di memoria che contienei dati da scrivere sul file;

transfer_size: specifica il numero di byte (caratteri) che sidesidera scrivere sul file, a partire dall’inizio del buffer.

Se la dimensione del buffer indirizzato da buffer_pointer e inferiore alnumero di byte da trasferire, l’effetto sul file sarebbe imprevedibile(scrivendo byte presi a caso dalla memoria) o, nel caso estremo, sipotrebbero andare a leggere in un’area di memoria non autorizzata.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 21 / 132

Page 22: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Scrittura su un file (2)

Se un file esistente viene aperto e si scrivono X byte, l’effetto sara quello disovrascrivere i primi X byte dello stesso. Se viene aperto in modalitaappend, i byte vengono accodati.La funzione write() riporta il numero di byte effettivamente scritti (che ameno di problemi corrisponde a transfer_size) o -1 in caso di errore.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 22 / 132

Page 23: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Esempio di lettura e scrittura combinati (1)

copy.c

#include "stdio.h"#include "stdlib.h"#include "fcntl.h"

#define BUFSIZE 1024

int main(int argc , char ∗argv []) {int sd,dd,size ,result;char buffer[BUFSIZE ];// controlla il numero di parametriif (argc != 3) {

printf (" utilizzo: %s sorgente destinazione\n",argv [0]);exit (1);

}// apre il file sorgente in sola letturasd=open(argv[1], O_RDONLY );if (sd == −1) {

perror(argv [1]);exit (1);

}// apre il file destinazione in sola scrittura , con troncamento e creazionedd=open(argv[2], O_WRONLY|O_CREAT|O_TRUNC ,0660);if (dd == −1) {

perror(argv [2]);exit (1);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 23 / 132

Page 24: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Esempio di lettura e scrittura combinati (2)

copy.c

// copia i dati dalla sorgente alla destinazionedo {

// legge fino ad un massimo di BUFSIZE byte dalla sorgentesize=read(sd ,buffer ,BUFSIZE );if (size == −1 ) {

perror(argv [1]);exit (1);

}// scrive i byte lettiresult=write(dd ,buffer ,size);if (result == −1) {

perror(argv [2]);exit (1);

}} while (size > 0);// chiude i file prima di uscireclose(sd);close(dd);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 24 / 132

Page 25: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Spostarsi all’interno di un file

Abbiamo detto che per ogni file aperto viene mantenuto un indice cherappresenta la posizione corrente (offset).Per fare degli spostamenti si usa la chiamata lseek():long lseek(int ds_file, long offset, int option)

ds_file: il descrittore del file;

offset: il numero di byte di cui ci si vuole spostare;

option: il tipo di spostamento che si vuole fare:

SEEK_SET (0): da inizio fileSEEK_CUR (1): dalla posizione correnteSEEK_END (2): dalla fine del file

Riporta la nuova posizione acquisita o -1 in caso di errore (nel cui caso laposizione resta invariata).Una invocazione del tipo pos=lseek(fd,0L,SEEK_CUR) puo essere usataper recuperare la posizione corrente.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 25 / 132

Page 26: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Duplicare il descrittore di un file

E’ possibile duplicare il descrittore di un file nella tabella dei file aperti, inmodo tale che:

il nuovo descrittore si riferisce allo stesso file, eredita il puntatore dilettura/scrittura e la modalita di accesso;

il nuovo descrittore utilizzera l’entry libera della tabella con indice piupiccolo.

La chiamata di sistema e dup():int dup(int file_descriptor)

Dove file_descriptor e il descrittore di file da duplicare. La funzioneriporta il numero del nuovo descrittore creato o -1 in caso di problemi.La duplicazione puo essere usata per fare la redirezione dei canali diinput-output.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 26 / 132

Page 27: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni di base

Redirezione dello standard input

In questo esempio lo standard input viene preso dal file passato comeparametro e poi viene eseguito il comando more (che eredita lo standardinput).

mymore.c

#include "stdio.h"#include "stdlib.h"#include "fcntl.h"#include "unistd.h"

#define STDIN 0

int main(int argc , char ∗argv []) {int fd;if (argc == 1) exit (1);fd = open(argv[1], O_RDONLY ); // apro il file in letturaclose(STDIN ); // chiudo lo standard inputdup(fd); // duplico il descrittore del fileclose(fd); // chiude il vecchio descrittore (ne resta una copia)execlp ("more","more",NULL); // eseguo "more" (vedremo piu ' avanti i dettagli)

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 27 / 132

Page 28: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Gli stream

Lavorare con gli stream

Le funzionalita che abbiamo appena visto per operare sui file del filesystemsono operazioni a basso livello (low-level), operano in modo molto simili acio che fa il kernel. Vengono spostati blocchi grezzi di dati senza curarsi dicosa ci sta dentro.Un modo alternativo di operare sui file e quello di utilizzare gli stream:permettono di utilizzare funzioni piu ad alto livello che semplificano, avolte, il lavoro del programmatore quando si trattano file di testo.Ogni stream identifica un file (o piu in generale un canale di I/O) erappresenta una astrazione a piu alto livello rispetto ai file descriptor: inogni caso, quando viene aperto uno stream per un file, viene creato unrelativo file descriptor nella tabella dei file aperti.Per operare sugli stream in genere e sufficiente includere gli headerstdio.h.Alla creazione del processo vengono creati tre flussi standard: stdin,stdout e stderr.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 28 / 132

Page 29: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Gli stream

Apertura di uno stream (1)

Per ogni funzione a basso livello vista in precedenza, esistono dellecontroparti che lavorano sugli stream. Poi ci sono anche altre funzionalitapiu avanzate che giustificano l’esistenza stessa degli stream.Per aprire uno stream relativamente ad un file esistente:FILE *fopen(char *path, char *mode)

Apre un file con pathname riferito da path e con la modalita di aperturaspecificata nella stringa riferita da mode:

r o r+: il file viene aperto in sola lettura (o lettura/scrittura) e lostream e posizionato all’inizio del file;

w o w+: se il file esiste questo viene aperto in sola scrittura (olettura/scrittura), viene troncato e il flusso si posiziona all’inizio; senon esiste viene creato;

a o a+: se il file non esiste allora viene creato, se esiste viene aperto inscrittura (o lettura/scrittura) e lo stream viene posizionato alla fine.

Se la chiamata non va a buon fine, viene riportato un puntatore a NULL.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 29 / 132

Page 30: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Gli stream

Apertura di uno stream (2)

Se l’apertura/creazione va a buon file, viene riportato un puntatore ad unastruttura standard chiamata FILE (in realta FILE e un alias di unastruttura). Tale puntatore sara il riferimento allo stream per tutto il restodell’esecuzione del processo.

E’ possibile aprire uno stream collegato ad un file descriptor gia creato conla variante:FILE *fdopen(int fd, char *mode)

Il file descriptor fd non viene duplicato (tipo con dup) ed i modi diapertura specificati in mode devono essere compatibili con quelli giaspecificati nella tabella dei file aperti.

Una stream si puo chiudere con la chiamata int fclose(FILE *fp).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 30 / 132

Page 31: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Gli stream

Lettura e scrittura di blocchi su uno stream

Esistono funzioni molto simili alle chiamate read() e write() chelavorano per blocchi:int fread(void *ptr, int size, int num, FILE *stream)

int fwrite(void *ptr, int size, int num, FILE *stream)

Queste funzioni leggono/scrivono num blocchi di dimensione size

utilizzando il buffer riferito da ptr per i blocchi letti/scritti.Le due funzioni riportano il numero di elementi (NON di byte!) letti oscritti. Se avviene un errore (o viene raggiunta la fine del file) un numeroinferiore a num viene riportato (non si fa distinzione tra l’errore el’end-of-file).Per vedere se si e raggiunto la fine del file si puo usare la funzioneint feof(FILE *stream).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 31 / 132

Page 32: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Gli stream

Leggere e scrivere caratteri singoli

Da uno stream e possible leggere e scrivere caratteri singoli (byte)utilizzando le seguenti funzioni:int fgetc(FILE *stream)

int fputc(int c, FILE *stream)

fgetc() legge un carattere dallo stream stream e lo ritorna come valore.Se viene raggiunta la fine del file, viene riportato il valore speciale EOF (chein effetti corrisponde a -1).In effetti viene riportato un int, ma e il risultato di un cast da ununsigned char.

La chiamata fputc scrive il carattere contenuto in c sullo stream stream

(subito dopo averne fatto un cast ad unsigned char).Ritorna il valore appena scritto o EOF in caso di errore.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 32 / 132

Page 33: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Gli stream

Esempio: copia di file carattere per carattere con stream

copystream.c

#include "stdio.h"#include "stdlib.h"

int main(int argc , char ∗argv []) {FILE ∗in,∗out;int c;if (argc != 3) { printf (" utilizzo: %s sorgente destinazione\n",argv [0]); exit (1); }// apre lo stream sorgente in sola letturaif ((in=fopen(argv [1],"r")) == NULL) {

perror(argv [1]);exit (1);

}// apre/crea lo stream destinazione in sola scrittura (con troncamento)if ((out=fopen(argv [2],"w")) == NULL) {

perror(argv [2]);exit (1);

}// copia i dati dalla sorgente alla destinazione carattere per caratterewhile ( (c = fgetc(in)) != EOF )

fputc(c,out);// chiude gli streamfclose(in);fclose(out);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 33 / 132

Page 34: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Gli stream

Leggere e scrivere stringhe

Per leggere una stringa da uno stream si puo usare:char *fgets(char *s, int size, FILE *stream)

Legge dallo stream stream un numero di caratteri pari al piu a (size-1)

e li memorizza nel buffer puntato da s. La lettura termina nel caso diincontri la fine del file o un carattere di ritorno a capo sul file (’\n’). Nelbuffer, subito dopo la fine della stringa appena letta, viene inserito uncarattere ’\0’ per rendere il buffer s un stringa valida in C.La chiamata fgets() riporta s stesso in caso di successo o NULL in casodi errore o fine file.

Per scrivere si puo utilizzare la chiamata:int fputs(char *s, FILE *stream)

Viene scritta la stringa puntata da s (senza il carattere ’\0’ finale).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 34 / 132

Page 35: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Gli stream

Esempio: reimplementazione di cat usando gli stream

cat.c

// legge un file di testo (o lo standard input) e lo visualizza#include "stdio.h"#include "stdlib.h"

#define BUFSIZE 2048

int main(int argc , char ∗argv []) {FILE ∗in;char buffer[BUFSIZE ];int c;if (argc >= 2) { // e' stato specificato almeno un parametro

if ((in=fopen(argv [1],"r")) == NULL) { perror(argv [1]); exit (1); }} else // legge dallo standard input

in=stdin;// copia i dati dalla sorgente alla destinazione carattere per caratterewhile ( (fgets(buffer ,BUFSIZE ,in)) != NULL )

printf ("%s",buffer );// chiude gli streamfclose(in);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 35 / 132

Page 36: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Gli stream

Scrittura avanzata di stringhe

La funzione printf() che in genere viene usata per scrivere stringheformattate con valori aggiuntivi a video puo in effetti essere utilizzata (omeglio, una sua variante) per scrivere su un generico stream. La variante ela seguente:int fprintf(FILE *stream, char *format, ...)

In effetti una chiamata del tipo printf(...), corrisponde ad unainvocazione del tipo fprintf(stdout,...).La sintassi di formattazione e identica a quella che conoscete perprintf(). Alcuni esempi di richiamo: %d per gli interi, %o e %x per gliinteri in ottale ed esadecimale, %c per un carattere, %s per una stringa, %fper un float, ecc.Il valore ritornato da fprintf() e il numero di caratteri scritti.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 36 / 132

Page 37: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Gli stream

Lettura avanzata di stringhe

La funzione scanf() puo essere utilizzata per leggere dallo standard input(stdin) una stringa formattata. Esiste l’analogo fscanf() che compie lostesso compito ma leggendo da uno stream specifico. In effetti, scanf()utilizza implicitamente lo stream predefinito stdin.int fscanf(FILE *stream, char *format, ...)

Gli stessi simboli speciali %? visti per fprintf() si possono utilizzare perfscanf(); per ognuno di essi deve essere specificato un puntatore allavariabile che conterra il valore letto.La chiamata riporta il numero di valori che sono stati assegnati (chepotrebbero anche meno di quelli specificati per mancanza di input o perdifferenze nel parsing).L’utilizzo di fscanf() non e sempre semplice e spesso puo portare adcomportamenti imprevisti se il file non e formattato esattamente.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 37 / 132

Page 38: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Gli stream

Altre funzioni sugli stream

Ecco qualche altra funzione sugli stream:

int fseek(FILE *stream, long offset, int mode)

sposta la posizione di offset byte, utilizzando il riferimentospecificato da mode (che ha lo stesso utilizzo che in lseek());

long ftell(FILE *stream)

riporta la posizione corrente nello stream;

void rewind(FILE *stream)

“riavvolge lo stream”, portando la posizione all’inizio dello stream;

int fflush(FILE *stream)

forza il sistema a scrivere tutti i “dati in sospeso” contenuti nei buffer(a livello di utente).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 38 / 132

Page 39: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni avanzate

Creare un link

Nella prima parte del corso abbiamo gia parlato degli hard-link e deisoft-link. Vediamo come creare un hard-link di un file attraverso lachiamata di sistema link():int link(char *pathname, char *alias)

pathname: puntatore alla stringa contenente il pathname del fileesistente di cui creare l’hard-link;

alias: puntatore al nome dell’hard-link da creare.

La funzione riporta 0 in caso di successo o -1 se l’operazione e fallita (adesempio non si hanno i diritti necessari o si e cercato di fare un hard-link acavallo di due file-system diversi o che non supportano la primitiva stessa).

Esiste anche la chiamata di sistema per creare i link simbolici:int symlink(char *pathname, char *alias)

Queste chiamate stanno in unistd.h.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 39 / 132

Page 40: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni avanzate

Rimuovere un link... ovvero cancellare

L’operazione inversa compiuta dalla chiamata link() si effettua conunlink(): rimuove il riferimento ad un file esistente. In pratica vienedecrementato il contatore dei link all’interno dell’inode e se esso arriva a 0,allora viene cancellato anche l’inode ed il contenuto del file. Se c’erano piudi un link, il file continua ad esistere ed rimane referenziato dai rimanentilink.In effetti, le cancellazioni sotto UNIX si fanno con la chiamata unlink():int unlink(char *pathname)

pathname: puntatore alla stringa contenente il pathname delriferimento da cancellare.

Ritorna 0 in caso di successo, -1 in caso di errore.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 40 / 132

Page 41: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni avanzate

Rinomina/spostamento tramite link

move.c

#include "stdio.h"#include "stdlib.h"

int main(int argc , char ∗argv []) {if (argc != 3) {

fprintf(stderr ," utilizzo: %s vecchionome nuovonome\n",argv [0]);exit (1);

}if (link(argv[1],argv [2]) == −1) {

perror(argv [2]);exit (1);

}if (unlink(argv [1]) == −1) {

perror(argv [1]);exit (1);

}exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 41 / 132

Page 42: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei File Operazioni avanzate

Altre funzioni di servizio

Alcune chiamate di sistema per la gestione e manipolazione del filesystem:

int chmod(char *path, mode_t mode)

cambia i diritti di accesso dell’oggetto specificato da path,impostando i diritti a mode; quest’ultimo utilizza le stesse maschere dibit che abbiamo visto in precedenza per creat();int chown(char *path, uid_t owner, gid_t group)

cambia il proprietario nell’utente specificato da owner ed il gruppogroup;int mkdir(char *pathname, mode_t mode)

crea un directory specificata da pathname con i permessi in mode;int rmdir(char *pathname)

cancella la directory (vuota) specificata da pathname;int chdir(char *pathname)

cambia la directory corrente in quella specificata da pathname;char *getcwd(char *buf, size_t size)

scrive nel buffer buf (di dimensione size) la directory corrente.Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 42 / 132

Page 43: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi I processi

Cos’e un processo?

Un processo puo essere considerato in prima approssimazione come unprogramma in esecuzione. Dato un programma in esecuzione siindividuano:

codice: l’insieme delle istruzioni che lo compongono;

spazio di memoria: l’attuale stato della memoria occupata dai dati delprogramma;

stato del processore: l’attuale valore dei registri della CPU.

Data una terna (codice,memoria,stato processore) abbiamo univocamentedefinito un programma in esecuzione. Questa terna e detta processo, chequindi e un concetto legato a quello di programma, ma che ne rappresentauna estensione.Quindi se ad un certo istante abbiamo in esecuzione un certo processo A esostituiamo i valori attuali con quelli di una terna associata ad un secondoprocesso B, allora il flusso di esecuzione di quest’ultimo riprendera dalpunto in cui era stato lasciato. Questo e un interlacciamento tra processi ecostituisce il concetto di base dei moderni sistemi multi-tasking.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 43 / 132

Page 44: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi I processi

Il kernel e i processi

Il kernel gestisce i processi in esecuzione attraverso una tabella dei processiattivi ed associa ad ogni processo un identificatore numerico chiamato PIDche lo individua in modo univoco.Il PID di un processo e assegnato dal kernel al processo nella fase dicreazione del processo stesso.

La suddetta tabella contiene per ogni processo una struttura nota comeProcess Control Block (PCB), contenente informazioni relative al processocome: il PID del processo, il suo stato (pronto per l’esecuzione, inesecuzione, in attesa, ecc.).

Ogni programma puo conoscere il suo PID utilizzando la chiamata disistema pid_t getpid().

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 44 / 132

Page 45: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi I processi

Esempio di recupero del PID attuale

pid.c

// visualizza il PID del processo corrente#include "stdio.h"#include "stdlib.h"

int main() {pid_t pid;pid = getpid ();printf ("PID che il kernel ha associato a questo processo: %d\n",pid);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 45 / 132

Page 46: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Creazione di un processo

Creazione di un processo (1)

Nei sistemi UNIX esiste un unico modo per creare un nuovo processo:utilizzando la chiamata di sistema pid_t fork().L’effetto di tale chiamata e quello di creare un esatto duplicato delprocesso corrente, quindi: stesso codice, stessa memoria (nel senso che necrea una copia esatta) e medesimo stato del processore. Ovviamente avraun PID diverso: non possono esistere due processi con PID uguale.Il primo processo sara detto processo padre, quello generato (il duplicato)sara detto processo figlio.

Da quanto detto fino adesso si puo capire perche il processo init e ilpadre di tutti i processi, infatti ha sempre PID pari a 1.

Il nome della chiamata sta a rappresentare appunto tale processo dibiforcazione: ma dov’e che avviene la biforcazione? Il processo padre ed ilprocesso figlio hanno il medesimo codice ed il medesimo stato:continueranno a compiere l’identico lavoro (in parallelo).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 46 / 132

Page 47: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Creazione di un processo

Creazione di un processo (2)

L’unica differenza nel flusso di esecuzione dei due processi imparentati ecostituito dal valore di ritorno della chiamata fork():

al processo padre riporta il PID del processo figlio appena creato;

al figlio restituisce sempre 0;

in caso di errore ritorna -1.

Quindi i due processi, analizzando il valore di ritorno della chiamata,possono capire se si tratta del padre o del figlio ed agire di conseguenza.

Da notare che in effetti il processo figlio eredita tutto lo stato del processopadre, compreso la tabella dei file aperti, che contiene anche i descrittoridello standard input e standard output. Il padre ed il figlio licondivideranno.

Il processo figlio (ma in generale, ogni processo) puo conoscere il PID delsuo processo padre (ognuno ha un padre... eccetto init) attraverso lachiamata pid_t getppid().

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 47 / 132

Page 48: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Creazione di un processo

Utilizzo di fork

fork.c

#include "stdio.h"#include "stdlib.h"

int main() {pid_t pid;printf ("PID del processo iniziale: %d\n",getpid ());

pid = fork ();

if (pid == 0)printf ("Sono il processo FIGLIO [%d] e mio padre ha PID %d.\n",getpid(),getppid ());

elseprintf ("Sono il processo PADRE [%d] e mio figlio ha PID %d.\n",getpid(),pid);

exit (0);}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 48 / 132

Page 49: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Creazione di un processo

Altro esempio con fork

fork2.c

#include "stdio.h"#include "stdlib.h"

int main() {pid_t pid;int i;

pid = fork ();

if (pid == 0) {// processo figliofor (i=0; i<200; i++)

printf ("Sono il FIGLIO (%d).\n",i);exit (0);

}

// processo padrefor (i=0; i<200; i++)

printf ("Sono il PADRE (%d).\n",i);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 49 / 132

Page 50: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Creazione di un processo

Esempio di creazione di piu figli

multifork.c

// esempio di creazione di piu ' figli#include "stdio.h"#include "stdlib.h"int main() {

pid_t pid;int i;pid = fork (); // creo un primo figlioif (pid == 0) {

// processo figlio 1for (i=0; i<20; i++) {

sleep(rand ()%2);printf ("Sono il FIGLIO 1.\n"); }

exit (0);}pid = fork (); // creo un secondo figlioif (pid == 0) {

// processo figlio 2for (i=0; i<20; i++) {

sleep(rand ()%2);printf ("Sono il FIGLIO 2.\n"); }

exit (0);}// processo padrefor (i=0; i<20; i++) {

sleep(rand ()%2);printf ("Sono il PADRE.\n"); }

exit (0);}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 50 / 132

Page 51: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Creazione di un processo

Schedulazione dei processi

Abbiamo visto dall’esecuzione degli esempi precedenti che non si puodeterminare in maniera cerca quale dei processi padre/figli (ma lo stessodiscorso vale per tutti i processi del sistema) verranno eseguiti per prima.Inoltre in un sistema multi-tasking non ha senso parlare di prima e dopo:vengono eseguiti praticamente in parallelo.

Puo essere desiderabile coordinare in qualche modo il lavoro del processopadre con quello del processo figlio: esiste una chiamata di sistemapid_t wait(int *state) che mette in attesa il processo corrente inattesa che termini almeno un processo figlio.Viene restituito il PID del processo figlio appena terminato o -1 in caso dierrore. Il puntantore int *state punta ad un intero in cui verra messo lostato di uscita del processo che puo eventualmente essere esaminato (senon ci interessa possiamo passare un valore NULL).Se uno dei figli e gia terminato, la chiamato ritorna subito. L’attesa puoessere interrotta da un segnale (qualunque o di uscita).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 51 / 132

Page 52: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Creazione di un processo

Esempio di utilizzo di wait

forkwait.c

#include "stdio.h"#include "stdlib.h"

int main() {pid_t pid;int i;

pid = fork ();

if (pid == 0) {// processo figliofor (i=0; i<10; i++) {

sleep(rand ()%2);printf ("Sono il FIGLIO .\n");

}exit (0);

}

sleep(rand ()%2);// processo padreprintf ("Sono il processo PADRE e mi metto in attesa ...\n");wait(NULL);printf ("Sono il processo PADRE e ho terminato il mio lavoro .\n");exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 52 / 132

Page 53: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Coordinazione tra processi

Attesa di un processo specifico

Attraverso una chiamata del tipo pid_t waitpid(pid_t pid,

int *state, int options) e possibile attendere l’esecuzione di unprocesso figlio specificato attraverso pid.Il parametro options contiene alcune opzioni che possiamo anchetrascurare (possiamo mettere 0).Anche qui possiamo mettere a NULL il parametro state se non ci interessail suo stato di uscita.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 53 / 132

Page 54: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Coordinazione tra processi

Esempio di un padre snaturato...

multiforkwait.c

// creazione di piu ' figli con l'attesa di uno specifico#include "stdio.h"#include "stdlib.h"int main() {

pid_t pid1 ,pid2;int i;pid1 = fork (); // creo un primo figlioif (pid1 == 0) { // processo figlio (lento)

for (i=0; i<15; i++) {sleep(rand ()%4);printf ("Sono il FIGLIO LENTO [%d].\n",getpid ()); }

printf ("Sono il FIGLIO LENTO [%d] e ho finito .\n",getpid ()); exit (0);}sleep (2); // gli diamo un po ' di vantaggiopid2 = fork (); // creo un secondo figlioif (pid2 == 0) { // processo figlio (veloce)

for (i=0; i<20; i++) {sleep(rand ()%2);printf ("Sono il FIGLIO VELOCE [%d].\n",getpid ()); }

printf ("Sono il FIGLIO VELOCE [%d] e ho finito .\n",getpid ()); exit (0);}// processo padresleep(rand ()%2);printf ("Sono il processo PADRE e mi metto in attesa del figlio veloce [%d]...\n",pid2);waitpid(pid2 ,NULL ,0);printf ("Sono il processo PADRE e ho terminato il mio lavoro .\n");exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 54 / 132

Page 55: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Coordinazione tra processi

Esecuzione di altri programmi (1)

Nei sistemi UNIX e possibile da parte di un processo eseguire unprogramma esterno specificato. Questa operazione rimpiazza l’immaginedel processo corrente (quello chiamante) con una immagine che eseguirauna istanza del programma specificato.Abbiamo a disposizione una famiglia di chiamate del tipo execX(), dovela X puo essere: l, lp, v o vp. Ognuna compie lo stesso compito ma inmodo leggermente diverso.

Un comportamento comune e quello per cui, se il lancio del programmaspecificato non va a buon fine, allora viene riportato un errore el’esecuzione del processo chiamante prosegue (magari stampando a videoun errore). Se la messa in esecuzione va a buon fine, l’esecuzione passaall’istanza del programma specificato e non tornera mai piu al processochiamante (che in effetti e stato sovrascritto).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 55 / 132

Page 56: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Coordinazione tra processi

Esecuzione di altri programmi (2)

In effetti il processo che esegue il programma specificato e esattamentequello che l’ha invocato: viene mantenuto pure il PID.Un altro effetto della chiamata e quello di chiudere tutti i descrittori di fileaperti dal processo chiamante ad eccezione di quelli predefiniti (input,output, error): ricordate l’esempio di redirezione dell’input con dup()?

Iniziamo con la chiamata:int execl(char *path, [char *arg0, char *arg1,..., char *argN,] NULL)

Con path viene specificato il pathname del programma da eseguire, diseguito viene specificata una lista di puntatori a stringhe cherappresentano la linea di comando (il primo arg0 e il nome con cui ilprogramma pensa di essere stato invocato). Tale lista deve terminare conun parametro speciale di valore nullo.La chiamata riporta -1 in caso di errore e l’esecuzione prosegue.

Domanda: Cosa riporta in caso di buon fine?

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 56 / 132

Page 57: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Coordinazione tra processi

Esecuzione di altri programmi (3)

La chiamata execv() ha una sintassi differente:int execv(char *path, char *argv[])

In questo caso la linea di comando del programma da eseguire vienepassata attraverso un array di stringhe in cui ogni stringa rappresenta unparametro (compreso sempre arg0).L’array deve terminare con un ultimo puntatore a stringa nullo. Altrimenticome fa la chiamata a capire quando sono finiti i parametri?

Domanda

Qual’e la differenza sostanziale tra execl() ed execv()? Cosa permettedi fare in piu execv() che execl() non puo fare?

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 57 / 132

Page 58: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Coordinazione tra processi

Esecuzione di altri programmi (4)

Le varianti execvp() ed execlp() si comportano nello stesso modo dellecontroparti ad eccezione del fatto che se il primo parametro pathname ineffetti non contiene dei caratteri / (quindi non si tratta di pathname!)cerca il programma da eseguire nella variabile d’ambiente PATH.Quindi e molto utile quando dobbiamo eseguire dei comandi di sistema chesi trovano nei percorsi predefiniti.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 58 / 132

Page 59: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Coordinazione tra processi

Esempio: invocare un comando

exec.c

// esegue un comando specificato come primo parametro#include "stdio.h"#include "stdlib.h"#include "unistd.h"

int main(int argc , char ∗argv []) {if (argc == 1) {

printf (" utilizzo: %s comando\n",argv [0]);exit (1);

}

printf (" Esegui il comando '%s'...\n",argv [1]);execlp(argv[1],argv[1],NULL); // eseguo il comando passato come primo parametro

// non e' necessario controllare se e' fallita ...printf ("L'invocazione di '%s' e' fallita !\n",argv [1]); // ... lo e' di sicuro ...// ... visto che il flusso di esecuzione e' arrivato fin qui.exit (1);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 59 / 132

Page 60: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Gestione dei Processi Coordinazione tra processi

Esempio: una mini shell

nanoshell.c

// un abbozzo di funzionamento di una shell#include "stdio.h"#include "stdlib.h"#include "unistd.h"#define LEN_BUFFER 1024int main(int argc , char ∗argv []) {

char comando[LEN_BUFFER ];int pid;while (1) {

printf (" Digitare un comando da eseguire ('quit ' per uscire ): ");scanf ("%s",comando );if (strcmp(comando ,"quit") == 0)

break;pid = fork ();if ( pid == −1 ) {

printf (" Errore nella fork.\n");exit (1);

}if ( pid == 0 ) {

execlp(comando , comando , NULL);printf (" Errore nell 'esecuzione di '%s'\n",comando );exit (2);

} elsewait(NULL);

}exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 60 / 132

Page 61: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Code di messaggi (1)

Nei sistemi UNIX i processi per scambiarsi i dati possono utilizzare deicostrutti di alto livello chiamati code di messaggi. In pratica,implementano delle code FIFO tra uno o piu processi. Possono essereutilizzate dai processi per scambiarsi dati di ogni genere secondo unoschema produttore/consumatore. Volendo un processo puo sia inserire cheestrarre messaggi dalla coda, cosı come ci possono essere piu di unprocesso ad inserire od estrarre da essa.

Le code di messaggi risiedono nella memoria centrale e non sul filesystem.Come concetto sono pero simili ad un file: ci deve essere un processo chela crea ed un processo per leggervi/scrivervi deve prima aprirla.Vengono identificate da un numero intero positivo univoco che i processiscelgono per riferirsi alla stessa coda: viene detta chiave. Rappresenta ilnome stesso della coda, cosı come il pathname serve ad identificare un fileall’interno di un filesystem.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 61 / 132

Page 62: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Code di messaggi (2)

Le code sono delle strutture permanenti, quindi sopravvivono al processoche le ha create. Bisogna cancellarle esplicitamente (cosı come i file).Sono permanenti anche i messaggi inseriti e non ancora estratti.I messaggi sotto UNIX non hanno delle strutture particolari, vengonotrattati come sequenze non strutturate di byte.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 62 / 132

Page 63: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Creazione/apertura di un coda (1)

Un processo puo creare una nuova coda o aprirne una gia esistenteattraverso la chiamata:int msgget(key_t key, int msgflg)

La chiamata crea o apre una coda di messaggi con chiave key e riporta undescrittore di coda che permette al processo di riferirsi a quella coda nellesuccessive operazioni (e un qualcosa di analogo ai descrittori di file). Sel’operazione non va a buon fine, viene riportato -1.

Il parametro msgflg specifica delle modalita di apertura (IPC_CREAT eIPC_EXCL) combinati attraverso l’operazione OR (|) con i permessi diaccesso alla coda che eventualmente sara creata. I permessi sono informato ottale (tipo 0660) con sintassi analoga a quanto visto per ilfilesystem (pero l’esecuzione non ha al momento alcun senso). In caso diapertura i permessi specificati sono ignorati.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 63 / 132

Page 64: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Creazione/apertura di un coda (2)

Se non esistono code con chiave key ed e stata specificata la modalitaIPC_CREAT, allora la coda viene creata.Se una coda con quella stessa chiave key dovesse gia esistere in memoria,allora msgget() riporta un descrittore per quella stessa coda (la apre).

Se insieme al flag IPC_CREAT e stato attivato anche il flag IPC_EXCL

allora il sistema si assicura che sia il processo chiamante ad aver creato lacoda (e non un altro processo).

In caso di errore la chiamata ritorna -1.L’uso delle code di messaggi richiede l’inclusione delle intestazioni:sys/ipc.h e sys/msg.h.

Come chiave key si puo utilizzare il valore speciale IPC_PRIVATE perspecificare una nuova coda non ancora creata.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 64 / 132

Page 65: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Manipolazione di code

Per cancellare una coda, modificarne i permessi o raccogliere dellestatistiche su di essa, si puo usare la chiamata:int msgctl(int msqid, int cmd, struct msqid_ds *buf)

Esegue il comando corrispondente a cmd sulla coda di descrittore msqid; leoperazioni specificabili attraverso cmd sono:

IPC_RMID: rimuove la coda associata a msqid (buf puo essere NULL);

IPC_STAT: raccoglie alcune statistiche sulla coda e le inserisce nellastruttura puntata da buf (non scendiamo nei dettagli);

IPC_SET: reimposta i diritti di accesso alla coda secondo quantospecificato nella struttura puntata da buf (non scendiamo neidettagli).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 65 / 132

Page 66: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Esempio: creazione e chiusura di una coda

msgcreate.c

#include "stdio.h"#include "stdlib.h"#include "sys/ipc.h"#include "sys/msg.h"

int main() {int ds_coda;key_t chiave =40;

printf ("Creo la coda di messaggi di chiave %d... ",chiave );ds_coda= msgget(chiave , IPC_CREAT|IPC_EXCL |0660);if ( ds_coda == −1 ) {

printf ("\ nErrore nella chiamata msgget ().\n");exit (1);

}printf (" creata (descrittore %d)\n",ds_coda );sleep (5);printf (" Chiudo la coda.\n");msgctl(ds_coda , IPC_RMID , NULL);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 66 / 132

Page 67: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Depositare messaggi nella coda

Una volta che un processo ha ottenuto il descrittore di coda (creandola oaprendola), vi puo inserire dei messaggi arbitrari con:int msgsnd(int msqid, void *ptr, size_t size, int msgflg)

Inserisce il messaggio puntato da ptr di dimensione size nella coda dimessaggi individuata dal descrittore msqid. Il parametro msgflg specificail comportamento della chiamata in caso di coda piena: 0 indica che lachiamata si deve bloccare se non c’e spazio nella coda in attesa che se neliberi a sufficienza; con la modalita IPC_NOWAIT la chiamata ritorna subitoun valore -1 (cosı come in caso di errore) nel caso di coda piena.

E’ importantissimo notare che in effetti il buffer puntato da ptr non hauna struttura del tutto libera: ogni messaggio ha un tipo indicato da unlong positivo inserito all’inizio del buffer. Pero la dimensione size siriferisce al contenuto del messaggio: quindi il buffer puntato da ptr devepuntare ad una struttura lunga 4+size byte, in cui i primi 4 byte (unlong) sono il tipo di messaggio.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 67 / 132

Page 68: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Esempio: inserimento di un messaggio nella coda (1)

msgsend.c

#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/ipc.h"#include "sys/msg.h"

#define DIM_MSG 1024

typedef struct{long mtype;char mtext[DIM_MSG ];

} msg;

int main() {int ds_coda ,risultato;key_t chiave =40;msg messaggio;

// crea la codads_coda= msgget(chiave , IPC_CREAT|IPC_EXCL |0660);if ( ds_coda == −1 ) { printf (" Errore nella chiamata msgget ().\n"); exit (1); }

// crea ed inserisce il messaggiomessaggio.mtype =1;strncpy(messaggio.mtext ,"Hello world!", DIM_MSG );risultato = msgsnd(ds_coda , &messaggio , strlen(messaggio.mtext )+1, IPC_NOWAIT );

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 68 / 132

Page 69: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Esempio: inserimento di un messaggio nella coda (2)

msgsend.c

if (risultato == −1) {printf (" Errore durante l'inserimento del messaggio .\n");exit (1);

}msgctl(ds_coda , IPC_RMID , NULL);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 69 / 132

Page 70: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Prelevare messaggi dalla coda

Per leggere messaggi da una coda:int msgrcv(int msqid, void *ptr, size_t size, long mtype,

int msgflg)

Legge dalla coda msqid un messaggio e lo scrive alla struttura puntata daptr. Anche qui si tratta di una struttura che inizia con un long seguito dasize byte destinati a contenere il contenuto del messaggio.Se non ci sono messaggi da leggere la chiamata e bloccante per il processochiamante a meno che non si utilizzi IPC_NOWAIT in msgflg.Il parametro mtype serve ad estrarre messaggi di un certo tipo:

0: viene estratto il primo messaggio disponibile (a prescindere dal tipo)secondo l’ordine FIFO;

>0: viene estratto il primo messaggio disponibile di tipo corrispondente amtype;

<0: viene estratto il primo tra i messaggi in coda di tipo t, con t minimo econ t≤|mtype|.

Ritorna -1 in caso di errore o il numero di byte del messaggio letto.Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 70 / 132

Page 71: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Esempio: estrazione di un messaggio dalla coda (1)

msgreceive.c

#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/ipc.h"#include "sys/msg.h"

#define DIM_MSG 1024

typedef struct{long mtype;char mtext[DIM_MSG ];

} msg;

int main() {int ds_coda;key_t chiave =40;msg messaggio;

if ( (ds_coda= msgget(chiave , IPC_CREAT|IPC_EXCL |0660)) == −1) {printf (" Errore nella chiamata msgget ().\n");exit (1); }

// crea ed invia il messaggiomessaggio.mtype =1;strncpy(messaggio.mtext ,"Hello world!", DIM_MSG );if (msgsnd(ds_coda , &messaggio , strlen(messaggio.mtext)+1, IPC_NOWAIT) == −1) {

printf (" Errore durante l'inserimento del messaggio .\n");exit (1); }

strncpy(messaggio.mtext ," CANCELLO IL MESSAGGIO DAL BUFFER", DIM_MSG );

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 71 / 132

Page 72: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Esempio: estrazione di un messaggio dalla coda (2)

msgreceive.c

// legge esso stesso il messaggioif (msgrcv(ds_coda , &messaggio , DIM_MSG , 0, 0) == −1) {

printf (" Errore durante l'estrazione del messaggio .\n");exit (1);

} elseprintf (" Messaggio letto: '%s'\n",messaggio.mtext);

// chiudo la codamsgctl(ds_coda , IPC_RMID , NULL);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 72 / 132

Page 73: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Esempio: Produttore/Consumatore di stringhe (1)

msgtext.c

// Crea due processi figli: uno legge stringhe e le mette in coda ,// l'altro legge i messaggi dalla coda e li visualizza.#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/ipc.h"#include "sys/msg.h"

#define DIM_MSG 1024typedef struct{

long mtype;char mtext[DIM_MSG ];

} msg;

// legge le stringhe da tastiera e le mette in codavoid produttore(int coda) {

msg messaggio;printf (" Inserire le stringhe ('quit ' per finire ): \n");do {

fgets(messaggio.mtext ,DIM_MSG ,stdin ); // legge una riga (compresso l'invio)messaggio.mtype =1;if (msgsnd(coda , &messaggio , strlen(messaggio.mtext)+1, IPC_NOWAIT) == −1) {

printf (" Errore durante l'inserimento del messaggio .\n");exit (1); }

printf (" Messaggio inviato: %s\n",messaggio.mtext);} while ( strcmp(messaggio.mtext ,"quit\n") != 0);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 73 / 132

Page 74: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Esempio: Produttore/Consumatore di stringhe (2)

msgtext.c

// riceve i messaggi dalla coda e li visualizza sullo standard outputvoid consumatore(int coda) {

msg messaggio;do {

// legge un messaggio bloccandosi se non ce ne sonoif (msgrcv(coda , &messaggio , DIM_MSG , 1, 0) == −1) {

printf (" Errore durante l'estrazione del messaggio .\n");exit (1); }

printf (" Messaggio ricevuto: %s\n",messaggio.mtext);} while ( strcmp(messaggio.mtext ,"quit\n") != 0);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 74 / 132

Page 75: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Esempio: Produttore/Consumatore di stringhe (3)

msgtext.c

int main() {int ds_coda;key_t chiave=IPC_PRIVATE;

// crea la codads_coda= msgget(chiave , IPC_CREAT|IPC_EXCL |0660);if ( ds_coda == −1 ) { printf (" Errore nella chiamata msgget ().\n"); exit (1); }

// crea i due processi figli: produttore e consumatoreif ( fork() != 0) {

if ( fork() != 0) {// corpo del padrewait(NULL);wait(NULL);

} elseproduttore(ds_coda );

} elseconsumatore(ds_coda );

msgctl(ds_coda , IPC_RMID , NULL);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 75 / 132

Page 76: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Esempio: Produttore di numeri a caso di 2 tipi (1)

numprod.c

// Un processo "produttore" che immette in una coda di messaggi dei numeri// di due tipi ( "A" e "B" ) generati a caso.#include "stdio.h"#include "stdlib.h"#include "sys/ipc.h"#include "sys/msg.h"

#define TYPE_A 1#define TYPE_B 2#define TYPE_EOF 3#define QUANTITY 10000typedef unsigned int numtype; // trasportiamo degli interitypedef struct{

long mtype;numtype mcontent;

} msg;

int main() {int ds_coda ,i;numtype numero;long tipo;msg messaggio;key_t chiave =50; // scelta arbitrariaunsigned long sommaA ,sommaB;srand(time(NULL )); // inizializza il generatore di numeri random con il timer corrente// crea la coda (o la apre se il consumatore e' gia ' partito)ds_coda= msgget(chiave , IPC_CREAT |0600);if ( ds_coda == −1 ) { printf (" Errore nella chiamata msgget ().\n"); exit (1); }

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 76 / 132

Page 77: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Esempio: Produttore di numeri a caso di 2 tipi (2)

numprod.c

sommaA=sommaB =0;printf (" Generero %d numeri interi :\n",QUANTITY );for (i=1;i<= QUANTITY;i++) {

numero =(rand ()%300); // genera un numero a casotipo=(rand ()%2)+ TYPE_A; // genera un tipo a caso tra TYPE_A e TYPE_Bmessaggio.mcontent=numero;messaggio.mtype=tipo;// invia il messaggio (bloccandosi se la coda e' piena)if (msgsnd(ds_coda , &messaggio , sizeof(numtype), 0) == −1) {

printf (" Errore durante l'inserimento del messaggio .\n");exit (1); }

if (tipo== TYPE_A)sommaA += numero;

elsesommaB += numero;

printf (" Messaggio n.%5d: numero =%3d, tipo=%c, sommaA =%8lu, sommaB =%8lu\n",\i,numero ,(tipo== TYPE_A?'A':'B'),sommaA ,sommaB );

// usleep (5000+( rand ()%40000));}// invia il messaggio di fine trasmissionemessaggio.mtype=TYPE_EOF;messaggio.mcontent =0;msgsnd(ds_coda , &messaggio , sizeof(numtype), 0);// esce senza cancellare la coda (ci pensera ' il consumatore)exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 77 / 132

Page 78: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Esempio: Consumatore di numeri (1)

numcons.c

// Un processo "consumatore" che preleva dei numeri (di due tipi) da una coda ,// e ne fa le somme (distinte per tipo) man mano che li estrae#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/ipc.h"#include "sys/msg.h"

#define TYPE_A 1#define TYPE_B 2#define TYPE_EOF 3

typedef unsigned int numtype; // trasportiamo degli interitypedef struct{

long mtype;numtype mcontent;

} msg;

int main() {int ds_coda ,i;msg messaggio;key_t chiave =50; // scelta arbitraria (deve essere uguale al produttore)unsigned long sommaA ,sommaB;

// apre la coda (eventualmente creandola se il produttore non e' ancora partito)ds_coda= msgget(chiave , IPC_CREAT |0600);if ( ds_coda == −1 ) { printf (" Errore nella chiamata msgget ().\n"); exit (1); }

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 78 / 132

Page 79: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Code di messaggi

Esempio: Consumatore di numeri (2)

numcons.c

i=1; sommaA=sommaB =0;while (1) {

// legge un messaggio di qualunque tipo (bloccandosi se non ce ne sono)if (msgrcv(ds_coda , &messaggio , sizeof(numtype), 0, 0) == −1) {

printf (" Errore durante l'estrazione del messaggio .\n");exit (1); }

if (messaggio.mtype == TYPE_EOF) // siamo arrivati alla fine?break;

else if (messaggio.mtype== TYPE_A) // e' di tipo A?sommaA += messaggio.mcontent;

else // no , e' di tipo BsommaB += messaggio.mcontent;

printf (" Messaggio n.%5d: numero =%3d, tipo=%c, sommaA =%8lu, sommaB =%8lu\n",\i,messaggio.mcontent ,( messaggio.mtype== TYPE_A?'A':'B'),sommaA ,sommaB );

i++;}// il consumatore distrugge la coda in uscitamsgctl(ds_coda , IPC_RMID , NULL);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 79 / 132

Page 80: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Memoria condivisa (1)

Un altro costrutto che UNIX ci mette a disposizione per la comunicazionetra processi e la memoria condivisa. Una memoria condivisa e una porzionedi memoria accessibile da piu processi.I processi che condividono la stessa area di memoria possono utilizzare talezona per scambiarsi dei dati in modo arbitrario (leggendovi e scrivendovi).Ogni memoria condivisa ha un nome univoco detto chiave di utilizzo deltutto analogo a quanto visto per le code di messaggi. La chiave e cio cheogni processo utilizza per riferirsi alla medesima area di memoria dacondividere.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 80 / 132

Page 81: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Memoria condivisa (2)

Se un processo vuole usare un’area di memoria condivisa deve prima aprirla(cosı come per le code di messaggi) ed attaccarla al proprio spazio diindirizzamento. Una volta fatta questa operazione, puo leggervi e scrivervicome una qualunque altra area disponibile, utilizzando le strutture dati piuidonee.Quando un processo non desidera piu utilizzare quell’area di memoriacondivisa puo staccarla dal suo spazio di indirizzamento (cio vienecomunque fatto in fase di distruzione del processo). Questa operazionenon implica la distruzione dell’area di memoria, infatti (cosı come per lecode di messaggi) si tratta di strutture di memoria permanenti. E’necessario distruggere esplicitamente l’area di memoria (cosı come per lecode di messaggi). Ovviamente, la proprieta di permanenza vale anche peri dati memorizzati nella memoria condivisa.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 81 / 132

Page 82: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Creare/aprire/distruggere aree di memoria condivisa (1)

Con una sintassi/semantica simile a quanto visto per le code di messaggi,per creare/aprire una area di memoria condivisa si deve utilizzare lachiamata di sistema:int shmget(key_t key, size_t size, int shmflg)

Dove key e la chiave per identificare l’area di memoria, size indica lataglia della memoria, ovvero quanto grande deve essere quest’area dimemoria. I flag che si possono passare con shmflg sono analoghi a quantovisto per le code: una maschera ottale di diritti di accesso, combinati conflag del tipo IPC_CREAT e IPC_EXCL.Come chiave key si puo utilizzare il valore speciale IPC_PRIVATE perspecificare una nuova area non gia allocata.Senza flag di apertura, la chiamata si aspetta che l’area gia esista(ignorando quindi dimensione e diritti specificati); con IPC_CREAT, se nonne esiste una con quella chiave, la crea. Aggiungendo anche IPC_EXCL siimpone una creazione esclusiva, quindi se gia esiste viene riportato unerrore.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 82 / 132

Page 83: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Creare/aprire/distruggere aree di memoria condivisa (2)

La chiamata riporta un descrittore di memoria che servira per la fasesuccessiva. In caso di errore viene riportato -1.

E’ interessante vedere che cosa succede alle aree di memoria condivise aseguito di alcune chiamate di sistema che hanno a che vedere con lagestione dei processi:

fork(): il figlio eredita le aree di memoria condivise ed attaccate;

exec() e exit() : tutte le aree di memoria condivise ed attaccate,vengono “staccate” (ma non distrutte).

Per distruggere un’area di memoria condivisa si deve usare esplicitamentela chiamata:int shmctl(int shmid, int cmd, struct shmid_ds *buf)

L’uso e i comandi di manipolazione sono analoghi a quanto visto permsgctl(), quindi ci limiteremo a dire che per l’eliminazione e sufficienteinvocare: shmctl(descrittore,IPC_RMID,NULL)

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 83 / 132

Page 84: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Annettere un’area di memoria condivisa (1)

Per poter leggere e/o scrivere in una area di memoria che si e appenacreata/aperta con shmget() e prima necessario chiedere al kernel dimappare tale area nel nostro spazio di indirizzamento, in modo tale che noipossiamo interagirvi come una qualunque altra area di memoria allocata.Per fare cio, usiamo la chiamata:void *shmat(int shmid, void *shmaddr, int shmflg)

Con shmid si indica il descrittore di memoria che ci ha riportatomsgget(), shmaddr e un puntatore che indica l’indirizzo a cui vogliamoessere mappati l’area di memoria oppure, passando NULL, lasciamo liberi ilkernel di sceglierci un qualunque indirizzo. In shmflg possiamo specificareil flag SHM_RDONLY per fare una mappatura in sola lettura (altrimenti vienemappato sia in lettura che in scrittura). Ci sono altri flag utili nel caso incui specifichiamo un puntatore su cui fare al mappatura che nonapprofondiremo. Tutti questi flag si trovano nella intestazione sys/shm.h.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 84 / 132

Page 85: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Annettere un’area di memoria condivisa (2)

La chiamata shmat() riporta l’indirizzo di memoria a cui e stata attaccatala memoria condivisa e a partire dal quale e possibile accedere allamemoria (come se fosse un array). Ricordatevi che le aree di memoriacondivise sono sempre mappate in modo contiguo.La chiamata riporta -1 (un indirizzo che non esiste) in caso di errore.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 85 / 132

Page 86: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Disnettere un’area di memoria collegata

Volendo staccare esplicitamente un’area di memoria gia collegata al nostrospazio di indirizzamento si puo usare la chiamata:int shmdt(void *shmaddr)

Dove shmaddr e l’indirizzo a cui la memoria e stata in precedenzamappata.Questa operazione non e, in effetti, strettamente necessaria: infattiquando il processo termina, il suo spazio di indirizzamento viene distruttoe quindi la memoria condivisa viene “staccata”. Questo pero non implicache l’area di memoria condivisa venga distrutta (continua ad esistere).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 86 / 132

Page 87: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Esempio: Scambio di numeri con memoria condivisa (1)

shmnumbers.c

// Crea un processo figlio che legge numeri da tastiera e li deposita in// un 'area di memoria condivisa; poi ne crea un altro che li rilegge.#include "stdio.h"#include "stdlib.h"#include "sys/ipc.h"#include "sys/shm.h"

#define MAX_NUMBERS 10

// legge numeri da tastiera mettendoli nella memoria condivisavoid scrittore(int id) {

int ∗p;int numero ,q=0;printf ("\ nProcesso Scrittore :\n");// collega la memoria condivisa con lo spazio di indirizzamento (in lettura/scrittura)p= (int ∗) shmat(id ,NULL ,0);if ( p == (int ∗)−1 ) { printf (" Errore nella chiamata shmat ().\n"); exit (1); }do {

printf ("\ nNumero da inserire ('0' per finire ): ");scanf ("%d",&numero );if (numero ==0) break;p[q+1]= numero; // scrive nella memoria (lasciando il primo slot libero)q++;printf (" Numero inserito: %d\n",numero );

} while ( q < MAX_NUMBERS );p[0]=q; // scrive nel primo slot , la quantita ' di numeri depositatiexit (0); // il primo figlio esce

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 87 / 132

Page 88: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Esempio: Scambio di numeri con memoria condivisa (2)

shmnumbers.c

// prende i numeri dalla memoria condivisa e li stampa a videovoid lettore(int id) {

int ∗p;int i,q=0;printf ("\ nProcesso Lettore :\n");// collega la memoria condivisa con lo spazio di indirizzamento (in sola lettura)p= (int ∗) shmat(id ,NULL ,SHM_RDONLY );if ( p == (int ∗)−1 ) { printf (" Errore nella chiamata shmat ().\n"); exit (1); }q=p[0]; // legge quanti numeri ci sono nell 'area condivisaprintf ("n.%d numeri depositati: ",q);for (i=1;i<=q;i++)

printf ("%d ",p[i]);printf ("\n");exit (0); // il secondo figlio esce

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 88 / 132

Page 89: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Esempio: Scambio di numeri con memoria condivisa (3)

shmnumbers.c

int main() {int id_shm;

// crea l'area di memoria condivisaid_shm=shmget(IPC_PRIVATE ,sizeof(int)∗(MAX_NUMBERS +1), IPC_CREAT|IPC_EXCL |0600);if ( id_shm == −1 ) { printf (" Errore nella chiamata shmget ().\n"); exit (1); }

// crea un primo processo figlio che scrive nella memoria condivisaif ( fork() != 0)

wait(NULL);else

scrittore(id_shm );

// dopo che il primo figlio e' uscito , il secondo legge dalla memoria condivisaif ( fork() != 0)

wait(NULL);else

lettore(id_shm );

// distrugge l'area di memoria condivisashmctl(id_shm , IPC_RMID , NULL);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 89 / 132

Page 90: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Controllo delle risorse occupate

Puo succedere che un processo allochi una risorsa persistente, come unacoda di messaggi o una area di memoria condivisa, e che non la distruggaalla fine, magari per una sua terminazione imprevista.

Per controllare le risorse persistenti allocate si puo usare il comando di shellipcs: lista le risorse (code, segmenti condivisi e semafori) allocate in quelmomento, con dati come la chiave, il proprietario e le modalita di accesso.

Per liberare una risorsa si puo usare il comando ipcrm [ shm|msg|sem ]

id, dove id e l’identificativo (non la chiave!) della risorsa.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 90 / 132

Page 91: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Modalita di utilizzo dell’area condivisa

Tra i meccanismi di comunicazione tra processi, l’uso di un’area dimemoria condivisa e il metodo piu efficiente.Inoltre le code di messaggi vincolano la trasmissione in modo sequenziale,limitandone l’utilita in certi contesti di uso.Nell’esempio precedente, il processo scrittore inserisce i dati nell’areacondivisa e, solo dopo che questo ha finito, il processo lettore estrae i dati.Questa modalita di utilizzo non crea problemi.I problemi sorgono quando i due processi cercano di accedervi (inlettura/scrittura) in modo concorrente.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 91 / 132

Page 92: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Esempio di problema nella modifica concorrente

Abbiamo gia visto nella teoria i problemi legati alle corse critiche.Riprendiamo qui un esempio: supponiamo che P1 e P2 siano due processidi gestione per il versamento su un conto corrente, dove x e il saldo delconto stesso.Una operazione di incremento del tipo x=x+1 non e atomica, infatti puoessere vista come una sequenza:

leggi x dalla memoria condivisa;

calcola x+1;

scrivi il nuovo valore nella memoria condivisa.

Potrebbe succedere che P1, incrementando x, venga interrotto dalloscheduler a meta della sequenza (poco prima di scrivere il nuovo valore).A questo punto il controllo passa a P2 che completa il suo incremento.Una volta ripreso il controllo P1, questo finisce la sua operazione con ilrisultato che il versamento fatto da P2 viene “sovrascritto”.Questo crea una inconsistenza nelle operazioni.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 92 / 132

Page 93: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Esempio: problema con la concorrenza (1)

concproblem.c

// Un esempio che mostra i problemi con le esecuzioni concorrenti:// entrambi i figli incrementano la variabile condivisa , controllandone i risultati finali#include "stdio.h"#include "stdlib.h"#include "sys/ipc.h"#include "sys/shm.h"

int main() {int id_shm;int ∗conto;int temp ,versamento =1000;

id_shm=shmget(IPC_PRIVATE ,sizeof(int),IPC_CREAT|IPC_EXCL |0600);if ( id_shm == −1 ) { printf (" Errore nella chiamata shmget ().\n"); exit (1); }

conto= (int ∗) shmat(id_shm ,NULL ,0); // i figli erediteranno l'area condivisa∗conto =0;

if ( fork() == 0) { // primo figliofor (; versamento >0; versamento−−){

//(∗conto )++;temp=∗conto;printf ("P1: letto =%d\t scritto =%d\n",temp ,temp +1);∗conto=temp +1;usleep(rand ()%5000);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 93 / 132

Page 94: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Memoria Condivisa

Esempio: problema con la concorrenza (2)

concproblem.c

exit (0);} else if ( fork() == 0) { // secondo figlio

for (; versamento >0; versamento−−){//(∗conto )++;temp=∗conto;printf ("P2: letto =%d\t scritto =%d\n",temp ,temp +1);∗conto=temp +1;usleep(rand ()%5000);

}exit (0);

}

wait(NULL); wait(NULL); // aspetta che finiscano entrambi i figliprintf (" Padre: valore finale =%d\n",∗conto );

shmctl(id_shm , IPC_RMID , NULL);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 94 / 132

Page 95: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Sezioni critiche

Il problema visto negli esempi precedenti si puo generalizzare e risolveredefinendo le cosiddette sezioni critiche: consideriamo un sistema in cui cisono n processi che competono per l’utilizzo di alcune risorse condivise(per esempio, aree di memoria, tabelle, file, dispositivi, ecc.).Per ogni processo che ha la necessita di accedere in scrittura alla risorsacondivisa, esiste una porzione di codice detta sezione critica che raccogliele istruzioni di alterazione della risorsa.Affinche non si presentino problemi di inconsistenza dello stato dellarisorsa si deve fare in modo che quando un processo entra nella sua sezionecritica, nessun altro processo e autorizzato ad entrare nella propria. Inaltre parole, le sezioni critiche dei vari processi devono essere eseguite inmutua esclusione.Questo e sufficiente per garantire la coerenza delle operazioni di accesso.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 95 / 132

Page 96: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

I semafori

Un modo semplice per risolvere il problema delle sezioni critiche e quello diimpiegare i semafori.

Un semaforo non e altro che una variabile numerica intera S, contenenteun valore non negativo, a cui si puo accedere solo con due operazioniatomiche standard denominate WAIT e SIGNAL:

WAIT: se S>0, allora decrementa S di una unita; se S ha valore nullo,allora mette il processo in attesa che il valore diventi strettamentepositivo;

SIGNAL: incrementa S di una unita.

Le operazioni sono atomiche nel senso che il sistema operativo devegarantire che l’esecuzione delle istruzioni che compongono tali operazioninon puo essere interrotta a meta dallo scheduler per l’esecuzione di unaltro processo (concorrente).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 96 / 132

Page 97: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

I semafori per la mutua esclusione

Per risolvere il problema delle sezioni critiche si puo utilizzare un semaforoS inizializzato a 1 e sfruttare le operazioni nel seguente modo:

WAIT: da chiamare quando il processo entra nella sua sezione critica;

SIGNAL: da usare quando si esce dalla sezione critica.

In questo modo, quando non c’e nessun processo nella sua sezione critica ilcontatore S ha sempre valore 1. Quando un processo e nella sua sezionecritica, l’operazione WAIT azzera S e vieta a qualunque altro processo dientrare nella propria sezione critica. Solo quando il processo esce dalla suasezione critica S tornera positivo e, se c’era un processo concorrentebloccato in attesa, questo verra sbloccato.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 97 / 132

Page 98: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

I semafori per il conteggio delle risorse (1)

I semafori si possono utilizzare anche per risolvere il problema delproduttore/consumatore: supponiamo di avere un processo produttore P

che produce dei dati che inserisce in una coda di dimensione n e di avereun processo consumatore C che preleva questi dati e che si deve bloccare inattesa se la coda e vuota.

Per implementare questo meccanismo di blocco si puo utilizzare unsemaforo S1 inizializzato a zero: il produttore P dopo aver depositato undato nella coda utilizza SIGNAL per incrementare il contatore S1, ilconsumatore prima di tentare di prelevare un dato dalla coda, invoca WAIT;cio implica un bloccaggio di C se la coda e vuota.

Praticamente S1 conteggia le posizioni occupate nella coda.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 98 / 132

Page 99: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

I semafori per il conteggio delle risorse (2)

In realta esiste anche il problema duale per il produttore nel caso in cui lacoda sia di dimensione finita: P si deve bloccare nell’inserimento se non c’espazio nella coda, in attesa che C estragga dei dati.Cio viene implementato con un secondo semaforo S2 inizializzato a n (ladimensione della coda): P invoca WAIT su S2 prima di un inserimento ed ilconsumatore C utilizza SIGNAL subito dopo aver fatto l’estrazione.Praticamente S2 conteggia le posizioni libere nella coda.

Per garantire una perfetta implementazione di un meccanismoproduttore/consumatore si deve utilizzare un ulteriore semaforo S3 per lamutua esclusione delle operazioni sulla coda (per un totale di 3 semafori).

Questo meccanismo in effetti si puo utilizzare anche con piu produttori epiu consumatori.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 99 / 132

Page 100: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

I Semafori sotto Unix

I sistemi UNIX offrono una implementazione dei semafori che e possibileutilizzare nei nostri programmi. In realta, vengono gestiti array di semafori.Ogni semaforo pero puo essere gestito individualmente.Per creare/aprire un array di semafori si usa la chiamata:int semget(key_t key, int nsems, int semflg)

Il significato e molto simile a quanto visto per le code e per le aree dimemoria condivise: key e una chiave numerica che identifica l’array esemflg permette di specificare le modalita di creazione/apertura con isoliti flag IPC_CREAT e IPC_EXCL. Per key si puo usare anche la costanteIPC_PRIVATE. nsems specifica il numero di semafori che l’array devegestire.In caso di successo, viene riportato un descrittore di semafori (oidentificatore di semafori) che individua l’array di semafori all’interno delsistema. In caso di problemi viene riportato -1.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 100 / 132

Page 101: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Operazioni sui semafori (1)

Per operare sul contatore di un semaforo (di un array) si deve usare lachiamata:int semop(int semid, struct sembuf *ops, unsigned nops)

semid e il descrittore riportato da semget() che identifica l’array disemafori su cui operare, ops punta ad un vettore formato da una o piuistanze di una struttura standard definita in sys/sem.h. Ogni elemento diquesto vettore rappresenta una operazione da compiere su un semaforodell’array. nops contiene il numero di elementi del vettore puntato da ops.

Ogni operazione di ops opera su un singolo semaforo. L’insieme delleoperazioni specificate con ops viene eseguito in modo atomico: o tutte leoperazioni vengono eseguite (se cio e possibile) o nessuna di esse vieneeseguita. Cio complica la gestione di piu operazioni.Per semplificare la spiegazione, supponiamo di voler fare una operazionealla volta, quindi supporremo che ops punti ad una singola struttura e chenops sia pari a 1.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 101 / 132

Page 102: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Operazioni sui semafori (2)

La struttura struct sembuf e formata dai seguenti campi:

sem_num: il numero del semaforo dell’array su cui operare (lanumerazione parte da 0);

sem_op: indica l’operazione da effettuare;

sem_flg: specifica la politica di attesa nel caso di piu operazioni (adesempio si puo utilizzare IPC_NOWAIT), ma per semplicitaassumeremo nel seguito che sem_flg=0.

Supponiamo che il valore del semaforo numero sem_num sia S:

sem_op<0: se S+sem_op<0, allora il processo chiamante viene sospesofinche S+sem_op≥0, nel qual caso viene aggiunto il valore negativosem_op ad S (che quindi continuera ad essere non negativo);

sem_op=0: il processo chiamante viene sospeso finche S=0;

sem_op>0: viene sommato sem_op ad S.

Vedremo come utilizzare semop() per implementare WAIT e SIGNAL.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 102 / 132

Page 103: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Implementare WAIT e SIGNAL

Il kernel non mette a disposizione le operazioni studiate nella teoria diWAIT e SIGNAL, ma si possono ottenere in modo molto semplice:

int WAIT(int sem_des, int num_semaforo){

struct sembuf operazioni[1] = {{num_semaforo,-1,0}};

return semop(sem_des, operazioni, 1);

}

int SIGNAL(int sem_des, int num_semaforo){

struct sembuf operazioni[1] = {{num_semaforo,+1,0}};

return semop(sem_des, operazioni, 1);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 103 / 132

Page 104: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Inizializzare e leggere un semaforo (1)

Prima di porter iniziare ad utilizzare i semafori e necessario imparare adimpostare il loro valore iniziale ed, eventualmente, leggerne quello corrente.Per fare cio esiste una chiamata apposita abbastanza complessa chepermette di fare anche molte altre cose. Noi ne vedremo solo alcunidettagli che ci mettano in grado di utilizzare i semafori sotto UNIX inmodo proficuo:int semctl(int sem_des, int sem_num, int cmd, union senum arg)

Il valore sem_des individua l’array di semafori e sem_num seleziona unospecifico semaforo su cui operare (la numerazione parte sempre da 0).Il campo arg rappresenta gli argomenti dell’operazione e variano daun’operazione all’altra. Si tratta di una union che raccoglie i vari tipirichiesti dalle operazioni. Semplificandola solo alle operazioni che vedremo,abbiamo:

union semun {

int val; /* usato se cmd == SETVAL */

unsigned short *array; /* se cmd == GETALL o SETALL */

}Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 104 / 132

Page 105: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Inizializzare e leggere un semaforo (2)

Tra le operazioni che possiamo specificare attraverso cmd abbiamo:

IPC_RMID: rimuove l’array di semafori indicato da sem_des; tutti glialtri argomenti vengono ignorati;

SETVAL: il valore in val (elemento della union semun) vieneassegnato al semaforo numero sem_num;

SETALL: tutti i semafori dell’array sem_des vengono inizializzati con ivalori contenuti nel vettore puntato dall’elemento della union array

(che il programmatore deve predisporre); il primo semaforo prendera ilvalore array[0], il secondo array[1], ecc.

GETVAL: la funzione ritorna il valore del semaforo numero sem_num;

GETALL: tutti i valori correnti dei semafori vengono scritti nel buffer(preallocato dal programmatore) puntato da array.

Ci sono altre operazioni che tralasciamo per semplicita.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 105 / 132

Page 106: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Esempio: memoria condivisa & semafori (1)

semtext.c

// Crea due processi figli: uno legge da tastiera stringhe e le manda al secondo.// I due processi usano memoria condivisa e semafori per comunicare.#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/ipc.h"#include "sys/shm.h"#include "sys/sem.h"

#define DIM_MSG 1024

// legge le stringhe da tastiera e le scrive sulla memoria condivisavoid produttore(int shm , int sem) {

char ∗p;struct sembuf SIGNAL [1]={{0 ,+1 ,0}};

// attacca la memoria condivisia al suo spazio di indirizzamentop= (char ∗) shmat(shm , NULL , 0);if ( p == (char ∗)−1 ) { printf (" Errore nella chiamata shmat ().\n"); exit (1); }

printf (" Inserire le stringhe da copiare sulla memoria condivisa ('quit ' per finire ): \n");do {

fgets(p,DIM_MSG ,stdin ); // legge una riga e la scrive direttamente sulla shmprintf (" Messaggio scritto: %s",p);semop(sem ,SIGNAL ,1); // invia SIGNAL al semaforo

} while ( strcmp(p,"quit\n") != 0);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 106 / 132

Page 107: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Esempio: memoria condivisa & semafori (2)

semtext.c

// legge i messaggi dalla memoria condivisa e li visualizza sullo standard outputvoid consumatore(int shm , int sem) {

char ∗p;struct sembuf WAIT[1]={{0,−1,0}};

// attacca (in sola lettura) la memoria condivisia al suo spazio di indirizzamentop= (char ∗) shmat(shm , NULL , SHM_RDONLY );if ( p == (char ∗)−1 ) { printf (" Errore nella chiamata shmat ().\n"); exit (1); }

do {semop(sem ,WAIT ,1); // invoca WAIT sul semaforo mettendosi in attesaprintf (" Messaggio letto: %s",p); // legge e stampa il messaggio

} while ( strcmp(p,"quit\n") != 0);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 107 / 132

Page 108: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Esempio: memoria condivisa & semafori (3)

semtext.c

int main() {int id_shm , id_sem;// crea l'area di memoria condivisaid_shm= shmget(IPC_PRIVATE , DIM_MSG , IPC_CREAT|IPC_EXCL |0600);if ( id_shm == −1 ) { printf (" Errore nella chiamata shmget ().\n"); exit (1); }

// crea il semaforo per segnalare da disponibilita ' di messaggiid_sem= semget(IPC_PRIVATE , 1, IPC_CREAT|IPC_EXCL |0600);if ( id_sem == −1 ) { printf (" Errore nella chiamata semget ().\n"); exit (1); }// imposta il valore iniziale del semaforo ad 0if ( semctl(id_sem , 0, SETVAL , 0) == −1 ) { printf (" Errore nella chiamata semctl ().\n"); exit (1); }

// crea i due processi figli: produttore e consumatoreif ( fork() != 0) {

if ( fork() != 0) {// corpo del padrewait(NULL);wait(NULL);

} elseproduttore(id_shm ,id_sem );

} elseconsumatore(id_shm ,id_sem );

// distrugge memoria condivisa e semaforoshmctl(id_shm , IPC_RMID , NULL); // l'ultimo parametro e' ignoratosemctl(id_sem , 0, IPC_RMID , 0);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 108 / 132

Page 109: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Esempio: versamenti concorrenti con semaforo (1)

concfixed.c

// L'esempio del problema con i versamenti concorrenti risolto con un semaforo.#include "stdio.h"#include "stdlib.h"#include "sys/ipc.h"#include "sys/shm.h"#include "sys/sem.h"

int main() {int id_shm , id_sem;int ∗conto;int temp ,versamento =1000;struct sembuf WAIT[1]={{0,−1,0}};struct sembuf SIGNAL [1]={{0 ,+1 ,0}};

id_shm=shmget(IPC_PRIVATE ,sizeof(int),IPC_CREAT|IPC_EXCL |0600);if ( id_shm == −1 ) { printf (" Errore nella chiamata shmget ().\n"); exit (1); }conto= (int ∗) shmat(id_shm ,NULL ,0); // i figli erediteranno l'area condivisa∗conto =0;

id_sem= semget(IPC_PRIVATE , 1, IPC_CREAT|IPC_EXCL |0600);if ( id_sem == −1 ) { printf (" Errore nella chiamata semget ().\n"); exit (1); }// imposta il valore iniziale del semaforo ad 1 (semaforo mutex)if ( semctl(id_sem , 0, SETVAL , 1) == −1 ) { printf (" Errore nella chiamata semctl ().\n"); exit (1); }

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 109 / 132

Page 110: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Esempio: versamenti concorrenti con semaforo (2)

concfixed.c

if ( fork() == 0) { // primo figliofor (; versamento >0; versamento−−){

semop(id_sem ,WAIT ,1); // inizio sezione critica: WAITtemp=∗conto;printf ("P1: letto =%d\t scritto =%d\n",temp ,temp +1);∗conto=temp +1;semop(id_sem ,SIGNAL ,1); // fine sezione critica: SIGNALusleep(rand ()%5000);

}exit (0);

} else if ( fork() == 0) { // secondo figliofor (; versamento >0; versamento−−){

semop(id_sem ,WAIT ,1); // inizio sezione critica: WAITtemp=∗conto;printf ("P2: letto =%d\t scritto =%d\n",temp ,temp +1);∗conto=temp +1;semop(id_sem ,SIGNAL ,1); // fine sezione critica: SIGNALusleep(rand ()%5000);

}exit (0);

}

wait(NULL); wait(NULL); // aspetta che finiscano entrambi i figliprintf (" Padre: valore finale =%d\n",∗conto );shmctl(id_shm , IPC_RMID , NULL);semctl(id_sem , 0, IPC_RMID , 0);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 110 / 132

Page 111: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Esempio: prod./cons. con memoria cond. e semafori (1)

shmsemprodcons.c

// Due figli: un produttore ed un consumatori di numeri generati a random.// I due processi usano una area di memoria condivisa che contiene piu '// elementi e 3 semafori per coordinarsi.#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/ipc.h"#include "sys/shm.h"#include "sys/sem.h"

#define DIM_BUFFER 10#define HOW_MANY 200#define S_MUTEX 0#define S_EMPTY 1#define S_FULL 2

int WAIT(int sem_des , int num_semaforo ){struct sembuf operazioni [1] = {{ num_semaforo ,−1,0}};return semop(sem_des , operazioni , 1);

}

int SIGNAL(int sem_des , int num_semaforo ){struct sembuf operazioni [1] = {{ num_semaforo ,+1 ,0}};return semop(sem_des , operazioni , 1);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 111 / 132

Page 112: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Esempio: prod./cons. con memoria cond. e semafori (2)

shmsemprodcons.c

// produce numeri random e li scrive nella memoria condivisavoid produttore(int shm , int sems) {

int ∗p,∗in,∗out;int i,number ,len;long tot=0;srand(time(NULL )); // randomizza il generatore di numeri pseudo−causalip= (int ∗) shmat(shm , NULL , 0); // attacca la memoria condivisiaif ( p == (int ∗)−1 ) { printf (" Errore nella chiamata shmat ().\n"); exit (1); }in=p+DIM_BUFFER; // la penultima posizione conterra ' l'indice dell 'inizio codaout=p+DIM_BUFFER +1; // l'ultima posizione conterra ' l'indice della fine della coda∗in=0; ∗out=0;for (i=0;i<HOW_MANY;i++) {

WAIT(sems ,S_EMPTY ); // diminuisce le posizioni vuoteWAIT(sems ,S_MUTEX ); // entra nella sezione critica

number=rand ()%500;p[∗in]= number; // scrive il numero nella coda∗in=(∗in+1)% DIM_BUFFER; // sposta l'indice in avanti (ciclicamente)len=(∗in>∗out? (∗in−∗out) : (DIM_BUFFER − (∗out−∗in)) ); // calcola in # di elementi in codaprintf (" Produttore: %d \t(in=%d out=%d len=%d)\n",number ,∗in,∗out ,len);tot+= number;

SIGNAL(sems ,S_MUTEX ); // esce dalla sezione criticaSIGNAL(sems ,S_FULL ); // incrementa le posizioni piene// usleep(rand ()%40000);

}printf (" Produttore: totale finale =%ld\n",tot);exit (0);

}Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 112 / 132

Page 113: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Esempio: prod./cons. con memoria cond. e semafori (3)

shmsemprodcons.c

// legge i numeri dalla coda e li stampa a videovoid consumatore(int shm , int sems) {

int ∗p,∗in,∗out;int i,number ,len;long tot=0;

p= (int ∗) shmat(shm , NULL , 0); // attacca la memoria condivisiaif ( p == (int ∗)−1 ) { printf (" Errore nella chiamata shmat ().\n"); exit (1); }in=p+DIM_BUFFER; // la penultima posizione conterra ' l'indice dell 'inizio codaout=p+DIM_BUFFER +1; // l'ultima posizione conterra ' l'indice della fine della coda

for (i=0;i<HOW_MANY;i++) {WAIT(sems ,S_FULL ); // diminuisce le posizioni pieneWAIT(sems ,S_MUTEX ); // entra nella sezione critica

number=p[∗out]; // legge il numero dalla coda∗out=(∗out +1)% DIM_BUFFER; // sposta l'indice in avanti (ciclicamente)len=(∗in>=∗out? (∗in−∗out) : (DIM_BUFFER − (∗out−∗in)) ); // calcola in # di elementi in codaprintf (" Consumatore: %d \t(in=%d out=%d len=%d)\n",number ,∗in,∗out ,len);tot+= number;

SIGNAL(sems ,S_MUTEX ); // esce dalla sezione criticaSIGNAL(sems ,S_EMPTY ); // incrementa le posizioni vuoteusleep(rand ()%50000);

}printf (" Consumatore: totale finale =%ld\n",tot);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 113 / 132

Page 114: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi Esecuzioni concorrenti

Esempio: prod./cons. con memoria cond. e semafori (4)

shmsemprodcons.c

int main() {int id_shm , id_sems;

// crea l'area di memoria condivisa per i DIM_BUFFER numeri e due variabili aggiuntiveid_shm= shmget(IPC_PRIVATE , (DIM_BUFFER +2)∗sizeof(int), IPC_CREAT|IPC_EXCL |0600);if ( id_shm == −1 ) { printf (" Errore nella chiamata shmget ().\n"); exit (1); }// crea i 3 semafori (S_MUTEX ,S_EMPTY ,S_FULL)id_sems= semget(IPC_PRIVATE , 3, IPC_CREAT|IPC_EXCL |0600);if ( id_sems == −1 ) { printf (" Errore nella chiamata semget ().\n"); exit (1); }// imposta i valori iniziali dei semaforisemctl(id_sems , S_MUTEX , SETVAL , 1);semctl(id_sems , S_EMPTY , SETVAL , DIM_BUFFER );semctl(id_sems , S_FULL , SETVAL , 0);// crea i due processi figli: produttore e consumatoreif ( fork() != 0) {

if ( fork() != 0) {// corpo del padrewait(NULL);wait(NULL);

} elseproduttore(id_shm ,id_sems );

} elseconsumatore(id_shm ,id_sems );

// distrugge memoria condivisa e semaforoshmctl(id_shm , IPC_RMID , NULL);semctl(id_sems , 0, IPC_RMID , 0);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 114 / 132

Page 115: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

I Segnali sotto UNIX

I segnali sono un semplice mezzo per notificare ai processi degli eventiasincroni. Si possono vedere come degli interrupt software. A differenzadei messaggi delle code FIFO:

un segnale puo essere inviato in qualsiasi momento, occasionalmenteda un processo, ma spesso dal kernel (ad esempio, per una istruzioneillegale);

un segnale non viene necessariamente ricevuto e processato;implicitamente la maggior parte dei segnali provoca la terminazionedel processo. Eventualmente un processo puo decidere di ignorarli ogestirli;

i segnali non hanno alcun contenuto informativo; in particolare, non sipuo conoscere il mittente del segnale.

E’ importante trattarli poiche i nostri processi ricevono segnali dal kernelindipendentemente dalla nostra volonta.I segnali dovrebbero essere usati solo per segnalare eventi eccezionali, enon come canale di comunicazione tra processi.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 115 / 132

Page 116: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Segnali piu comuni (1)

Di seguito riportiamo una lista dei segnali principali con relative costanti:

SIGHUP (1): hangup. Un processo riceve questo segnale quando il terminalea cui era associato viene chiuso o scollegato (la finestra viene chiusa o, nelcaso di un collegamento remoto, la connessione cade). Spesso molti processiserver rileggono il loro file di configurazione alla ricezione di questo segnale;

SIGINT (2): Interrupt. Viene ricevuto da un processo quando l’utente premela combinazione di tasti di interrupt (solitamente CTRL+C);

SIGQUIT (3): Quit. Simile a SIGINT ad eccezione del fatto che il processogenera un code dump, ovvero un file in cui viene messa l’immagine dellamemoria del processo al momento in cui si riceve il segnale SIGQUIT. Questaimmagine puo essere utile ai fini del debugging. Solitamente questo segnaleviene invocato dalla combinazione di tasti CTRL+\;SIGILL (4): Illegal Instruction. Il processo ha tentato di eseguire unaistruzione proibita o inesistente;

SIGKILL (9): questo segnale non puo essere intercettato dal processo, chenon puo fare altro che terminare. E’ il modo piu certo e brutale per“uccidere” un processo;

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 116 / 132

Page 117: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Segnali piu comuni (2)

SIGSEGV (11): Segmentation Violation. Generato quando il processo tentadi accedere ad un indirizzo fuori dal suo spazio di indirizzamento;

SIGALRM (14): Alarm. Un segnale che il processo si puo auto-inviare allascadenza di un certo intervallo di tempo (lo vedremo dopo);

SIGTERM (15): Termination. Inviato ad un processo come richiesta nonforzata di terminazione. Simile a SIGKILL ma puo essere ignorato;

SIGUSR1, SIGUSR2 (10,12): User defined. Non hanno un significato precisoe possono essere utilizzati dai processi per implementare un rudimentaleprotocollo di comunicazione;

SIGCHLD (17): Child Death. Inviato ad un processo quando uno dei suoi figlitermina.

Per ulteriori segnali e dettagli consultare: man 7 signal.Tutte queste costanti sono definite nell’header signal.h.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 117 / 132

Page 118: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Inviare un segnale

Per inviare un segnale ad un altro processo si puo utilizzare la chiamata disistema:int kill(pid_t pid, int sig)

Dove pid e il PID del processo a cui vogliamo inviare il segnale di numerosig.La chiamata kill() ritorna -1 in caso di errore, 0 in caso di successo.

Un processo puo inviare segnali solo ad altri processi che appartengono allostesso proprietario. Ad esempio, non possiamo uccidere i processi di unaltro utente. A tutto questo fa eccezione l’amministratore root, lui puotutto...

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 118 / 132

Page 119: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Puntiamo la sveglia...

La chiamata di sistema alarm() puo essere utilizzata per istruire il kernelper inviare al processo stesso un segnale SIGALRM trascorso un certotempo. Cio puo essere utile per realizzare compiti periodici o implementaremeccanismi di “timeout”.unsigned int alarm(unsigned int seconds)

Il parametro seconds specifica dopo quanti secondi deve essere lanciato ilsegnale. Se seconds e pari a 0, un eventuale allarme precedente vienecancellato.

La chiamata ritorna il numero di secondi rimanenti ad un eventuale allarmeprecedentemente impostato o 0 nel caso in cui non ce ne siano.A seguito di una fork(), il figlio eredita gli eventuali allarmi, ma poi questiverranno gestisti (rinnovi, cancellazioni) in modo indipendente dal padre.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 119 / 132

Page 120: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Gestire i segnali (1)

Un processo puo decidere di gestire in qualche modo i segnali: comportarsicome da default, ignorarli o associargli una funzione (in questo caso si diceche stiamo “armando il segnale”).sighandler_t signal(int signum, sighandler_t handler)

Il tipo sighandler_t definisce un gestore di un segnale ed e un puntatoread una funzione. Piu specificatamente e definito come:typedef void (*sighandler_t)(int)

Quindi un gestore di segnali e una funzione che prende un intero e nonriporta nulla.La chiamata ritorna il puntatore al precedente gestore o -1 in caso dierrore.Il parametro signum specifica il numero del segnale da gestire, ilparametro handler determina il modo in cui viene gestito il segnale:

SIG_DFL: imposta il comportamento di default, ovvero quello di farterminare il processo, ad eccezione del segnale SIGCHLD che di defaultviene ignorato;

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 120 / 132

Page 121: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Gestire i segnali (2)

SIG_IGN: stabilisce che il segnale signum deve essere ignorato dalprocesso. Il segnale SIGKILL non puo essere ignorato. In genere isegnali che potrebbero andare ignorati sono SIGHUP, SIGINT eSIGQUIT; gli altri segnali dovrebbero lasciare una traccia ad indicareche qualcosa di eccezionale e avvenuto;

un puntatore a funzione: “arma”il segnale segnum stabilendo che deveessere “catturato” dalla funzione specificata; tutti i segnali possonoessere gestiti ad eccezione di SIGKILL. Come gia detto, un gestore disegnale deve seguire il prototipo: void gestoreSegnale(int).

In seguito ad una chiamata fork(), la gestione dei segnali del padre vieneereditata dal figlio.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 121 / 132

Page 122: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Segnali, fork() e exec()

Per quanto riguarda una chiamata exec(), solo le impostazioni SIG_DFL eSIG_IGN vengono ereditate, mentre per ogni segnale armato con ungestore viene automaticamente impostato il comportamento di default:infatti il codice del gestore non esistera piu in seguito alla chiamataexec(). Ovviamente, il nuovo programma puo impostare i suoi gestori.

Ad esempio, questo meccanismo viene utilizzato dal comando nohup perdisabilitare il segnale SIGHUP per un qualunque comando specificato(consultare man nohup) per evitare che il processo generato muoia aseguito della chiusura del terminale.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 122 / 132

Page 123: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Meccanismi di interruzione

Quando un segnale viene inviato ad un processo, il segnale diventapendente e viene notificato al processo quanto prima.Il sistema di gestione dei segnali si e evoluto nei sistemi UNIX: le primearchitetture implementavano una semantica non affidabile (o unreliable)che studieremo qui nei dettagli; i sistemi UNIX recenti (come i BSD e leversioni recenti di Linux) propongono una semantica affidabile (o reliable).

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 123 / 132

Page 124: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Gestione non affidabile dei segnali (1)

Iniziamo a vedere il sistema piu semplice: le chiamate di sistema sonoquasi tutte considerate atomiche (quindi i segnali non ne interromponol’esecuzione) ad eccezione di alcune “chiamate lente” che per loro naturapotrebbero protrarsi per lungo tempo e che implicherebbero una notificatroppo ritardata del segnale, ad esempio:

letture da dispositivi o pipe che potrebbero bloccare il processo inattesa di disponibilita di dati;

lettura da un dispositivo che potrebbe non essere subito pronto (unitadi backup lente);

chiamate di comunicazione tra processi che potrebbero bloccarsi (adesempio, invio e ricezione di messaggi su code FIFO);

la chiamata wait() con cui un padre si mette in attesa indefinitadella morte di un figlio.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 124 / 132

Page 125: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Gestione non affidabile dei segnali (2)

Quando il kernel decide che e il momento di interrompere il processo perdare la notifica del segnale cio che succede e:

il segnale e resettato al suo valore di default, ovvero viene “disarmato”disattivandone la cattura;

il controllo passa al gestore del segnale specificato in precedenza dalprocesso (passando il numero di segnale come parametro); quandoquesto ha finito, il controllo ritorna al processo nel seguente modo:

se il processo e stato interrotto all’interno del suo codice (non in unachiamata di sistema), allora l’esecuzione riprende da lı;se la computazione e stata interrotta all’interno di una chiamata disistema (quindi “lenta”, altrimenti non si sarebbe interrotta), allora lachiamata non prosegue, ritorna al processo con un valore di errore -1

ed impostando la variabile di errore errno al valore EINTR; quindi ilflusso non prosegue normalmente!

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 125 / 132

Page 126: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Conseguenze dell’interruzione di una chiamata

Poiche le “chiamate lente” (o “bloccanti”) possono essere interrotte in modo nontrasparente e necessario che il programma controlli se il codice di errore riportatoindica che il fallimento e dovuto alla interruzione da parte di un segnale; in genereuna chiamata di sistema andrebbe invocata nel seguente modo:

if ( chiamataSistema() == -1 ) {

perror(Errore);

exit(1);

}

Per gestire l’interruzione si dovrebbe invece usare:while ( chiamataSistema() == -1 )

if (errno != EINTR) {

perror(Errore);

exit(1);

}

Questo compito e cosı comune, che sotto Linux esiste una macro apposita:

TEMP_FAILURE_RETRY(expr) che reitera expr finche questa non genera un

errore non temporaneo.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 126 / 132

Page 127: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Altre conseguenze: disattivazione del gestore

Se il processo vuole continuare a tenere attivo il gestore del segnale, poichequesto viene disattivato all’inizio dell’attivazione, e necessario il gestorestesso “si riattivi” al suo interno. La situazione potrebbe essere la seguente:

void sig_handler(int num) {

signal(SIGINT, sig_handler);

. . .

}

int main() {

. . .

signal(SIGINT, sig_handler);

. . .

}

Questo pero puo portare all’interruzione del processo o alla perdita diqualche segnale, infatti una nuova interruzione potrebbe arrivare prima chel’handler si “reinstalli”. In altre parole, la gestione dei segnali e lareinstallazione non sono operazioni atomiche e si possono creare racecondition.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 127 / 132

Page 128: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Principi di scritture di un gestore di segnali

Alcuni principi che bisogna seguire nella scrittura di un handler di segnalipotrebbero essere:

la funzione dovrebbe essere il piu semplice possibile;

la funzione non dovrebbe invocare chiamate a sistemi bloccanti (le“chiamate lente”);

se si deve riattivare l’handler, bisognerebbe farlo come prima cosa.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 128 / 132

Page 129: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Gestione affidabile dei segnali

I sistemi BSD hanno risolto questi problemi cambiando la semantica delleattivazioni: i gestori non vengono disattivati, i segnali vengono accodati ele chiamate di sistema vengono automaticamente reiterate.

Sotto Linux si e scelto di affiancare al sistema classico, anche un sistemaaffidabile che pero usa delle altre chiamate: in questo modo si mantiene lacompatibilita con i vecchi programmi (e voi potete provare cio che aveteappena visto...).

Le nuove chiamate sono parecchio piu complicate e non le vedremo.

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 129 / 132

Page 130: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Esempio: puntiamo la sveglia!

alarm.c

#include "stdio.h"#include "stdlib.h"#include "fcntl.h"#include "signal.h"

void gestione_timeout(int s) {printf (" Sveglia !\n");signal(SIGALRM , gestione_timeout );alarm (5);

}

int main() {alarm (5);signal(SIGALRM , gestione_timeout );

while (1) {printf (" Ciclo !\n");sleep (1);

}exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 130 / 132

Page 131: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Esempio: gestione di segnali (1)

signals.c

#include "stdio.h"#include "stdlib.h"#include "fcntl.h"#include "signal.h"

void signal_handler(int s) {signal(s, signal_handler ); // reinstalla il gestoreif (s == SIGHUP)

printf ("[%d] Chi ha chiuso la finestra ?!\n",s);else if ( (s == SIGINT) || (s == SIGQUIT) || (s == SIGTERM) )

printf ("[%d] Non mi va di uscire !\n",s);else if ( s == SIGUSR1 )

printf ("[%d] Hello world!\n",s);else if ( s == SIGUSR2 ) {

printf ("[%d] Ok , allora esco ...\n",s);exit (0);

}}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 131 / 132

Page 132: Programmazione su sistemi UNIX - Sistemi Operativi€¦ · UNIX (in particolare su Linux). In particolare, ci si occuper a dell’interfacciatra il programmatore ed il sistema operativo.

Comunicazione tra processi I Segnali

Esempio: gestione di segnali (2)

signals.c

int main() {// installa i gestori per alcuni segnali comunisignal(SIGHUP , signal_handler );signal(SIGINT , signal_handler );signal(SIGQUIT , signal_handler );signal(SIGTERM , signal_handler );signal(SIGUSR1 , signal_handler );signal(SIGUSR2 , signal_handler );signal(SIGKILL , signal_handler ); // tenta di gestire anche il SIGKILL

while (1) sleep (1);exit (0);

}

Mario Di Raimondo (DMI) Programmazione su sistemi UNIX A.A. 2009-2010 132 / 132