E.Mumolo, DEEI

Documenti analoghi
POSIX - Gestione dei file. E.Mumolo, DEEI

Per operare su un file abbiamo bisogno di aprirlo, scriverlo, leggerlo, chiuderlo:

Laboratorio di Sistemi Operativi

Chiamate di sistema per la Gestione dei processi in POSIX. E.Mumolo, DEEI

Sistemi Operativi. Marzo-Giugno 2011 matricole congrue 0 mod 3. Controllo dei processi - I

&& (nessun altro processo ha il file aperto) && (il fd e chiuso) Cancella il file;

File I/O. M. R. Guarracino: File I/O 1

Controllo dei Processi. M. R. Guarracino - Primitive di Controllo dei Processi

Laboratorio di Sistemi Operativi Marzo-Giugno 2008 matricole congrue 0 mod 3

Gestione dei file: filosofia. carica in memoria l i-node del file read / write. close cose rilascia le strutture dati di memoria del file

Gestione di File e Directory

Gestione dei file: filosofia. carica in memoria l i-node del file read / write. close cose rilascia le strutture dati di memoria del file

Laboratorio di Sistemi Operativi primavera 2009

File e Directory. M. Guarracino - File e Directory 1

Scrivere alla fine di un file Vi sono due modi per scrivere alla fine di un file:

Digressione: man 2...

File e Directory. M. Guarracino - File e Directory 1

Programmazione di sistema in UNIX. Immagine di un processo in UNIX. Area dati. File comandi utente

Comandi. Sistema Operativo

Programmazione di sistema in Linux: gestione dei file. E. Mumolo

Scrivere alla fine di un file Vi sono due modi per scrivere alla fine di un file:

Funzioni di libreria. user programs libraries. process control subsystem. character block device drivers. system call interface.

Il processo figlio : utilizza lo stesso codice che sta eseguendo il padre ;

Laboratorio di Sistemi Operativi. System Call

System call per la gestione di processi

Laboratorio di Sistemi Operativi

Programmazione di sistema in Linux: System Call per il controllo processi. E. Mumolo, DIA

System calls. permettono ai programmi utente di richiedere servizi al Sistema Operativo. servizi come scrittura di file, stampa su video, ecc.

Il sistema operativo LINUX Il file system. Indice. Blocchi logici. Indice. Super block. G. Di Natale, S. Di Carlo

I Processi nel Sistema Operativo Unix. Gerarchie di processi Unix. Stati di un processo Unix. Stati di un processo Unix.

Digressione: man (2)...

Sistemi Operativi Teledidattico

Laboratorio di Sistemi Operativi Marzo-Giugno 2008 matricole congrue 0 mod 3

Esercitazione di Lab. di Sistemi Operativi 1 a.a. 2011/2012

1. File normale: contiene dati 2. Directory: contiene nomi di altri file ed informazioni sugli stessi

Igino Corona

CORSO DI SISTEMI OPERATIVI A - ESERCITAZIONE 3. 1 strace : visualizzazione delle system call invocate da un processo

Controllo dei Processi 1

Processi: Exit, Wait, Exec

CORSO DI SISTEMI OPERATIVI A - ESERCITAZIONE 3

Comunicazione tra processi: pipe Le pipe sono un meccanismo UNIX di Inter Process Communication (IPC)

System Calls per la Gestione dei Processi

Capitolo 5 -- Stevens

Corso di Laboratorio di Sistemi Operativi

Lab. di Sistemi Operativi - Esercitazione n 7- -Gestione dei processi Unix-

SC per Inter Process Comminication. Pipe senza nome e con nome (FIFO)

CREAZIONE DI UN FILE

Directory. Le directory unix sono file.

Processi in UNIX. Spazio di sistema (residente) Tabella dei. file APERTI OPEN FILE. Tabella dei codici

Program m azione di Sistem a 2a

Progetto II: Il linguaggio C e le chiamate di sistema sui processi

Modulo 5: Programmazione di sistema --- Parte C: System call per la gestione dei file

Processi Concetti di base. Esecuzione parallela e sequenziale Il concetto di processo Gestione dei processi

Il comando make. Per produrre un eseguibile da un programma C sono necessari tre passi compiuti dai seguenti moduli:

POSIX Systems Programming. geek evening 0x0d. ambienti POSIX. By lord_dex ZEI e Salug! presentano:

SC per Inter Process Communication. Pipe senza nome e con nome (FIFO)

Modello di Programma in UNIX

Chiamate di sistema per la Inter Process Communication (IPC) in POSIX. E.Mumolo, DEEI

I Processi nel SO UNIX

Gestione dei processi

ACSO Programmazione di Sistema e Concorrente

L ambiente di un processo (I)

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A Pietro Frasca.

il tipo di parallelismo dipende dal grado di cooperazione

Corso di Programmazione Concorrente Processi. Valter Crescenzi

I Processi nel Sistema Operativo Unix

Chiamate di sistema. Pipe Flussi di I/O

eseguire comandi dati dall'utente, utilizzando una macchina reale, di livello inferiore,

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A Pietro Frasca.

In generale può essere utile che i due processi eseguano del codice diverso

perror: individuare l errore quando una system call restituisce -1

Esame Laboratorio di Sistemi Operativi Cognome Nome Mat.

Capitolo 3 -- Stevens

Processi UNIX. I Processi nel SO UNIX. Gerarchie di processi UNIX. Modello di processo in UNIX

Processi - II. Franco Maria Nardini

Programmazione multiprocesso

SmallShell Piccolo processore comandi

Sezione 2: chiamate di sistema

Il File-System. I file in ambiente Linux Stefano Quer Dipartimento di Automatica e Informatica Politecnico di Torino

File. Gestione memoria - 19/01/2003 1/11

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A Pietro Frasca.

Struttura interna del sistema operativo Linux

Perché il linguaggio C?

5. I device driver. Device driver - gestori delle periferiche. Struttura interna del sistema operativo Linux. Tipi di periferiche. Tipi di periferiche

Gestione dei processi. Marco Bonola Lezione tratta da

File binari e file di testo

La Comunicazione tra Processi in Unix

Esame Laboratorio di Sistemi Operativi Cognome Nome Mat.

Signalling (IPC) Signalling (segnalazione)

Programmazione di Sistema 3

Files, File I/O, File Sharing. Franco Maria Nardini

Interfaccia del file system

Introduzione al Multithreading

Sistemi Operativi. Esercitazione 2 Compilazione, Makefile e Processi

INTERPROCESS COMMUNICATION 27

Introduzione. P4 termina prima di P3, P2 e P3 prima di P1 P1 P2 P3 P4 P1 P1 P2 P3 P4. Padre. P1,..., P4 sono processi. Figlio

Sistemi operativi Modulo II I semafori 2 Select

File System ext2. Struttura del filesystem ext2.

La famiglia di system call exec. Modulo 6. Laboratorio di Sistemi Operativi I Anno Accademico

Lab. di Sistemi Operativi - Esercitazione n 9- -Thread-

Transcript:

E.Mumolo, DEEI mumolo@units.it

