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
Scarica

PPT - DISI