I SISTEMI OPERATIVI (1) 1 un computer può essere pensato come una stratificazione di macchine virtuali, ciascuna delle quali mette a disposizione alcuni servizi, che sono utilizzabili mediante chiamate effettuate in un linguaggio di programmazione caratteristico del livello considerato (ad es. linguaggio macchina, C, comandi). Il sistema operativo, è la più esterna delle macchine virtuali, quella che permette all utente (o agli utenti) di utilizzare ad alto livello il computer. E anch esso stratificato a livelli e prevede: Interfaccia Utente: Testuale o Grafica (con finestre) Ambiente di programmazione: editor, compilatori, debugger, Librerie, data base, Linguaggi di sistema: L. di programmazione per applicazioni, L. di configurazione (es. makefile), L. interfaccia utente(es.shell), L. servizi di sistema (funzioni del S.O. system call ).
I SISTEMI OPERATIVI (2) 2 Sarà compito del s.o. gestire - risorse software quali: Archiviazione dati e programmi, database, librerie, Esecuzione di programmi, compilatori e debugger Ingresso e uscita (I/O), Comunicazioni, remote login, remote file transfer, posta elettronica - risorse hardware quali: processore, memoria, dispositivi, I moderni s.o. prevedono la multiprogrammazione: più processi di utenti diversi vengono eseguiti contemporaneamente condividendo le risorse quali CPU, memoria, periferiche. Il s.o. effettua l'allocazione delle risorse agli utenti, ottimizzandone la gestione tramite opportune politiche e risolvendo i conflitti di accesso tramite sincronizzazione e mutua esclusione. Per tali scopi tutti i s.o. prevedono alcuni moduli fondamentali: Scheduler, Gestore della Memoria, Gestore dell I/O.
I PROCESSI E LA LORO GESTIONE 3 La gestione dei processi, la loro creazione e terminazione e la gestione delle comunicazioni fra processi è la parte più importante (il nucleo) di un Sistema Operativo. Definizione di Processo: è un attività sequenziale, controllata da un programma, che si svolge su un processore, in un proprio ambiente costituito dal contenuto dei registri del processore e dallo stack. Nel caso di sistemi multiprocessore, il processo può non essere legato ad un particolare processore. In un sistema Multiprogrammazione più processi condividono (in interleaving) la stessa CPU, quindi ciascun processo non avanza in modo continuo, però quando avanza, occupa in modo esclusivo un processore.
I PROCESSI 4 Un processo è rappresentabile da una terna P = (IM, IP, S) dove: IM Immagine in Memoria: insieme del codice e dei dati, modificabile dal processo in avanzamento. Può essere interamente contenuta in memoria, in modo statico, o essere presente solo parzialmente (gestione dinamica). IP Immagine del Processore: Informazioni sul processo mantenute nei registri del processore, anch'esse modificabili. S Stato di avanzamento: un processo si trova in un certo stato tra tre stati possibili: Ready, Running e Blocked Stato Running: il processore è assegnato al processo (dal S.O.) che puo' avanzare dell esecuzione. Stato Blocked: il processo non dispone del processore perchè bloccato in attesa di un servizio (es. attesa I/O). Stato Ready: il processo è in attesa dell'assegnazione del processore, che è occupato da un altro processo. Un processo può essere bloccato o mandato in esecuzione a seconda della disponibilità del processore e della politica di assegnazione della CPU ai processi. I processi in stato ready (pronti) sono selezionati per l esecuzione in base ad una opportuna politica (schedulazione). Gli obiettivi sono diversi: 1) evitare che la CPU sia inattiva, 2) fare avanzare i processi con velocità dipendenti dalla loro priorità, 3) evitare che un processo debba attendere per un tempo indefinitamente lungo prima di essere eseguito (attesa indefinita o starvation).
I PROCESSI IN UNIX 5 Fase di Compilazione programma compilatore eseguibile program text data Fase di Caricamento: viene caricato il codice in memoria con la exec text regioni data stack Fase di Esecuzione: esecuzione user mode user stack system call kernel mode kernel stack
ARCHITETTURA UNIX 6 L architettura dei s.o. UNIX, è composta di 3 livelli, hardware, kernel e utente. Il kernel schedula i processi, gestisce le comunicazione tra i i processi, alloca e dealloca memoria, gestisce il file system e le periferiche. In particolare il kernel mette a disposizione dell utente delle funzioni (system call) contenute in librerie di sistema, per eseguire I/O, leggere scrivere file, creare e eliminare processi, modificarne le priorità, ecc. Le system call possono essere chiamate all'interno di un programma, esattamente come normali funzioni implementate dall utente. La loro esecuzione tuttavia e' diversa dall'esecuzione di una funzione normale in quanto vengono eseguite in modo kernel.
SYSTEM CALL In Unix non esistono processi del kernel e processi utente separati ma solo processi che passano da modo kernel a utente e viceversa. Infatti Unix è un S.O. con modello a chiamata di procedura. Una procedura eseguita da un processo può non appartenere al pro_ cesso che la sta eseguendo, potrebbe ad esempio essere una procedura di schedulazione per scegliere il successivo processo da attivare, o una routine di interruzione a seguito di un segnale esterno. Tali procedure del kernel sono eseguite in modo kernel. Quando un processo opera in modo kernel utilizza uno stack del kernel (all interno dell'area utente) separato dallo stack utente. Il codice del kernel e le sue strutture dati sono condivise da tutti i processi, e non fanno parte di un processo particolare. Le system call che modificano le tabelle e i dati del kernel devono essere eseguite in mutua esclusione. Il sistema utilizzato da Unix è quello di impedire l'interruzione di tali routine a causa di interrupt (anche da parte del clock) o rischedulazione. Le procedure eseguite in modo non interrompibile devono quindi essere brevissime. Un processo quindi può essere: in esecuzione in modo utente, In esecuzione in modo kernel, in stato ready, in stato waiting (detto anche sleeping). 7
FILE SYSTEM UNIX 8 Dal Punto di Vista dell Utente: eseguibili File regolari: testo.. Directory: Per organizzare il file system file speciali: device fisici (tty, disks, printers) Pipe: (per la comunicazione fra processi) Dal Punto di Vista del Sistema Operativo: i-nodes + dati - Gli i-node (Index NODE) sono la rappresentazione interna dei file unix, - risiedono su disco, possono essere copiati in memoria, - esiste un i-node per ogni file fisico, - possono esistere più file, detti link, (anche con nomi diversi) che rappresentano lo stesso file fisico (i-node) pippo link pluto file i-node..
INFORMAZIONI NEGLI I-NODE 9 Un i-node su disco contiene: Proprietario del file tipo di file diritti di accesso data di accesso numero di link al file (link counter) indirizzi dei blocchi che contengono il file dimensioni del file Un i-node in memoria contiene: indice di quell inode su disco file system di appartenenza numero di istanze attive del file (es. compilatore) STATO DELL I-NODE: locked? c é un processo in attesa? l i-node in memoria é stato modificato?
STRUTTURA DEI FILE REGOLARI 10 Ogni blocco del disco é identificato da un numero Ogni i-node contiene un elenco dei blocchi che memorizzano il file Unix adotta l allocazione gerarchica indicizzata dei blocchi del file data blocks direct 0 direct 1 direct 9 single indirect double indir. triple indirect 256 indirizzi 1024 B 1024 B 1024 B 1024 B 1024 B 1024 B 1024 B (1KB x 10)+(1KB x 256)+(1KB x 256 2 ) +(1KB x 256 3 )=16GB
STRUTTURE DATI PER GESTIRE I FILE APERTI User file table (per il singolo processo): lista dei file aperti per processo (nell area utente) di solito ha 20 entry (max 20 file aperti contemp.) le entry puntano alla File table (globale) ogni entry è indicizzata da un file descriptor restituito dalla system call open() al momento dell apertura del file fd=open(nomefile, mode); File table (globale, per tutti i processi) : 0 1 2 34 5 6 7 lista globale dei file aperti Lista degli i-node in memoria sempre in memoria centrale ogni entry punta ad un i-node in memoria ogni entry contiene l offset della prossima read/write user file table file table count read 1.... count rd-wrt 1. count write 1 i-node list count /etc/passwd 2.... count myfile 1. 11
SYSTEM CALL OPEN 12 fd = open( file,mode) (fd: user file descriptor) ilkernel: recupera l i-node di file e controlla i permessi alloca una nuova entry nella FILE TABLE che punterá all i-node in memoria setta a zero l offset del puntatore in lettura/scrittura alloca una nuova entry nella USER FILE TABLE che punta alla entry corrispondente nella FILE TABLE il file descriptor fd ha come valore l indice della entry nella USER FILE TABLE
USER FILE DESCRIPTOR 0, 1, 2 13 0 = Standard Input (STDIN) 1 = Standard Output (STDOUT) 2 = Standard Error (SDTERR) stdin, stdout, stderr sono assunti di default da tutti i processi la convenzione é utile per la redirezione dell input/output e per l uso delle pipe possono essere gestiti come normali file (cioé chiusi, riassegnati, riaperti, )
system call CLOSE 14 close(3) close(4) count := count -1 user file table 0 1 2 3 NULL 4 NULL 5 6 7 rilasciato.... file table rilasciato. count rite w 1 i-node table count /etc/passwd 1.... i-node ritorna in free list.
ALTRI TIPI DI FILE UNIX: PIPE file di contenuto transitorio i dati possono essere letti solo nell ordine in cui sono stati scritti (FIFO) dimensione massima: 10 blocchi (i 10 blocchi ad indirizzamento diretto dell i-node) servono per le comunicazioni veloci tra processi distinguiamo Unnamed pipe e Named pipe Unnamed Pipe risiedono solo in memoria vengono riferite solo mediante file descriptor Vengono implementate come file normali usando un i-node. Solo i blocchi indirizzati direttamente vengono usati per lettura/scrittura, e sono gestiti in modo circolare. ad ogni pipe sono associati un file descriptor in lettura ed uno in scrittura solo processi padre/figlio possono usare una unnamed pipe sono automaticamente rimosse alla morte dei processi Named Pipe esistono nel file system qualsiasi processo puó usarne una (se ha i permessi) 15
UNNAMED PIPE 16 pipe(fds) int fds[2] fds[0] = lettura fds[1] = scrittura read pointer write pointer offset 0 offset 10239 0 1 2 3 4 5 6 7 8 9 blocchi diretti dell i-node i dati vengono letti nell ordine in cui sono stati scritti (no lseek) i dati possono essere letti una sola volta (vengono consumati ) un dato non puó essere sovrascritto prima che sia stato letto (il processo scrittore é messo in wait) la lettura di una pipe vuota provoca la sospensione del processo lettore
ESEMPIO DI USO DI UNNAMED PIPE 17 char string[] = hello main() { char buf[1024]; char *cp1, *cp2; int fds[2]; pid_t pid; pipe(fds); pid=fork(); // creo un nuovo processo // si crea una nuova user file table per il figlio if(pid==0) { // sono il processo figlio close(fds[0]); for (;;) write(fds[1], string, 6); //scrivo verso il padre } else { // sono il processo padre close(fds[1]); for (;;) read (fds[0], buf, 6): /* ricevo dal padre tramite pipe */ } Il processo figlio scrive (produce dati) sulla pipe all infinito. Il processo padre legge (consuma) dalla pipe, per sempre, attendendo ogni volta che il figlio abbia prodotto un dato.
NAMED PIPES 18 mknod(file_name, PIPE, 0); crea il file file_name che viene gestito come una pipe file_name é un file del file system, quindi ogni processo lo vede e puó usarlo, se ha i permessi. anche questa pipe é sospensiva
ESEMPIO DI USO DI NAMED PIPE 19 #include <fcntl.h> char string[] = hello ; main(argc,argv) int argc; char *argv[]; { int fd; char buf[256]; /* creazione di una named pipe con permessi di lettura/scrittura per tutti gli utenti */ mknod( fifo, 010777,0); if (argc == 2 ) fd = open( fifo, O_WRONLY); else fd = open( fifo, O_RDONLY); for(;;) } if (argc == 2) write(fd, string, 6); else read(fd, string, 6);
CREAZIONE PROCESSI IN UNIX 20 STRUTTURE DATI PER PROCESSI Un processo può essere in escuzione in 2 modi: kernel e utente. Un processo ha almeno 3 regioni: codice, dati e stack Lo stack è allocato dinamicamente. Unix usa 2 tipi di stack, user e kernel, a seconda del modo di esecuzione.il meccanismo che opera il passaggio da user a kernel viene detto trap (o software interrupt). Il kernel possiede una Tabella dei Processi contenente informazioni su ciascun processo in esecuzione. Il Contesto del processo: è costituito dal contenuto dello spazio utente (le tre regioni), dei registri hardware, e dalle strutture dati del kernel relativi a quel processo. MECCANISMO DI BIFORCAZIONE proc_id=fork() La system call fork() duplica un processo. Dopo la fork, se questa ha avuto successo, esistono due processi: il genitore e il figlio. I due processi avranno identificatori di processo diversi. Poichè il program counter è lo stesso, entrambi i processi ritengono di aver eseguito la funzione fork. Entrambi ricevono un valore di ritorno, ma diverso: proc_id > 0 per processo padre == 0 per il figlio < 0 per il padre (solo in caso di errore della fork) pid_t pid; pid=fork(); // creo un nuovo processo if(pid<0) exit(1); // errore, duplicazione non eseguita else { if(pid==0) {.. sono il processo figlio } else {.. sono il processo padre } }
RELAZIONE PADRE FIGLIO 21 Il figlio condivide il codice con il genitore, mentre la memoria, i registri e le informazioni sui files (tabelle) vengono duplicate. Quindi, dopo la fork, ogni processo può modificare le variabili contenute nel proprio spazio utente senza alterare le analoghe variabili dell altro processo. I file descriptor sono duplicati. Le tabelle del kernel dei file aperti non sono duplicate. Il figlio eredita i permessi di accesso ai file aperti. I reference counter nelle tabelle del kernel sono incrementati per: 1. area codice 2. entry nella tabella file del kernel (per file aperti) 3. entry nella tabella i-node in memoria
Terminazione Sincrona di Processo 22 void exit( int status) La system call void exit(int status) viene chiamata implicitamente dalla libreria C all'uscita dal main. Fra le operazioni eseguite dal kernel c è la chiusura dei file aperti, la liberazione della memoria e il cambiamento dello stato del processo in zombie. Un processo zombie non è più schedulato anche se è ancora presente nella Tavola dei processi. Infine viene inviato al processo genitore un segnale di avvenuta terminazione. Sono comunque mantenuti e aggiornati (nella Tavola dei processi) i tempi di esecuzione relativi al processo terminato. Insieme al segnale di avvenuta terminazione, al padre viene anche passato un valore intero di terminazione, lo status.
Attesa Terminazione Figlio 23 pid_t wait( int *status) Il processo che chiama la system call pid = wait(&status) rimane sospeso (non piu' schedulato, quindi in stato waiting) fino alla morte di uno dei suoi figli, in particolare fino a che uno dei figli esegue la funzione exit. Il genitore raccoglie il signal emesso durante la exit. Se il processo non ha figli e' ritornato -1 e settato errno. In caso contrario, viene restituito il pid del figlio terminato. Se un figlio termina prima che il genitore esegua la wait, ne viene tenuta traccia. Quando il genitore chiamerà la wait() non verrà quindi sospeso, ma continuerà immediatamente la sua esecuzione. I figli che hanno chiamato la exit, ma il cui padre non ha ancora chiamato la wait vanno in uno stato detto zombie e rimangono nella tabella dei processi del kernel. Nel momento in cui il padre chiama la wait i figli zombie sono cancellati dalla tavola dei processi. int status; pid_t pid; if ((pid = fork()) == 0) { /* processo figlio */.. exit(1); } /* processo padre */ pid = wait (&status);
Esecuzione di Programmi 24 Famiglia di funzioni int exec(diversi parametri) E' possibile mandare in esecuzione altri programmi all'interno di un processo, mediante la famiglia di funzioni exec. Tali funzioni differiscono solo per i tipo di parametri con cui possono essere chiamate. Esistono ad esempio: int execv (char *nomefile, char *argv[] ); int execl (char *nomefile,char *arg1,char *arg2,.,char *argn,0); e altre ancora. definite in <unistd.h> dove: nomefile e' il nome del file che contiene l'eseguibile argv e' il puntatore ad un array di puntatori a caratteri, ognuno dei quali punta ad una stringa che verrà passata come argomento al processo chiamato arg1, arg2, argn puntano ciascuno ad una stringa che verrà passata come argomento al programma Il nuovo programma si sostituisce interamente, come dati e codice, a quello vecchio, che non è più raggiungibile mentre restano inalterate le tavole file (file aperti, posizionamento all'interno di essi, relazioni con altri processi ecc.). ESEMPIO.. editare il file augusto.txt con l editor /usr/bin/vi #include <unistd.h> void main(void) { execl( /usr/bin/vi, augusto.txt,0); printf("errore in chiamata a /usr/bin/vi\n"); } La funzione printf viene eseguita solo in caso di errore della funzione exec, cioe' solo se l'operazione fallisce, per cui il processo continua con lo stesso programma, e non chiama l altro.
Esecuzione di più Programmi 25 Le system call di tipo exec possono essere associate alla fork per ottenere l esecuzione del programma chiamato senza interrompere l esecuzione del programma chiamante. ESEMPIO.. editare il file augusto.txt con l editor /usr/bin/vi #include <unistd.h> void main(void) { pid_t pid; int status; pid=fork(); if(pid==0) ) { /* figlio */ execl( /usr/bin/vi, augusto.txt,0); printf("errore in chiamata a /usr/bin/vi\n"); exit(1); } wait (&status); } Questo meccanismo viene applicato anche nelle shell di comandi per eseguire dei programmi: prima con una fork si duplica la shell, e il padre viene messo in attesa della terminazione del figlio. Il figlio chiama la exec per eseguire il programma voluto, tale programma si sostituisce al figlio e alla fine dell esecuzione avvisa il padre, la shell, che riprenderà il controllo.
Esecuzione di più Programmi 26 Lo stesso meccanismo (fork + exec) è utilizzato a Linux, per creare i processi iniziali del s.o.