Sistemi Operativi: Il kernel linux - Lezione 06

155
1 Modello architetturale di Linux Kernel monolitico Esecuzione di tutte le funzionalità critiche in kernel mode Modello ibrido tramite l'esecuzione di codice critico (device driver) in user mode (fuse) Kernel stratificato Diversi livelli di chiamate a funzione (6-10) prima di svolgere concretamente un compito http://www.linuxdriver.co.il/kernel_map Kernel modulare Meccanismo dei moduli per caricare a tempo di esecuzione funzionalità secondarie

Transcript of Sistemi Operativi: Il kernel linux - Lezione 06

1

Modello architetturale di Linux

• Kernel monolitico– Esecuzione di tutte le funzionalità critiche in

kernel mode– Modello ibrido tramite l'esecuzione di codice

critico (device driver) in user mode (fuse)

• Kernel stratificato– Diversi livelli di chiamate a funzione (6-10)

prima di svolgere concretamente un compito– http://www.linuxdriver.co.il/kernel_map

• Kernel modulare– Meccanismo dei moduli per caricare a tempo di

esecuzione funzionalità secondarie

2

Il linguaggio C

• Sviluppato da Ken Thompson e Dennis Ritchie all'inizio degli anni '70

• Concepito per la programmazione del kernel e degli applicativi di sistema UNIX

• Successore del linguaggio BCPL• Duplice scopo:– definire un linguaggio sufficientemente ad alto

livello per poter implementare un SO senza impazzire

– definire un linguaggio sufficientemente a basso livello da poter permettere il colloquio con le periferiche

3

Caratteristiche del linguaggio C

• Linguaggio compilato– traduzione da alto livello (programma C) a

basso livello (codice macchina)– compilatore

• Linguaggio estremamente compatto– funzionalità aggiuntive fornite da librerie

esterne

• Paradigma di programmazione procedurale– programmazione strutturata

• Linguaggio fortemente tipizzato– impedisce operazioni non valide sui dati

4

Caratteristiche del linguaggio C

• Accesso non controllato, a basso livello, alla memoria– meccanismo dei puntatori

• Set di istruzioni minimalistico– solo lo stretto necessario per implementare cicli,

assegnamenti, salti

• Passaggio di parametri per valore/riferimento• Meccanismo di puntatori a funzione– scheletro generico

– funzionalità dipendenti dall'implementazione

• Tipi di dati strutturati– parola chiave struct

5

Manchevolezze del C

• Mancanza di type safety– char -> int, int->char– somma di interi e float produce conversioni

automatiche

• Garbage collection automatica– meccanismo per ripulire automaticamente la

memoria allocata e non più utilizzata

• Meccanismi diretti per l'object oriented programming

• Annidamento di funzioni• Overloading degli operatori– “string1” + “string2” = “string1string2”

6

Un primo programma in C

#include <stdio.h>

int main(void) {

printf(“Hello, world\n”);

return 0;

}

• Editate il file prova.c con il contenuto di cui sopra• Che cosa fa questo programma?– stampa “Hello, world” sullo STDOUT del terminale

7

Un primo programma in C

#include <stdio.h>

• Direttiva di preprocessamento #include• Il preprocessore (primo strumento a toccare il

codice) esamina prova.c e sostituisce #include <stdio.h> con il file /usr/include/stdio.h

• Il file /usr/include/stdio.h contiene le definizioni di costanti e funzioni legate all'I/O

8

Un primo programma in C

#include <stdio.h>

• Le parentesi <> stanno ad indicare che stdio.h è un file include di sistema– distribuito con la libreria del C

– localizzato nel percorso di sistema /usr/include

9

Un primo programma in C

#include <stdio.h>

• Si possono anche utilizzare le virgolette “” per includere un file include

• Le virgolette “” stanno ad indicare un file include non di sistema

• Viene cercato nella directory corrente oppure nelle directory specificate dall'opzione -I del compiler

10

Un primo programma in C

int main(void) {

}

• Definizione di una funzione (in questo caso, la funzione main())– int: tipo di dato ritornato dalla funzione (intero)

– main: nome della funzione

– (...): lista di argomenti forniti alla funzione♦ void: nessun argomento

11

Un primo programma in C

int main(void) {

}

• Definizione di una funzione (in questo caso, la funzione main())– { ... }: delimitatori di inizio/fine funzione

♦ contengono la sequenza di statement (linee di codice) C

12

Un primo programma in C

int main(void) {

}

• La funzione main() è speciale– punto di ingresso dell'esecuzione del programma

– contiene lo “scheletro” del programma

– DEVE essere presente in ciascun programma♦ altrimenti, il programma non si compila

13

Un primo programma in C

printf(“Hello, world\n”);

• Viene invocata la funzione di libreria printf(), con l'argomento “Hello world\n”

• \n: sequenza di escape– si traduce in: “vai all'inizio della prossima riga”

14

Un primo programma in C

printf(“Hello, world\n”);

• Il valore di ritorno di printf è un intero, ma in questo caso, non interessandoci, non viene memorizzato in alcuna variabile– viene scartato automaticamente

15

Un primo programma in C

printf(“Hello, world\n”);

• Se invece avessi voluto memorizzare il valore:ret = printf(“Hello, world\n”);

16

Un primo programma in C

return 0;

• Questo è il codice di ritorno della funzione, ossia il valore ritornato dalla stessa

17

Tipi di dato del C

• Caratteri:– char: singolo carattere

• Interi– unsigned short int: intero da 0 a 65535– short int: intero da -32768 a +32767– unsigned int: intero da 0 a 4294967296– int: intero da -2147483648 a 2147483647

• Virgola mobile– float: da 0.29 * 10-38 a 1.7 * 1038

18

Tipi di dato del C

• Tipi di dato enumerati:– enum: lista di elementi “etichettati”enum stati {

attivo = 1,inattivo,interrotto

}

19

Tipi di dato del C

• Record– struct: tipo di dato complesso, contenente più

datistruct esempio {

int a;char b;

} mio_esempio;

• L'accesso ad un record avviene con l'operatore .:mio_esempio.a = 10;

20

Tipi di dato del C

• Unioni disgiunte di strutture– union: alternativa fra 2 o più struttureunion unione {

int primo_valore;char secondo_carattere;struct mystruct terza_struttura;

} mia_unione;

• L'accesso ad una unione avviene con l'operatore .:mia_unione.primo_valore=10;

21

Tipi di dato del C

• Indirizzamento della memoria– puntatori: indirizzo di una locazione di memoria

♦byte (8 bit), word (16 bit), long word (32 bit)

• Dichiarazione di una variabile puntatore:– int *int_ptr;

• Dereferenziazione (dereference) di un puntatore: ottenimento del valore della variabile a partire dall'indirizzo– int int_var, *int_ptr; int_var = *int_ptr;

22

Tipi di dato del C

• Indirizzamento della memoria– puntatori: indirizzo di una locazione di memoria

♦byte (8 bit), word (16 bit), long word (32 bit)

• Dichiarazione di una variabile puntatore:– int *int_ptr;

• Indirizzamento (addressing) di un puntatore: ottenimento dell'indirizzo di una variabile a partire dalla variabile stessa– int int_var, *int_addr; int_addr= &int_var;

23

Tipi di dato del C

• Casting: conversione esplicita di un puntatore in un altro

