Sistemi Operativi (M. Cesati)

Documenti analoghi
Sistemi Operativi (M. Cesati)

Sistemi Operativi Teledidattico

Le strutture. Una struttura C è una collezione di variabili di uno o più tipi, raggruppate sotto un nome comune.

Capitolo 5 -- Stevens

Sistemi Operativi (M. Cesati)

SmallShell Piccolo processore comandi

file fisico file logico

Scrittura formattata - printf

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

Input/output da file I/O ANSI e I/O UNIX FLUSSI E FILE FLUSSI FLUSSI di TESTO FLUSSI BINARI FILE

Precedenza e associatività. Complementi sul C - 2. Esempi. Esempi

I file Laboratorio di Linguaggi di Programmazione a.a. 2001/2002

Puntatori. Un puntatore contiene un numero che indica la locazione di memoria dove è presente la variabile puntata

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

Gestione dei file. File di testo e binari

Stringhe e allocazione dinamica della memoria

L Allocazione Dinamica della Memoria

Un file è un astrazione di memorizzazione di dimensione potenzialmente illimitata (ma non infinita), ad accesso sequenziale.

Esercizi di programmazione in linguaggio C English Dictionary

La gestione della memoria dinamica Heap

Introduzione al C. Stream e disk file

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

Introduzione al Linguaggio C

ESERCIZI RISOLTI IN C LANGUAGE (programmazione avanzata)

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

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

LABORATORIO di Reti di Calcolatori

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

FILE BINARI FILE BINARI

Chiamate di sistema. Pipe Flussi di I/O

CORSO DI SISTEMI OPERATIVI A - ESERCITAZIONE 3

Processore Danilo Dessì. Architettura degli Elaboratori.

I File. Il file e` l'unita` logica di memorizzazione dei dati su memoria di massa.

Gestione dei file. Stefano Ferrari. Università degli Studi di Milano Programmazione. anno accademico

Definizione Allocazione e deallocazione di variabili Allocazione e deallocazione di vettori

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

IL CONCETTO DI FILE. È illecito operare oltre la fine del file.

Esercizio 2 (punti 7) Dato il seguente programma C: #include <stdio.h> int swap(int * nome, int length);

Introduzione al C. Unità Gestione Dinamica della Memoria

Gestione dei File. dischi nastri cd

Sulla libreria standard, III. Manipolare file con stdio.h

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

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

Allocazione dinamica della memoria

Allocazione Dinamica. Allocazione Statica. malloc() La funzione malloc()

Gestione dinamica della memoria

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

L accesso ai dispositivi esterni (tastiera, monitor, file,...) viene gestito mediante canali di comunicazione.

Introduzione. L elaborazione dei files in C. Elaborazione dei files (1) Elaborazione dei files (2) D.D. cap. 11+ pp K.P. pp.

Sistemi operativi Modulo II I semafori 2 Select

