Programmazione di Sistema 1 Linguaggio C • Larga diffusione nel software applicativo • Standard di fatto per lo sviluppo di software di sistema – Visione a basso livello della memoria – Capacità di manipolare singoli bit – Possibilità di incapsulare parti di codice assembler che effettuano compiti impossibili da esprimere in linguaggi ad alto livello • es. esecuzione di istruzioni ‘speciali’ (TRAP), manipolazione diretta di registri – Compilazione estremamente efficiente – Occupazione di memoria ridotta 2 C : caratteristiche fondamentali • Paradigma imperativo – costrutti di controllo (while-do, for, if (..) else, switch) – tipi base (int, double etc…) – la principale forma di strutturazione sono le funzioni • un programma C è una collezione di funzioni di cui una di nome main • le funzioni possono risiedere in uno o più file – in C è possibile conoscere e denotare l’indirizzo della cella di memoria in cui è memorizzata una variabile tramite i puntatori 3 Struttura tipica di un programma C define/include typedef variabili globali prototipi F1..FN main def F1 … def FN 4 Come si esegue un programma C? • Prima di essere eseguito dal processore il programma deve essere – – – – 1. pre-processato 2. compilato 3. collegato (linking) 4. caricato in memoria (loading) • Vediamo come funzionano le varie fasi 5 Pre-processing e compilazione • Il programma deve essere editato e salvato in un file con estensione ‘.c’ – es. posso salvare il programma esempio nel file prova.c • Non c’è alcun rapporto fra il nome del file ed il nome delle funzioni in esso contenute 6 Pre-processing e compilazione (2) • Il compilatore può essere invocato con il comando gcc -Wall -pedantic -g prova.c -o nomees – -Wall -pedantic opzioni che aumentano il numero di controlli e di messaggi di ‘avvertimento’ visualizzati – -g opzione che include anche le informazioni necessarie al debugger – prova.c nome del file da compilare/preprocessare – -o nomees opzione che permette di decidere il nome del file eseguibile (nomees). Altrimenti il file eseguibile si chiama di default a.out 7 Pre-processing e compilazione (3) • Preprocessing – #include <file> – #include “file” queste linee vengono rimpiazzate dal contenuto dei file specificati (<file> viene cercato tra gli header standard, su Linux /usr/include; “file” viene cercato a partire dalla directory corrente) – #define nome testo • rimpiazza ogni occorrenza di nome con il testo specificato • è possibile specificare del testo parametrico 8 Pre-processing e compilazione (4) • Compilazione – trasforma il file preprocessato (senza più #include o #define) in un file eseguibile che contiene • alcune informazioni di carattere generale • rappresentazione binaria di (parte dei) dati del programma (variabili globali) • codice assembler eseguibile dal processore target (testo) – per capire come avviene questa traduzione, dobbiamo specificare come un programma vede la memoria durante la sua esecuzione (spazio di indirizzamento) 9 Spazio di indirizzamento 232 - 1 Stack Pila di FRAME, uno per ogni chiamata di funzione da cui non abbiamo ancora fatto ritorno Area vuota Variabili globali a tutte le funzioni Data 0 Text Traduzione in assembler delle funzioni che compongono il programma 10 Spazio di indirizzamento (2) 232 - 1 Stack Area vuota Data 0 Text Direzione di crescita dello stack Contenuti tipici di un FRAME : - variabili locali della funzione - indirizzo di ritorno (indirizzo dell’istruzione successiva a quella che ha effettuato la chiamata alla funzione) - copia dei parametri di chiamata 11 Spazio di indirizzamento (3) • Spazio di indirizzamento 232 - 1 All’inizio dell’esecuzione lo Stack contiene solo il FRAME per la funzione main Stack Area vuota Data 0 Text Successivamente : * ogni volta che viene chiamata una nuova funzione viene inserito un nuovo frame nello stack * ogni volta che una funzione termina (es. return 0) viene eliminato il frame in cima allo stack e l’esecuzione viene continuata a partire dall’indirizzo di ritorno 12 Spazio di indirizzamento (4) 232 - 1 Stack Area vuota heap Data 0 Text Direzione di crescita dello heap Spazio di memoria dedicato ai dati globali a tutte le funzioni. Diviso in statico per i dati la cui dimensione è nota a tempo di compilazione e dinamico (heap) 13 Compilazione : un esempio Ogni variabile corrisponde ad una o più parole/celle di memoria int main (void){ int i, tmp, max=0; printf(“Inserisci %d interi positivi\n”,N); for (i = 0; i < N; i++) { scanf(“%d”, &tmp); max = (max < tmp)? max : tmp ; } printf(“Il massimo è %d \n”,max); 32 bit return 0; } &i &max &tmp 14 Compilazione : un esempio (2) Ogni statement viene tradotto in - istruzioni assembler che lavorano sulle celle rilevanti - CALL di altre funzioni (es. printf) int main (void){ int i, tmp, max=0; printf(“Inserisci %d interi positivi\n”,N); for (i = 0; i < N; i++) { scanf(“%d”, &tmp); max = (max < tmp)? max : tmp ; } printf(“Il massimo è %d \n”,max); return 0; } &i &max &tmp 15 Formato del file eseguibile • La compilazione produce un file eseguibile (es. a.out) • Il formato di un eseguibile dipende dal sistema operativo • In Linux un eseguibile ha il formato ELF (Executable and Linking Format) 16 Formato del file eseguibile (2) – Formato di un eseguibile ELF File a.out Magic number Altre info Numero che contraddistingue il file come eseguibile Ampiezza area di memoria occupata dalle variabili globali NON inizializzate Ampiezza BSS Variabili globali I-Data segment inizializzate Text segment Codice del programma (assemblato) 17 Formato del file eseguibile (3) – L’eseguibile contiene tutte le informazioni per creare la configurazione iniziale dello spazio di indirizzamento (loading) FRAME per la funzione main File a.out Magic number Altre info Stack 232 - 1 Area vuota BSS-segment Ampiezza BSS I-Data segment I-Data segment Text segment Text Data 0 18 E se non me lo esegue? Se avete cercato di eseguire a.out e non ci riuscite: $$ a.out 19 E se non me lo esegue?(2) Se avete cercato di eseguire un file eseguibile e non ci siete riusciti: $$ a.out bash:command not found Significa che la variabile di ambiente PATH non contiene la directory corrente quindi usate il path completo $$ ./a.out 20 E se non me lo esegue?(3) Se avete cercato di eseguire un file eseguibile e non ci siete riusciti: $$ a.out bash:command not found Per settare il PATH – bash $$ export PATH=$PATH:. – csh e tcsh $$ setenv PATH=${PATH}:. 21 Argomenti della linea di comando • Gli argomenti della linea di comando sono accessibili all’interno della funzione main() – il SO li mette sulla pila prima di attivare il processo – il formato in cui sono resi disponibili è fisso int main (int argc, char* argv [] ){ …… } Numero di argomenti nella linea di comando Array di puntatori agli argomenti (ciascuno è una stringa, tipo char*) 22 Argomenti della linea di comando (2) • Un esempio : %> a.out una stringa per a.out argv[0] 5 a . u n a \O s t r i p e r \O a . o o u t \O n g argc argv u a \O t \O 23 Argomenti della linea di comando (3) • Es. schema di programma che stampa gli argomenti sulla linea di comando : int main (int argc, char* argv [] ){ …… for(i=0;i<argc;i++) printf(“arg %d: %s”,i,argv[i]); … … } 24 Compilazione separata • Scopi di suddividere il codice sorgente C su più file – per raggruppare un insieme di funzioni congruenti (e solo quelle) in un unico file – per incapsulare un insieme di funzioni esportando solo quelle che vogliamo fare utilizzare agli altri (pubbliche) – per compilare una volta per tutte un insieme di funzioni e metterlo a disposizione di altri in una libreria • es. le librerie standard viste finora …. 25 Compilazione separata • Tipicamente : define/include glob.h typedef variabili globali main fun_toK.h prototipi F1..Fk def F1 … def Fk fun_toK.c main.c fun_1toN.h prototipi Fk+1..FN def Fk+1 fun_toN.c … def FN 26 define/include glob.h typedef variabili globali fun_toK.h #include “glob.h ” #include “fun_toK.h” main.c #include “fun_toN.h” …... prototipi F1..Fk ass F1 … ass Fk fun_toK.o Moduli oggetto fun_1toN.h prototipi Fk+1..FN ass Fk+1 fun_toN.o … ass FN 27 Moduli oggetto • Come si crea il modulo oggetto? – gcc -c file.c produce un file file.o che contiene il modulo oggetto di file.c – Il formato dell’oggetto dipende dal sistema operativo – Che informazioni contiene l’oggetto ? • L’assemblato del sorgente testo e dati (si assume di partire dall’indirizzo 0) • La tabella di rilocazione (indirizzi risolti dal compilatore) • La tabella dei simboli (esportati ed esterni, risolti dal linker) 28 Linker • Il linker si occupa di risolvere i simboli. – Analizza tutte le tabelle dei simboli. – Per ogni simbolo non risolto (esterno) cerca • in tutte le altre tabelle dei simboli esportati degli oggetti da collegare (linkare) assieme • nelle librerie standard • nelle librerie esplicitamente collegate (opzione -l) 29 Chiamate di sistema Introduzione Errori : perror() Chiamate che lavorano su file 30 Chiamate al sistema e librerie di funzioni • System call= insieme di funzioni che un programma può chiamare, per le quali viene generata un'interruzione del processo passando il controllo dal programma al kernel. • Ciascuna di queste chiamate al sistema viene rimappata in opportune funzioni con lo stesso nome definite dentro la Libreria Standard del C, che, oltre alle interfacce alle system call, contiene anche le funzioni definite dai vari standard (es. ANSI C e POSIX), che sono comunemente usate nella programmazione. • Programmare in Linux significa anzitutto essere in grado di usare le varie interfacce contenute nella GNU Standard C Library (glibc) 31 Chiamate di sistema • Dal C è possibile invocare le chiamate di sistema POSIX utilizzando la libreria standard – header vari da includere : unistd.h, sys/types.h, sys/wait.h etc... • Queste informazioni tipicamente si ricavano dai manuali in linea – es. man 2 fork 32 Manuali in linea ... • Tipico formato : NAME perror - print a system error msg SYNOPSIS include,prototipi, globali DESCRIPTION descrizione a parole CONFORMING TO standard ... SEE ALSO funzioni collegate 33 Manuali in linea …(2) • Ci sono 3 sezioni : – 1 (default) le utility chiamabili da shell – 2 le system call – 3 le funzioni di libreria standard C • Ci sono utility che hanno lo stesso nome delle funzioni nelle sezioni 2/3, – specificare la sezione per avere l’informazione corretta • Se non funzionano: – controllare il valore della variabile di ambiente MANPATH 34 UNIX: struttura generale Utenti Interfaccia di libreria C Interfaccia delle chiamate di sistema Programmi di utilità standard (shell, editori, compilatori etc.) Modo utente Libreria standard (Open, close, read, write …) Sistema operativo Unix (gestione processi, memoria, file system, I/0..) Hardware Modo kernel 35 Chiamate di sistema: errori • Le chiamate di sistema possono fallire – in caso di fallimento ritornano un valore diverso da 0 (tipicamente -1) – il codice relativo all’errore rilevato è inserito nella variabile globale errno (errno.h) – i codici di errore sono definiti in vari file di include – perror() routine della libreria standard che stampa i messaggi di errore relativi a diversi codici (stdio.h) 36 Chiamate di sistema: errori (2) • Esempi di codici di errore /* no such file or directory*/ #define ENOENT 2 /* I/O error*/ #define EIO 5 /* Operation not permitted */ #define EPERM 1 37 Chiamate di sistema: errori (3) • Come funziona perror(“pippo”) – legge il codice di errore contenuto nella globale errno – stampa “pippo” seguito da “:” seguito dal messaggio di errore relativo al codice – uso tipico : perror(“fun, descr”) dove fun è il nome della funzione che ha rilevato l’errore, descr descrive cosa stiamo tentando di fare – la stampa viene effettuata sullo standard error del processo in esecuzione (tipic. schermo) 38 Chiamate di sistema: errori (4) • Es. int main (void) { errno = 1; /* EPERM */ perror(“main, provaerr”); return 0; } • Compilato ed eseguito ….. $ a.out main, provaerr : Operation not permitted $ 39 Chiamate di sistema: errori (5) • Errno viene sovrascritto dalla SC successiva (se erronea) • Il programma deve controllare l’esito di ogni SC immediatamente dopo il ritorno ed agire • L’azione minima è chiamare la perror() per stampare un messaggio di errore 40 Esempio Possiamo usare delle macro con parametri che inseriscono test e perror() ad ogni chiamata (le useremo in alcuni esempi successivi) /* stampa errore e termina */ #define IFERROR(s,m) \ if((s)==-1) {perror(m); exit(errno);} /* stampa errore ed esegue c */ #define IFERROR3(s,m,c) \ if((s)==-1) {perror(m); c;} /* uso tipico */ int main (void) { IFERROR3(read(…),”main, lettura”, return -1); IFERROR(read(…),”main, lettura”); } 41 SC che operano su file (1) open(), read(), write(), close() 42 Apertura di un file : SC open() int open(const char * pathname, int flags) – pathname : PN relativo o assoluto del file – flags : indicano come voglio accedere al file • O_RDONLY sola lettura, O_WRONLY sola scrittura, O_RDWR entrambe • eventualmente messe in or bit a bit una o più delle seguenti maschere : O_APPEND scrittura in coda al file, O_CREAT se il file non esiste deve essere creato, O_TRUNC in fase di creazione, se il file esiste viene sovrascritto, O_EXCL in fase di creazione, se il file esiste si da errore 43 Apertura di un file : SC open() (2) int open(const char * pathname, int flags) – risultato : un intero, il descrittore di file (fd) Tabella dei descrittori di file (nella user area) -- Array di strutture, una per ogni file aperto -- Di ampiezza fissa (max 20) Il fd è l’indice del descrittore assegnato al file appena aperto 44 Apertura di un file : SC open() (3) • Tipico codice di apertura di un file : int fd; /*file descriptor */ /* tento di aprire */ fd = open(“s.c”, O_RDONLY); /* controllo errori*/ if(fd==-1) { perror(“fk, in apertura”); exit(errno); /* termina */ } 45 Apertura di un file : SC open() (4) • Tipico codice di apertura di un file – uso della macro IFERROR : int fd; /*file descriptor */ /* apertura e controllo errori usando la macro */ IFERROR(fd = open(“s.c”, O_RDONLY), “fk, in apertura”)); 46 Apertura di un file : SC open() (5) • Cosa fa la open : – segue il path del file per recuparare i suoi attributi e gli indirizzi ai suoi blocchi dati (i-node) – controlla i diritti di accesso (li confronta con le richieste in flags) – se l’accesso è consentito assegna al file l’indice di una posizione libera nella tabella dei descr. (fd) • aggiorna le strutture dati interne al nucleo … – se si è verificato un errore ritorna -1 (errno) – altrimenti ritorna fd, che deve essere usato come parametro per tutti gli accessi successivi 47 Apertura di un file : SC open() (6) • Strutture di nucleo legate ai file Pos.Corrente 0 write/read fd Tabella dei descrittori di file (user area) Copia dell’i-node Tabella dei file aperti Tabella degli i-node attivi 48 Lettura: SC read() • Es: lung = read(fd,buffer,N) File descriptor -1 : errore n > 0 : numero byte letti 0 : Pos.Corrente è a fine file Numero massimo di byte da leggere (void *) puntatore all’area di memoria dove andare a scrivere i dati Effetto: Legge al più N byte a partire da Pos.Corrente, Pos.Corrente += lung 49 Lettura: SC read() (2) • Tipico ciclo di lettura da file: int fd, lung; /* fd, n byte letti */ char buf[N]; /*buffer*/ /* apertura file */ IFERROR(fd = open(“s.c”, O_RDONLY), “fk, in apertura”)); while ((lung = read(fd,buf,N))>0){ … } IFERROR(lung,”fk, in lettura”); 50 Scrittura: SC write() • Es: lung = write(fd,buffer,N) File descriptor -1 : errore n => 0 : numero byte scritti Numero massimo di byte da scrivere (void *) puntatore all’area di memoria dove andare a prendere i dati Effetto: Scrive al più N byte a partire da Pos.Corrente, Pos.Corrente += lung 51 Lettura: SC write() (2) • Es. scrittura sullo stdout (fd 1): int fd, lung; /* fd, n byte letti */ char buf[N]; /*buffer*/ IFERROR(fd = open(“s.c”, O_RDONLY), “fk, in apertura”)); while ((lung = read(fd,buf,N))>0){ IFERROR(write(1, buf, lung), “fk, in scrittura”)); } IFERROR(l,”fk, in lettura”); 52 Chiusura: la SC close() • Libera le aree di occupate nelle varie tabelle • int close (int fd) 53 Chiusura: SC close() (2) • Es. chiusura di un file …. int fd, lung; /* fd, n byte letti */ char buf[N]; /*buffer*/ IFERROR(fd = open(“s.c”, O_RDONLY), “fk, in apertura”)); while ((lung = read(fd,buf,N))>0){ IFERROR(write(1, buf, lung), “fk, in scrittura”)); } IFERROR(lung,”fk, in lettura”); IFERROR(close(fd),”fk, in chiusura”); 54 Standard input, output and error • Ogni processo Unix ha dei ‘canali di comunicazione’ predefiniti con il mondo esterno – es. $sort stdout Tipicamente lo schermo stdin Tipicamente la tastiera P stderr Tipicamente lo schermo 55 Standard input, output and error (2) • Un esempio stdin 0 stdout 1 Copia dell’i-node di ttyX stderr 2 Tabella dei descrittori di file (user area) Tabella dei file aperti Tabella degli i-node attivi 56 Su: open() vs fopen()e similari • open(), read(), write(), close() fanno parte della libreria standard POSIX per i file e corrisponde ad una SC • fopen(), fread(), fwrite(), fclose(), printf() fanno parte della libreria standard di I/O (stdio.h) definito dal comitato ANSI 57 Su: open() vs fopen()e similari (2) • le funzioni di stdio.h effettuano un I/O bufferizzato – se il programma termina in modo anomalo i buffer possono non essere svuotati in tempo • mischiare chiamate ad I/O bufferizzato e non può portare a risultati impredicibili – nel vostro programma usate o le chiamate POSIX (non bufferizzate) o le chiamate a funzioni in stdio.h (bufferizzate) ma non entrambe 58 Posizionamento : lseek() off_t lseek(int fd, off_t offset, int whence) – fd : file descriptor – offset : di quanti byte voglio spostarmi – whence : da dove calcolo lo spostamento. Può essere una delle seguenti macro • SEEK_SET dall’inizio, • SEEK_END dalla fine, • SEEK_CUR dalla posizione corrente – Ritorna : • • la posizione corrente in caso di successo , -1 in caso di fallimento 59 Attributi : stat() int stat(const char* pathfile, struct stat *buf) – pathfile : path del file – buf : puntatore alla struttura struct stat in cui verranno inserite le informazioni 60 Attributi : stat() (2) struct stat … ino_t mode_t nlink_t uid_t off_t unsgn long unsgn long time_t time_t time_t } { st_ino; /* st_mode; /* st_nlink; /* st_uid; /* st_size; /* st_blksize;/* st_blocks; /* st_atime; /* st_mtime; /* st_ctime; /* numero dell’i-node*/ diritti protezione*/ # hard link */ ID owner */ lung totale (byte)*/ dim blocco */ #blk 512byte occupati*/ ultimo accesso*/ ultima modifica */ ultima var dati */ 61 Attributi : stat() (3) struct stat info; IFERROR(stat(“dati”,&info), “In stat”); if if if if if (S_ISLNK(info.st_mode)){/* (S_ISREG(info.st_mode)){/* (S_ISDIR(info.st_mode)){/* (S_ISCHR(info.st_mode)){/* (S_ISBLK(info.st_mode)){/* link simbolico*/} file regolare*/} directory */} spec caratteri */} spec blocchi */} if (info.st_mode & S_IRUSR){/* r per owner */} if (info.st_mode & S_IWGRP){/* w per group */} 62 Directory • Il formato delle directory varia nei vari FS utilizzati in ambito Unix • Quando una directory viene aperta viene restituito un puntatore a un oggetto di tipo DIR (definto in dirent.h) – es. DIR* mydir; 63 Directory: opendir, closedir DIR* opendir(const char* pathdir);, – pathdir: path directory – ritorna il puntatore all’handle della directory, o NULL se si è verificato un errore int closedir(DIR* dir); – dir: puntatore all’ handle di una directory già aperta 64 Directory: opendir, closedir (2) DIR * d; /* esempio di apertura directory */ if ((d = opendir(".")) == NULL){ perror("nell'apertura"); exit(errno); } /* lavoro sulla directory */ /* chiusura directory */ IFERROR(closedir(d),"nella chiusura"); 65 Directory: readdir struct dirent* readdir(DIR * dir);, – dir : handle della directory – ritorna il puntatore ad una struttura struct dirent contenente le informazioni dell’elemento della directory che descrive il prossimo file – letture successive forniscono i file successivi – ritorna NULL quando i file sono finiti – per tornare all’inizio void rewinddir(DIR * dir);, 66 Directory: readdir (2) /* campi interessanti di dirent … */ struct dirent { … /* # di i-node */ long d_ino; /*lunghezza di d_name */ unsigned short d_reclen; /* nome del file */ char d_name[NAMEMAX+1]; … } 67 Directory: readdir (3) DIR * d; struct dirent * file; /* …. apertura directory */ /* lettura di tutte le entry della directory */ while ( (file = readdir(d))!=NULL) { /* ad esempio stampo gli attributi di un file */ printattr(file->d_name); } /* chiusura directory */ IFERROR(closedir(d),"nella chiusura"); } 68 Directory corrente ... int chdir(const char* path) int fchdir(int fd) • sono vere chiamate di sistema • cambiano la directory corrente con quella indicata char* getcwd(char* buf, size_t size) • permette di leggere la directory corrente • scrive il nome in buf (per un massimo di size caratteri) • se non ci riesce ritorna NULL 69