• Puntatore nullo: rappresenta l'indirizzo 0, e serve ad indicare che l'indirizzo contenuto dalla variabile puntatore è invalido– costante NULL

24

Tipi di dato del C

• Aritmetica dei puntatori: a seconda della grandezza della variabile puntata, gli operatori + e – assumono il seguente significato:– operatore di somma +: incrementa l'indirizzo

puntato del numero di byte occupato dal tipo di variabile puntata

– operatore di sottrazione -: decrementa l'indirizzo puntato del numero di byte occupato dal tipo di puntatore

25

Tipi di dato del C

• Casting a int pointer:a= (int *) puntatore_ad_altro_tipo;

• Aritmetica: In questo caso, a viene incrementata di 4 * 4 byte (la dimensione di un int) = 16 byteint a=10;

int *int_ptr = &a;

a=a+4;

• Aritmetica: In questo caso, a viene incrementata di 4 * 1 byte (la dimensione di un char) = 4 bytechar a='a';

int *int_ptr = &a;

a=a+4;

26

Tipi di dato del C

• Vettori di elementi– array: vettore di elementi dello stesso tipo, avente

una lunghezza fissaint a[10];

a[0] = 2;

a[1] = 3;

• Il nome di un array è anche l'indirizzo del primo elementoint *int_ptr = a;

• Array multidimensionali: uso di più indiciint a[10][20];

a[1][2] = 4;

27

Allocazione della memoria

• Tre modalità di allocazione della memoria:– statica: attraverso array– dinamica:

♦prenotazione della memoria attraverso l'uso della funzione di libreria malloc()

int *init_addr=(int *)malloc( 10 * sizeof(int));♦rilascio della memoria prenotata con free()free(init_addr);

– automatica: variabili locali allocate sullo stack dell'applicativo

28

Allocazione della memoria:pregi e difetti

• Allocazione statica:– molto veloce– impossibile a runtime

• Allocazione dinamica:– più lenta rispetto alla statica (overhead malloc()/

free())– utilizzabile a runtime

• Allocazione automatica:– molto veloce– locale alla funzione che la contiene

29

Sintassi del C

• Statement: ciascun statement (singola istruzione) deve terminare con il carattere ;

• Commenti: un commento è delimitato dalla sequenza /* *//* esempio di commento */

• Definizione di variabili: si fa precedere il tipo di dato al nome della variabileint a, b=4;

struct mystruct {int c=4;int *d;

} struttura = { 4, NULL };

30

Sintassi del C

• parola chiave static:– applicata alle variabili locali, permette di salvare il

valore di una variabile automatica fra più chiamate

– applicata alle variabili globali, limita l'accesso alla variabile alle sole funzioni definite nello stesso file

– applicata alle funzioni, limita l'accesso alla funzione alle sole funzioni definite nello stesso file

static int a = 10;

static int f(int b) { return b – 2 };

• Meccanismo per proteggere funzioni private, di implementazione, dall'uso di moduli esterni

• Le funzioni non-static costituiscono l'interfaccia di un modulo di codice

31

Sintassi del C

• Nel caso in cui un programma C sia costituito da più file, è necessario dire al compilatore che alcune funzioni o variabili utilizzati in un file sono definiti in altri file

• Parola chiave externextern int var_esterna;

• In questo esempio, la definizione della variabile var_esterna viene cercata definita in un altro file– se non viene trovata alcuna definizione, il processo

di linking termina con un errore

– abusare di extern è segno di programmazione poco pulita

32

Operatori più comuni

• Aritmetici:– somma, sottrazione, moltiplicazione, divisione: +, -,

*, /

– resto modulo n: %

• Bit:– shift a sinistra: << (divide * 2)

– shift a destra: >> (moltiplica * 2)

– AND bit a bit: &

– OR bit a bit: |

• Logici:

– AND logico: &&

– OR logico: ||

33

Operatori più comuni

• Assegnamento: si usa l'operatore =int a = 10;

• Confronto– Uguaglianza: si usa l'operatore ==

if ( a==b )printf “a uguale a b.\n”;

– Minore di: si usa l'operatore <

– Minore o uguale di: si usa l'operatore <=

– Maggiore di: si usa l'operatore >

– Maggiore o uguale di: si usa l'operatore >=

34

Operatori più comuni

• Alcuni esempi di operazioniint a = 10, b=255, c;

c = a & b; /* c=10 */

c = a | b; /* c=255 */

c= a && b; /* c=1 */

c= a || b; /* c=1 */

35

Controllo di flusso del programma

• Ciclo while: esegue il blocco di codice specificato fintantoché la condizione rimane vera

while ( condizione ) {

<blocco di codice>

}

• Ciclo do-while: esegue il blocco di codice specificato fintantoché la condizione rimane vera

do {

<blocco di codice>

} while ( condizione );

• Il ciclo do-while esegue almeno una volta

36

Controllo di flusso del programma

• Ciclo for: esegue il blocco di codice specificato fintantoché la condizione rimane vera, aggiornando delle variabili nel contempofor ( i=1; i < 10; i++ ) {

<blocco di codice>

}

for (p=list->next; p != NULL; p=p->next) {printf(“P has %s\n”, p->string;

}

37

Particolarità del kernel• Il kernel è una applicazione radicalmente

diversa da quelle che girano in user mode• Elementi di diversità del kernel:– Caratteristiche avanzate del C

– Nessun utilizzo della libreria del C

– File include

– Nessuna protezione della memoria

– Nessun utilizzo (semplice) di virgola mobile

– Dimensione dello stack

– Portabilità

– Allineamento delle strutture dati

– Byte ordering

– Sincronizzazione e concorrenza

38

Caratteristiche avanzate del C

• Il kernel fa ampio uso delle caratteristiche avanzate del C

• Alcune specificate nel primo standard del C (standard K&R)– K&R sta per Kernighan & Ritchie

• Altre specificate negli standard successivi ANSI(1983), ISO/C90 (1990)

• Altre caratteristiche definite come estensioni del compilatore GNU (gcc)

39

Macro

• Talvolta risulta comodo definire con un nome una sequenza di caratteri ripetitiva– una costante di uso frequente

– uno statement di uso frequente

• Direttiva del preprocessore #define(arg1,...,argn): permette di definire una “macro definizione” (o macro)#define DBG(str) printf (”%s\n”, str);

• Il compilatore, prima di compilare il programma, sostituisce ad ogni istanza di DBG(argomento) una istanza di printf(“%s\n”, argomento);

• La sostituzione non è una generazione di codice!

40

Macro

• Si può controllare se una macro è definita oppure no con le direttive del preprocessore #ifdef, #ifndef#ifdef CONFIG_VIDEO

stampa_16mil_colori(msg);

#elsestampa_1_colore(msg);

#endif

• Le direttive #ifdef, #ifndef sono molto comode per:– escludere/includere codice a seconda della

configurazione

– evitare che un file include locale, incluso due volte per errore, generi un messaggio di errore in compilazione

41

Macro

• Una #define particolarmente lunga può essere spezzata in più righe, terminando ciascuna riga con la stringa \ (escape di fine riga)#define FOO(x) \

printf(“arg is %s\n”, x); \do_something_useful(x);

• Le macro definite tramite la direttiva #define su più righe è dichiarata obsoleta nei nuovi compilatori, che consigliano l'utilizzo di:– meccanismi di protezione delle #define

– uso delle funzioni inline

42

Macro

• Che cosa ha di tanto grave la seguente macro?#define FOO(x) \

printf(“arg is %s\n”, x); \do_something_useful(x);

• Supponiamo che tale macro sia richiamata dal seguente codice:if (blah == 2)

FOO(blah);

• Che cosa fa il compilatore, prima di compilare il programma?

43

Macro

• Il compilatore sostituisce#define FOO(x) \

printf(“arg is %s\n”, x); \do_something_useful(x);

nel seguente codice:if (blah == 2)

FOO(blah);

ottenendo:if (blah == 2)

printf(“arg is %s\n”, blah);do_something_useful(blah);;

44

Macro

• Non notate niente di strano?if (blah == 2)

printf(“arg is %s\n”, blah);do_something_useful(blah);;

• Ho 2 errori:1.La macro termina con ;, l'istruzione che

richiama la macro termina con ;, ed alla fine ho ;; (errore sintattico)

2.La macro non è protetta con un blocco di codice { }; dopo l'espansione, se blah==2 viene eseguita solo la printf()

45

Macro

• Come posso correggere questo errore?– Incastro il codice della macro in un ciclo do { }

while(0), che viene eseguito una ed una sola volta

#define FOO(x) \do {

printf(“arg is %s\n”, x); \do_something_useful(x); \

} while (0)

