Programmazione di Sistema – 1 Tool per la
Transcript
Programmazione di Sistema – 1 Tool per la
Tool per la programmazione C Programmazione di Sistema – 1 Paolo Baldan Università Ca’ Foscari Venezia Corso di Laurea in Informatica Parte di questo materiale è rielaborato dalle slide del Corso di Laboratorio di Sistemi Operativi tenuto da Rosario Pugliese presso l'Università di Firenze, anno accademico 2001/02. Il linguaggio C Studio delle system call (chiamate di sistema) che consentono di sfruttare appieno le funzionalità di Unix quali gestione dei file multitasking interprocess Strumenti per la programmazione C Si assume una conoscenza dei costrutti di base (vedi Corso di Programmazione). Vedremo qualche cenno/ripasso Compilazione communication (IPC) e linking modulare Programmazione I linguaggi più comunemente usati per la programmazione in ambiente Unix sono C e C++. (Unix è scritto in C !) Suddivisione di un programma in moduli e header file Dipendenze tra i moduli: make Archiviazione dei moduli: ar Cenni al debug (gdb, kdevelop) delle versioni (sccs – cssc, cvs) Profile, Strip Gestione Utilizzeremo il C come linguaggio “ambiente” per sperimentare la programmazione di sistema. 3 4 Programma in un singolo modulo Compilazione di un programma C Ogni versione di Unix (quasi) ha un compilatore standard per il linguaggio C. /* REVERSE.C */ #include <stdio.h> #include <string.h> Nel caso di GNU Linux è presente gcc (GNU C compiler) Per compilare un programma sorgente source.c /* Function Prototype */ void reverse ( char *, char * ); gcc source.c che genera l'eseguibile a.out. Inverte la stringa data come argomento ... Si può scegliere il nome dell'eseguibile utilizzando l'opzione -o gcc source.c -o target /*********************************************************/ int main (int argc, char *argv[]) { char str [strlen(argv[1])+1]; /* Buffer per la stringa invertita */ reverse (argv[1], str); /* Inverte la stringa in ingresso */ printf ("reverse (\"%s\") = %s\n", argv[1], str); /* Mostra il risultato */ return 0; } 6 5 Programma in un singolo modulo (cont.) Programma in un singolo modulo: Problemi ... continua Realizzare un intero programma come un singolo modulo presenta vari inconvenienti: Ogni (minima) modifica richiede la ricompilazione dell'intero programma void reverse ( char *before, char *after ) { /* before: puntatore alla stringa originaria */ /* after: puntatore alla stringa invertita */ int i, j, len; tempi di compilazione elevati !! Non è facile riutilizzare funzioni (di utilità generale) definite nel programma (es. reverse()). Nota: Per il secondo problema un semplice “cut&paste” delle funzioni è una pessima soluzione per len = strlen (before); for (j=len-1, i=0; j>=0; j--, i++) /* Ciclo */ after[i] = before[j]; after[len] = '\0'; /* \0 termina la stringa invertita */ } 7 manutenzione: ogni operazione di aggiornamento (es. sostituzione del codice della funzione con uno più efficiente) su ogni copia! efficienza: il cut&paste è lento e ciascuna copia della funzione occupa spazio disco. 8 Suddivisione in più moduli (cont.) Suddivisione in più moduli Un programma C complesso è normalmente articolato in più file sorgenti se un file sorgente utilizza una funzione, non definita nello stesso file, deve contenere la dichiarazione del prototipo della funzione compilati quindi Se un programma è diviso in più moduli sorgente indipendentemente collegati in un unico eseguibile Es: per utilizzare reverse() occorre dichiarare void *reverse(char *, char *) Risolve i problemi menzionati precedentemente direttive per il pre-processore / definizioni di tipo devono essere presenti in ogni file che le utilizza. Es. (non legate all'esempio) Uno stesso file può essere utilizzato da diversi programmi (funzioni riusabili) La rigenerazione di un eseguibile richiede la ricompilazione dei soli file sorgente modificati ed il linking. #define MAX_REVERSABLE 255 typedef char MyString[MAX_REVERSABLE] 10 9 Suddivisione in più moduli (cont.) Suddivisione in più moduli (cont.) Per rendere un modulo module.c facilmente riusabile si predispone un header file (file di intestazione) module.h contenente: gcc -c modulo.c direttive, prototipi definizioni di tipo delle funzioni definite Il file modulo module.c include il proprio header Ogni file che utilizza il modulo ne include l'header #include “modulo.h” 11 La compilazione produce il file oggetto modulo.o che contiene codice tabella #include “modulo.h” Ogni file sorgente può essere compilato separatamente macchina dei simboli La tabella dei simboli permette di ricombinare (tramite il compilatore gcc o il loader ld) il codice macchina con quello di altri moduli oggetto per ottenere file eseguibili. 12 Suddivisione in più moduli: Esempio Suddivisione in più moduli: Esempio /* reverse.h */ void reverse (char *, char *); /* Prototipo della funzione reverse */ /* reverse.c */ #include <stdio.h> #include <string.h> #include "reverse.h" int main (int argc, char *argv[]) { char str[strlen(argv[1])+1]; /* Buffer per la stringa invertita */ reverse (argv[1], str); /* Inverte la stringa in ingresso */ printf ("reverse (\"%s\") = %s\n", argv[1], str); /* Mostra il risultato */ return 0; len = strlen (before); for (j=len-1, i=0; j>=0; j--, i++) /* Ciclo */ after[i] = before[j]; after[len] = '\0'; /* \0 termina la stringa invertita */ } 14 13 Compilatore standard /* contiene il prototipo di reverse */ /****************************************************************/ /*****************************************************************/ void reverse ( char *before, char *after ) { /* before: puntatore alla stringa originaria */ /* after: puntatore alla stringa invertita */ int i, j, len; } /* usaRev.c */ #include <stdio.h> #include <string.h> #include "reverse.h" Compilatore standard: Opzioni Comuni Il compilatore C standard di GNU/Linux è gcc, conforme allo standard POSIX cc (cc è presente come link a gcc). La sintassi esprime in maniera vaga l'ordine degli argomenti nella riga di comando, e in effetti non c'è una particolare rigidità. gcc [<opzioni>|<file>]... .o .a Genera i file oggetto, senza effettuare il link dell'eseguibile finale. -g Aggiunge informazioni diagnostiche utili per il debugging attraverso strumenti appositi come gdb. -o file Indica che il file generato dalla compilazione (eseguibile, oggetto o altro) si deve chiamare file. -l libreria Utilizza la libreria il cui nome inizia per lib, continua con il nome indicato, e termina con .a oppure .so. Estensioni tipiche dei nomi dei file .c -c Sorgente C. File oggetto. Libreria di file oggetto. -v Genera informazioni dettagliate sulla compilazione. 15 16 Compilazione separata: Esempio Compilazione separata: Esempio Con l'opzione -c ... $ gcc -c reverse.c $ gcc -c usaRev.c si generano i file oggetto reverse.o e usaRev.o $ ls -l -rw-r--r--rw-r--r--rw-r--r--rw-r--r--rw-r--r-- 1 1 1 1 1 rossi rossi rossi rossi rossi users 517 Apr 27 11:07 users 1056 Apr 27 11:16 users 503 Apr 27 11:14 users 99 Apr 27 11:13 users 884 Apr 27 11:14 $ gcc reverse.o usaRev.o -o usaRev $ ls -l usaRev -rwxr-xr-x 1 rossi users 4325 Apr 27 usaRev usaRev.c usaRev.o reverse.c reverse.h reverse.o $ usaRev gatto reverse ("gatto") = ottag Infine il linking dei file oggetto, risolve i riferimenti incrociati e crea un file eseguibile usaRev Alternativamente, con un solo comando Se volessimo cambiare reverse.c dovremmo ricompilare solo questo file e poi rifare il linking. $ gcc -c reverse.c usaRev.c 18 17 Lo stand-alone loader Lo stand-alone loader Se usato per combinare più file oggetto, gcc invoca lo stand-alone loader, noto come linker ld -static {-Lpath}* {objMod}* {lib}* {-lX}* [-ofile] “collega” i file oggetto e i moduli di libreria per produrre un unico file eseguibile (a.out per default, oppure specificato dall'opzione -o) Esempio: ld [-dynamic-linker /lib/ld-linux.so.2|static] -L/usr/lib/gcc-lib/i486-linux/3.3.4 /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o usaReverse.o reverse.o -lc -lgcc -static: crea un eseguibile stand-alone (anziché un modulo con dynamic-linking). -lX cerca nelle directory standard /lib, /usr/lib e /usr/local/lib un file di libreria chiamato libX.a. -Lpath estende la lista di path standard suddetta. Nota: Per effettuare il linking direttamente con ld si devono indicare il modulo oggetto a runtime del C, crt1.o, crti.o e come moduli di libreria la libreria standard del C, libc.a. e di gcc, ovvero libgcc.a. 19 20 Esempio: Riutilizzo di reverse() Esempio: Riutilizzo di reverse() Verifica se una stringa è palindroma. /* palindroma.h */ int palindroma (char *); Il main ... /* usaPal.h */ #include <stdio.h> #include "palindroma.h" /* palindroma.c */ #include <string.h> #include "reverse.h" #include "palindroma.h" /*****************************************************************/ /****************************************************************/ int palindroma (char *str) { char revstr [strlen(str)+1]; /* Buffer per la stringa invertita */ reverse (str, revstr); /* Inverte la stringa in ingresso */ return !strcmp(str, revstr); /* vero se le stringhe sono uguali */ } int main (int argc, char *argv[]) { if (palindroma(argv[1])) printf ("La stringa \"%s\" e` palindroma.\n", argv[1]); else printf ("La stringa \"%s\" non e` palindroma.\n", argv[1]); } 22 21 Esempio: Compilazione e linking Gestione delle Dipendenze $ gcc -c palindroma.c $ gcc -c usaPal.c $ gcc reverse.o palindroma.o usaPal.o -o usaPal $ ls -l reverse.o palindrome.o usaPal.o usaPal -rwxr-xr-x 1 rossi users 4596 Apr 27 11:26 usaPal -rw-r--r-- 1 rossi users 1052 Apr 27 11:25 usaPal.o -rw-r--r-- 1 rossi users 892 Apr 27 11:25 palindroma.o -rw-r--r-- 1 rossi users 884 Apr 27 11:14 reverse.o $ usaPal elena La stringa “elena” non è palindroma. $ usaPal anna La stringa “anna” è palindroma. $ La ricompilazione di un programma suddiviso in moduli può essere un'operazione laboriosa: si potrebbe predisporre uno script di shell ... #!/bin/bash if [ reverse.o -ot reverse.c ] || [ reverse.o -ot reverse.h ]; then cc -c reverse.c fi if [ palindroma.o -ot palindroma.c ] || [ palindroma.o -ot palindroma.h ] || [ palindroma.o -ot reverse.h ]; then cc -c palindroma.c ... 23 24 Makefile Gestione delle Dipendenze: Make Unix mette a disposizione una utility apposita targetList: dependencyList commandList make [-f makefile] Aggiorna un “progetto” sulla base di regole di dipendenza contenute in un file con un formato speciale detto makefile. dove contiene una lista di file target è una lista di file da cui i file obiettivo dipendono commandList è una lista di zero o più comandi separati da newline (che “ricostruiscono” i target) ogni riga di commandList inizia con un TAB! targetList dependencyList Per default, make si aspetta che le regole di dipendenza si trovino nel file Makefile (anche makefile). Con l'opzione -f si può specificare un makefile diverso (per convenzione con suffisso “.make”). 25 Esempio: Palindroma Il makefile contiene un insieme di regole del tipo: All'interno di un makefile, si possono inserire commenti preceduti da #. 26 Makefile: Regole di dipendenza Il makefile per il programma usaPal (stringhe palindrome) può essere: Un file oggetto dipende dal # Makefile per usaPal, versione 1 da usaPal : usaPal.o palindroma.o reverse.o cc usaPal.o palindroma.o reverse.o -o usaPal usaPal.o : usaPal.c palindroma.h cc -c usaPal.c palindroma.o : palindroma.c palindroma.h reverse.h cc -c palindroma.c file sorgente tutti i file inclusi nel sorgente (con #include) Un file eseguibile dipende dai file oggetto dei quali si effettua il link per ottenerlo. Si esegue la commandList di una regola Quando un file nella targetList è più recente di uno dei file nella dependencyList reverse.o : reverse.c reverse.h cc -c reverse.c Quando 27 un file obiettivo non esiste 28 Regole predefinite Make: Funzionamento Costruisce un albero delle dipendenze esaminando le regole nell'ordine: Un modulo oggetto (.o) è creato dal sorgente (.c) sempre nello stesso modo. Questo è rappresentato dalla regola implicita ogni file nella targetList è radice di un albero i cui figli sono i file nella dependencyList make ha delle regole predefinite. Es: Visita l'albero in profondità ed esegue la commandList associata ad un nodo %.o: %.c cc -c -o $@ $< se la data di ultima modifica di un figlio è più recente di quella del nodo se non esiste il file che etichetta il nodo usaPal usaPal.o palindroma.o usaPal.c palindroma.h palindroma.c palindroma.h reverse.o target è inserito “automaticamente” target.o, con l'azione per la compilaz. 30 29 Make: Macro La presenza delle regole predefinite consente di semplificare il makefile. Es. Tra le dipendenze di un eseguibile reverse.c reverse.h reverse.h Regole predefinite: semplificazione per le stringhe palindrome ... # Makefile per usaPal, versione semplificata usaPal : usaPal.o palindroma.o reverse.o cc usaPal.o palindroma.o reverse.o -o usaPal usaPal.o : palindroma.h Una macro è un assegnamento di una stringa ad un nome, che da quel momento la rappresenta nome = stringa Il valore della macro è quindi riferito con $(nome) oppure ${nome} Le regole implicite contengono solitamente macro %.o: %.c $(CC) -c $(CFLAGS)-o $@ $< palindroma.o : palindroma.h reverse.h reverse.o : reverse.h e % è un pattern istanziato per ottenere la regola attuale $@ e $< sono variabili automatiche che rappresentano $@ il nome del file target, $< il nome del primo requisito Il nome di un modulo oggetto e del sorgente corrispondente sono solitamente modulo.o e modulo.c. L'ultimo può non essere menzionato ! e CFLAGS hanno valori standard che possono essere modificati CC anche la parte sottolineata può essere rimossa. 31 32 Makefile tipico Make: Macro Le macro si utilizzano spesso per definire path di interesse. Es. azione da compiere quando non si indica alcun obiettivo (tipicamente la compilazione) install: installa l'eseguibile dopo la compilazione clean: elimina i file oggetto e binari compilati prefix=/usr/local bin_prefix=${prefix}/bin Il makefile tipico automatizza le fasi legate alla compilazione ed installazione di un programma. Si distinguono tre obiettivi comuni all: Possono poi essere personalizzate ... Quindi $ make richiama l'obiettivo all e compila il programma $ make install installa gli eseguibili nella destinazione prevista $ make clean pulisce la directory utilizzata per la compilazione 34 33 Gestione degli archivi: ar Makefile tipico: Esempio Makefile per creare / installare usaRev e usaPal. L'utility ar consente di raggruppare # directory destinazione prefix=/usr/local bindir=${prefix}/bin gestire all: usaPal usaRev install: cp usaPal usaRev ${bindir} clean: rm *.o usaPal usaRev Sintassi moduli oggetto in un archivio (suffisso .a) l'archivio (inserire, estrarre, listare i moduli) ar key archiveName {fileName}* key può essere cancella il file dall'archivio appende il file all'archivio r: sostituisce nell'archivio la vecchia versione del file (lo aggiunge se non c'è ) t: mostra la table of contents x: estrae i file dell'archivio nella directory corrente v: genera output verboso d: q: usaPal : palindroma.o reverse.o usaPal.o : palindroma.h palindroma.o : palindroma.h reverse.h usaRev : reverse.o usaRev.o : reverse.h reverse.o : reverse.h 35 36 Gestione degli archivi: make Gestione degli archivi: cc e ld gcc (e ld) possono accedere direttamente ad un archivio Esempio $ cat usaPal.make usaPal : string.a(palindroma.o) string.a(reverse.o) string.a(usaPal.o) : palindroma.h string.a(palindroma.o) : palindroma.h reverse.h string.a(reverse.o) : reverse.h $ ar r string.a reverse.o palindroma.o $ ar t string.a reverse.o palindroma.o $ make -f usaPal.make cc -c -o palindroma.o palindroma.c ar rv string.a palindroma.o a – palindroma.o cc -c -o reverse.o reverse.c ar rv string.a reverse.o a – reverse.o rm reverse.o palindroma.o $ cc usaPal.o string.a -o usaPal 37 Debugger gdb è il debugger standard di GNU / Linux file specificato come argomento è caricato nel debugger quindi vari comandi permettono esecuzione passo-passo (step) vedere il valore di una variabile (print) ed il tipo (whatis) analizzare il sorgente (list) stabilire dei breakpoint (break) visualizzare espressioni (display) cercare funzioni compilare il programma con l'opzione -g di gcc (inserisce informazioni aggiuntive per il debugger) Esistono strumenti grafici molto “friendly” (es. il debugger di kdevelop) 39 splint Analizza i file forniti in input, evidenziando interazioni pericolose o possibilmente anomale (es.: una variabile definita e non utilizzata) gprof Profiler: genera una tabella che indica il tempo utilizzato, il numero di ripetizioni, ecc. di ogni funzione (il programma deve essere compilato con l'opzione -p). strip Elimina le informazioni aggiuntive nell'eseguibile inserite per il debugger o il profiler occorre 38 Altri strumenti utili ... il Anche il makefile può accedere agli archivi (make ha delle regole implicite per la gestione) Esempio sccs, cssc, cvs Strumenti per la gestione di versioni multiple di un progetto (utili nello sviluppo in team). 40