Il comando gcc c file.c compila file.c, segnala eventuali errori e scrive il risultato della compilazione (oggetto) nel file.o il comando viene ripetuto per tutti i file che compongono il programma. I file oggetto sono linkati con il comando gcc file1.o file2.o -l<libreria> -o<nome file uscita> Se si desidera compilare senza esplicitamente generare i file oggetto, si indicano direttamente le componenti come file.c Se non viene indicato un file d uscita il codice eseguibile viene salvato nel file a.out Una libreria e un insieme di file oggetto riuniti in un file archivio. Le librerie sono contenute in /lib e /usr/lib con nome lib<nome>.a Come si richiamano? Con la sintassi -l<nomelibreria> Alcune librerie: lc libreria standard. Viene caricata automaticamente lcurses gestione del video ad alto livello lm funzioni matematiche Se il numero di file sorgenti e maggiore di 10, allora diventa necessario usare il comando make per gestire il programma

make il comando make indica come vogliamo generare il programma. Il programma e l obiettivo (target) della operazione e dipende da uno o piu file, (componenti), i quali a loro volta possono dipendere da altri file. Il comando make deve conoscere questa gerarchia di dipendenze che vengono scritte nel file makefile o Makefile Esempio: programma composto da tre sorgenti: file1.c, file2.c e file3.c # questo e un commento programma : main.o file1.o file2.o file3.o # linea dipendenza cc o programma main.o file1.o file2.o file3.o # linea comando main.o : main.c # dipendenza cc c main.c # comando file1.o : file1.c # dipendenza cc c file1.c # comando file2.o : file2.c # dipendenza cc c file2.c # comando file3.o : file3.c # dipendenza cc c file3.c # comando Le linee che contengono i due punti specificano le dipendenze. A sinistra dei due punti c e il target; a destra ci sono le componenti Le linee che cominciano con il tab sono linee comando; specificano come costruire i target make verifica le date per vedere se i file oggetto sono aggiornati. Se sono aggiornati non fa niente e scrive semplicemente programma is up to date

System call Entry point per il kernel; mettono a disposizione del programmatore i servizi del sistema operativo Chiamate con il meccanismo delle trap Funzioni di libreria: funzioni di utilità che forniscono servizi general purpose al programmatore queste funzioni non sono entry point del kernel, sebbene alcune di esse possano fare uso delle system call per realizzare i propri servizi Nota E possibile sostituire le funzioni di libreria con altre funzioni che realizzino lo stesso compito, magari in modo diverso In generale, non è possibile sostituire le system call, che dipendono dal sistema operativo

POSIX = Portable Operating System Interface for Computing Environments Definisce una interfaccia standard con il Sistema Operativo Standard IEEE. Prima pubblicazione 1990 Basato su Unix Riguarda: Operazioni su file Gestione processi Gestione segnali Gestione dispositivi Estensioni in Tempo Reale Multi-threading

POSIX = famiglia di oltre 30 standard, chiamati IEEE Std 1003.n Ultima versione: 30 aprile 2004 POSIX non si occupa di: Interfacce grafiche Interfacce con sistemi di gestione di basi dati Portabilità di codice oggetto o eseguibile Gli standard più importanti: IEEE 1003.1 (POSIX.1): Definizione delle system call (operating system interface) IEEE 1003.2 (POSIX.2): Shell e utility IEEE 1003.7 (POSIX.7): System administration In queste slide è trattato 1003.1

Uno sguardo agli Standard: POSIX.1(1003.1) interfaccia base POSIX.1a estensioni varie POSIX.2 comandi shell POSIX.3 metodi di test POSIX.4(1003.1b) estensioni real time POSIX.4a(1003.1c) estensione ai thread POSIX.4b(1003.1d) altre estensioni real-time POSIX.5 estensione ad ADA POSIX.6(1003.1e) sicurezza POSIX.7 amministrazione di sistema POSIX.8(1003.1f) accesso ai file di rete

Gli standard più importanti per la programmazione Real Time sono: 1003.1a Definizioni interfacce base, supporto per processo singolo e multiplo, controllo dei processi, segnali, file system, pipe, fifo 1003.1b Estensioni Real Time segnali, schedulazione prioritaria, timer, I/O asincrono, prioritario, sincrono, messaggi, semafori 1003.1c Thread controllo dei thread,attributi, schedulazione, variabili condizione 1003.1d Estensioni RT aggiuntive nuove creazioni processi, schedulazione sporadic server, monitoraggio del tempo di esecuzione, timeout sulle funzioni bloccanti 1003.1j Funzioni RT avanzate sincronizzazione, code di messaggi 1003.2l funzioni RT distribuito supporto per la comunicazione distribuita in tempo reale, operazioni sincrone e asincrone, priorità di messaggi I primi tre standard sono quelli più supportati

I sistemi dedicati hanno generalmente risorse limitate Lo standard 1003.13 è stato definito per questo tipo di sistemi Non contiene tutte le caratteristiche; raggruppa le funzioni in unità di funzionalità, chiamati profili. I profili correntemente definiti sono quattro: Profilo 51: singolo processore, thread, non funzioni di file system Profilo 52: singolo processore, thread, con funzioni di file system Profilo 53: processori multipli, thread, senza funzioni di file system Profilo 54: processori multipli, thread, con funzioni di file system Gli standard 1b, 1d e 1j definiscono estensioni utili per lo sviluppo di sistemi in tempo reale; lo standard 1003.1b è il più diffuso e supportato. Le funzionalità definite in 1003.1b riguardano: Timer, schedulazione prioritaria, segnali in tempo reale, semafori, code, memoria condivisa, bloccaggio della memoria

Supporto POSIX nei sistemi operativi commerciali: Solaris: supporto POSIX 1a, 1b, 1c completi LynxOS: supporta parzialmente 1a, 1b completo, 1c parziale VxWorks:1a, 1b parziale, 1c attraverso un terzo fornitore IRIX: 1a parziale, 1b, 1c completo Linux: 1a, 1c completo, 1b parziale

Per scrivere un programma conforme a POSIX, bisogna includere gli header files richiesti dalle varie primitive usate, scelti fra: header files della C Standard Library (contenenti opportune modifiche POSIX) header files specifici POSIX Nel seguito, gli header files saranno omessi dalle slide; per ottenere la lista completa degli header necessari man <funzione Se vogliamo compilare un programma in modo tale che dipenda solo dallo standard POSIX dobbiamo inserire, prima degli #include, #define _POSIX_SOURCE 1 Lo header sys/types.h definisce i tipi dei dati di sistema primitivi (dipendenti dall implementazione): Esempio: in linux sys/types.h definisce il tipo dev_t come una quantità a 16 bit questi tipi sono definiti per garantire portabilità sono definiti tramite typedef

Alcuni dei tipi di dati primitivi di sistema clock_t counter of clock ticks dev_t device numbers fd_set file descriptor set fpos_t file position gid_t group id ino_t inode number mode_t file types, file creation mode nlink_t number of hard links off_t offset pid_t process id size_t dimensioni (unsigned) ssize_t count of bytes (signed) (read, write) time_t counter of seconds since the Epoch uid_t user id

POSIX.1 definisce un gran numero di costanti che definiscono limiti implementativi del sistema operativo È uno degli aspetti più complessi di POSIX.1 Limiti runtime alcuni limiti runtime possono essere fissi in un implementazione altri possono variare all interno della stessa implementazione Esempio: la dimensione massima di nome di file dipende dal particolare file system considerato