46

Macro

• Cosa risulta dall'espansione?if (blah == 2)

do {printf(“arg is %s\n”, x); \do_something_useful(x); \

} while (0);

che risulta essere codice corretto• OSS.: la protezione di macro tramite do {} while(0);

è molto frequente nel codice del kernel di Linux• Se proprio dovete scrivere macro multiriga,

proteggetele!

47

Puntatori a funzione

• Come dice il nome stesso, un puntatore a funzioneè una variabile puntatore che, se dereferenziata, invoca una funzione

• Come si definisce una variabile puntatore a funzione?int (*ptr_to_function(int));

• Come si dereferenzia un puntatore a funzione?ret_code = (*ptr_to_function) (2);

ret_code =ptr_to_function(2);

• Come si assegna una funzione ad un puntatore?ptr_to_function=&my_func;

ptr_to_function=my_func;

48

Puntatori a funzione

• A cosa servono i puntatori a funzione?– implementazione meccanismo callback

♦ un programma è progettato “per eventi” ed associa un puntatore di funzione a ciascun evento

♦ quando si verifica l'evento, viene invocata la funzione opportuna tramite il function pointer opportuno

♦ scheletro molto semplice: struttura dati con associazione evento-funzione, e loop che invoca la funzione

– polimorfismo (object oriented programming)♦ cambiare modo di operare in funzione del tipo di dato

che viene fornito

49

Puntatori a funzione

• Definizione di struttura contenente puntatori a funzione:include/linux/fs.h:

struct file_operations {loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t,

loff_t *);ssize_t (*write) (struct file *, const char __user *,

size_t, loff_t *);...int (*setlease)(struct file *, long, struct file_lock **);

};

Questa è la strutturagenerica delle operazioni

su di un file

Q

50

Puntatori a funzione

• Inizializzazione di una struttura puntatrice a blocchi con le operazioni relative ad un dispositivo a blocchi:fs/block_dev.c:

struct file_operations def_blk_fops = {.open = blkdev_open,.release = blkdev_release,....read = do_sync_read,...

};

Qui vengono definite lefunzioni per un devicea blocchi, tramite deipuntatori a funzione

51

Puntatori a funzione

• Funzione vfs_read():fs/read_write.c:

ssize_t vfs_read(struct file *file, char __user *buf, size_t cnt, loff_t *pos) {

ssize_t ret;

<controlli vari>;

if (file->f_op->read) {ret = file->f_op->read(file, buf, count, pos);

}

<aggiorna contatori>

} La semantica di vfs_read()non dipende dal device sucui sarà effettuata la read().

52

Utilizzo estensioni GCC• Il kernel è, in larga parte, scritto in C• Per motivi di efficienza spaziale e temporale, il

kernel fa uso massiccio di estensioni del C– Estensioni del compilatore GNU gcc– Estensioni dello standard C99

• Estensioni più comuni:– Inline functions– Inline assembly– Asmlinkage– Branch annotation– Volatile

53

Estensioni GCC: inline functions• Solitamente, quando una funzione viene

tradotta in linguaggio macchina, il compilatore inserisce del codice per:– Passare gli argomenti sullo stack– Creare spazio per variabili locali– Ritornare il risultato attraverso lo stack

• Quando tale funzione viene invocata, si mettono i parametri sullo stack e si chiama (call) la funzione

• Una funzione inline cerca di ovviare agli aggravi computazionali legati a tale procedura

• Una funzione si dichiara inline con la parola chiave inline

54

Estensioni GCC: inline functions• Per una funzione inline, il compilatore non

genera il codice di gestione dei parametri attraverso lo stack

• La chiamata alla funzione viene sostituita al codice oggetto della funzione stessa

• La funzione viene eseguita più velocemente (non c'è più il codice di gestione dello stack)

• La dimensione del codice prodotto aumenta considerevolmente– Per ogni chiamata, al posto della call ho l'intero

codice oggetto della funzione

• Non posso avere più definizioni identiche di una stessa funzione inline

55

Estensioni GCC: inline functionsint funzione(int a) {

return (a + 2);

}

...

int b = funzione(10);

...

pushl %ebp

movl %esp, ebp

subl $N, %esp

movl 8(%ebp), %eax

addl $2, %eax

leave

ret

...

pushl 10

call funzione

...

56

Estensioni GCC: inline functionsInline int funzione(int a) {

return (a + 2);

}

...

int b = funzione(10);

...

...

movl $1, %eax

addl $2, %eax

...

57

Estensioni GCC: inline functions• Non tutte le funzioni dichiarate inline possono

essere effettivamente sostituite con il loro codice oggetto– Le funzioni ricorsive– Le funzioni che utilizzano l'indirizzo iniziale

della funzione– Le funzioni che sono invocate prima della loro

definizione

• “inline” è un suggerimento al compilatore– Il suggerimento può essere applicato oppure no

58

Estensioni GCC: inline functions• Il comportamento di inlining può essere

ulteriormente raffinato con le parole chiave static ed extern

• static inline:– Il compilatore può generare oppure no il codice

oggetto per la funzione

– Se tutte le chiamate a funzione sono inlined, non viene generato codice per la funzione, bensì viene copiato il codice oggetto al posto della chiamata

– Se almeno una chiamata non può essere inlined, allora viene creata una versione static della funzione, col proprio codice oggetto

– Si possono avere più definizioni di una funzione senza avere errori di ridefinizione

59

Estensioni GCC: inline functions• Il comportamento di inlining può essere

ulteriormente raffinato con le parole chiave static ed extern

• extern inline:– Il compilatore non genera mai codice oggetto per la

funzione

– La funzione viene considerata alla stregua di una macro con parametri

– Si possono avere più definizioni di una funzione senza avere errori di ridefinizione

– Se l'inlining non può essere applicato, deve essere presente una definizione non-inline della funzione che possa essere invocata, altrimenti il compilatore genera un errore di linker

60

Estensioni GCC: inline functions• Quando e dove vengono utilizzate static inline

ed extern inline?• static inline:– Viene utilizzata per le funzioni interne, che non

si vogliono esportare pubblicamente– Utilizzata nei file *.c e file *.h

• extern inline:– Viene utilizzata per le funzioni pubbliche, che

rappresentano l'interfaccia con altri moduli– interfaccia popolari definite nei file include– Utilizzata nei file *.h

61

Estensioni GCC: Assembler inline• Il GCC permette l'inserimento di istruzioni

assembler all'interno di codice scritto in C• Utilizzato per implementare porzioni del kernel

dipendenti dall'architettura• Si utilizza la parola chiave asm (oppure

__asm__ se asm è già definita)• Due varianti:– Basic inlining: inserimento di un gruppo di

