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