33 limiti e costanti, suddivisi in 7 categorie: valori minimi invarianti (13 costanti definite in limits.h) questi valori non cambiano da un sistema ad un altro. Rappresentano i valori minimi che devono essere supportati da qualunque implementazione POSIX _POSIX_ARG_MAX number of arguments 4096 _POSIX_CHILD_MAX number of child processes per uid 6 _POSIX_LINK_MAX number of links to a file 8... nota: limiti troppo bassi per essere significativi limiti invarianti: SSIZE_MAX max size of type ssize limiti che possono crescere a run-time: NGROUPS_MAX no. Massimo di Group ID associati ad un processo

33 limiti e costanti, suddivisi in 7 categorie: Limiti invarianti run-time (eventualmente indeterminati): ARG_MAX max. number of arguments for exec CHILD_MAX max. number of processes per user ID CLK_TCK number of clocks ticks per second OPEN_MAX max number of open files per process PASS_MAX max. number of significant char. in password STREAM_MAX max. number of standard I/O streams TZNAME_MAX max. number of byte for names of a time zone Costanti simboliche a tempo di compilazione: _POSIX_SAVED_IDS (se supportata dall implementazione) _POSIX _VERSION versione posix _POSIX _JOB_CONTROL (se supportata dall implementazione)

limiti variabili per i pathname (eventualmente indeterm.): LINK_MAX max value of link s count MAX_CANON terminal-related MAX_INPUT terminal-related NAME_MAX max number of chars for a filename (no-null) PATH_MAX max number of chars for a pathname (no-null) PIPE_BUF max. number of bytes atomic. written in a pipe flag variabili: _POSIX_NO_TRUNC se i nomi di file maggiori di NAME_MAX sono troncati _POSIX_CHOWN_RESTRICTED Se l uso di chown è vincolato

long sysconf(int name); legge i valori invarianti esempio di nome: _SC_nome costante long pathconf(const char *path, int name); long fpathconf(int filedes, int name) leggono i valori che possono variare a seconda del file a cui sono applicati esempio di nome: _PC_nome_costante

#include <errno.h> #include <limits.h> #include <unistd.h> #include <stdio.h> static void static void pr_sysconf(char *, int); pr_pathconf(char *, char *, int); Int main(int argc, char *argv[]) { if (argc!= 2) { printf("usage: a.out <dirname>"); exit(1); pr_sysconf("arg_max =", _SC_ARG_MAX); pr_sysconf("child_max =", _SC_CHILD_MAX); pr_sysconf("clock ticks/second =", _SC_CLK_TCK); pr_sysconf("ngroups_max =", _SC_NGROUPS_MAX); pr_sysconf("open_max =", _SC_OPEN_MAX); #ifdef _SC_STREAM_MAX pr_sysconf("stream_max #endif #ifdef _SC_TZNAME_MAX pr_sysconf("tzname_max #endif =", _SC_STREAM_MAX); =", _SC_TZNAME_MAX);