Sono file di caratteri, organizzati in linee. Ogni linea e` terminata da una marca di fine linea (newline, cara ttere '\n').

GESTIONE DEI FILE IN C

LP1 Lezione 13: i File in C. Maurizio Tucci

I puntatori. Un puntatore è una variabile che contiene l indirizzo di un altra variabile. puntatore

Linguaggio C: i file

Librerie C. Corso di Linguaggi e Traduttori 1 AA Corso di Linguaggi e Traduttori 1 AA stdio.h

Consideriamo un vettore allocato dinamicamente

Digressione: man 2...

! Per quanto sappiamo finora, in C le variabili sono sempre definite staticamente

Informatica 1. Corso di Laurea Triennale in Matematica. Gianluca Rossi

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

Corso di Programmazione Concorrente Processi. Valter Crescenzi

Librerie C. Corso di Linguaggi e Traduttori 1 AA

Fondamenti di Informatica T-1 Modulo 2

Gestione di files Motivazioni

Gestione dei file. Linguaggio ANSI C Input/Output - 13

Gli array. Gli array. Gli array. Classi di memorizzazione per array. Inizializzazione esplicita degli array. Array e puntatori

Esercizio 1. Esercizio 1 - Soluzione

Lab 10 Gestione file di testo

Puntatori. Unità 6. Corso di Laboratorio di Informatica Ingegneria Clinica BCLR. Domenico Daniele Bloisi

Directory. Le directory unix sono file.

I/O su Socket TCP: read()

Struttura interna del sistema operativo Linux

I file possono essere manipolati (aperti, letti, scritti ) all interno di programmi C. dischi nastri cd

Introduzione al C. Unità 9 File. D. Bloisi, S. Peluso, A. Pennisi, S. Salza

L'allocazione dinamica della memoria

Struct, enum, Puntatori e Array dinamici

Lezione 8 Struct e qsort

DIPARTIMENTO DI ELETTRONICA E INFORMAZIONE. File. Marco D. Santambrogio Ver. aggiornata al 15 Maggio 2013

AXO. Operativo. Architetture dei Calcolatori e Sistema. programmazione di sistema

Introduzione ai puntatori in C Definizione

Capitolo 11 Elaborazione di file

OTTAVA ESPERIENZA DI LABORATORIO. L elaborazione dei files in C

Scope delle variabili e passaggio parametri. Danilo Ardagna Politecnico di Milano

Fondamenti di Informatica e Laboratorio T-AB T-16 Progetti su più file. Funzioni come parametro. Parametri del main

Puntatori. Unità 6. Domenico Daniele Bloisi. Corso di Fondamenti di Informatica Ingegneria delle Comunicazioni BCOR Ingegneria Elettronica BELR

Esercizi. I File ed il C

Operazioni sulle stringhe Corso di Fondamenti di Informatica Ingegneria delle Comunicazioni BCOR Ingegneria Elettronica BELR

Architettura dei calcolatori e sistemi operativi. M2 Organizzazione della memoria virtuale Struttura dello spazio virtuale kernel e utente

Program m azione di Sistem a 5

I puntatori e l allocazione dinamica di memoria

LIBRERIE STANDARD in C. LIBRERIE STANDARD in C

Se vogliamo cambiarlo: i dati vengono sovrascritti. 300 White Jones (vecchio valore) 300 Worthington 0.00

Il linguaggio C. Puntatori e dintorni

FILE: tipo operazione Istruì. prototipo esempi lettura di un carattere fgetc Int fgetc(file *flusso) Vedi sotto

ESERCIZI DI PROGRAMMAZIONE C IN AMBIENTE UNIX

puntatori Lab. Calc. AA 2007/08 1

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

Programmazione I - Laboratorio

Passare argomenti al programma

Transcript:

Sistemi Operativi (M. Cesati) Compito scritto del 2 luglio 2015 (Turno 1) Nome: Matricola: Corso di laurea: Cognome: Crediti da conseguire: 5 6 9 Scrivere i dati richiesti in stampatello. Al termine consegnare tutti i fogli. Per conseguire un voto sufficiente è necessario ottenere un voto non gravemente insufficiente in entrambi gli esercizi. Tempo a disposizione: 2,5 ore. Esercizio 1 Un file contiene record di lunghezza variabile memorizzati con il seguente formato: n 1 record1 n 2 record2 n 3 record3 ove n i rappresenta la dimensione in byte dell i-esimo record nel formato nativo del calcolatore, e recordi è la sequenza di byte corrispondente al contenuto dell i-esimo record. Scrivere una applicazione C/POSIX costituita da due processi che comunicano tramite una pipe. Il primo processo legge il file ed trasmette sulla pipe al secondo processo un record alla volta rovesciando l ordine dei suoi byte. Il secondo processo deve scrivere sullo standard output il contenuto dei record ricevuti, evidenziando chiaramente l inizio e la fine di ciascun record. Esercizio 2 Si descriva in modo chiaro ed esauriente lo scopo ed i principi di funzionamento dell algoritmo slab allocator utilizzato nei moderni sistemi operativi.

Sistemi Operativi (M. Cesati) Compito scritto del 2 luglio 2015 (Turno 2) Nome: Matricola: Corso di laurea: Cognome: Crediti da conseguire: 5 6 9 Scrivere i dati richiesti in stampatello. Al termine consegnare tutti i fogli. Per conseguire un voto sufficiente è necessario ottenere un voto non gravemente insufficiente in entrambi gli esercizi. Tempo a disposizione: 2,5 ore. Esercizio 1 Un file contiene record di lunghezza variabile memorizzati con il seguente formato: q n 1 n 2 n q record1 record2 recordq ove q rappresenta il numero di record nel file nel formato nativo del calcolatore, n i rappresenta la dimensione in byte dell i-esimo record nel formato nativo del calcolatore, e recordi è la sequenza di byte corrispondente al contenuto dell i-esimo record. Scrivere una applicazione C/POSIX costituita da 100 processi che mappano in memoria il file e ne riscrivono il suo contenuto rovesciando l ordine dei byte di ciascun record. Il processo j-esimo deve rovesciare l ordine del record j, del record 100 + j, del record 200 + j, ecc. Pertanto il primo processo deve rovesciare l ordine del record 1, del record 101, del record 201, ecc.; il secondo processo deve rovesciare l ordine del record 2, del record 102, del record 202,... ; e così via. Esercizio 2 Si descrivano in modo chiaro ed esauriente le operazioni svolte dal nucleo di un moderno sistema operativo in conseguenza di una eccezione di pagina (page fault) avvenuta durante l esecuzione di un processo in User Mode.

Sistemi Operativi (M. Cesati) Esempi dei programmi del compito scritto del 2 luglio 2015 Turno 1 Risolviamo l esercizio con un approccio top-down. Le operazioni principali da svolgere nella funzione main( ) sono: creazione della pipe, creazione di un processo figlio, apertura e processamento del file di ingresso. Poiché il testo dell esercizio non specifica la modalità con cui il file di input viene selezionato, scegliamo di leggerne il nome dalla linea comando. # include <stdio.h> # include < stdlib.h> # include < unistd.h> # include <sys / types.h> # include <sys / stat.h> # include <fcntl.h> int main ( int argc, char * argv []) int pfd [2], fd; if ( argc!= 2) fprintf ( stderr, " Usage : %s <file >\n", argv [0]) ; return EXIT_ FAILURE ; create_pipe ( pfd ); spawn_child ( pfd ); close_fd ( pfd [0]) ; fd = open_file ( argv [1]) ; send_records (fd, pfd [1]) ; close_fd ( pfd [1]) ; close_fd (fd); return EXIT_ SUCCESS ; Dopo aver controllato il numero di parametri in ingresso, main( ) invoca la funzione create pipe( ) per creare una pipe: void create_pipe ( int pfd [2]) int rc = pipe ( pfd ); if (rc == -1) perror (" pipe ");

Successivamente main( ) invoca la funzione spawn child( ) per creare un processo figlio: void spawn_child ( int pfd [2]) pid_t p = fork (); if (p == -1) perror ( NULL ); if (p!= 0) return ; print_records ( pfd ); /* NEVER REACHED */ Dal punto di vista del processo figlio, la funzione spawn child( ) non termina mai. Sospendiamo momentaneamente l analisi del processo figlio, e continuiamo invece l analisi della funzione main( ). Assegniamo al padre il compito di leggere i record dal file di ingresso, invertirli, e di scriverli sulla pipe. Pertanto, nella funzione main( ) viene invocata la funzione close fd( ) per chiudere il descrittore di file associato al terminale di lettura della pipe: void close_fd ( int fd) if ( close (fd) == -1) perror (" close "); Successivamente in main( ) viene invocata la funzione open file( ) per aprire il file di ingresso: int open_ file ( const char * name ) int fd = open (name, O_RDWR ); if (fd == -1) perror ( name ); return fd;

Per processare i record contenuti nel file di ingresso si utilizza la funzione send records( ), a cui vengono passati i descrittori del file di ingresso e del terminale di scrittura della pipe: void send_ records ( int ifd, int ofd ) for (;;) char * buf ; int n = read_int ( ifd ); if (n == 0) return ; buf = read_record (ifd, n); invert_bytes (buf, n); write_buf (ofd, ( char *)&n, sizeof (n)); write_buf (ofd, buf, n); free ( buf ); La funzione legge un intero dal file, corrispondente alla dimensione del successivo record. Poi legge il record stesso, inverte i suoi byte, e scrive il record sulla pipe. La funzione read int( ) legge un intero in formato nativo del calcolatore dal file: int read_int ( int fd) size_t len = sizeof ( int ); int rc, v; char *p = ( char *) &v; do rc = read (fd, p, len ); if (rc == -1) perror ( NULL ); if (rc == 0) if ( len!= sizeof ( int )) fprintf ( stderr, " Unexpected end of file \n") ; return 0; len -= rc; p += rc; while ( len > 0); return v;

La funzione legge una sequenza di sizeof(int) byte dal file, e la memorizza in una variabile di tipo int. Nel far ciò, si considerano eventuali letture corte che restituiscono meno byte di quanti effettivamente richiesti. Se il file non termina in corrispondenza della fine di un record viene mostrato un messaggio di avvertimento, in quanto il formato del file non è quello previsto dal programma. La funzione read record( ) viene invece utilizzata per leggere dal file di ingresso un record di lunghezza conosciuta: char * read_ record ( int fd, int size ) char *p, *q; p = q = malloc ( sizeof ( char )* size ); if (p == NULL ) perror (" malloc "); while ( size > 0) int rc = read (fd, q, size ); if (rc == -1) perror (" read "); if (rc == 0) fprintf ( stderr, " Unexpected end of file \n"); q += rc; size -= rc; return p; La funzione dapprima alloca dinamicamente un buffer in grado di memorizzare l intero record. Successivamente il buffer viene letto dal file, gestendo in modo analogo a quanto fatto in read int( ) le letture corte e la fine prematura del file di ingresso. Per invertire l ordine dei byte del record send records( ) invoca la funzione invert bytes( ): void invert_ bytes ( char * rec, int len ) char * last ; for ( last = rec +( len -1) ; rec < last ; ++ rec, -- last ) char c = * rec ; * rec = * last ; * last = c;

Successivamente, send records( ) invoca due volte la funzione write buf( ) per scrivere sulla pipe dapprima la dimensione del record, e poi il record stesso (invertito). A questo proposito si deve osservare che in linea generale non è ammissibile nello svolgimento dell esercizio trascurare che la dimensione del record è indicata esplicitamente nel file stesso insieme al record. A titolo di esempio, non è ammissibile imporre una dimensione massima per il record, oppure imporre un formato al suo contenuto, quale una stringa di testo terminata da \n oppure da \0. Di conseguenza, il modo più semplice per realizzare l applicazione richiesta è trasmettere sulla pipe la dimensione del record prima del record stesso. void write_ buf ( int fd, char * rec, int size ) while ( size > 0) int rc = write (fd, rec, size ); if (rc == -1) perror (" write "); rec += rc; size -= rc; La funzione send records( ) termina l esecuzione dell iterazione del ciclo liberando la memoria dinamica utilizzata per memorizzare il contenuto del buffer. L esecuzione ritorna in main( ) soltanto dopo aver terminato il processamento dell intero file di ingresso. main( ) invoca due volte close fd( ) per chiudere i descrittori del file di ingresso e del terminale di scrittura della pipe, e termina l esecuzione del processo. Torniamo ora ad analizzare il codice eseguito dal processo figlio. In questo caso spawn child( ) invoca la funzione print records( ): void print_records ( int pfd [2]) char * buf ; close_fd ( pfd [1]) ; for (;;) int n = read_int ( pfd [0]) ; if (n == 0) exit ( EXIT_SUCCESS ); buf = read_record ( pfd [0], n); printf (" --- START OF RECORD ---\n");

if ( fwrite (buf, n, 1, stdout )!= 1u) perror (" stdout "); printf ("\n--- END OF RECORD ---\n"); free ( buf ); La funzione chiude il terminale di scrittura della pipe, ed entra in un ciclo in cui legge dalla pipe la dimensione di un record, poi legge il record stesso memorizzandolo in un buffer allocato dinamicamente, stampa il contenuto del buffer in standard output, e libera la memoria occupata dal buffer. Si noti che il record viene scritto in standard output per mezzo di fwrite( ), in quanto non è possibile assumere che il record stesso contenga testo stampabile. Il processo figlio termina dopo aver riconosciuto la condizione EOF sulla pipe, ovvero in seguito alla chiusura della pipe da parte del processo padre. Turno 2 Risolviamo l esercizio con un approccio top-down. Le operazioni principali da svolgere nella funzione main( ) sono: apertura del file, lettura della dimensione del file, mapping del file in memoria, creazione dei processi, e svolgimento del lavoro di ciascun processo. Poiché il testo dell esercizio non specifica la modalità con cui il file di input viene selezionato, scegliamo di leggerne il nome dalla linea comando. # include <stdio.h> # include < stdlib.h> # include < unistd.h> # include <sys / types.h> # include <sys / stat.h> # include <fcntl.h> # include <sys / mman.h> int main ( int argc, char * argv []) int fd, p; size_t len ; char * map ; if ( argc!= 2) fprintf ( stderr, " Usage : %s <file >\n", argv [0]) ; return EXIT_ FAILURE ; fd = open_file ( argv [1]) ;

len = compute_file_size (fd); map = map_file (fd, len ); if ( close (fd) == -1) perror ( argv [1]) ; p = fork_ children ( 100) ; invert_records (p+1, map, 100) ; unmap_file (map, len ); return EXIT_ SUCCESS ; Si noti che dopo aver creato il memory mapping del file è possibile chiudere il file stesso: tutte le successive letture e scritture del contenuto del file sono effettuate accedendo direttamente all area di memoria associata al memory mapping. Per aprire il file utilizziamo la funzione open file( ): int open_ file ( const char * name ) int fd = open (name, O_RDWR ); if (fd == -1) perror ( name ); return fd; Per semplicità scegliamo di mappare il file per intero, prima di creare gli altri processi. A tale scopo abbiamo la necessità di determinare la dimensione totale del file. Possiamo farlo in due modi: tramite le API POSIX (ad esempio, utilizzando lseek( )), oppure leggendo i valori interi memorizzati all inizio del file. In questo esempio adottiamo la seconda soluzione: size_t compute_file_size ( int fd) size_ t i, q, tot = 0; q = read_int (fd); for (i =0; i<q; ++i) tot += read_int (fd); return tot + (1+ q)* sizeof ( int ); La funzione legge dapprima il numero di record q di cui è costituito il file, e poi effettua la somma dei successivi q interi, in modo da ottenere la dimensione totale di tutti i record. Infine si calcola la dimensione del file sommando la dimensione dei valori interi all inizio del file.

Per leggere i valori dal file utilizziamo la funzione read int( ): int read_int ( int fd) int rc, v; size_t len = sizeof ( int ); char *p = ( char *) &v; do rc = read (fd, p, len ); if (rc == -1) perror ( NULL ); if (rc == 0) fprintf ( stderr, " Unexpected end of file \n"); len -= rc; p += rc; while ( len > 0); return v; La funzione legge una sequenza di sizeof(int) byte dal file, e la memorizza in una variabile di tipo int. Nel far ciò, si considerano eventuali letture corte che restituiscono meno byte di quanti effettivamente richiesti. Per mappare il file in memoria (sia in lettura che in scrittura) utilizziamo la funzione map file( ): char * map_file ( int fd, size_t len ) char *p = mmap (NULL, len, PROT_READ PROT_WRITE, MAP_SHARED, fd, 0); if ( p == MAP_ FAILED ) perror ( NULL ); return p; Per creare i 100 processi utilizziamo la funzione fork children( ), che restituisce il numero logico del processo, da 0 (il processo originale) a 99:

int fork_ children ( int n) int i; pid_t p; for (i =1; i<n; ++i) p = fork (); if (p == -1) perror ( NULL ); if (p == 0) return i; return 0; Il lavoro di inversione del contenuto dei record è affidato alla funzione invert records( ), eseguita da ciascuno dei 100 processi. Il primo parametro corrisponde al numero del processo, traslato da 1 a 100. Oltre all indirizzo dell area di memoria con il memory mapping viene anche passato il numero di processi, e quindi la distanza tra i record che ciascun processo deve invertire. void invert_ records ( int p, char * map, int nproc ) int *n = ( int *) map ; int i, j, q = n [0]; char * rec = map + sizeof ( int ) * (q +1) ; for (j=1, i=p; i <=q; i+= nproc ) for (; j<i; ++j) rec += n[j]; invert_bytes (rec, n[j]); La variabile p contiene l indice del processo; la variabile i contiene il numero del record che si deve invertire; la variabile j serve a ricordare l ultimo offset aggiunto alla posizione corrente nel file per arrivare alla posizione del record da invertire. Per leggere con facilità i valori interi all inizio del file, il puntatore map viene assegnato con un cast al puntatore ad interi n; perciò n[0] corrisponde al valore q, n[1] al valore n 1, ecc. Per invertire il record viene utilizzata la funzione invert bytes( ), che riceve l indirizzo iniziale del record e la sua lunghezza in byte:

void invert_ bytes ( char * rec, int len ) char * last ; for ( last = rec +( len -1) ; rec < last ; ++ rec, -- last ) char c = * rec ; * rec = * last ; * last = c; Infine, prima di terminare il programma, il memory mapping viene rilasciato utilizzando la funzione unmap file( ): void unmap_file ( char *map, size_t len ) if ( munmap (map, len )!= 0) perror ( NULL );