Sistemi operativi, A.A. 2011/2012 (M. Cesati) Scaletta esercitazione #8, 3.05.2012 1 Creazione di un nuovo processo 1.1 Chiamata di sistema fork() +-----------------+ |pid_t fork(void);| +-----------------+ 1.2 Chiamata di sistema getpid() +-------------------+ |pid_t getpid(void);| +-------------------+ 2 Quiz: quanti processi vengono creati? +----------------------------------------+ |#include | |#include | |#include | |int main(int argc, char *argv[]) | |{ | | fork(); | | fork(); | | fork(); | | exit(EXIT_SUCCESS); | |} | +----------------------------------------+ 2.1 Risposta: vengono creati 7 nuovi processi (8 compreso il processo originale) FORK#3 FORK#2 / / \ FORK#1 FORK#3 \ FORK#3 FORK#2 / \ FORK#3 2.2 Verifica con stampa del PID per ciascun processo 3 Condivisione dei file aperti dopo una fork FD (parent) FD (child) +---+ +---+ 0 | |---- Open File Table -----| | 0 +---+ | +--------------+ | +---+ 1 | | | | | | | | 1 +---+ | | | | +---+ 2 | | | +--------------+ | | | 2 +---+ ---------->| File # xxx |<-------- +---+ 3 | | ---------->| RW, POS @10 |<-------- | | 3 +---+ | +--------------+ | +---+ 4 | |---- | | -----| | 4 +---+ | | +---+ 5 | | +--------------+ | | 5 +---+ | File # yyy | +---+ 6 | |------------->| RO, POS @20 |<------------| | 6 +---+ +--------------+ +---+ | | | | +--------------+ 3.1 La tabella dei descrittori dei file viene duplicata 3.2 La tabella dei file aperti non viene duplicata (globale per il sistema) 3.3 Di conseguenza padre e figlio condividono il file aperto: modalita' di accesso e posizione corrente 3.4 Esempio: +--------------------------------------------------+ |#include | |#include | |#include | |#include | |#include | |#include | |int main(int argc, char *argv[]) | |{ | | int fd; | | fd = open(argv[1], O_WRONLY|O_CREAT, 0644); | | if (fd == -1) exit(EXIT_FAILURE); | | write(fd, "Inizio\n", 7); | | if (fork() == 0) | | write(fd, "Figlio\n", 7); | | else | | write(fd, "Padre\n", 6); | | return 0; | |} | +--------------------------------------------------+ 3.5 L'ordine di esecuzione di padre e figlio dopo la fork() dipende dal SO ed anche dalla specifica versione di SO 3.5.1 Attualmente in Linux il padre esegue prima del figlio, ma non si deve contare su questo fatto 4 Terminazione di un processo 4.1 Funzione di libreria exit() 4.1.1 Esegue le funzioni registrate con atexit() oppure on_exit() 4.1.2 Sincronizza i buffer di stdio 4.1.3 Esegue _exit() 4.2 Chiamata di sistema _exit(): termina il processo 4.3 Esempio +------------------------------------------------------------+ |#include | |#include | |#include | |void exit_work(void) | |{ | | fprintf(stderr, "Good bye cruel world!\n"); | |} | |int main(void) | |{ | | if (atexit(exit_work) != 0) { | | fprintf(stderr, "Cannot register exit handler\n"); | | exit(EXIT_FAILURE); | | } | | if (!fork()) { | | printf("I am the child\n"); | | _exit(EXIT_SUCCESS); | | } | | printf("I am the parent\n"); | | exit(EXIT_SUCCESS); | |} | +------------------------------------------------------------+ 5 Il programma 'puzzle': +-------------------------------------------+ |#include | |#include | |#include | |int main(int argc, char *argv[]) | |{ | | printf("Hello world\n"); | | write(STDOUT_FILENO, "Ciao\n", 5); | | if (fork() == -1) exit(EXIT_FAILURE); | | exit(EXIT_SUCCESS); | |} | +-------------------------------------------+ 5.1 Se invocato con stdout su terminale: $ ./puzzle Hello world Ciao 5.2 Se invocato con stdout rediretto su file: $ ./puzzle | cat Ciao Hello world Hello world 5.3 I motivi di questo diverso comportamento: 5.3.1 Le funzioni di libreria come printf() sono in genere "bufferizzate" 5.3.1.1 Funzione setvbuf() per cambiare tipo di bufferizzazione 5.3.1.2 Funzione fflush() per forzare la sincronizzazione 5.3.2 Per default, il buffer di stdout e' sincronizzato linea per linea quando e' su terminale, mentre e' sincronizzato su un blocco quando e' su file 5.3.3 La chiamata di sistema write() non e' bufferizzata 5.3.4 La funzione exit() sincronizza il buffer di stdout, e viene eseguita sia dal processo padre che dal figlio 5.3.5 Aggiungendo fflush() prima di fork() si ha un comportamento consistente: +-------------------------------------------+ |#include | |#include | |#include | |int main(int argc, char *argv[]) | |{ | | printf("Hello world\n"); | | fflush(stdout); | | write(STDOUT_FILENO, "Ciao\n", 5); | | if (fork() == -1) exit(EXIT_FAILURE); | | exit(EXIT_SUCCESS); | |} | +-------------------------------------------+ 6 Attesa sui processi figli 6.1 La chiamata di sistema wait() attende che un qualunque figlio termini +------------------------+ |pid_t wait(int *status);| +------------------------+ 6.1.1 Restituisce il PID del figlio terminato, oppure -1 se errore 6.1.2 Se status != NULL si ottengo info sul figlio terminato 6.1.3 Alcune macro per leggere le info in status: WIFEXITED(status): vero se figlio uscito normalmente WEXITSTATUS(status): valore d'uscita (8 bit) 6.2 Esempio: +------------------------------------------------------+ |#include | |#include | |#include | |#include | |#include | |#include | |#include | |int main(int argc, char *argv[]) | |{ | | int fd; | | fd = open(argv[1], O_WRONLY | O_CREAT, 0644); | | if (fd == -1) | | exit(EXIT_FAILURE); | | write(fd, "Inizio\n", 7); | | if (fork() == 0) | | write(fd, "Figlio\n", 7); | | else { | | if (wait(NULL) == -1) | | exit(EXIT_FAILURE); | | write(fd, "Padre\n", 6); | | } | | return 0; | |} | +------------------------------------------------------+ 6.3 La chiamata di sistema waitpid() permette di aspettare un singolo processo o gruppo di processi: +---------------------------------------------------+ |pid_t waitpid(pid_t pid, int *status, int options);| +---------------------------------------------------+ 7 Esercizio: Scrivere una funzione che costruisce una immagine contenente una rappresentazione dell'insieme di Mandelbrot. La funzione riceve quattro argomenti: (1) il nome del file di output (2) la posizione x dell'angolo inferiore sinistro (3) la posizione y dell'angolo inferiore sinistro (4) la lunghezza del quadrato contenente l'insieme da rappresentare. L'insieme di Mandelbrot e' caratterizzato dai punti complessi c=x+i*y tali che l'equazione complessa z'=z*z+c non diverge. [mandelbrot.c] ==========