istruzioni fisse, senza operandi– Extended Asm: inserimento di un gruppo di

operazioni con passaggio di valori

62

Estensioni GCC: Assembler inline• Basic inlining:

asm("movl %ecx, %eax");asm( "movl %eax, %ebx\n\t"

"movl $56, %esi\n\t""movl %ecx, $label(%edx,%ebx,$4)\n\t""movb %ah, (%ebx)");

63

Estensioni GCC: Assembler inline• Extended asm: formato

asm( assembler template: output operands (opzionali): input operands (opzionali): list of clobbered registers (opzionali)

);

• Extended asm: esempioint a=10, b;

asm( "movl %1, %%eax;movl %%eax, %0;": “=r”(b) /* b è l'output (%0) */: =”r”(a) /* a è l'input (%1) */: “%eax” ); /* %eax modificato */

64

Estensioni GCC: Assembler inline• Formato operandi:– L'operando di output viene contrassegnato con

uno %0– Gli operandi di input vengono contrassegnati

con %1, %2, ...

• Register constraints:– “=r(x)”: l'operando è memorizzato nel registro x

♦x=a: %eax, x=b: %ebx, x=c: %ecx, x=d: %edx, x=S: %esi, x=D: %edi

– “m(y)”: l'operando è memorizzato nella locazione di memoria y

• Clobber list: specificihiamo i registri modificati nell'operazione (il gcc non li usa per il caching)

65

Estensioni GCC: Asmlinkage• Alcune funzioni sono marcate con la parola

chiave asmlinkage• La parola chiave asmlinkage istruisce il

compilatore a non prelevare i parametri attraverso registri, bensì solo sullo stack

• Usato nelle system call (ed in tante altre funzioni)

66

Estensioni GCC: Branch annotation• Il compilatore GNU gcc mette a disposizione

una direttiva (__builtin__expect()) che si può applicare a funzioni

• Il kernel di Linux mette a disposizione due macro (likely() ed unlikely()) che sfruttano in maniera semplice tale direttiva

• Se il risultato di una espressione è quasi sempre vero, si può ottimizzare il codice generato in modo tale da renderlo molto veloce per il caso frequente (vero)– Idem se il risultato è una espressione quasi

sempre falsa

67

Estensioni GCC: Branch annotation• Se foo è una espressione quasi sempre vera, si

può scrivere:if (likely(foo)) {

...

}

che risulta in un codice molto più veloce diif (foo) {

...

}

• Analogamente, se foo è una espressione quasi sempre falsa, si può scrivere:if (unlikely(foo)) {

...

}

Il confronto è molto piùlento del normale se

foo è falso

Il confronto è molto piùlento del normale se

foo è vero

68

Estensioni GCC: volatile• Supponiamo che il compilatore debba

compilare queste linee di codice:static int foo;

void bar(void) {foo = 0;while (foo != 255) ;

}

• La variabile foo non sembra valere mai 255• Il compilatore vede questo fatto, e prova ad

ottimizzare le cose– Perché mantenere quel controllo foo!=255?– Non è equivalente a while(true)?

69

Estensioni GCC: volatile• Detto fatto, il compilatore decide di

“ottimizzare” il codice in:static int foo;

void bar(void) {foo = 0;while (true) ;

}

