Appunti di Laboratorio Sistemi Operativi PDF

Summary

Questi appunti di informatica trattano concetti di base sui sistemi operativi, focalizzandosi sul laboratorio UNIX e sulle tecniche di comunicazione interprocesso (IPC), comprese pipe e FIFO.

Full Transcript

laboratorio di sistemi operativi introduzione IPC: pipe e FIFO Daniele Radicioni argomenti del laboratorio UNIX introduzione a UNIX; integrazione C: operatori bitwise, precedenze, preprocessore, pacchettizzazione del codice, compilazione condizionale e utility make; cont...

laboratorio di sistemi operativi introduzione IPC: pipe e FIFO Daniele Radicioni argomenti del laboratorio UNIX introduzione a UNIX; integrazione C: operatori bitwise, precedenze, preprocessore, pacchettizzazione del codice, compilazione condizionale e utility make; controllo dei processi; segnali; pipe e fifo; code di messaggi; memoria condivisa; semafori; introduzione alla programmazione bash. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 2 credits il materiale di questa lezione è tratto prevalentemente dai testi: - Michael Kerrisk, The Linux Programming interface - a Linux and UNIX® System Programming Handbook, No Starch Press, San Francisco, CA, 2010. - W. Richard Stevens (Author), Stephen A. Rago, Advanced Programming in the UNIX® Environment (2nd Edition), Addison-Wesley, 2005. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 3 intro IPC IPC facilities i vari strumenti che UNIX offre per la comunicazione e la sincronizzazione possono essere suddivisi in tre ampie categorie funzionali: - Comunicazione: facilities utilizzate per lo scambio di dati fra processi. - Sincronizzazione: facilities utilizzate per sincronizzare le azioni dei processi. - Segnali: sebbene i segnali siano nati prevalentemente con altri fini, in alcune circostanze possono essere utilizzati come strumenti di sincronizzazione. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 5 Although some of these facilities are concerned with synchronization, the general term interprocess communication (IPC) is often used to describe them all. byte stream Michael Kerrisk, The Linux Programming interface - a Linux and UNIX® System Programming Handbook, No Starch Press, San data pseudoterminal transfer message communication shared memory anonymous mapping Francisco, CA, 2010. signal semaphore fcntl()) file lock synchronization flock()) Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 6 Figure 43-1: A taxonomy of UNIX IPC facilities As Figure 43-1 illustrates, often several facilities provide similar IPC functionality. There are a couple of reasons for this: Communication Facilities Although some of these facilities are concerned with synchronization, the general term interprocess communication (IPC) is often used to describe them all. byte stream Michael Kerrisk, The Linux Programming interface - a Linux and UNIX® System Programming Handbook, No Starch Press, San data pseudoterminal transfer message communication shared memory anonymous mapping Francisco, CA, 2010. signal semaphore fcntl()) file lock synchronization flock()) Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 8 Figure 43-1: A taxonomy of UNIX IPC facilities As Figure 43-1 illustrates, often several facilities provide similar IPC functionality. There are a couple of reasons for this: Communication Facilities Data-transfer facilities: l'elemento fondamentale che distingue questi strumenti è la nozione di scrittura e lettura. - per comunicare, un processo scrive i dati alla facility per l'IPC e un altro processo legge questi dati. - questi strumenti richiedono due trasferimenti dati fra la memoria utente e quella del kernel: un trasferimento durante la scrittura e un trasferimento durante la lettura. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 9 Communication Facilities Memoria condivisa: la memoria condivisa permette ai processi di scambiarsi le informazioni mettendole in una regione della memoria condivisa fra i processi. - un processo può rendere i dati disponibili per gli altri processi collocandoli in una regione di memoria condivisa. - poiché la comunicazione non richiede system call o trasferimento di dati fra la memoria utente e quella del kernel, la memoria condivisa è uno strumento di comunicazione molto veloce. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 10 Data-transfer facilities: byte streams Le data-transfer facilities possono essere ulteriormente suddivise nelle seguenti sottocategorie: Byte stream: i dati scambiati per mezzo di pipe, FIFOs, e datagram sockets sono uno stream di byte. - ogni operazione di lettura può leggere un numero arbitrario di byte, senza considerare la dimensione dei blocchi scritti dallo scrivente. - questo modello riflette il tradizionale modello di UNIX in cui il file è visto come una sequenza di byte. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 11 Data-transfer facilities: messaggi Le data-transfer facilities possono essere ulteriormente suddivise nelle seguenti sottocategorie: Messaggio: i dati scambiati con le code di messaggi, e i socket hanno la forma di messaggi delimitati. - ogni operazione di lettura legge un intero messaggio, così come scritto dal processo scrivente. - non è possibile leggere parzialmente un messaggio, lasciando il resto sulla IPC facility, e non è possibile leggere molteplici messaggi con una singola operazione di lettura. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 12 Data-transfer facilities Le data-transfer facilities sono distinte dalla memoria condivisa per alcune caratteristiche generali: - Sebbene le data-transfer facilities possano avere molteplici lettori, le operazioni di lettura sono distruttive. Una operazione read consuma i dati, e i dati non sono più disponibili per altri processi. - La sincronizzazione fra processo lettore e scrittore è automatica. Se un lettore tenta di consumare dati da una facility che attualmente non ne contiene, (di default) l'operazione di lettura si bloccherà finché un processo non avrà scritto dei dati su quella facility. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 13 Shared memory Sebbene la memoria condivisa fornisca una comunicazione veloce, questo vantaggio è bilanciato dalla necessità di sincronizzare le operazioni sulla memoria condivisa. - per esempio, un processo non dovrebbe cercare di accedere a una struttura dati presente nella memoria condivisa mentre un altro processo la sta modificando. il semaforo è lo strumento di sincronizzazione abitualmente utilizzato con la memoria condivisa. - i dati presenti nella memoria condivisa sono visibili a tutti i processi che condividono quel segmento di memoria, diversamente dalla semantica distruttiva delle operazioni di lettura messe a disposizione dalle data-transfer facilities. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 14 Synchronization Facilities Although some of these facilities are concerned with synchronization, the general term interprocess communication (IPC) is often used to describe them all. byte stream Michael Kerrisk, The Linux Programming interface - a Linux and UNIX® System Programming Handbook, No Starch Press, San data pseudoterminal transfer message communication shared memory anonymous mapping Francisco, CA, 2010. signal semaphore fcntl()) file lock synchronization flock()) Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 16 Figure 43-1: A taxonomy of UNIX IPC facilities As Figure 43-1 illustrates, often several facilities provide similar IPC functionality. There are a couple of reasons for this: Semafori Semafori: un semaforo è un intero mantenuto dal kernel, il cui valore non può divenire minore di 0. - Un processo può decrementare o incrementare il valore di un semaforo. Se viene fatto un tentativo di decrementare il valore di un semaforo sotto lo 0, il kernel blocca l'operazione finché il valore del semaforo aumenta a un livello che permette di eseguire l'operazione. - In alternativa, il processo può richiedere una nonblocking operation; in questo caso, invece di bloccarlo, il kernel provoca una restituzione immediata con un errore che indica che l'operazione non ha potuto essere eseguita immediatamente. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 17 pipes pipe I pipe forniscono una soluzione a un problema frequente: avendo creato due processi per eseguire programmi diversi (comandi), come può la shell fare in modo che l'output prodotto da un processo sia utilizzato come input per l'altro processo? I FIFO sono una variazione del concetto di pipe. La differenza sostanziale è che i FIFO possono essere utilizzati per la comunicazione fra processi qualsiasi. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 19 Pipes $ ls -al | wc -l 74 per eseguire questi i comandi, la shell crea due processi, che eseguono ls e wc, rispettivamente. - questo è fatto utilizzando la fork() e la exec(). Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 20 $ ls -al | wc -l 74 pipe stdout byte stream; stdin ls wc (fd 1) unidirectional (fd 0) write end read end of pipe of pipe Figure 44-1: Using a pipe to connect two processes i due processi cono collegati al pipe: il processo scrittore (ls) ha One point to note in Figure 44-1 is that the two processes are connected to the il proprio standard so that the writingoutprocess(file descriptor 1) collegato (ls) has its standard outputcon(file il write 1) joine descriptor end del thepipe, mentre write end of theilpipe, processo while the lettore reading (wc) processha il(wc) proprio has its standard input descriptor standard input 0) joined (file to the read descriptor 0) end of the pipe. collegato al read In effect, end these two processe del pipe. unaware of the existence of the pipe; they just read from and write to the stan - NB: i due processi sono file descriptors. The completamente shell must do some ignariworkdell'esistenza in order to setdel pipe: things up in this semplicemente and we see leggono how this isedone scrivono da/su44.4. in Section descrittori di file standard. In the following paragraphs, we cover a number of important characteri of pipes. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 21 A pipe is a byte stream i pipe sono stream di byte un pipe è uno stream di byte: usando un pipe, non facciamo riferimento ad alcun concetto di messaggio o di delimitazione di messaggio. - il processo che legge da un pipe può leggere blocchi di qualsiasi dimensione, indipendentemente dalla dimensione dei blocchi scritti dal processo che scrive. i dati passano attraverso il pipe in sequenza: i byte sono letti nello stesso ordine in cui sono stati scritti. non è possibile accedere ai dati in maniera casuale utilizzando lseek(). Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 22 used for all messages from all clients. An alternative is to use a single connection for each message. The sender opens the communication channel, sends its message, and then closes the channel. The reading process knows that the message is complete when it encounters end-of-file. If multiple writers hold a FIFO open, then this approach is not feasible, because the reader wonCt see end-of-file when Michael Kerrisk, The Linux Programming interface - a Linux and UNIX® System protocol one of the writers closes the FIFO. This approach is, however, feasible when using stream sockets, where a server process creates a unique communication Programming Handbook, No Starch Press, San Francisco, CA, 2010. channel for each incoming client connection. delimiter character 1) delimiter character data data data len bytes 2) header with length field len data len data len data n bytes n bytes n bytes 3) fixed-length messages data data data Figure 44-7: Separating messages in a byte stream In our example application, we use the third of the techniques described above, with each client sending messages of a fixed size to the server. This message is defined byDaniele the request Radicioni structure - Laboratorio didefined in Listing Sistemi Operativi, corso A 44-6. Each request to the server23 - turno T1 includes the clientCs process ID, which enables the server to construct the name of the FIFO used by the client to receive a response. The request also contains a field (seqLen) specifying how many sequence numbers should be allocated to this client. lettura da pipe i tentativi di leggere da un pipe vuoto restano bloccati finché almeno un byte non è stato scritto sul pipe. se il write end di un pipe viene chiuso, un processo che legge dal pipe riceverà il codice end-of-file (i.e., read() restituirà 0) una volta che avrà letto tutti i dati presenti nel pipe. i pipe sono unidirezionali. I dati possono viaggiare solo in una direzione. - un'estremità (end) del pipe è utilizzato in scrittura, e l'altro in lettura. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 24 Capacità limitata Un pipe è semplicemente un buffer mantenuto in memoria. Questo buffer ha una capacità massima. Una volta che un pipe è pieno, ulteriori tentativi di scrittura si bloccano finché il lettore rimuove alcuni dati dal pipe. - In generale, un'applicazione non ha bisogno di conoscere la capacità del pipe. - Se vogliamo evitare ai processi scrittori di restare bloccati, è necessario che i processi che leggono dal pipe siano progettati in modo da leggere i dati appena questi sono disponibili. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 25 Capacità limitata In teoria, non ci sono motivi per cui un pipe non debba utilizzare capacità minime, fino al buffer costituito da un solo byte. La ragione per utilizzare buffer di dimensioni maggiori è l'efficienza: ogni volta che uno scrittore riempie il pipe, il kernel deve eseguire un context switch per consentire al lettore di essere 'scheduled' per prelevare qualche dato dal pipe. - L'utilizzo di un buffer di dimensione maggiore comporta la riduzione del numero di context switch. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 26 We can also use the stdio functions (printf(), scanf(), and so on) with pipes by first using fdopen() to obtain a file stream corresponding to one of the descriptors in filedes (Section 13.7). However, when doing this, we must be aware of the stdio buffering issues described in Section 44.6. #include The call ioctl(fd, FIONREAD, &cnt) returns the number of unread bytes in the pipe orpipe(int int FIFO referred to by the file descriptor fd. This feature is also available filedes); on some other implementations, but is not specified in SUSv3. Figure 44-2 shows the situationReturns 0 has after a pipe on been success, createdor by –1 onwith pipe(), error the call- ing process having file descriptors referring to each end. la system call pipe() crea un nuovo pipe; se va a calling process filedes filedes buon fine, la chiamata alloca un array (filedes) contenente due descrittori di file aperti. pipe - Un estremo è aperto in lettura (filedes) e uno in direction of data flow scrittura (filedes). Come Figure 44-2: Process file descriptors- after con creating qualsiasi descrittore di file, possiamo a pipe utilizzare A pipe has few uses within a single processle(we system callone consider read() e write() in Section per 63.5.2). Normally, we use a pipe to alloweseguire le operazioni communication di I/O between two sul pipe. processes. To con- nect two processes using a pipe, we follow the pipe() call with a call to fork(). Dur- Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 27 ing a fork(), the child process inherits copies of its parentUs file descriptors (Section 24.2.1), bringing about the situation shown on the left side of Figure 44-3. parent process parent process filedes filedes Di norma si utilizzano i pipe per filedes permettere la comunicazione fra due pipe processi. pipe Per collegare due processi con un pipe, eseguiamo prima una system call pipe() e poi una fork(). filedes filedes - filedes Con la fork(), il processo figlio eredita child process child process copia dei descrittori di file dei genitori. a) After fork() b) After closing unused descriptors Figure 44-3: Setting up a pipe to transfer data from a parent to a child Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 28 While it is possible for the parent and child to both read from and write to the pipe, this is not usual. Therefore, immediately after the fork(), one process closes its descriptor for the write end of the pipe, and the other closes its descriptor for the tecnicamente sarebbe possibile leggere e parent process scrivere sul pipe per il genitore e il figlio, filedes ma non è il modo standard di utilizzare i pipe. pipe subito dopo la fork(), un processo chiude il proprio descrittore per l'estremità in scrittura, e l'altro chiude il proprio descrittore per l'estremità in lettura. filedes Per esempio, se il genitore deve inviare dei - child process dati al figlio, deve chiudere l'estremità aperta b) After closing unused descriptors in lettura, filedes, mentre il figlio deve sfer data from a parent to a child chiudere filedes. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 29 ent and child to both read from and write to the , immediately after the fork(), one process closes its he pipe, and the other closes its descriptor for the int filedes; if (pipe(filedes) == -1) // creazione pipe ddErrExit("pipe"); switch (fork()) { case -1: ddErrExit(“fork"); // da implementare case 0: // ----- Child if (close(filedes) == -1)// --- chiude il write end ddErrExit("close"); // --- il figlio compie qualche operazione --- break; default: // padre if (close(filedes) == -1) // --- chiude il read end ddErrExit("close"); // --- il padre compie qualche operazione --- break; } Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 30 Comunicazione fra processi non imparentati I pipe possono essere usati per la comunicazione fra (due o più) processi parenti, supponendo che il pipe sia creato da un antenato comune e prima della serie di fork() con cui sono stati creati i vari figli. - Per esempio, un pipe potrebbe essere usato per mettere in comunicazione un processo e un processo nipote (granchild) del primo. Il primo processo crea il pipe, e quindi effettua una fork(), e il figlio effettua una ulteriore fork() creando così il nipote. - Un altro scenario possibile è l'utilizzo di un pipe per la comunicazione fra due processi fratelli (siblings): il loro genitore crea il pipe, e quindi crea i due figli. così facendo, la shell crea una pipeline. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 31 Chiusura dei descrittori inutilizzati (lettore) I file descriptors inutilizzati in lettura e in scrittura devono essere chiusi. Il processo che legge dal pipe chiude il proprio write descriptor, così che quando l'altro processo completa il proprio output e chiude il proprio descrittore write, il lettore riceve un carattere terminatore, end- of-file. - Se invece il processo lettore non chiude la propria estremità aperta in scrittura, dopo che l'altro processo avrà chiuso il proprio descrittore write, il lettore non riceverà l'end-of-file neppure dopo avere letto tutti i dati dal pipe. - In questo caso, una read() si bloccherebbe in attesa di dati (che sappiamo non arriveranno!) perché il kernel sa che c'è ancora almeno un descrittore di file aperto per il pipe. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 32 Chiusura dei descrittori inutilizzati (scrittore) Il processo scrittore chiude l'estremità aperta in lettura del pipe per una ragione diversa. Quando un processo tenta di scrivere su un pipe per il quale nessun processo ha un descrittore aperto in lettura, il kernel invia il segnale SIGPIPE al processo scrittore. - Per default, tale segnale uccide il processo. Un processo può organizzarsi per intercettare o ignorare tale segnale; in questo caso la write() sul pipe fallisce con un errore EPIPE (broken pipe). - Ricevere il segnale SIGPIPE o ricevere l'errore EPIPE è un'indicazione utile in merito allo status del pipe, ed è la ragione per cui i descrittori aperti in lettura dovrebbero essere chiusi. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 33 int main(int argc, char** argv) { int n; int fd; pid_t pid; char line[MAXLINE]; if (pipe(fd) < 0) ddErrExit("pipe error”); if ((pid = fork()) < 0) { ddErrExit("fork error"); } else if (pid > 0) { // --------- padre --- close(fd); write(fd, "hello world\n", 12); } else { // ---------------------- figlio --- close(fd); n = read(fd, line, MAXLINE); write(STDOUT_FILENO, line, n); } exit(EXIT_SUCCESS); } Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 34... int main(int argc, char *argv[]) { int pfd; char buf[BUF_SIZE]; ssize_t numRead; if (argc != 2 || strcmp(argv, "--help") == 0) ; if (pipe(pfd) == -1) ; switch (fork()) { case -1: ;... pipe1.c Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 35 case 0: if (close(pfd) == -1) ; for (;;) { numRead = read(pfd, buf, BUF_SIZE); if (numRead == -1) ; if (numRead == 0) break; if (write(STDOUT_FILENO, buf, numRead) != numRead) ; } write(STDOUT_FILENO, "\n", 1); if (close(pfd) == -1) ; exit(EXIT_SUCCESS); pipe1.c Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 36... default: if (close(pfd) == -1) ; if(write(pfd,argv,strlen(argv))!=strlen(argv)) ; if (close(pfd) == -1) ; wait(NULL); exit(EXIT_SUCCESS); } // end switch } pipe1.c Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 37 #include FILE *popen(const char *command, const char *mode); Returns file stream, or NULL on error La popen() crea un pipe, quindi esegue una fork(); il figlio generato esegue (execs) una shell, che a sua volta crea un processo figlio che esegue l'istruzione presente in command. L'argomento mode è una stringa che determina se il processo chiamante leggerà dall'estremità read del pipe (mode settato a r) o vi scriverà (mode a w). Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 38 #include FILE *popen(const char *command, const char *mode); Returns file stream, or NULL on error Il valore di mode determina se lo standard output del comando eseguito è connesso all'estremità del pipe aperta in scrittura, o se il suo standard input è connesso all'estremità del pipe aperta in lettura. - In caso di successo, popen() restituisce un file stream pointer che può essere gestito con le funzioni della libreria stdio. - In caso di fallimento (e.g., il mode non è r o w, la creazione del pipe fallisce, o fallisce la fork() per creare il figlio), allora la popen() restituisce NULL e assegna errno indicando la causa dell'errore. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 39 #include FILE *popen(const char *command, const char *mode); Returns file stream, or NULL on error fp = popen(cmdstring, "r") Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 40 #include FILE *popen(const char *command, const char *mode); Returns file stream, or NULL on error fp = popen(cmdstring, "r") fp = popen(cmdstring, "w") Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 41 #include int pclose(FILE *stream); Returns termination status of child process, or -1 on error Completate le operazioni di I/O, si utilizza la funzione pclose() per chiudere il pipe ed attendere che la shell figlia termini. - In caso di successo, la pclose() ottiene lo status di terminazione della shell figlia (cioè, lo status di terminazione dell'ultimo comando eseguito dalla shell, a meno che la shell sia uccisa da un segnale). Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 42 convenience vs. efficiency L'utilizzo della popen() garantisce semplicità d'uso. - La popen() crea il pipe, esegue la duplicazione dei descrittori, chiude i descrittori inutilizzati, e gestisce tutti i dettagli della fork() e della exec() per nostro conto. Tale semplicità d'uso ha un costo in termini di efficienza. Vengono creati almeno due processi in più: uno per la shell e uno o più per i comandi eseguiti dalla shell. - Come per system(), popen() non dovrebbe essere utilizzata da programmi 'privileged'. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 43 #include #define PATH_MAX 1024 int main(int argc, char** argv) { FILE *fp; int status; char path[PATH_MAX]; fp = popen("ls", "r"); if (fp == NULL) ; // gestione errore while (fgets(path, PATH_MAX, fp) != NULL) printf("%s", path); status = pclose(fp); if (status == -1) { ; // gestione errore } else { ; // analisi dell'exit status } return(0); } Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 44 FIFOs FIFO e pipe Un FIFO è simile a un pipe. La principale differenza è che un FIFO ha un nome all'interno del file system ed è aperto nello stesso modo di un file. Questo permette a un FIFO di essere utilizzato per comunicazioni fra processi non imparentati (e.g., un client e un server). Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 46 FIFO e pipe Una volta che un FIFO è stato aperto possiamo utilizzare le stesse system call dell'I/O utilizzate con i pipe e gli altri file (cioè, read(), write(), e close()). Come i pipe, anche i FIFO hanno un'estremità aperta in scrittura e una aperta in lettura, e come per i pipe, i dati sono letti nello stesso ordine in cui sono stati scritti. - Questa caratteristica conferisce ai FIFO il loro nome: first in, first out. I FIFO sono anche noti come named pipes. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 47 Possiamo creare un FIFO dalla shell con il comando mkfifo: $ mkfifo [ -m mode ] pathname Il pathname è il nome del FIFO che si intende creare, e l'opzione -m specifica i permessi analogamente al comando chmod. Listato con il comando ls -l, un FIFO è mostrato con il carattere p nella prima colonna. $ mkfifo -m 644 prima_fifo $ ls -l prima_fifo prw-r--r-- 1 radicion staff 0 Nov 09 03:55 prima_fifo Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 48 set indicates the permissions for owner, which h sions enabled. The next set indicates the perm and execute enabled, but not write. The final which doesnDt have any permissions enabled. #include The header file defines consta st_mode of the stat structure, in order to check w are set. int mkfifo(const char *pathname, (These constants mode_t mode);are also defined via the types the open() system call.) These constants are Returns 0 on success, or -1 on error Table 15-4: Constants for file permission bits Constant Octal value Permission bit La funzione mkfifo() function crea S_ISUID 04000 Set-user-ID un nuovo FIFO con il pathname S_ISGID 02000 Set-group-ID S_ISVTX 01000 Sticky dato. S_IRUSR 0400 User-read User-write L'argomento mode specifica i S_IWUSR 0200 S_IXUSR 0100 User-execute permessi per il nuovo FIFO. Tali S_IRGRP 040 Group-read Group-write permessi sono specificati mettendo S_IWGRP S_IXGRP 020 010 Group-execute in OR (ORing) varie possibili S_IROTH 04 Other-read Other-write costanti. S_IWOTH S_IXOTH 02 01 Other-execute Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 49 In addition to the constants shown in Table 15 equate to masks for all three permissions for ea and other: S_IRWXU (0700), S_IRWXG (070), and S_IR sincronizzazione con FIFO L'utilizzo di un FIFO serve ad avere un processo lettore e uno scrittore alle due estremità del FIFO. - di default, l'apertura di un FIFO in lettura (flag O_RDONLY della open()) si blocca finché un altro processo apre il FIFO in scrittura (flag O_WRONLY della open()). - per contro, l'apertura del FIFO in scrittura si blocca finché un altro processo non apre la FIFO in lettura. - In altri termini, l'apertura di un FIFO sincronizza i processi lettori e scrittori. - Se l'altra estremità del FIFO è già aperta (per esempio nel caso in cui una coppia di processi hanno già aperto ciascuna estremità del FIFO), la open() va immediatamente a buon fine. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 50 user1.c #define FIFOSIZE 128 int main(){ int fd; char * myfifo = "/tmp/myfifo"; mkfifo(myfifo, 0666); char arr1[FIFOSIZE], arr2[FIFOSIZE]; while (1) { fd = open(myfifo, O_WRONLY); // apertura FIFO in scrittura fgets(arr2, FIFOSIZE, stdin); // lettura da stdin su arr2 write(fd, arr2, strlen(arr2)+1);// scrittura e chiusura FIFO close(fd); fd = open(myfifo, O_RDONLY); // apertura FIFO in lettura read(fd, arr1, sizeof(arr1)); // lettura da FIFO su arr1 printf("User2: %s\n", arr1); close(fd); // chiusura FIFO } return 0; } Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 51 user2.c #define FIFOSIZE 128 int main(){ int fd1; char * myfifo = "/tmp/myfifo"; mkfifo(myfifo, 0666); char arr1[FIFOSIZE], arr2[FIFOSIZE]; while (1){ fd1 = open(myfifo,O_RDONLY); // apertura FIFO read(fd1, arr1, FIFOSIZE); // lettura da FIFO su arr1 e chiusura close(fd1); printf("User1: %s\n", arr1); fd1 = open(myfifo,O_WRONLY); // apertura FIFO in scrittura fgets(arr2, FIFOSIZE, stdin); // lettura da stdin su arr2 write(fd1, arr2, strlen(arr2)+1); // scrittura su FIFO close(fd1); } return 0; } Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 52 FIFOs and tee() Una delle caratteristiche delle pipeline di shell è che esse sono sequenziali; ogni processo nella pipeline legge dati prodotti dal proprio predecessore, e invia i dati al proprio successore. Utilizzando i FIFO, è possibile creare una fork nella pipeline, così che un duplicato dell'output di un processo viene inviato a un altro processo oltre che al proprio successore nella pipeline. - Per fare questo si utilizza il comando tee, che scrive due copie di ciò che legge dal proprio standard input: uno su standard output e l'altro sul file specificato dal suo argomento della linea di comando. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 53 $ mkfifo myfifo $ ls -l total 24 -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file1 -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file2 -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file3 prw-r--r-- 1 radicion staff 0 Nov 11 18:00 myfifo $ wc -l < myfifo & 24651 $ ls -l | tee myfifo | sort -k5n 5 prw-r--r-- 1 radicion staff 0 Nov 11 18:01 myfifo total 24 -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file1 -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file2 -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file3 + Done wc -l < myfifo Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 54 FIFO. (The /k5n option to sort causes the output of ls to be sort numerical order on the fifth space-delimited field.) $ mkfifo myfifo $ ls -l $ mkfifo myfifo total 24 $ wc -l < myfifo & -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file1 $ ls -l | tee -rw-r--r-- 1 radicion staffmyfifo 1| Nov sort11 -k5n 18:00 file2 -rw-r--r--(Resulting output 1 radicion not shown) staff 1 Nov 11 18:00 file3 prw-r--r-- 1 radicion staff 0 Nov 11 18:00 myfifo $ wc -l < myfifo & Diagrammatically, the above commands create the situation shown 24651 $ ls -l | tee myfifo | sort -k5n 5 The tee program is so named because of its shape. We can cons prw-r--r-- 1 radicion staff 0 Nov 11 18:01 myfifo total 24 ing similarly to a pipe, but with an additional branch that send Diagrammatically, -rw-r--r-- 1 radicion staff 1 Nov 11 this has the 18:00 shape of a capital letter T (see F file1 -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file2 tion to -rw-r--r-- 1 radicion the purpose staff 1 Nov 11 described 18:00 file3here, tee is also useful for debug + Done for saving the results wc -l produced at some intervening point in a c < myfifo ls tee sort FIFO wc Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 55 Figure 44-5: Using a FIFO and tee(1) to create a dual pipeline FIFO. (The /k5n option to sort causes the output of ls to be sort numerical order on the fifth space-delimited field.) $ mkfifo myfifo $ ls -l $ mkfifo myfifo creo una FIFO di nome myfifo total 24 $ wc -l < myfifo & -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file1 $ ls -l | tee -rw-r--r-- 1 radicion staffmyfifo 1| Nov sort11 -k5n 18:00 file2 -rw-r--r--(Resulting output 1 radicion not shown) staff 1 Nov 11 18:00 file3 prw-r--r-- 1 radicion staff 0 Nov 11 18:00 myfifo $ wc -l < myfifo & Diagrammatically, the above commands create the situation shown 24651 $ ls -l | tee myfifo | sort -k5n 5 The tee program is so named because of its shape. We can cons prw-r--r-- 1 radicion staff 0 Nov 11 18:01 myfifo total 24 ing similarly to a pipe, but with an additional branch that send Diagrammatically, -rw-r--r-- 1 radicion staff 1 Nov 11 this has the 18:00 shape of a capital letter T (see F file1 -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file2 tion to -rw-r--r-- 1 radicion the purpose staff 1 Nov 11 described 18:00 file3here, tee is also useful for debug + Done for saving the results wc -l produced at some intervening point in a c < myfifo ls tee sort FIFO wc Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 56 Figure 44-5: Using a FIFO and tee(1) to create a dual pipeline FIFO. (The /k5n option to sort causes the output of ls to be sorted numerical order on the fifth space-delimited field.) $ mkfifo myfifo $ ls -l $ mkfifo myfifo comando wc in background, che apre la FIFO total 24$ wc -l < myfifo & per leggerne l'output (NB: wc resta bloccato -rw-r--r-- 1 radicion staff $ ls -l | tee myfifo | 1sort Nov -k5n 11 18:00 file1 -rw-r--r-- 1 radicion stafffinché la 1FIFO Nov non viene aperta 11 18:00 file2 in scrittura...) (Resulting -rw-r--r-- outputstaff 1 radicion not shown)1 Nov 11 18:00 file3 prw-r--r-- 1 radicion staff 0 Nov 11 18:00 myfifo Diagrammatically, the above commands create the situation shown i $ wc -l < myfifo & 24651 $ ls -l | tee myfifo | sort -k5n 5 The tee program is so named because of its shape. We can consid prw-r--r-- 1 radicion staff 0 Nov 11 18:01 myfifo total 24 ing similarly to a pipe, but with an additional branch that sends d Diagrammatically, -rw-r--r-- 1 radicion staff 1 Novthis 11 has thefile1 18:00 shape of a capital letter T (see Figu tion to the purpose described here, tee is also useful for debuggi -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file2 -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file3 + Done for saving thewcresults -l < produced myfifo at some intervening point in a com ls tee sort FIFO wc Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 57 Figure 44-5: Using a FIFO and tee(1) to create a dual pipeline FIFO. (The /k5n option to sort causes the output of ls to be sorted numerical order on the fifth space-delimited field.) $ mkfifo myfifo $ ls -l $ mkfifo myfifoesecuzione della pipeline che invia l'output di ls total 24$ wc -l < myfifo a tee, & che lo duplica passandolo sia a sort, sia a -rw-r--r-- $ ls 1-l radicion myfifo staff (che | tee myfifo loNov | 1sort passa a wc) file1 11 18:00 -k5n -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file2 (Resulting output -rw-r--r-- 1 radicion staffnot shown)1 Nov 11 18:00 file3 prw-r--r-- 1 radicion staff 0 Nov 11 18:00 myfifo Diagrammatically, the above commands create the situation shown i $ wc -l < myfifo & 24651 $ ls -l | tee myfifo | sort -k5n 5 The tee program is so named because of its shape. We can consid prw-r--r-- 1 radicion staff 0 Nov opzione –k5n:myfifo 11 18:01 sort utilizza l'output di ls total 24 ing similarly to a pipe, but with an additional branch that sends d per un ordinamento numerico (n) Diagrammatically, -rw-r--r-- 1 radicion staff 1 Novthis 11 has condotto the sul 18:00 shape quinto file1 campo of (key, a capital k). letter T (see Figu tion to the purpose described here, tee is also useful for debuggi -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file2 -rw-r--r-- 1 radicion staff 1 Nov 11 18:00 file3 + Done for saving thewcresults -l < produced myfifo at some intervening point in a com ls tee sort FIFO wc Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 58 Figure 44-5: Using a FIFO and tee(1) to create a dual pipeline Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 59 un'applicazione client-server di esempio Client-Server Application with FIFOs The server provides the service of assigning unique sequential numbers to each client that requests them. all clients send their requests to the server using a single server FIFO. The header file defines the well-known name that the server uses for its FIFO. - This name is fixed, so that all clients know how to contact the server. however, it is not possible to use a single FIFO to send responses to all clients, since multiple clients would race to read from the FIFO, and possibly read each other’s response messages rather than their own. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 61 Client-Server Application with FIFOs Therefore, each client creates a unique FIFO that the server uses for delivering the response for that client, and the server needs to know how to find each client’s FIFO. - Client and server can agree on a convention for constructing a client FIFO pathname, and, as part of its request, the client can pass the server the information required to construct the pathname specific to this client. Each client’s FIFO name is built from a template consisting of a pathname containing the client’s process ID. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 62 Client A FIFO Re /tmp/seqnum_cl.6514 spo Client A (se n (PID=6514) Requ q. se est #) (PID + len gth) Server FIFO Server est Requ en gth) /tmp/seqnum_sv Client B (PID + l o nse sp (PID=6523) Re q. #) (se Client B FIFO /tmp/seqnum_cl.6523 Figure 44-6: Using FIFOs in a single-server, multiple-client application Recall that the data in pipes and FIFOs is a byte stream; boundaries between mul- tiple messages are not preserved. This means that when multiple messages are Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 63 being delivered to a single process, such as the server in our example, then the sender and receiver must agree on some convention for separating the messages. Various approaches are possible: protocol Recall that the data in pipes and FIFOs is a byte stream; boundaries between multiple messages are not preserved. - When multiple messages are being delivered to a single process, the sender and receiver must agree on some convention for separating the messages. Terminate each message with a delimiter character, such as a newline character. Include a fixed-size header with a length field in each message specifying the number of bytes in the remaining variable-length component of the message. Use fixed-length messages, and have the server always read messages of this fixed size. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 64 used for all messages from all clients. An alternative is to use a single connection for each message. The sender opens the communication channel, sends its message, and then closes the channel. The reading process knows that the message is complete when it encounters end-of-file. If multiple writers hold a FIFO open, then this approach is not feasible, because the reader wonCt see end-of-file when Michael Kerrisk, The Linux Programming interface - a Linux and UNIX® System protocol one of the writers closes the FIFO. This approach is, however, feasible when using stream sockets, where a server process creates a unique communication Programming Handbook, No Starch Press, San Francisco, CA, 2010. channel for each incoming client connection. delimiter character 1) delimiter character data data data len bytes 2) header with length field len data len data len data n bytes n bytes n bytes 3) fixed-length messages data data data Figure 44-7: Separating messages in a byte stream In our example application, we use the third of the techniques described above, with each client sending messages of a fixed size to the server. This message is defined byDaniele the request Radicioni structure - Laboratorio didefined in Listing Sistemi Operativi, corso A 44-6. Each request to the server65 - turno T1 includes the clientCs process ID, which enables the server to construct the name of the FIFO used by the client to receive a response. The request also contains a field (seqLen) specifying how many sequence numbers should be allocated to this client. protocol #define SERVER_FIFO "seqnum_server" #define CLIENT_FIFO_TEMPLATE "seqnum_client.%ld" #define CLIENT_FIFO_NAME_LEN (sizeof(CLIENT_FIFO_TEMPLATE) + 20) struct request { pid_t pid; int seqLen; }; struct response { int seqNum; }; Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 66 server The server performs the following steps: - Create the server’s well-known FIFO and open the FIFO for reading. The server’s open() blocks until the first client opens the other end of the server FIFO for writing. - Open the server’s FIFO once more, this time for writing. This will never block, since the FIFO has already been opened for reading. This second open is a convenience to ensure that the server doesn’t see end-of-file if all clients close the write end of the FIFO. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 67 server The server performs the following steps: - Enter a loop that reads and responds to each incoming client request. To send the response, the server constructs the name of the client FIFO and then opens that FIFO. - If the server encounters an error in opening the client FIFO, it abandons that client’s request. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 68 client The client performs the following steps: - Create a FIFO to be used for receiving a response from the server. This is done before sending the request, in order to ensure that the FIFO exists by the time the server attempts to open it and send a response message. - Construct a message for the server containing the client’s process ID and a number (taken from an optional command-line argument) specifying the length of the sequence that the client wishes the server to assign to it. If no command-line argument is supplied, the default sequence length is 1. - Open the server FIFO and send the message to the server. - Open the client FIFO, and read and print the server’s response. Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 69 $./seq_server & 29685 $./seq_client 3 0 $./seq_client 2 3 $./seq_client 5 $ Daniele Radicioni - Laboratorio di Sistemi Operativi, corso A - turno T1 70

Use Quizgecko on...
Browser
Browser