pr_sysconf("_posix_job_control =", _SC_JOB_CONTROL); pr_sysconf("_posix_saved_ids =", _SC_SAVED_IDS); pr_sysconf("_posix_version =", _SC_VERSION); pr_pathconf("max_canon =", "/dev/tty", _PC_MAX_CANON); pr_pathconf("max_input =", "/dev/tty", _PC_MAX_INPUT); pr_pathconf("_posix_vdisable =", "/dev/tty", _PC_VDISABLE); pr_pathconf("link_max =", argv[1], _PC_LINK_MAX); pr_pathconf("name_max =", argv[1], _PC_NAME_MAX); pr_pathconf("path_max =", argv[1], _PC_PATH_MAX); pr_pathconf("pipe_buf =", argv[1], _PC_PIPE_BUF); pr_pathconf("_posix_no_trunc =", argv[1], _PC_NO_TRUNC); pr_pathconf("_posix_chown_restricted =", argv[1], _PC_CHOWN_RESTRICTED); exit(0); static void pr_sysconf(char *mesg, int name) { long val; fputs(mesg, stdout); errno = 0; if ( (val = sysconf(name)) < 0) { if (errno!= 0) { perror("sysconf error"); exit(1); fputs(" (not defined)\n", stdout); else printf(" %ld\n", val);

static void pr_pathconf(char *mesg, char *path, int name) { long val; fputs(mesg, stdout); errno = 0; if ( (val = pathconf(path, name)) < 0) { if (errno!= 0) { perror("pathconf error"); exit(1); fputs(" (no limit)\n", stdout); else printf(" %ld\n", val);

se non è definito nello header limits.h se sysconfig() o pathconfig() ritornano 1 allora il valore non è definito Esempio: molti programmi hanno necessità di allocare spazio per i pathname; la domanda è: quanto? pathalloc.c cerca di determinare PATH_MAX in caso il valore di default non sia sufficiente, in alcuni casi è possibile aumentare lo spazio previsto e ritentare esempio: getcwd() (get current working directory)

Esempio una sequenza di codice comune in un processo daemon (che esegue in background, non connesso ad un terminale) è quella di chiudere tutti i file aperti come? #include <sys/param.h> for (i=0; i < NOFILE; i++) close(i); oppure può utilizzare il codice seguente

#include <errno.h> #include <limits.h> #include <unistd.h> #ifdef OPEN_MAX static int openmax = OPEN_MAX; #else static int openmax = 0; #endif #define OPEN_MAX_GUESS 256 /* se OPEN_MAX e indeterminato */ /* non sappiamo se questo e sufficiente */ int main(void) { open_max(); printf("open max: %d\n", openmax); Int open_max(void) { if (openmax == 0) { /* la prima volta */ errno = 0; if ( (openmax = sysconf(_sc_open_max)) < 0) { if (errno == 0) openmax = OPEN_MAX_GUESS; /* e indeterminato */ else { perror("sysconf error for _SC_OPEN_MAX"); exit(1); return(openmax);

La maggior parte delle system call: restituisce il valore -1 in caso di errore ed assegna lo specifico codice d errore alla variabile globale extern int errno; Nota: se la system call ha successo, errno non viene resettato Lo header file errno.h contiene la definizione dei nomi simbolici dei codici di errore # define EPERM 1 /* Not owner */ # define ENOENT 2 /* No such file or dir */ # define ESRCH 3 /* No such process */ # define EINTR 4 /* Interr. system call */ # define EIO 5 /* I/O error */

La primitiva void perror (const char *str ) converte il codice in errno in un messaggio in inglese, e lo stampa anteponendogli il messaggio di errore str Esempio:... fd=open("nonexist.txt", O_RDONLY); if (fd==-1) perror ("main");... main: No such file or directory

Gestione file: generalità Un file per essere usato deve essere aperto (open) L operazione open: Nota: localizza il file nel file system attraverso il suo pathname copia in memoria il descrittore del file (i-node) associa al file un intero non negativo (file descriptor), che verrà usato nelle operazioni di accesso al file, invece del pathname I file standard non devono essere aperti, perchè sono aperti dalla shell. Sono associati ai file descriptor 0 (input), 1 (output) e 2 (error). La close rende disponibile il file descriptor per ulteriori usi Un file può essere aperto più volte, e quindi avere più file descriptor associati contemporaneamente

System call: int open(const char *path, int oflag,...); apre (o crea) il file specificato da pathname (assoluto o relativo), secondo la modalità specificata in oflag restituisce il file descriptor con il quale ci si riferirà al file successivamente (o -1 se errore) Valori di oflag O_RDONLY read-only (0) O_WRONLY write-only (1) O_RDWR read and write (2) Solo una di queste costanti può essere utilizzata in oflag Altre costanti (che vanno aggiunte in or ad una di queste tre) permettono di definire alcuni comportamenti particolari

Altri valori di oflag: O_APPEND append O_CREAT creazione del file O_EXECL con O_CREAT, ritorna un errore se il file esiste O_TRUNC se il file esiste, viene svuotato O_NONBLOCK file speciali (discusso in seguito) O_SYNC synchronous write Se si specifica O_CREAT, è necessario specificare anche i permessi iniziali come terzo argomento La maschera O_ACCMODE (uguale a 3) permette di isolare la modalità di accesso oflag & O_ACCMODE può essere uguale a O_RDONLY, O_WRONLY, oppure O_RDWR

int creat(const char *path, mode_t mode); crea un nuovo file normale di specificato pathname, e lo apre in scrittura mode specifica i permessi iniziali; l'owner è l'effective user-id del processo se il file esiste già, lo svuota (owner e mode restano invariati) restituisce il file descriptor, o -1 se errore Equivalenze: creat(path, mode); open(path, O_WRONLY O_CREAT O_TRUNC, mode); int close(int filedes); chiude il file descriptor filedes restituisce l esito dell operazione (0 o -1) Quando un processo termina, tutti i suoi file vengono comunque chiusi automaticamente

Ogni file aperto e associato a: un current file offset, la posizione attuale all interno del file un valore non negativo che misura il numero di byte dall inizio del file operazioni read/write leggono dalla posizione attuale e incrementano il current file offset in avanti del numero di byte letti o scritti Quando viene aperto un file Il current file descriptor viene posizionato a 0 a meno che l opzione O_APPEND non sia specificata

off_t lseek(int filedes, off_t offset, int whence); sposta la posizione corrente nel file filedes di offset bytes a partire dalla posizione specificata in whence: SEEK_SET dall'inizio del file SEEK_CUR dalla posizione corrente SEEK_END dalla fine del file restituisce la posizione corrente dopo la lseek, o -1 se errore lseek non effettua alcuna operazione di I/O

ssize_t read(int filedes, void *buf, size_t nbyte); legge in *buf una sequenza di nbyte byte dalla posizione corrente del file filedes aggiorna la posizione corrente restituisce il numero di bytes effettivamente letti, o -1 se errore Esistono un certo numero di casi in cui il numero di byte letti e inferiore al numero di byte richiesti: Fine di un file regolare Per letture da stream provenienti dalla rete Per letture da terminale etc. ssize_t write(int filedes, const void *buf, size_t nbyte); scrive da *buf una sequenza di nbyte byte dalla posizione corrente del file filedes aggiorna la posizione corrente restituisce il numero di bytes effettivamente scritti, o -1 se errore

SEEK #include <sys/types.h> #include <unistd.h> int main(void) { if (lseek(stdin_fileno, 0, SEEK_CUR) == -1) printf("cannot seek\n"); else printf("seek OK\n"); exit(0); CAT #include <unistd.h> #define BUFFSIZE 8192 int main(void) { int n; char buf[buffsize]; while ( (n = read(stdin_fileno, buf, BUFFSIZE)) > 0) if (write(stdout_fileno, buf, n)!= n) { perror("write error"); exit(1); if (n < 0) { perror("read error"); exit(1); exit(0);

Uso dei File come semafori #include <curses.h> #include <sys/errno.h> #define LOCKDIR "./" #define MAXTRIES 3 #define NAPTIME 5 int lock(name) char *name; { char *path, *lockpath(); int fd, tries; extern int errno; path = lockpath(name); tries = 0; while ((fd = creat(path,0)) == -1 && errno ==EACCES) { if (++tries >= MAXTRIES) return(false); if (fd == -1 close(fd) == -1) syserr("lock"); return(true);

!" # void unlock(name) char *name; { char *lockpath(); if (unlink(lockpath(name)) == -1) syserr("unlock"); static char *lockpath(name) char *name; { char *nn; static char path[20]; char *strcat(); strcpy(path, LOCKDIR); nn = strcat(path,name); printf("lockpath: %s\n",nn); return(nn);

/* copia di file. Tiene conto delle scritture incomplete */ #include <stdio.h> #include <errno.h> #include <fcntl.h> #define BUFSIZE 512 void copyok(char *from, char *to); main() { char nome1[10], nome2[10]; printf("nomi file? "); scanf("%s %s",nome1,nome2); printf("nomi letti: %s, %s\n", nome1,nome2); copyok(nome1,nome2); void copyok(char *from, char *to) { int fromfd, tofd, nread, nwrite, n; char buf[bufsize]; if((fromfd = open(from,0)) == -1) syserr("from"); if ((tofd = creat(to,0666)) == -1) syserr("to"); while ((nread = read(fromfd, buf, sizeof(buf)))!= 0) { if (nread == -1) syserr("read"); nwrite = 0; do{ if ((n=write(tofd,&buf[nwrite], nread - nwrite)) == -1) syserr("write"); nwrite += n; while (nwrite < nread); if(close(fromfd) == -1 close(tofd) == -1) syserr("close");

$ % #include "boolean.h" #include "stream.h" #include <stdio.h> #include <errno.h> #include <fcntl.h> void copybuffered(char *from, char *to); void timestart(); void timestop(char *msg); main() { char nome1[10], nome2[10]; printf("nomi file? "); scanf("%s %s",nome1,nome2); printf("nomi letti: %s, %s\n", nome1,nome2); copy2(nome1,nome2); void copy2(char *from, char *to) { STREAM *stfrom, *stto; int c; extern int errno; if((stfrom = Sopen(from,"r")) == NULL) syserr(from); if((stto = Sopen(to,"w")) == NULL) syserr(to); timestart(); while((c=sgetc(stfrom))!= -1) if(!sputc(stto,c)) syserr("sputc"); timestop("tempi di copy2"); if(errno!= 0) syserr("sgetc"); if (!Sclose(stfrom)!Sclose(stto)) syserr("sclose");

&'()*+ #include <fcntl.h> #include <stdio.h> #include "boolean.h" #include "stream.h" extern int errno; STREAM *Sopen(char *path,char *dir) { STREAM *z; int fd, flags; char *malloc(); switch(dir[0]){ case 'r': flags = O_RDONLY; break; case 'w': flags = O_WRONLY O_CREAT O_TRUNC; break; default: errno = SEINVAL; return(null); if((fd=open(path,flags,0666)) == -1) return(null); if((z = (STREAM *)malloc(sizeof(stream))) == NULL) { errno = SENOMEM; return(null); z->fd = fd; z->dir = dir[0]; z->total = z->next = 0; return(z);

()*+" # static BOOLEAN readbuf(stream *z) { switch(z->total = read(z->fd, z->buf, sizeof(z->buf))) { case -1: return(false); case 0: errno = 0; return(false); default: z->next = 0; return(true); static BOOLEAN writebuf(stream *z) { int n,total; total=0; while(total < z->next){ if((n=write(z->fd,&z->buf[total],z->next - total))==-1) return(false); total += n; z->next = 0; return(true);

()*+" # int Sgetc(STREAM *z) { int c; if(z->next >= z->total &&!readbuf(z)) return(-1); return(z->buf[z->next++] & 0377); BOOLEAN Sputc(STREAM *z, char c) { z->buf[z->next++] = c; if (z->next >= sizeof(z->buf)) return(writebuf(z)); return(true); BOOLEAN Sclose(STREAM *z) { int fd; if(z->dir == 'w' &&!writebuf(z)) return(false); fd = z->fd; free(z); return(close(fd)!= -1);

, % Tabelle coinvolte: User File Descriptor Table, File Table, Inode table Esempio: un processo che apre due file distinti

, % Esempio: due processi che aprono lo stesso file

-. Alla conclusione di ogni write il current offset nella file table entry viene incrementato e il current offset è maggiore della dimensione del file nella v-node table entry, questa viene incrementata se il file è aperto con il flag O_APPEND un flag corrispondente è settato nella file table entry ad ogni write, il current offset viene prima posto uguale alla dimensione del file nella v-node table entry lseek modifica unicamente il current offset nella file table entry corrispondente E possibile che più file descriptor entry siano associate a una singola file table entry tramite la funzione dup, all interno dello stesso processo tramite fork, tra due processi diversi E interessante notare che esistono due tipi di flag: alcuni sono associati alla file descriptor entry, e quindi sono particolari del processo altri sono associati alla file table entry, e quindi possono essere condivisi fra più processi Esiste la possibilità di modificare questi flag (funzione fcntl) E importante notare che questo sistema di strutture dati condivise può portare a problemi di concorrenza

/ Funzione dup Seleziona il più basso file descriptor libero della tabella dei file descriptor Assegna la nuova file descriptor entry al file descriptor selezionato Ritorna il file descriptor selezionato Funzione dup2 Con dup2, specifichiamo il valore del nuovo descrittore come argomento filedes2 Se filedes2 è già aperto, viene chiuso e sostituito con il descrittore duplicato Ritorna il file descriptor selezionato

+ int fcntl(int filedes, int cmd, /* int arg */) La funzione fcntl può cambiare le proprietà di un file aperto Argomento 1: è il descrittore del file su cui operare Argomento 2: è il comando da eseguire Argomento 3: quando presente, parametro del comando; in generale, un valore intero nel caso di record locking, un puntatore Comandi: duplicazione di file descriptor (F_DUPFD) get/set file descriptor flag (F_GETFD, F_SETFD) get/set file status flag (F_GETFL, F_SETFL) get/set async. I/O ownership (F_GETOWN, F_SETOWN) get/set record locks (F_GETLK, F_SETLK, F_SETLKW)

+ int fcntl(int filedes, F_DUPFD, int min) Duplica il file descriptor specificato da filedes Ritorna il nuovo file descriptor Il file descriptor scelto è uguale al valore più basso corrispondente ad un file descriptor non aperto e che sia maggiore o uguale a min int fcntl(int filedes, F_GETFD) Ritorna i file descriptor flag associati a filedes Attualmente, è definito un solo file descriptor flag: Se FD_CLOEXEC è true, il file descriptor viene chiuso eseguendo una exec

+ int fcntl(int filedes, F_SETFD, int newflag) Modifica i file descriptor flag associati a filedes, utilizzando il terzo argomento come nuovo insieme di flag int fcntl(int filedes, F_GETFL) Ritorna i file status flag associati a filedes I file status flag sono quelli utilizzati nella funzione open Per determinare la modalità di accesso, è possibile utilizzare la maschera ACC_MODE Per determinare gli altri flag, è possibile utilizzare le costanti definite (O_APPEND, O_NONBLOCK, O_SYNC)

+ int fcntl(int filedes, F_SETFL, int newflag) Modifica i file status flag associati a filedes con il valore specificato in newflag I soli valori che possono essere modificati sono O_APPEND,O_NONBLOCK, O_SYNC; l access mode deve rimanere inalterato Gli altri comandi di fcntl verranno trattati in seguito Quando introduciamo il concetto di segnale Quando introduciamo il concetto di locking Il codice proposto prende un argomento singolo da linea di comando (che specifica un descrittore di file) e stampa una descrizione delle flag del file per quel descrittore

// Questo programma prende un argomento singolo da linea di comando (che specifica un // descrittore di file) e stampa una descrizione delle flag del file per quel descrittore #include <sys/types.h> #include <fcntl.h> #include <unistd.h> Int main(int argc, char *argv[]) { int accmode, val; if (argc!= 2) { printf("usage: a.out <descriptor#>"); exit(1); if ( (val = fcntl(atoi(argv[1]), F_GETFL)) < 0) { perror("fcntl error for fd %d", atoi(argv[1])); exit(1); accmode = val & O_ACCMODE; if (accmode == O_RDONLY) printf("read only"); else if (accmode == O_WRONLY) printf("write only"); else if (accmode == O_RDWR) printf("read write"); else printf("unknown access mode"); if (val & O_APPEND) printf(", append"); if (val & O_NONBLOCK) printf(", nonblocking"); #if!defined(_posix_source) && defined(o_sync) if (val & O_SYNC) printf(", synchronous writes"); #endif putchar('\n'); exit(0);

+ Altro esempio d uso di fcntl: La funzione set_fl mette a 1 i flag specificati La funzione richiede prima i flag correnti, utilizza la maschera con OR, e salva di nuovi i flag void set_fl(int fd, int flags) { int val; if ( (val = fcntl(fd, F_GETFL, 0)) < 0) err_sys("fcntl F_GETFL error"); val = flags; /* turn on flags */ if (fcntl(fd, F_SETFL, val) < 0) err_sys("fcntl F_SETFL error");

* 01 int iocntl(int filedes, int request, ) Categorie di comandi: disk labels I/O, file I/O, magnetic tapes I/O, socket I/O, terminal I/O fsync() effettua l'operazione di "flush" dei dati bufferizzati dal s.o. per il file descriptor fd, ovvero li scrive sul disco o sul dispositivo sottostante Motivazioni il gestore del file system può mantenere i dati in buffer in memoria per diversi secondi, prima di scriverli su disco per ragioni efficienza ritorna 0 in caso di successo, -1 altrimenti

! int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); permette ad un processo di aspettare contemporaneamente su file descriptor multipli, con un timeout opzionale Parametri di input *fds sono insiemi di file descriptor, realizzati tramite mappe di bit n è la dimensione massima di questi insiemi timeval è il timeout opzionale Parametri di output ritorna il numero di file descriptor che sono pronti per operazioni di I/O immediate modifica gli insiemi di wait descriptor, ponendo a 1 i bit relativi ai file descriptor pronti per l'i/o

!2. Ogni processo possiede una file descriptor entry ed una file table entry personale La v-node table è condivisa Si consideri il seguente interleaving tra i due processi: inizialmente, la dimensione del file sia 1500 processo 1 esegue lseek e pone il suo current offset a 1500 processo 2 esegue lseek e pone il suo current offset a 1500 processo 2 esegue write; scrive i byte 1500-1599 e pone la dimensione del file a 1600 processo 1 esegue write; sovrascrive i byte 1500-1599 e pone la dimensione del file a 1600 Soluzione: utilizzate open con O_APPEND (write atomica)

!2. Si consideri un singolo processo che voglia creare un file, riportando un messaggio di errore se il file è già esistente Nelle prime versioni di UNIX, i flag O_CREAT and O_EXCL non esistevano Il codice poteva essere scritto nel modo seguente: if ( ( fd = open(pathname, O_WRONLY) ) < 0) if (errno == ENOENT) { if ( (fd = creat(pathname, mode) < 0) err_sys( creat error ); else err_sys( open error ); Questo funziona quando abbiamo un solo processo che accede al file: Cosa succede se abbiamo due (o più) processi? Cosa succede se il file viene creato da un altro processo tra l operazione open e l operazione creat, e questo processo scrive delle informazioni su questo file? I dati vengono persi, perché sovrascritti Il problema è evitato dalla system call open con i flag O_CREAT e O_EXCL

!. Queste due sequenze di codice sono equivalenti: dup(filedes) fcntl(filedes, F_DUPFD, 0); Queste due sequenze di codice sono equivalenti? dup2(filedes, filedes2) close(filedes2); dup2(filedes, F_DUPFD, filedes2); Risposta: no! Possono esserci altri file descriptor liberi Il codice può essere interrotto da un signal catcher, il quale potenzialmente può aprire nuovi file

*.3 Informazioni sui file #include <sys/types.h> #include <sys/stat.h> int stat(const char *pathname, struct stat *buf); int fstat(int filedes, struct stat* buf); int lstat(const char *pathname, struct stat *buf); Le tre funzioni ritornano un struttura stat contenente informazioni sul file stat identifica il file tramite un pathname fstat identifica un file aperto tramite il suo descrittore lstat, se applicato ad un link simbolico, ritorna informazioni sul link simbolico, non sul file linkato

*.3 Il secondo argomento delle funzioni stat è un puntatore ad una struttura di informazioni sul file specificato struct stat { mode_t st_mode; ino_t st_ino; dev_t st_dev; dev_t st_rdev; nlink_t st_nlink; uit_t uid; gid_t gid; off_t st_size; time_t st_atime; time_t st_mtime; time_t st_ctime; long st_blksize; long st_blocks; // File type & mode // i-node number // device number (file system) // device n. for special files // number of links // user ID of owner // group ID of owner // size in bytes, for reg. files // time of last access // time of last modif. // time of last status change // best I/O block size // number of 512-byte blocks

#include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int main(int argc, char *argv[]) { int i; struct stat buf; char *ptr; for (i = 1; i < argc; i++) { printf("%s: ", argv[i]); if (lstat(argv[i], &buf) < 0) { perror("lstat error"); continue; if (S_ISREG(buf.st_mode)) ptr = "regular"; else if (S_ISDIR(buf.st_mode)) ptr = "directory"; else if (S_ISCHR(buf.st_mode)) ptr = "character special"; else if (S_ISBLK(buf.st_mode)) ptr = "block special"; else if (S_ISFIFO(buf.st_mode)) ptr = "fifo"; #ifdef S_ISLNK else if (S_ISLNK(buf.st_mode)) ptr = "symbolic link"; #endif #ifdef S_ISSOCK else if (S_ISSOCK(buf.st_mode)) ptr = "socket"; #endif else ptr = "** unknown mode **"; printf("%s\n", ptr); exit(0);

*.3 set-user-id e set-group-id: in st_mode, esiste un flag (set-user-id) che fa in modo che quando questo file viene eseguito, effective user id == st_uid in st_mode, esiste un flag (set-group-id) che fa in modo che quando questo file viene eseguito, effective group id == st_gid E possibile utilizzare questi due bit per risolvere il problema dell accesso al file passwd: l owner del comando passwd è root quando passwd viene eseguito, il suo effective user id è uguale a root il comando può allora modificare in scrittura il file /etc/passwd

*.3 Costanti per accedere ai diritti di lettura e scrittura contenuti in st_mode S_ISUID set-user-id S_ISGID set-group-id S_IRUSR accesso in lettura, owner S_IWUSR accesso in scrittura, owner S_IXUSR accesso in esecuzione, owner S_IRGRP accesso in lettura, gruppo S_IWGRP accesso in scrittura, gruppo S_IXGRP accesso in esecuzione, gruppo S_IROTH accesso in lettura, altri S_IWOTH accesso in scrittura, altri S_IXOTH accesso in esecuzione, altri

*.3 int access(const char* pathname, int mode) Quando si accede ad un file, vengono utilizzati effective uid e effective gid A volte, può essere necessario verificare l accessibilità in base a real uid e real gid Per fare questo, si utilizza access mode è un maschera ottenuta tramite bitwise or delle seguenti costanti: R_OK test per read permission W_OK test per write permission X_OK test per execute permission

*.3 mode_t umask(mode_t cmask); Cambia la maschera utilizzata per la creazione dei file; ritorna la maschera precedente; Per formare la maschera, si utilizzano le costanti S_IRUSR, S_IWUSR viste in precedenza Funzionamento: La maschera viene utilizzata tutte le volte che un processo crea un nuovo file Tutti i bit che sono accesi nella maschera, verranno spenti nell access mode del file creato

*.3 int chmod (const char* path, mode_t mode); int fchmod (int filedes, mode_t mode); Cambia i diritti di un file specificato dal pathname (chmod) o di un file aperto (fchmod) Per cambiare i diritti di un file, l effective uid del processo deve essere uguale all owner del file oppure deve essere uguale a root int chown(char* pathname, uid_t owner, gid_t group); int fchown(int filedes, uid_t owner, gid_t group); int lchown(char* pathname, uid_t owner, gid_t group); Queste tre funzioni cambiano lo user id e il group id di un file Nella prima, il file è specificato come pathname Nella seconda, il file aperto è specificato da un file descriptor Nella terza, si cambia il possessore del link simbolico, non del file stesso Restrizioni: In alcuni sistemi, solo il superuser può cambiare l owner di un file (per evitare problemi di quota) Costante POSIX_CHOWN_RESTRICTED definisce se tale restrizione è in vigore

4 5 Hard link per directory: ogni directory dir ha un numero di hard link maggiore uguale a 2: Uno perché dir possiede un link nella sua directory genitore Uno perché dir possiede un entry. che punta a se stessa ogni sottodirectory di dir aggiunge un hard link: Uno perché la sottodirectory possiede un entry.. che punta alla directory genitore

4 5 int link(char* oldpath, char* newpath); crea un nuovo link ad un file esistente operazione atomica: aggiunge la directory entry e aumenta il numero di link per l inode identificato da oldpath errori: oldpath non esiste newpath esiste già solo root può creare un hard link ad una directory (per evitare di creare loop, che possono causare problemi) oldpath e newpath appartengono a file system diversi utilizzato dal comando ln

4 5 int unlink(char* path); int remove(char* path); rimuove un hard link per il file specificato da path operazione atomica: rimuove la directory entry e decrementa di uno il numero di link del file errori: path non esiste un utente diverso da root cerca di fare unlink su una directory utilizzato dal comando rm un file può essere effettivamente cancellato dall hard disk quando il suo conteggio raggiunge 0 Unlink: cosa succede quando un file è aperto? Il file viene rimosso dalla directory Il file non viene rimosso dal file system fino alla chiusura Quando il file viene chiuso, il sistema controlla se il numero di hard link è sceso a zero; in tal caso rimuove il file

*.3 int rename(char* oldpath, char* newpath); Cambia il nome di un file da oldpath a newpath Casi: se oldpath specifica un file regolare, newpath non può essere una directory esistente se oldpath specifica un file regolare e newpath è un file regolare esistente, questo viene rimosso e sostituito sono necessarie write permission su entrambe le directory se oldpath specifica una directory, newpath non può essere un file regolare esistente se oldpath specifica una directory, newpath non può essere una directory non vuota

5' file speciali che contengono il pathname assoluto di un altro file E un puntatore indiretto ad un file (a differenza degli hard link che sono puntatori diretti agli inode) Introdotto per superare le limitazioni degli hard link: hard link possibili solo fra file nello stesso file system hard link a directory possibili solo al superuser attenzione: un link simbolico può introdurre loop i programmi che analizzano il file system devono essere in grado di gestire questi loop (esempio in seguito)

5' int symlink(char* oldpath, char* newpath); crea una nuova directory entry newpath con un link simbolico che punta al file specificato da oldpath nota: oldpath e newpath non devono risiedere necessariamente nello stesso file system int readlink(char* path, char* buf, int size); poiché la open segue il link simbolico, abbiamo bisogno di una system call per ottenere il contenuto del link simbolico questa system call copia in buf il valore del link simbolico

( Tre valori temporali sono memorizzati nella struttura stat st_atime (opzione u in ls) Ultimo tempo di accesso read, creat/open (when creating a new file), utime st_mtime (default in ls) Ultimo tempo di modifica del contenuto write, creat/open, truncate, utime st_ctime (opzione c in ls) Ultimo cambiamento nello stato (nell inode) chmod, chown, creat, mkdir, open, remove, rename, truncate, link, unlink, utime, write Attenzione ai cambiamenti su contenuto, inode e accesso per quanto riguarda le directory Per modificare st_ctime int utime(char* pathname, struct utimbuf *times); struct utimbuf { time_t actime; time_t modtime;

/3 int mkdir(char* path, mode_t); Crea una nuova directory vuota dal path specificato Modalità di accesso: I permessi specificati da mode_t vengono modificati dalla maschera specificata da umask E necessario specificare i diritti di esecuzione (search) int rmdir(char* path); Rimuove la directory vuota specificata da path Se il link count della directory scende a zero, la directory viene effettivamente rimossa; altrimenti si rimuove la directory

/3 Ogni processo ha una directory corrente a partire dalla quale vengono fatte le ricerche per i pathname relativi System call: int chdir(char* path); int fchdir(int filedes); Cambia la directory corrente associata al processo, utilizzando un pathname oppure un file descriptor Funzione: char* getcwd(char* buf, size_t size); Legge la directory corrente e riporta il pathname assoluto nel buffer specificato da buf e di dimensione size

ogni processo ha un identificatore univoco (intero non negativo) utilizzato spesso per creare altri identificatori e garantire unicità funzione char* tmpnam(char* ); Identificatori standard: pid 0: Non assegnato o assegnato a un kernel process pid 1: Processo init (/sbin/init): invocato dal kernel al termine della procedura di bootstrap. Porta il sistema ad un certo stato (ad es. multiuser)

pid_t getpid(); // Process id del processo chiamante pid_t getppid(); // Process id del processo padre uid_t getuid(); // Real user id uid_t geteuid(); // Effective user id gid_t getgid(); // Real group id gid_t getegid(); // Effective group id

, System call: pid_t fork(); crea un nuovo processo child, copiando completamente l immagine di memoria del processo parent data, heap, stack vengono copiati il codice viene spesso condiviso in alcuni casi, si esegue copy-on-write sia il processo child che il processo parent continuano ad eseguire l istruzione successiva alla fork fork viene chiamata una volta, ma ritorna due volte processo child: ritorna 0 (E possibile accedere al pid del parent tramite la system call getppid) processo parente: ritorna il process id del processo child errore: ritorna valore negativo

#include <sys/types.h> #include "ourhdr.h" int glob = 6; /* variabile esterna */ char buf[] = "a write to stdout\n"; Int main(void) { int var; pid_t pid; var = 88; if (write(stdout_fileno, buf, sizeof(buf)-1)!= sizeof(buf)-1) err_sys("write error"); printf("before fork\n"); if ( (pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) { /* processo figlio */ glob++; /* modifica la variabile globale */ var++; else sleep(2); /* processo padre */ printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var); exit(0);

" # Esecuzione: in generale, l ordine di esecuzione tra child e parent dipende dal meccanismo di scheduling il trucco utilizzato in questo prima programma (sleep per due secondi) non garantisce nulla; meglio usare wait() Relazione tra fork e funzioni di I/O. Esempio di esecuzione: $ a.out > temp.out ; cat temp.out a write to stdout before fork pid = 432, glob = 7, var = 89 variabili child cambiate before fork pid = 429, glob = 6, var = 88 variabili parent immutate Spiegazione: write non è bufferizzata printf è bufferizzata line-buffered se connessa ad un terminal device full-buffered altrimenti

) una caratteristica della chiamata fork è che tutti i descrittori che sono aperti nel processo parent sono duplicati nel processo child

) Relazione tra fork e file aperti (redirezione) è importante notare che parent e child condividono lo stesso file offset. Esempio: si consideri un processo che esegue fork e poi attende che il processo child termini (system call wait) supponiamo che lo stdout sia rediretto ad un file, e che entrambi i processi scrivano su stdout se parent e child non condividessero lo stesso offset, avremmo un problema: il child scrive su stdout e aggiorna il proprio current offset il parent sovrascrive stdout e aggiorna il proprio current offset Come gestire i descrittori dopo una fork il processo parent aspetta che il processo child termini in questo caso, i file descriptor vengono lasciati immutati eventuali modifiche ai file fatte dal processo child verranno riflesse nella file table entry e nella v-node entry del processo figlio i processi parent e child sono indipendenti in questo caso, ognuno chiuderà i descrittori non necessari e proseguirà opportunamente

) Proprietà che il processo child eredita dal processo parent: real uid, real gid, effective uid, effective gid gids supplementari id del gruppo di processi session ID terminale di controllo set-user-id flag e set-group-id flag directory corrente directory root maschera di creazione file (umask) maschera dei segnali flag close-on-exec per tutti i descrittori aperti environment Proprietà che il processo child non eredita dal processo parent: valore di ritorno di fork process ID process ID del processo parent file locks l insieme dei segnali in attesa viene svuotato

, System call: pid_t vfork(); Stessa sequenza di chiamata e stessi valori di ritorno di fork Semantica differente: vfork crea un nuovo processo allo scopo di eseguire un nuovo programma con exec() vfork non copia l immagine di memoria del parent; i due processi condividono lo spazio di indirizzamento fino a quando il child esegue exec() o exit() Guadagno di efficienza vfork garantisce che il child esegua per primo: continua ad eseguire fino a quando non esegue exec() o exit() il parent continua solo dopo una di queste chiamate Nota: Incrementare le variabili nel processo generato cambia i valori nell immagine di memoria del processo padre Il child chiama una versione speciale di exit(): _exit()

, #include <sys/types.h> #include "ourhdr.h" int glob = 6; /* vsrisbilr globale */ Int main(void) { int var; pid_t pid; var = 88; printf("before vfork\n"); if ( (pid = vfork()) < 0) err_sys("vfork error"); else if (pid == 0) { /* processo figlio */ glob++; /* modifica la variabile del padre */ var++; _exit(0); /* termina il figlio */ /* processo padre */ printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var); exit(0);

( Esistono tre modi per terminare normalmente: eseguire un return da main; equivalente a chiamare exit() chiamare la funzione exit: invocazione di tutti gli exit handlers che sono stati registrati chiusura di tutti gli I/O stream standard specificata in ANSI C; non completa per POSIX chiamare la system call _exit si occupa dei dettagli POSIX-specific; chiamata da exit

( Esistono due modi per terminare in modo anormale: Ricevere certi segnali Generati dal processo stesso Generati da altri processi Generati dal kernel Chiamando abort() genera il segnale SIGABRT In ogni caso, l azione del kernel e lo stesso: Rimozione della memoria utilizzata dal processo Chiusura dei descrittori aperti

( Exit status: Il valore che viene passato ad exit e _exit e che notifica il parent su come è terminato il processo (errori, ok, etc.) Termination status: Il valore che viene generato dal kernel nel caso di una terminazione normale/anormale Nel caso si parli di terminazione anormale, si specifica la ragione per questa terminazione anormale Come ottenere questi valori? Tramite le funzioni wait e waitpid descritte in seguito

( Cosa succede se il parent termina prima del child? si vuole evitare che un processo divenga "orfano" il processo child viene "adottato" dal processo init (pid 1) meccanismo: quando un processo termina, si esamina la tabella dei processi per vedere se aveva figli; in caso affermativo, il parent pid viene posto uguale a 1 Cosa succede se il child termina prima del parent? se il child scompare, il parent non avrebbe più modo di ottenere informazioni sul termination/exit status del child per questo motivo, alcune informazioni sul child vengono mantenute in memoria e il processo diventa uno zombie

( Stato zombie vengono mantenute le informazioni che potrebbero essere richieste dal processo parent tramite wait e waitpid Process ID Termination status Accounting information (tempo impiegato dal processo) il processo resterà uno zombie fino a quando il parent non eseguirà una delle system call wait Processi figli del processo init: non possono diventare zombie tutte le volte che un child di init termina, init esegue una chiamata wait e raccoglie eventuali informazioni in questo modo gli zombie vengono eliminati

( Notifica della terminazione di un figlio: Quando un processo termina (normalmente o no), il parent viene notificato tramite un segnale SIGCHLD Notifica asincrona Il processo padre: Può ignorare il segnale (default) Può fornire un signal handler (una funzione che viene chiamata quando il segnale viene lanciato) Chiamate di sistema per ottenere informazioni sulla terminazione dei processi: pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options);

( Quando un processo chiama wait o waitpid: può bloccarsi, se tutti i suoi child sono ancora in esecuzione può ritornare immediatamente con il termination status di un processo figlio, se esso è terminato ed il suo termination status è in attesa di essere raccolto può ritornare immediatamente con un errore, se il processo non ha generato nessun processo Nota: se eseguiamo una system call wait poiché abbiamo ricevuto SIGCHLD, essa termina immediatamente, altrimenti può bloccarsi

( Significato degli argomenti delle chiamate di sistema wait e waitpid: status è un puntatore ad un intero; se diverso da NULL, lo stato della terminazione viene messo in questa locazione Le chiamate ritornano l ID del processo che è terminato Differenza tra wait e waitpid: wait può bloccare il chiamante fino a quando un qualsiasi processo generato non termina; waitpid ha delle opzioni per evitare di bloccarsi waitpid può mettersi in attesa di uno specifico processo

( Stato della terminazione in POSIX: Macro WIFEXITED(status) Ritorna true se lo stato corrisponde ad un processo che ha terminato normalmente. In questo caso, possiamo usare: Macro WEXITSTATUS(status) Ritorna l'exit status specificato dal comando Macro WIFSIGNALED(status) Ritorna true se lo status corrisponde ad un child che ha terminato in modo anormale. In questo caso possiamo usare: Macro WTERMSIG(status) Ritorna il numero di segnale che ha causato la terminazione Macro WIFSTOPPED(status) Argomento riguardante il job control Ritorna true se il processo è stato stoppato, nel qual caso è possibile utilizzare: Macro WSTOPSIG(status) Ritorna il numero di segnale che ha causato lo stop

( #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> void pr_exit(int status) // questa procedura stampa lo stato d uscita { if (WIFEXITED(status)) printf("terminazione normale, stato d uscita = %d\n", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) printf("terminazione anormale, numero del segnale = %d%s\n",wtermsig(status), #ifdef WCOREDUMP WCOREDUMP(status)? " (core dumped)" : ""); #else ""); #endif else if (WIFSTOPPED(status)) printf("child stopped, signal number = %d\n", WSTOPSIG(status));

#include <sys/types.h> #include <sys/wait.h> #include "ourhdr.h" Int main(void) { pid_t pid; int status; if ( (pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) /* processo figlio */ exit(7); if (wait(&status)!= pid) /* aspetta la terminazione del processo figlio */ err_sys("wait error"); pr_exit(status); /* e stampa il suo stato */ if ( (pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) /* processo figlio */ abort(); /* genera SIGABRT */ if (wait(&status)!= pid) /* aspetta la terminazione del processo figlio */ err_sys("wait error"); pr_exit(status); /* e stampa il suo stato */ if ( (pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) /* processo figlio */ status /= 0; /* la divisione per 0 genera SIGFPE */ if (wait(&status)!= pid) /* aspetta la terminazione del processo figlio */ err_sys("wait error"); pr_exit(status); /* e stampa il suo stato */ exit(0);

( Esaminiamo la system call waitpid: pid_t waitpid(pid_t pid, int *statalloc, int options); Argomento pid: Opzioni: pid == -1 si comporta come wait pid > 0 attende la terminazione del processo generato con process id corrispondente pid == 0 attende la terminazione di qualsiasi processo con process group id uguale a quello del processo chiamante pid < -1 attende la terminazione di qualsisi child con process group ID uguale a pid WNOHANG non si blocca se il child non ha terminato

// Questo programma mostra come sia possibile evitare di stare in attesa di un processo figlio ed evitare che questo diventi uno zombie // Evitare i processi ZOMBIE ha il vantaggio di diminuire l utilizzo della Process Table #include <sys/types.h> #include <sys/wait.h> #include "ourhdr.h" int main(void) { pid_t pid; if ( (pid = fork()) < 0) err_sys("fork error"); else if (pid == 0) { /* primo processo figlio*/ if ( (pid = fork()) < 0) err_sys("fork error"); else if (pid > 0) exit(0); /* il primo figlio termina */ /* qui siamo all interno del secondo processo figlio; il padre diventa init non appena il suo padre (il primo processo figlio) chiama exit(). Il secondo processo figlio continua...*/ sleep(2); printf( sono il secondo figlio. L ID del mio processo padre e = %d\n", getppid()); exit(0); if (waitpid(pid, NULL, 0)!= pid) /* aspetta la terminazione del primo figlio */ err_sys("waitpid error"); /* A questo punto sono tornato al processo originale cioe il padre dei due figli che sono terminati. */ exit(0);