• Il codice risultante è molto più veloce (non si deve valutare l'espressione foo != 255)

• Supponiamo che qualcuno (ad es., una interruzione) scriva un valore nell'indirizzo di foo

• Il valore di foo può cambiare “al di fuori” del file C ora descritto

70

Estensioni GCC: volatile• Il compilatore, preso dal desiderio di

ottimizzare, ha generato un codice non corretto!

• Come si elimina la brama di ottimizzazione?• Parola chiave volatile– Dice al compilatore di non fare alcun tipo di

ottimizzazione riguardante una variabilestatic volatile int foo;

void bar(void) {foo = 0;while (true) ;

}

71

Estensioni GCC: volatile• La parola chiave volatile viene utilizzata per

proteggere variabili che possono essere cambiate da una interruzione o da un altro processore

• La parola chiave volatile viene anche usata spesso in congiunzione con la parola chiave asm (inline assembly): asm volatile– In tal modo, vengono impedite ottimizzazioni

sulle istruzioni assembler

72

Nessun accesso alla libreria del C• Le applicazioni normali vengono “fuse” con I

codici oggetto della libreria C, che provvedono a dare le necessarie funzionalità– Gestione file, gestione memoria, I/O

• Uno dei compiti principali del kernel è quello di implementare tali funzionalità correttamente, efficientemente, in maniera portabile

• Di conseguenza, il kernel non può utilizzare alcuna funzione della libreria del C!– Scordatevi printf(), malloc(), open(), close(),

read(), write()

73

Nessun accesso alla libreria del C• In realtà, nel kernel è implementato un piccolo

insieme di funzioni “di libreria”– printk(): analogo della funzione printf(); stampa

stringhe su una console/log– kmalloc(): analogo della funzione malloc();

alloca memoria dinamicamente– kfree(): analogo della funzione free(); libera

memoria dinamicamente– Str...(): anloghe delle funzioni str...(); gestione di

stringhe

• Per una descrizione non esaustiva dell'API di Linux, si consulti:– http://www.gelato.unsw.edu.au/~dsw/public-

files/kernel-docs/kernel-api/

74

File include• I file include (detti anche header) sono utilizzati

come meccanismo di definizione dell'API– strutture dati– prototipi di funzioni (API)

• La libreria del C definisce la propria API tramite i suoi file include– directory /usr/include

• Il kernel definisce la propria API tramite I suoi file include– sottodirectory include di un qualunque albero

sorgente del kernel

75

File include• La libreria del C invoca le chiamate di sistema del

kernel, passando gli opportuni parametri e ricevendo gli opportuni valori di ritorno

• Il kernel deve conoscere il formato sia delle strutture dati passate come parametri, sia delle strutture dati passate come valore di ritorno

• D'altro canto, il kernel implementa una sua API interna (non utilizzabile dagli applicativi user mode) per ciascun strato interno

• Due API diverse definite nel kernel:– API interna: strutture dati e prototipi delle funzioni

usate negli strati interni del kernel

– API di raccordo: strutture dati e prototipi delle funzioni usate dalla libreria del C

76

File include• L'API di raccordo del kernel deve essere

coerente con l'API definita dalla libreria del C– Condizione solitamente garantita dal gestore

dei pacchetti del sistema– Durante il processo di compilazione della

libreria del C, viene fatto esplicito riferimento ai file include del kernel (API di raccordo)

– Se si compila il kernel a mano, bisogna stare attenti alla compatibilità fra le due API!

– Non è necessario che le due versioni dei file include (libreria C e kernel) siano identiche

77

File include• La libreria del C cerca di essere compatibile

all'indietro con l'API di raccordo del kernel– Una libreria C compilata con una data versione

dei file include del kernel funziona con tutti i kernel aventi file include “più vecchi”

– Se si compila una libreria del C con file include del kernel vecchi e poi si usa un kernel con file include più recenti, le nuove funzionalità introdotte a livello di kernel potrebbero non essere supportate (o, peggio, introdurre bachi)

• Si consulti la FAQ della libreria del C per ulteriori approfondimenti:– http://www.gnu.org/software/libc/FAQ.html

78

File include• Solitamente, la libreria del C messa a

disposizione dalla distribuzioni più popolari è compilata con i file include di un kernel abbastanza recente

• Il problema della compatibilità non dovrebbe porsi anche se si compila l'ultimissima versione del kernel

79

File include• Un file include del kernel può essere incluso

da molteplici file• Una inclusione doppia può portare a

ridefinizioni di variabili/funzioni, con conseguente errore da parte del compilatore

• I file include del kernel sono inclusi una sola volta tramite l'utilizzo di una costante#ifndef COSTANTE#define COSTANTE 1...#endif

80

Nessuna protezione della memoria• Quando una applicazione in user mode accede

ad indirizzo invalido, il kernel intercetta tale errore ed uccide il processo relativo

• Il kernel non ha un meccanismo analogo!• Un accesso ad una area di memoria illegale (ad

es., l'indirizzo 0x00000000) causa un blocco istantaneo ed irreversibile del sistema (panic)

• La memoria del kernel non può essere gestita in modalità virtuale, per motivi di efficienza– Niente swap, solo memoria fisica– Occorre allocare solo lo stretto necessario

81

Nessun (semplice) uso di floating point• L'uso di aritmetica in virgola mobile (floating

point) richiede l'uso di registri speciali• Tali registri non vengono salvati durante un

cambio di contesto, per motivi di efficienza• Non si può usare artimetica floating point in

punti nei quali lo scheduler oppure una interruzione ci possono interrompere!

• Morale: non usiamo aritmetica floating point nel kernel!

82

Dimensione dello stack• Gli applicativi eseguiti in user mode hanno uno

stack di dimensione variabile• Lo stack del kernel è piccolo e di dimensione

fissa– 4KB su architetture a 32 bit– 8KB su architetture a 64 bit

• Non si può sprecare tale stack con:– chiamate ricorsive– Passaggio di strutture grandi (meglio passare

puntatori a tali strutture)– Ritorno di strutture grandi (meglio passare

puntatori a tali strutture)– Variabili locali di grandi dimensioni

83

Portabilità• Il kernel è scritto per poter essere eseguito su

una miriade di architetture diverse• Architetture diverse hanno formati e

dimensioni diverse di rappresentazione dei dati– Architetture a 16, 32, 64 bit– Alcuni tipi di dato (int, long) possono avere

dimensioni diverse

• La parte architecture-independent del kernel deve essere scritta in modo tale che funzioni su ciascuna di tali architetture– Non si possono fare assunzioni sulle

dimensioni dei tipi di dato

84

Portabilità• Si definisce “parola” la quantità di dati che una

architettura è in grado di elaborare in un colpo di clock

• Una architettura a n bit è una architettura in cui la parola ha dimensione n bit– I registri general purpose hanno la stessa

dimensione di una parola– La dimensione del bus di memoria ha almeno la

stessa dimensione di una parola– Per le architetture supportate da Linux,

l'”almeno” si traduce in “uguale”– Ne consegue che la dimensione di un puntatore

è pari alla dimensione della parola– Il tipo di dato long è equivalente alla parola

85

Portabilità• Un esempio: nelle architetture a 64 bit– Registri, puntatori, long sono a 64 bit– Il tipo di dato int è a 32 bit– puntatore != intero (come invece è nelle

architetture a 32 bit)

• Sarebbe un grosso errore supporre, in generale, puntatori di 32 bit

• Il kernel definisce il numero di bit in un tipo di dato long:– macro BITS_PER_LONG, in <asm/types.h>

86

Portabilità• Alcune regole da ricordare quando si usano i

tipi di dato– Il tipo di dato char è sempre lungo 8 bit– Il tipo di dato int vale 32 bit su tutte le

architetture supportate da Linux– Il tipo di dato short vale 16 bit su tutte le

architetture supportate da Linux– Mai fare assunzioni sulla lunghezza di long o di

un puntatore– Mai assumere che int e long abbiano la stessa

lunghezza– Mai assumere che int e puntatore abbiano la

stessa lunghezza

87

Portabilità• Il kernel fa ampio uso di tipi di dato opachi• Un tipo di dato opaco è un tipo di dato

generico definito tramite l'operatore C typedef• Architetture diverse possono definire il tipo di

dato opaco in modi diversi• Obiettivi:– Permettere dimensioni più grandi su

architetture più potenti (es.: offset di seek dei file)

– Forzare un tipo di dato ad avere la stessa dimensione su tutte le architetture(es.: indirizzi IP)

88

Portabilità• Il kernel fa ampio uso di tipi di dato espliciti• Un tipo di dato esplicito è l'esatto contrario di

un tipo di dato opaco• Il tipo di dato esplicito è definito tramite

l'operatore typedef– File include/asm/types.h

• Il tipo di dato esplicito ha sempre la stessa dimensione, su tutte le architetture

• Alcuni esempi:– s8, u8: signed/unsigned 16 bit integer– s16, u16: signed/unsigned 16 bit integer– s32, u32: signed/unsigned 32 bit integer– s64, u64: signed/unsigned 64 bit integer

89

Allineamento• L'allineamento è il posizionamento di una

variabile in un indirizzo multiplo della sua dimensione

• Esempio: un tipo di dato a 32 bit è allineato se è memorizzato in un indirizzo di memoria multiplo di 4 (gli ultimi 2 bit sono a zero)

• In generale, un tipo di dato di 2n byte è allineato se è memorizzato in un indirizzo di memoria avente gli ultimi n bit impostati a 0

• In alcuni sistemi (Intel), l'accesso a dati non allineati è possibile ma lento

• In altri sistemi (RISC), l'accesso a dati non allineati provoca una eccezione

90

Allineamento• Tutte le strutture dati devono essere allineate• Di solito, il compilatore pensa ad allineare

automaticamente le strutture dati– Gli array sono allineati automaticamente ad un

multiplo della dimensione del tipo– Le union sono allineate al tipo di dato più

grande incluso– Le strutture sono allineate al tipo di dato più

grande incluso– Le strutture dati sono soggette a padding

91

Allineamento: padding delle strutture• Padding: gli elementi di una struttura dati sono

allineati• Consideriamo la seguente struttura su una

macchina a 32 bit:struct foo_struct {

char dog; /* 1 byte */unsigned long cat; /* 4 byte */unsigned short pig; /* 2 byte */char fox; /* 1 byte */

};

• Il compilatore inserisce degli zeri in modo tale da allineare ciascun elemento a 4 byte

92

Allineamento: padding delle strutture• Padding: gli elementi di una struttura dati sono

allineati• Ecco la struttura dati in memoria risultante dal

processo di padding:struct foo_struct {

char dog; /* 1 byte */u8 __pad0[3]; /* 3 byte */unsigned long cat; /* 4 byte */unsigned short pig; /* 2 byte */char fox; /* 1 byte */u8 __pad1; /* 1 byte */

};

4 byte

4 byte

93

Byte ordering• Il byte ordering definisce il posizionamento dei

byte all'interno di una parola• Due possibili ordinamenti:– Big endian: il byte più significativo della parola

è memorizzato per primo– Little endian: il byte più significativo della

parola è memorizzato per ultimo

Byte

B

0

0

1 2 3

OrdinamentoBig Endian

Byte

3 2 1 0

OrdinamentoLittle Endian

Piùsignificativo

Menosignificativo

Menosignificativo

Piùsignificativo

94

Byte ordering• Curiosità: I nome Big Endian e Little Endian

vengono dal romanzo “I viaggi di Gulliver” di Jonathan Swift

• Architetture Big Endian:– PowerPC, System/370, SPARC

• Architetture Little Endian:– 6502, z80, x86, VAX, PDP-11

B0

95

Byte ordering• Un esempio: memorizziamo il numero 1027 nei

formati Big Endian e Little Endian

B0

1027 in binario: 00000000 00000000 00000100 00000011

Indirizzo Big Endian Little Endian0 00000000 000000111 00000000 000001002 00000100 000000003 00000011 00000000

96

Sincronizzazione e concorrenza• Il kernel è un software interrompibile in

maniera sincrona ed asincrona– Sincrona: scadenza quanto di tempo, eccezioni

di vario genere– Asincrona: interruzioni

• Che cosa succede se il codice eseguito in seguito all'interruzione modifica il codice che stava eseguendo precedentemente?– Inconsistenza dei dati– Crash

97

Sincronizzazione e concorrenza• Il kernel è un software che può gestire

l'esecuzione concorrente di più processi• I processi possono condividere informazioni,

strutture dati, altre risorse• Che cosa succede se le risorse possono

essere accedute senza alcun meccanismo di protezione/arbitrio/accesso?– Inconsistenza dei dati– Crash

98

Contesti di esecuzione• In ogni istante, il processore può trovarsi in

una di queste condizioni– User mode: esecuzione di codice di un

processo in user mode– Kernel mode: esecuzione di codice di un

processo in kernel mode (system call)– Gestione interruzioni: esecuzione di codice

legato ad una interruzione hardware, per conto di nessun processo

99

Contesti di esecuzione

Applicazione

C

User space

Kernel space

Hardware

Una applicazione vuoleleggere un blocco giàbufferizzato del disco

100

Contesti di esecuzione

Applicazione

C

User space

Kernel spacen=fread(...)

vfs_read()

Hardware

Lista blocchibufferizzati

Il processo invoca la fread(),che a sua volta invoca la vfs_read()

101

Contesti di esecuzione

Applicazione

C

User space

Kernel spacen=fread(...)

vfs_read()

Hardware

Lista blocchibufferizzati

Ad un certo punto, arriva una interruzione,in seguito alla quale un blocco vuoleessere inserito nella posizione da cuivuole leggere il processo

102

Contesti di esecuzione

Applicazione

C

User space

Kernel spacen=fread(...)

vfs_read()

Hardware

Lista blocchibufferizzati

Si ha un accesso simultaneo alla stessastruttura dati (corsa critica)

103

Contesti di esecuzione• La corsa critica, nella “migliore” delle ipotesi,

riesce a sovrascrivere velocemente il buffer, facendo leggere fischi per fiaschi al processo

• Nella peggiore delle ipotesi, l'interruzione arriva nel momento in cui il processo stava estraendo il buffer dalla lista– Il gestore delle interruzioni si trova una lista

mozzata a metà– Viene provata la scansione della lista– Ad un certo punto si prova a saltare ad un

elemento NULL della lista– Accesso illegale -> la macchina si pianta

104

Contesti di esecuzione• Questo problema può essere impedito tramite

due meccanismi– Un gestore veloce di interruzioni, che opera su

un buffer locale, e successivamente copia il buffer locale nel buffer condiviso coi processi

♦Upper half/bottom half– Un meccanismo di prenotazione/rilascio delle

strutture dati (non del codice!)♦Locking/semafori

105

Contesti di esecuzione

Applicazione

C

User space

Kernel space

Hardware

Una applicazione vuoleleggere un blocco giàbufferizzato del disco

106

Contesti di esecuzione

Applicazione

C

User space

Kernel spacen=fread(...)

vfs_read()

Hardware

Lista blocchibufferizzati

Il processo invoca la fread(),che a sua volta invoca la vfs_read()La vfs_read() blocca l'uso dellastruttura dati “lista”

107

Contesti di esecuzione

Applicazione

C

User space

Kernel spacen=fread(...)

vfs_read()

Hardware

Lista blocchibufferizzati

Ad un certo punto, arriva una interruzione,che provoca l'inserimento di un bloccoin una lista locale

Buffer associatoal gestore IRQ

UPPERHALF

108

Contesti di esecuzione

Applicazione

C

User space

Kernel spacen=fread(...)

vfs_read()

Hardware

Lista blocchibufferizzati

Successivamente, una proceduradilazionata nel tempo prova ad inserirel'elemento dal buffer locale al bufferdei blocchi

Buffer associatoal gestore IRQ

BOTTOMHALF

109

Contesti di esecuzione

Applicazione

C

User space

Kernel spacen=fread(...)

vfs_read()

Hardware

Lista blocchibufferizzati

vfs_read() rilascia la struttura dati

110

Contesti di esecuzione

Applicazione

C

User space

Kernel space

vfs_read()

Hardware

Lista blocchibufferizzati

Viene immediatamente preso ilblocco da parte del gestore diinterruzioni dilazionato

111

Contesti di esecuzione

Applicazione

C

User space

Kernel space

vfs_read()

Hardware

Lista blocchibufferizzati

Avviene l'inserimentodell'elemento nella lista

112

Contesti di esecuzione

Applicazione

C

User space

Kernel space

vfs_read()

Hardware

Lista blocchibufferizzati

Il gestore dilazionato rilasciail blocco sulla lista

113

Anatomia di una funzione del kernel• Una funzione del kernel segue uno scheletro

ben preciso– Dal punto di vista della forma (commenti,

indentazione, coding style in generale)– Dal punto di vista strutturale (prologo,

prenotazione/rilascio risorse, corpo, epilogo, gestione degli errori)

114

Funzione del kernel: Forma• Definita nel file Documentation/CodingStyle• Obiettivi: fare in modo che una funzione sia

leggibile e ben commentata• Indentazione:– 8 spazi (tab) per riconoscere agevolmente I

blocchi di controllo– Limitazione del numero di livelli di indentazione

• Larghezza delle linee di codice:– Non dovrebbe mai superare gli 80 caratteri– Entra in uno schermo

• Lunghezza delle funzioni:– Mai più di 1-2 schermate video

115

Funzione del kernel: Forma• Commenti:– Solo prima dell'intestazione di una funzione– Mai commentare i singoli statement, a meno

che non sia necessario

• Piazzamento delle parentesi:– Stile Kernighan & Richie: il blocco inizia sulla

stessa linea dello statementif ( x is true ) {

do y

}

• Nomi delle variabili:– I più semplici ed intuitivi possibili– ThisVarIsATempCounter: NO, cnt: SI

116

Funzione del kernel: Struttura• Quanto segue è soggetto a 1000 eccezioni! Non

prenderlo come regola aurea!• In generale, una funzione del kernel ha il seguente

scheletro:int funzione(int, char *, ...) {

error = ERROR_CODE;

if ( !valid_check)

goto out;

prenota_risorsa();

if ( !valid_thing )

goto out_rilascia;

...

ret = 0;

out_rilascia:

rilascia_risorsa();

out:

return error;

}

117

Funzione del kernel: Struttura• Le funzioni ritornano, solitamente, un valore < 0

oppure un puntatore NULL per indicare un errore oppure l'incapacità di svolgere il compito assegnato

• La prima fase della funzione consiste in alcuni controlli generici; se i controlli falliscono, si esce direttamente

• Successivamente, si prenota una risorsa e si cerca di portare a termine il compito richiesto; se si fallisce, si salta al percorso di uscita che sblocca la risorsa

• Se tutto va bene, si imposta un valore di uscita opportuno e si segue l'intero percorso di uscita (che sblocca le risorse e ritorna il valore)

118

Gestione delle liste• Una lista concatenata è una struttura dati che

collega un certo numero di elementi (nodi)• Diverse implementazioni:– Lista statica (array)– Lista dinamica (collegamento puntatori)

• Il kernel fa ampio uso di liste dinamiche– Le dimensioni delle liste sono spesso ignote– Si può ottimizzare il consumo di memoria

• Diverse tipologie di collegamento:– Collegamento singolo– Collegamento doppio– Collegamento circolare

119

Gestione delle liste

next

n

...

.

next

n

...

.

next

n

...

.

null

Lista a collegamento singolo

null prev ... next prev ... next prev ... next null

Lista a collegamento doppio

120

Gestione delle liste

next

n

...

.

next

n

...

.

next

n

...

.

Lista circolare a collegamento singolo

prev ... next prev ... next prev ... next

Lista circolare a collegamento doppio

121

Gestione delle liste• Il problema principale di tale struttura è che

risulta essere definita insieme al tipo di dato che essa ospitastruct my_list {

void *my_item;struct my_list *next;struct my_list *prev;

}

• E' impossibile astrarre la definizione di lista in una libreria

• Ogni struttura dati ha la sua libreria per la gestione delle liste

• Duplicazione inutile di codice, con tutti gli svantaggi che ne derivano

122

Gestione delle liste• Il kernel di Linux mette a disposizione una

funzionalità generica di liste, indipendente dal tipo di dato (type oblivious)– Codice riutilizzabile per qualunque tipo di dato/

struttura

• Definizione in include/linux/list.h• Implementazione di lista doppia concatenata• Utilizzo di un primo elemento sentinella

(dummy)– Non rappresenta un elemento– Serve a collegare primo ed ultimo elemento

123

Gestione delle liste• L'elemento chiave delle liste è una

concatenatore (struct list_head), definito come una coppia di puntatori al prossimo concatenatore ed precedente concatenatore:struct list_head {

struct list_head *next;struct list_head *prev;

}

• Come potete vedere, non c'è alcun riferimento diretto ad alcun tipo di struttura dati– Solo puntatori in avanti ed indietro, sempre ad

un'altro concatenatore di lista

• Dove $#%?!&#*! sono i dati?

124

Gestione delle liste• Un passo alla volta :-)• Il concatenatore viene inserito all'interno della

struttura dati che si vuole concatenarestruct kool_list {

int to;struct list_head list;int from;

} my_kool_list;

• Posso inserire il concatenatore dove voglio, all'interno della struttura kool_list

• Posso chiamare il concatenatore come mi pare (list, lista, pinco pallino)

• Posso avere multipli concatenatori

125

Gestione delle liste

int to;

struct list_headlist;

Int from;

struct list_headlist;

int to;

struct list_headlist;

Int from;

ElementosentinellaElementosentinella

Elementolista

Elementolista

structkool_list

structkool_list

126

Gestione delle liste: operazioni• Come posso utilizzare la mia lista nei

programmi?• Devo definire meccanismi per:– Inizializzare la lista– Aggiungere elementi alla lista– Cancellare elementi dalla lista– Controllare se la lista è vuota– Estrarre, dal concatenatore, l'elemento (qui

avviene una vera e propria magia)– Scorrere una lista

127

Gestione delle liste: inizializzazione• Avviene tramite l'utilizzo di apposite macro• Macro diverse a seconda di dove, nel codice,

effettuo l'inizializzazione• Inizializzazione tramite comando:– INIT_LIST_HEAD(&my_kool_list.list);– Vuole un puntatore al concatenatore– INIT_LIST_HEAD(list) effettua le operazioni:

list->next = list;list->prev = list;

128

Gestione delle liste: inizializzazione• Avviene tramite l'utilizzo di apposite macro• Macro diverse a seconda di dove, nel codice,

effettuo l'inizializzazione• Inizializzazione tramite assegnazione:– Vuole una variabile concatenatore (non

puntatore!)– LIST_HEAD_init(list_head_var);struct kool_list my_kool_list = {

.to = ...,

.list = LIST_HEAD_INIT(list_head_var)

.from = ...

}

– LIST_HEAD_INIT(name) si espande in:{ &(name), &(name) }

129

Gestione delle liste: inizializzazione• Avviene tramite l'utilizzo di apposite macro• Macro diverse a seconda di dove, nel codice,

effettuo l'inizializzazione• Inizializzazione tramite assegnazione

automatica:– Vuole un nome di variabile– Alloca un concatenatore con quel nome– LIST_HEAD(my_kool_list);– LIST_HEAD(name) si espande in:

struct list_head_name = LIST_HEAD_INIT( name )

130

Gestione delle liste: inizializzazione

struct list_head list;

prev

next

131

Gestione delle liste: inserimento• Si utilizza la funzione list_add()– list_add(struct list_head *new, struct list_head *

head)– new è il puntatore al concatenatore

dell'elemento che voglio inserire– head è il puntatore al concatenatore sentinella

della lista– Viene inserito l'elemento in cima alla lista

132

Gestione delle liste: inserimento

struct list_headlist;

int to;

struct list_headlist;

Int from;

ElementosentinellaElementosentinella

Elementolista

structkool_list

133

Gestione delle liste: inserimento

int to;

struct list_headlist;

Int from;

struct list_headlist;

int to;

struct list_headlist;

Int from;

ElementosentinellaElementosentinella

Elementoda aggiungere

in testa alla lista

Elementolista

structkool_list

structkool_list

134

Gestione delle liste: cancellazione• Si utilizza la funzione list_del()– list_del(struct list_head *entry)– entry è il puntatore al concatenatore

dell'elemento che voglio rimuovere– Il concatenatore viene staccato dalla lista– Non viene distrutto l'elemento puntato dal

concatenatore!

135

Gestione delle liste: cancellazione

int to;

struct list_headlist;

Int from;

struct list_headlist;

int to;

struct list_headlist;

Int from;

ElementosentinellaElementosentinella

Elementoda rimuovere

Elementolista

structkool_list

structkool_list

136

Gestione delle liste: cancellazione

int to;

struct list_headlist;

Int from;

struct list_headlist;

int to;

struct list_headlist;

Int from;

ElementosentinellaElementosentinella

Elementorimosso

Elementolista

structkool_list

structkool_list

prev

next

=0

=0

137

Gestione delle liste: lista vuota• Si utilizza la funzione list_empty()– int list_empty(struct list_head *head)– head è il puntatore al concatenatore sentinella

della lista che voglio controllare– Ritorna 1 se la lista è vuota, 0 altrimenti

138

Gestione delle liste: elemento• Si utilizza la macro list_entry()– list_entry(ptr, type, member)– ptr è il puntatore al concatenatore dell'elemento– type è il tipo di dato dell'elemento (struct ...)– member è il nome della variabile concatenatore

all'interno dell'elemento– struct my_kool_list *element =

list_entry( &my_kool_list.list,struct kool_list, list );

• Fa uso della macro container_of (include/linux/kernel.h)

• Si basa su un trucco ben noto:– ftp://rtfm.mit.edu/pub/usenet-by-

group/news.answers/C-faq/faq (2.14)

139

Gestione delle liste: elemento• Macro container_of(ptr, type, member):

#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) ); \

})

Indirizzo1000100410121016

struct kool_list {int to;struct list_head list;int from;

} my_kool_list;

140

Gestione delle liste: elemento• Macro container_of(ptr, type, member):

#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) ); \

})

Indirizzo1000100410121016

struct kool_list {int to;struct list_head list;int from;

} my_kool_list;

141

Gestione delle liste: elemento• Macro container_of(ptr, type, member):

#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) ); \

})

Indirizzo1000100410121016

struct kool_list {int to;struct list_head list;int from;

} my_kool_list;

Prendo l'indirizzo 0 e lovedo (cast) come puntatorealla struttura kool_list.Posso farlo perché non stoscrivendo in memoria.

142

Gestione delle liste: elemento• Macro container_of(ptr, type, member):

#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) ); \

})

Indirizzo1000100410121016

struct kool_list {int to;struct list_head list;int from;

} my_kool_list;

Con la dereferenziazione(->)ottengo una variabiledi tipo “member” alloindirizzo 4 (parto da 0!).Non la sto scrivendo; ilsistema non crasha.

143

Gestione delle liste: elemento• Macro container_of(ptr, type, member):

#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) ); \

})

Indirizzo1000100410121016

struct kool_list {int to;struct list_head list;int from;

} my_kool_list;

Quale è il tipo dellavariabile di tipo memberche ho appena derefe-renziato? Ovviamente,member!

144

Gestione delle liste: elemento• Macro container_of(ptr, type, member):

#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) ); \

})

Indirizzo1000100410121016

struct kool_list {int to;struct list_head list;int from;

} my_kool_list;

Definiamo una variabilepuntatore di tipo member,di nome __mptr. Il doppiounderscore “__” si usaper nomi di variabiliinterne (per convenzione).

145

Gestione delle liste: elemento• Macro container_of(ptr, type, member):

#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) ); \

})

Indirizzo1000100410121016

struct kool_list {int to;struct list_head list;int from;

} my_kool_list;

Const indica che ilpuntatore punta ad unaarea di memoria chenon deve essere maimodificata. Se provo ascriverci, il compilatoreemette un errore.E' una misura di protezionecontro codice che scrivail contenuto di __mptraccidentalmente.

146

Gestione delle liste: elemento• Macro container_of(ptr, type, member):

#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) ); \

})

Indirizzo1000100410121016

struct kool_list {int to;struct list_head list;int from;

} my_kool_list;

Assegniamo il puntatore__mptr di tipo member,protetto contro la scritturaaccidentale, al valore ptr,che rappresenta l'indirizzoiniziale di list.

147

Gestione delle liste: elemento• Macro container_of(ptr, type, member):

#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) ); \

})

Indirizzo1000100410121016

struct kool_list {int to;struct list_head list;int from;

} my_kool_list;

Prendiamo il puntatore__mptr e vediamolo (cast)come un puntatore acaratteri. In questo modo,l'aritmetica sui puntatoridiventa l'aritmetica suibyte.

148

Gestione delle liste: elemento• Macro container_of(ptr, type, member):

#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) ); \

})

Indirizzo1000100410121016

struct kool_list {int to;struct list_head list;int from;

} my_kool_list;

L'operatore offsetofcalcola l'offset (in byte)di member all'internodella struttura kool_list(4 byte).

149

Gestione delle liste: elemento• Macro container_of(ptr, type, member):

#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) ); \

})

Indirizzo1000100410121016

struct kool_list {int to;struct list_head list;int from;

} my_kool_list;

La differenza dei duevalori (indirizzo di list,offset di list all'internodi kool_list), interpretatain aritmetica dei char *,mi fornisce il valorerichiesto, ossial'indirizzo iniziale dimy_kool_list.

150

Gestione delle liste: elemento• Macro container_of(ptr, type, member):

#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) ); \

})

Indirizzo1000100410121016

struct kool_list {int to;struct list_head list;int from;

} my_kool_list;

L'indirizzo finale vieneconvertito ad un puntatoredi tipo “type” (kool_list).

151

Gestione delle liste: scorrimento• Si utilizza la macro list_for_each()– list_for_each(pos, head)– pos è un puntatore a concatenatore generico,

contiene i vari concatenatori della lista– head è il puntatore al concatenatore sentinella

della lista– Viene scorso l'elenco dei concatenatori di una

listastruct list_head *pos;

struct kool_list my_kool_list;

list_for_each(pos, &my_kool_list.list) {tmp = list_entry( pos, struct kool_list, list );...

}

152

Gestione delle liste: scorrimento

struct list_headlist;

Elementosentinella

Primaiterazione

...

structkool_list

Secondaiterazione

Ultimaiterazione

pos:tmp:

pos:tmp:

pos:tmp:

153

Gestione delle liste: scorrimento• Variante list_for_each_entry()– list_for_each_entry(pos, head, member)– pos è un puntatore al tipo di dato– head è il puntatore al concatenatore sentinella

della lista– member il nome del concatenatore sentinella

nella lista– Scorre la lista assegnando a pos i puntatori

degli elementistruct kool_list pos, my_kool_list;

list_for_each_entry(pos, &my_kool_list.list, list) {# pos punta ad un elemento di tipo kool_list...

}

154

Gestione delle liste: scorrimento• Variante list_for_each_entry()– list_for_each_entry(pos, head, member)– pos è un puntatore al tipo di dato– head è il puntatore al concatenatore sentinella

della lista– member il nome del concatenatore sentinella

nella lista– Scorre la lista assegnando a pos i puntatori

degli elementistruct kool_list pos, my_kool_list;

list_for_each(pos, &my_kool_list.list, list) {# pos punta ad un elemento di tipo kool_list...

}

155

Gestione delle liste: scorrimento• Variante list_for_each_safe()– list_for_each_safe(pos, n, head)– pos è un puntatore a concatenatore generico,

contiene i vari concatenatori della lista– n un puntatore a concatenatore generico, viene

usato come variabile di appoggio– head il nome del concatenatore sentinella nella

lista– Scorre la lista in modalità sicura per permettere

la cancellazione di elementilist_for_each_safe(pos, q, &my_kool_list.list) {

tmp = list_entry(pos, struct kool_list, list);

list_del(pos);

kfree(pos); /* o free(pos) */

}