0
Modello architetturale di Linux
• Kernel monolitico
  – Esecuzione di tutte le funzionalità critiche in
    kernel mode
  ...
Il linguaggio C
• Sviluppato da Ken Thompson e Dennis Ritchie
  all'inizio degli anni '70
• Concepito per la programmazion...
Caratteristiche del linguaggio C
• Linguaggio compilato
  – traduzione da alto livello (programma C) a
    basso livello (...
Caratteristiche del linguaggio C
• Accesso non controllato, a basso livello, alla
  memoria
   – meccanismo dei puntatori
...
Manchevolezze del C
• Mancanza di type safety
  – char -> int, int->char
  – somma di interi e float produce conversioni
 ...
Un primo programma in C

#include <stdio.h>

int main(void) {
    printf(“Hello, worldn”);
    return 0;
}

• Editate il f...
Un primo programma in C

#include <stdio.h>




• Direttiva di preprocessamento #include
• Il preprocessore (primo strumen...
Un primo programma in C

#include <stdio.h>




• Le parentesi <> stanno ad indicare che stdio.h è
  un file include di si...
Un primo programma in C

#include <stdio.h>




• Si possono anche utilizzare le virgolette “” per
  includere un file inc...
Un primo programma in C



int main(void) {



}

• Definizione di una funzione (in questo caso, la
  funzione main())
   ...
Un primo programma in C



int main(void) {



}

• Definizione di una funzione (in questo caso, la
  funzione main())
   ...
Un primo programma in C



int main(void) {



}

• La funzione main() è speciale
    – punto di ingresso dell'esecuzione ...
Un primo programma in C




   printf(“Hello, worldn”);




• Viene invocata la funzione di libreria printf(), con
  l'arg...
Un primo programma in C




   printf(“Hello, worldn”);




• Il valore di ritorno di printf è un intero, ma in
  questo c...
Un primo programma in C




  printf(“Hello, worldn”);




• Se invece avessi voluto memorizzare il valore:
  ret = printf...
Un primo programma in C




   return 0;


• Questo è il codice di ritorno della funzione, ossia il
  valore ritornato dal...
Tipi di dato del C
• Caratteri:
   – char: singolo carattere
• Interi
   –   unsigned short int: intero da 0 a 65535
   – ...
Tipi di dato del C

• Tipi di dato enumerati:
  – enum: lista di elementi “etichettati”
  enum stati {
     attivo = 1,
  ...
Tipi di dato del C
• Record
  – struct: tipo di dato complesso, contenente più
    dati
  struct esempio {
      int a;
  ...
Tipi di dato del C
• Unioni disgiunte di strutture
  – union: alternativa fra 2 o più strutture
  union unione {
     int ...
Tipi di dato del C
• Indirizzamento della memoria
  – puntatori: indirizzo di una locazione di memoria
     ♦ byte (8 bit)...
Tipi di dato del C
• Indirizzamento della memoria
  – puntatori: indirizzo di una locazione di memoria
     ♦ byte (8 bit)...
Tipi di dato del C

• Casting: conversione esplicita di un puntatore
  in un altro
• Puntatore nullo: rappresenta l'indiri...
Tipi di dato del C

• Aritmetica dei puntatori: a seconda della
  grandezza della variabile puntata, gli operatori
  + e –...
Tipi di dato del C

• Casting a int pointer:
   a= (int *) puntatore_ad_altro_tipo;
• Aritmetica: In questo caso, a viene ...
Tipi di dato del C

• Vettori di elementi
  – array: vettore di elementi dello stesso tipo, avente
    una lunghezza fissa...
Allocazione della memoria

• Tre modalità di allocazione della memoria:
  – statica: attraverso array
  – dinamica:
     ♦...
Allocazione della memoria:
              pregi e difetti
• Allocazione statica:
  – molto veloce
  – impossibile a runtime...
Sintassi del C
• Statement:     ciascun    statement      (singola
  istruzione) deve terminare con il carattere ;
• Comme...
Sintassi del C
• parola chiave static:
  – applicata alle variabili locali, permette di salvare il
    valore di una varia...
Sintassi del C
• Nel caso in cui un programma C sia costituito
  da più file, è necessario dire al compilatore che
  alcun...
Operatori più comuni
• Aritmetici:
   – somma, sottrazione, moltiplicazione, divisione: +, -,
     *, /
   – resto modulo ...
Operatori più comuni
• Assegnamento: si usa l'operatore =
int a = 10;
• Confronto
   – Uguaglianza: si usa l'operatore ==
...
Operatori più comuni

• Alcuni esempi di operazioni
int a = 10, b=255, c;
c = a & b; /* c=10 */
c = a | b; /* c=255 */
c= ...
Controllo di flusso del programma
• Ciclo while: esegue il blocco di codice specificato
  fintantoché la condizione rimane...
Controllo di flusso del programma

• Ciclo for: esegue il blocco di codice
  specificato fintantoché la condizione rimane
...
Particolarità del kernel
• Il kernel è una applicazione radicalmente
  diversa da quelle che girano in user mode
• Element...
Caratteristiche avanzate del C

• Il kernel fa ampio uso delle caratteristiche
  avanzate del C
• Alcune specificate nel p...
Macro
• Talvolta risulta comodo definire con un nome una
  sequenza di caratteri ripetitiva
  – una costante di uso freque...
Macro
• Si può controllare se una macro è definita oppure
  no con le direttive del preprocessore #ifdef,
  #ifndef
  #ifd...
Macro
• Una #define particolarmente lunga può essere
  spezzata in più righe, terminando ciascuna riga
  con la stringa  (...
Macro
• Che cosa ha di tanto grave la seguente macro?
  #define FOO(x) 
     printf(“arg is %sn”, x); 
     do_something_u...
Macro
• Il compilatore sostituisce
  #define FOO(x) 
     printf(“arg is %sn”, x); 
     do_something_useful(x);
  nel seg...
Macro
• Non notate niente di strano?
  if (blah == 2)
       printf(“arg is %sn”, blah);
       do_something_useful(blah);...
Macro
• Come posso correggere questo errore?
  – Incastro il codice della macro in un ciclo do { }
    while(0), che viene...
Macro
• Cosa risulta dall'espansione?
  if (blah == 2)
       do {
          printf(“arg is %sn”, x); 
          do_someth...
Puntatori a funzione
• Come dice il nome stesso, un puntatore a funzione
  è una variabile puntatore che, se dereferenziat...
Puntatori a funzione
• A cosa servono i puntatori a funzione?
  – implementazione meccanismo callback
     ♦ un programma ...
Puntatori a funzione
• Definizione di struttura contenente puntatori a
  funzione:                     Questa è la struttu...
Puntatori a funzione
• Inizializzazione di una struttura puntatrice a
  blocchi con le operazioni relative ad un
  disposi...
Puntatori a funzione
• Funzione vfs_read():
fs/read_write.c:
ssize_t vfs_read(struct file *file, char __user *buf, size_t ...
Utilizzo estensioni GCC
• Il kernel è, in larga parte, scritto in C
• Per motivi di efficienza spaziale e temporale, il
  ...
Estensioni GCC: inline functions
• Solitamente, quando una funzione viene
  tradotta in linguaggio macchina, il compilator...
Estensioni GCC: inline functions
• Per una funzione inline, il compilatore non
  genera il codice di gestione dei parametr...
Estensioni GCC: inline functions
int funzione(int a) {   pushl %ebp
    return (a + 2);     movl %esp, ebp
}              ...
Estensioni GCC: inline functions
Inline int funzione(int a) {
    return (a + 2);
}                              ...
...
i...
Estensioni GCC: inline functions
• Non tutte le funzioni dichiarate inline possono
  essere effettivamente sostituite con ...
Estensioni GCC: inline functions
• Il comportamento di inlining può essere
  ulteriormente raffinato con le parole chiave
...
Estensioni GCC: inline functions
• Il comportamento di inlining può essere
  ulteriormente raffinato con le parole chiave
...
Estensioni GCC: inline functions
• Quando e dove vengono utilizzate static inline
  ed extern inline?
• static inline:
  –...
Estensioni GCC: Assembler inline
• Il GCC permette l'inserimento di istruzioni
  assembler all'interno di codice scritto i...
Estensioni GCC: Assembler inline
• Basic inlining:
  asm(quot;movl %ecx, %eaxquot;);
  asm( quot;movl %eax, %ebxntquot;
  ...
Estensioni GCC: Assembler inline
• Extended asm: formato
  asm( assembler template
       : output operands (opzionali)
  ...
Estensioni GCC: Assembler inline
• Formato operandi:
  – L'operando di output viene contrassegnato con
    uno %0
  – Gli ...
Estensioni GCC: Asmlinkage
• Alcune funzioni sono marcate con la parola
  chiave asmlinkage
• La parola chiave asmlinkage ...
Estensioni GCC: Branch annotation
• Il compilatore GNU gcc mette a disposizione
  una direttiva (__builtin__expect()) che ...
Estensioni GCC: Branch annotation
• Se foo è una espressione quasi sempre vera, si
  può scrivere:
                       ...
Estensioni GCC: volatile
• Supponiamo che il compilatore debba
  compilare queste linee di codice:
  static int foo;
  voi...
Estensioni GCC: volatile
• Detto fatto, il compilatore decide di
  “ottimizzare” il codice in:
  static int foo;
  void ba...
Estensioni GCC: volatile
• Il compilatore, preso dal desiderio di
  ottimizzare, ha generato un codice non
  corretto!
• C...
Estensioni GCC: volatile
• La parola chiave volatile viene utilizzata per
  proteggere variabili che possono essere
  camb...
Nessun accesso alla libreria del C
• Le applicazioni normali vengono “fuse” con I
  codici oggetto della libreria C, che p...
Nessun accesso alla libreria del C
• In realtà, nel kernel è implementato un piccolo
  insieme di funzioni “di libreria”
 ...
File include
• I file include (detti anche header) sono utilizzati
  come meccanismo di definizione dell'API
  – strutture...
File include
• La libreria del C invoca le chiamate di sistema del
  kernel, passando gli opportuni parametri e
  ricevend...
File include
• L'API di raccordo del kernel deve essere
  coerente con l'API definita dalla libreria del C
  – Condizione ...
File include
• La libreria del C cerca di essere compatibile
  all'indietro con l'API di raccordo del kernel
  – Una libre...
File include
• Solitamente, la libreria del C messa a
  disposizione dalla distribuzioni più popolari è
  compilata con i ...
File include
• Un file include del kernel può essere incluso
  da molteplici file
• Una inclusione doppia può portare a
  ...
Nessuna protezione della memoria
• Quando una applicazione in user mode accede
  ad indirizzo invalido, il kernel intercet...
Nessun (semplice) uso di floating point
• L'uso di aritmetica in virgola mobile (floating
  point) richiede l'uso di regis...
Dimensione dello stack
• Gli applicativi eseguiti in user mode hanno uno
  stack di dimensione variabile
• Lo stack del ke...
Portabilità
• Il kernel è scritto per poter essere eseguito su
  una miriade di architetture diverse
• Architetture divers...
Portabilità
• Si definisce “parola” la quantità di dati che una
  architettura è in grado di elaborare in un colpo
  di cl...
Portabilità
• Un esempio: nelle architetture a 64 bit
  – Registri, puntatori, long sono a 64 bit
  – Il tipo di dato int ...
Portabilità
• Alcune regole da ricordare quando si usano i
  tipi di dato
  – Il tipo di dato char è sempre lungo 8 bit
  ...
Portabilità
• Il kernel fa ampio uso di tipi di dato opachi
• Un tipo di dato opaco è un tipo di dato
  generico definito ...
Portabilità
• Il kernel fa ampio uso di tipi di dato espliciti
• Un tipo di dato esplicito è l'esatto contrario di
  un ti...
Allineamento
• L'allineamento è il posizionamento di una
  variabile in un indirizzo multiplo della sua
  dimensione
• Ese...
Allineamento
• Tutte le strutture dati devono essere allineate
• Di solito, il compilatore pensa ad allineare
  automatica...
Allineamento: padding delle strutture
• Padding: gli elementi di una struttura dati sono
  allineati
• Consideriamo la seg...
Allineamento: padding delle strutture
• Padding: gli elementi di una struttura dati sono
  allineati
• Ecco la struttura d...
Byte ordering
 • Il byte ordering definisce il posizionamento dei
   byte all'interno di una parola
 • Due possibili ordin...
Byte ordering
• Curiosità: I nome Big Endian e Little Endian
  vengono dal romanzo “I viaggi di Gulliver” di
  Jonathan Sw...
Byte ordering
• Un esempio: memorizziamo il numero 1027 nei
  formati Big Endian e Little Endian
  1027 in binario: 000000...
Sincronizzazione e concorrenza
• Il kernel è un software interrompibile in
  maniera sincrona ed asincrona
  – Sincrona: s...
Sincronizzazione e concorrenza
• Il kernel è un software che può gestire
  l'esecuzione concorrente di più processi
• I pr...
Contesti di esecuzione
• In ogni istante, il processore può trovarsi in
  una di queste condizioni
  – User mode: esecuzio...
Contesti di esecuzione
               Una applicazione vuole
               leggere un blocco già
Applicazione   bufferizz...
Contesti di esecuzione
                      Il processo invoca la fread(),
                      che a sua volta invoca l...
Contesti di esecuzione
                      Ad un certo punto, arriva una interruzione,
                      in seguito ...
Contesti di esecuzione
                      Si ha un accesso simultaneo alla stessa
                      struttura dati ...
Contesti di esecuzione
• La corsa critica, nella “migliore” delle ipotesi,
  riesce a sovrascrivere velocemente il buffer,...
Contesti di esecuzione
• Questo problema può essere impedito tramite
  due meccanismi
  – Un gestore veloce di interruzion...
Contesti di esecuzione
               Una applicazione vuole
               leggere un blocco già
Applicazione   bufferizz...
Contesti di esecuzione
                      Il processo invoca la fread(),
                      che a sua volta invoca l...
Contesti di esecuzione
                      Ad un certo punto, arriva una interruzione,
                      che provoca...
Contesti di esecuzione
                      Successivamente, una procedura
                      dilazionata nel tempo pr...
Contesti di esecuzione
                      vfs_read() rilascia la struttura dati

Applicazione
                         ...
Contesti di esecuzione
               Viene immediatamente preso il
               blocco da parte del gestore di
Applicaz...
Contesti di esecuzione
               Avviene l'inserimento
               dell'elemento nella lista
Applicazione
        ...
Contesti di esecuzione
               Il gestore dilazionato rilascia
               il blocco sulla lista
Applicazione
  ...
Anatomia di una funzione del kernel
• Una funzione del kernel segue uno scheletro
  ben preciso
  – Dal punto di vista del...
Funzione del kernel: Forma
• Definita nel file Documentation/CodingStyle
• Obiettivi: fare in modo che una funzione sia
  ...
Funzione del kernel: Forma
• Commenti:
  – Solo prima dell'intestazione di una funzione
  – Mai commentare i singoli state...
Funzione del kernel: Struttura
• Quanto segue è soggetto a 1000 eccezioni! Non
  prenderlo come regola aurea!
• In general...
Funzione del kernel: Struttura
• Le funzioni ritornano, solitamente, un valore < 0
  oppure un puntatore NULL per indicare...
Gestione delle liste
• Una lista concatenata è una struttura dati che
  collega un certo numero di elementi (nodi)
• Diver...
Gestione delle liste


...    next         ...   next         ...   next           null

              Lista a collegament...
Gestione delle liste


 ...     next       ...     next      ...     next


Lista circolare a collegamento singolo



prev...
Gestione delle liste
• Il problema principale di tale struttura è che
  risulta essere definita insieme al tipo di dato
  ...
Gestione delle liste
• Il kernel di Linux mette a disposizione una
  funzionalità generica di liste, indipendente dal
  ti...
Gestione delle liste
• L'elemento chiave delle liste è una
  concatenatore (struct list_head), definito come
  una coppia ...
Gestione delle liste
• Un passo alla volta :-)
• Il concatenatore viene inserito all'interno della
  struttura dati che si...
Gestione delle liste
                           struct             struct
                          kool_list          koo...
Gestione delle liste: operazioni
• Come posso utilizzare la mia lista nei
  programmi?
• Devo definire meccanismi per:
  –...
Gestione delle liste: inizializzazione
• Avviene tramite l'utilizzo di apposite macro
• Macro diverse a seconda di dove, n...
Gestione delle liste: inizializzazione
• Avviene tramite l'utilizzo di apposite macro
• Macro diverse a seconda di dove, n...
Gestione delle liste: inizializzazione
• Avviene tramite l'utilizzo di apposite macro
• Macro diverse a seconda di dove, n...
Gestione delle liste: inizializzazione


 struct list_head list;


       prev

       next




                          ...
Gestione delle liste: inserimento
• Si utilizza la funzione list_add()
  – list_add(struct list_head *new, struct list_hea...
Gestione delle liste: inserimento
                                  struct
                                 kool_list
    ...
Gestione delle liste: inserimento
                                            struct
                                     ...
Gestione delle liste: cancellazione
• Si utilizza la funzione list_del()
   – list_del(struct list_head *entry)
   – entry...
Gestione delle liste: cancellazione
                      struct             struct
                     kool_list        ...
Gestione delle liste: cancellazione
                                          struct
                                     ...
Gestione delle liste: lista vuota
• Si utilizza la funzione list_empty()
  – int list_empty(struct list_head *head)
  – he...
Gestione delle liste: elemento
• Si utilizza la macro list_entry()
  – list_entry(ptr, type, member)
  – ptr è il puntator...
Gestione delle liste: elemento
• Macro container_of(ptr, type, member):
  #define container_of(ptr, type, member) ({ 
    ...
Gestione delle liste: elemento
• Macro container_of(ptr, type, member):
  #define container_of(ptr, type, member) ({ 
    ...
Gestione delle liste: elemento
• Macro container_of(ptr, type, member):
  #define container_of(ptr, type, member) ({ 
    ...
Gestione delle liste: elemento
• Macro container_of(ptr, type, member):
  #define container_of(ptr, type, member) ({ 
    ...
Gestione delle liste: elemento
• Macro container_of(ptr, type, member):
  #define container_of(ptr, type, member) ({ 
    ...
Gestione delle liste: elemento
• Macro container_of(ptr, type, member):
  #define container_of(ptr, type, member) ({ 
    ...
Gestione delle liste: elemento
• Macro container_of(ptr, type, member):
  #define container_of(ptr, type, member) ({ 
    ...
Gestione delle liste: elemento
• Macro container_of(ptr, type, member):
  #define container_of(ptr, type, member) ({ 
    ...
Gestione delle liste: elemento
• Macro container_of(ptr, type, member):
  #define container_of(ptr, type, member) ({ 
    ...
Gestione delle liste: elemento
• Macro container_of(ptr, type, member):
  #define container_of(ptr, type, member) ({ 
    ...
Gestione delle liste: elemento
• Macro container_of(ptr, type, member):
  #define container_of(ptr, type, member) ({ 
    ...
Gestione delle liste: elemento
• Macro container_of(ptr, type, member):
  #define container_of(ptr, type, member) ({ 
    ...
Gestione delle liste: scorrimento
• Si utilizza la macro list_for_each()
  – list_for_each(pos, head)
  – pos è un puntato...
Gestione delle liste: scorrimento
                       struct
                      kool_list


                        ...
Gestione delle liste: scorrimento
• Variante list_for_each_entry()
  – list_for_each_entry(pos, head, member)
  – pos è un...
Gestione delle liste: scorrimento
• Variante list_for_each_entry()
  – list_for_each_entry(pos, head, member)
  – pos è un...
Gestione delle liste: scorrimento
• Variante list_for_each_safe()
  – list_for_each_safe(pos, n, head)
  – pos è un puntat...
Upcoming SlideShare
Loading in...5
×

Sistemi Operativi: Il kernel linux - Lezione 06

1,333

Published on

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
1,333
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
31
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Transcript of "Sistemi Operativi: Il kernel linux - Lezione 06"

  1. 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 1
  2. 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 2
  3. 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 3
  4. 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 4
  5. 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” 5
  6. 6. Un primo programma in C #include <stdio.h> int main(void) { printf(“Hello, worldn”); 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 6
  7. 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 7
  8. 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 8
  9. 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 9
  10. 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 10
  11. 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 11
  12. 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 12
  13. 13. Un primo programma in C printf(“Hello, worldn”); • Viene invocata la funzione di libreria printf(), con l'argomento “Hello worldn” • n: sequenza di escape – si traduce in: “vai all'inizio della prossima riga” 13
  14. 14. Un primo programma in C printf(“Hello, worldn”); • Il valore di ritorno di printf è un intero, ma in questo caso, non interessandoci, non viene memorizzato in alcuna variabile – viene scartato automaticamente 14
  15. 15. Un primo programma in C printf(“Hello, worldn”); • Se invece avessi voluto memorizzare il valore: ret = printf(“Hello, worldn”); 15
  16. 16. Un primo programma in C return 0; • Questo è il codice di ritorno della funzione, ossia il valore ritornato dalla stessa 16
  17. 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 17
  18. 18. Tipi di dato del C • Tipi di dato enumerati: – enum: lista di elementi “etichettati” enum stati { attivo = 1, inattivo, interrotto } 18
  19. 19. Tipi di dato del C • Record – struct: tipo di dato complesso, contenente più dati struct esempio { int a; char b; } mio_esempio; • L'accesso ad un record avviene con l'operatore .: mio_esempio.a = 10; 19
  20. 20. Tipi di dato del C • Unioni disgiunte di strutture – union: alternativa fra 2 o più strutture union 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; 20
  21. 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; 21
  22. 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; 22
  23. 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 23
  24. 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 24
  25. 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 byte int 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 byte char a='a'; int *int_ptr = &a; a=a+4; 25
  26. 26. Tipi di dato del C • Vettori di elementi – array: vettore di elementi dello stesso tipo, avente una lunghezza fissa int a[10]; a[0] = 2; a[1] = 3; • Il nome di un array è anche l'indirizzo del primo elemento int *int_ptr = a; • Array multidimensionali: uso di più indici int a[10][20]; a[1][2] = 4; 26
  27. 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 27
  28. 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 28
  29. 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 variabile int a, b=4; struct mystruct { int c=4; int *d; } struttura = { 4, NULL }; 29
  30. 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 30
  31. 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 extern extern 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 31
  32. 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: || 32
  33. 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 >= 33
  34. 34. Operatori più comuni • Alcuni esempi di operazioni int 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 */ 34
  35. 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 35
  36. 36. Controllo di flusso del programma • Ciclo for: esegue il blocco di codice specificato fintantoché la condizione rimane vera, aggiornando delle variabili nel contempo for ( i=1; i < 10; i++ ) { <blocco di codice> } for (p=list->next; p != NULL; p=p->next) { printf(“P has %sn”, p->string; } 36
  37. 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 37
  38. 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) 38
  39. 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 (”%sn”, str); • Il compilatore, prima di compilare il programma, sostituisce ad ogni istanza di DBG(argomento) una istanza di printf(“%sn”, argomento); • La sostituzione non è una generazione di codice! 39
  40. 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); #else stampa_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 40
  41. 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 %sn”, 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 41
  42. 42. Macro • Che cosa ha di tanto grave la seguente macro? #define FOO(x) printf(“arg is %sn”, 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? 42
  43. 43. Macro • Il compilatore sostituisce #define FOO(x) printf(“arg is %sn”, x); do_something_useful(x); nel seguente codice: if (blah == 2) FOO(blah); ottenendo: if (blah == 2) printf(“arg is %sn”, blah); do_something_useful(blah);; 43
  44. 44. Macro • Non notate niente di strano? if (blah == 2) printf(“arg is %sn”, 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() 44
  45. 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 %sn”, x); do_something_useful(x); } while (0) 45
  46. 46. Macro • Cosa risulta dall'espansione? if (blah == 2) do { printf(“arg is %sn”, 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! 46
  47. 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; 47
  48. 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 48
  49. 49. Puntatori a funzione • Definizione di struttura contenente puntatori a funzione: Questa è la struttura include/linux/fs.h: generica delle operazioni su di un file 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 **); }; 49
  50. 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 le ... funzioni per un device }; a blocchi, tramite dei puntatori a funzione 50
  51. 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 su cui sarà effettuata la read(). 51
  52. 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 52
  53. 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 53
  54. 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 54
  55. 55. Estensioni GCC: inline functions int funzione(int a) { pushl %ebp return (a + 2); movl %esp, ebp } subl $N, %esp movl 8(%ebp), %eax addl $2, %eax leave ret ... ... int b = funzione(10); pushl 10 call funzione ... ... 55
  56. 56. Estensioni GCC: inline functions Inline int funzione(int a) { return (a + 2); } ... ... int b = funzione(10); movl $1, %eax addl $2, %eax ... ... 56
  57. 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 57
  58. 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 58
  59. 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 59
  60. 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 60
  61. 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 61
  62. 62. Estensioni GCC: Assembler inline • Basic inlining: asm(quot;movl %ecx, %eaxquot;); asm( quot;movl %eax, %ebxntquot; quot;movl $56, %esintquot; quot;movl %ecx, $label(%edx,%ebx,$4)ntquot; quot;movb %ah, (%ebx)quot;); 62
  63. 63. Estensioni GCC: Assembler inline • Extended asm: formato asm( assembler template : output operands (opzionali) : input operands (opzionali) : list of clobbered registers (opzionali) ); • Extended asm: esempio int a=10, b; asm( quot;movl %1, %%eax; movl %%eax, %0;quot; : “=r”(b) /* b è l'output (%0) */ : =”r”(a) /* a è l'input (%1) */ : “%eax” ); /* %eax modificato */ 63
  64. 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) 64
  65. 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) 65
  66. 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 66
  67. 67. Estensioni GCC: Branch annotation • Se foo è una espressione quasi sempre vera, si può scrivere: Il confronto è molto più if (likely(foo)) { lento del normale se ... foo è falso } che risulta in un codice molto più veloce di if (foo) { ... } • Analogamente, se foo è una espressione quasi sempre falsa, si può scrivere: Il confronto è molto più if (unlikely(foo)) { lento del normale se ... foo è vero } 67
  68. 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)? 68
  69. 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 69
  70. 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 variabile static volatile int foo; void bar(void) { foo = 0; while (true) ; } 70
  71. 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 71
  72. 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() 72
  73. 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/ 73
  74. 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 74
  75. 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 75
  76. 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 76
  77. 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 77
  78. 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 78
  79. 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 79
  80. 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 80
  81. 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! 81
  82. 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 82
  83. 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 83
  84. 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 84
  85. 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> 85
  86. 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 86
  87. 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) 87
  88. 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 88
  89. 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 89
  90. 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 90
  91. 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 91
  92. 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 */ 4 byte u8 __pad0[3]; /* 3 byte */ unsigned long cat; /* 4 byte */ unsigned short pig; /* 2 byte */ char fox; /* 1 byte */ 4 byte u8 __pad1; /* 1 byte */ }; 92
  93. 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 Ordinamento Ordinamento Big Endian Little Endian Byte Byte 0 1 2 3 3 2 1 0 Più Meno Meno Più significativo93 significativo significativo significativo
  94. 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 94
  95. 95. Byte ordering • Un esempio: memorizziamo il numero 1027 nei formati Big Endian e Little Endian 1027 in binario: 00000000 00000000 00000100 00000011 Indirizzo Big Endian Little Endian 0 00000000 00000011 1 00000000 00000100 2 00000100 00000000 3 00000011 00000000 95
  96. 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 96
  97. 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 97
  98. 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 98
  99. 99. Contesti di esecuzione Una applicazione vuole leggere un blocco già Applicazione bufferizzato del disco User space Kernel space Hardware 99
  100. 100. Contesti di esecuzione Il processo invoca la fread(), che a sua volta invoca la vfs_read() Applicazione User space n=fread(...) Kernel space Lista blocchi bufferizzati vfs_read() Hardware 100
  101. 101. Contesti di esecuzione Ad un certo punto, arriva una interruzione, in seguito alla quale un blocco vuole Applicazione essere inserito nella posizione da cui vuole leggere il processo User space n=fread(...) Kernel space Lista blocchi bufferizzati vfs_read() Hardware 101
  102. 102. Contesti di esecuzione Si ha un accesso simultaneo alla stessa struttura dati (corsa critica) Applicazione User space n=fread(...) Kernel space Lista blocchi bufferizzati vfs_read() Hardware 102
  103. 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 103
  104. 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 104
  105. 105. Contesti di esecuzione Una applicazione vuole leggere un blocco già Applicazione bufferizzato del disco User space Kernel space Hardware 105
  106. 106. Contesti di esecuzione Il processo invoca la fread(), che a sua volta invoca la vfs_read() Applicazione La vfs_read() blocca l'uso della struttura dati “lista” User space n=fread(...) Kernel space Lista blocchi bufferizzati vfs_read() Hardware 106
  107. 107. Contesti di esecuzione Ad un certo punto, arriva una interruzione, che provoca l'inserimento di un blocco Applicazione in una lista locale User space n=fread(...) Kernel space Lista blocchi bufferizzati vfs_read() Buffer associato UPPER al gestore IRQ HALF Hardware 107
  108. 108. Contesti di esecuzione Successivamente, una procedura dilazionata nel tempo prova ad inserire Applicazione l'elemento dal buffer locale al buffer dei blocchi User space n=fread(...) Kernel space Lista blocchi bufferizzati vfs_read() BOTTOM HALF Buffer associato al gestore IRQ Hardware 108
  109. 109. Contesti di esecuzione vfs_read() rilascia la struttura dati Applicazione User space n=fread(...) Kernel space Lista blocchi bufferizzati vfs_read() Hardware 109
  110. 110. Contesti di esecuzione Viene immediatamente preso il blocco da parte del gestore di Applicazione interruzioni dilazionato User space Kernel space Lista blocchi bufferizzati vfs_read() Hardware 110
  111. 111. Contesti di esecuzione Avviene l'inserimento dell'elemento nella lista Applicazione User space Kernel space Lista blocchi bufferizzati vfs_read() Hardware 111
  112. 112. Contesti di esecuzione Il gestore dilazionato rilascia il blocco sulla lista Applicazione User space Kernel space Lista blocchi bufferizzati vfs_read() Hardware 112
  113. 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) 113
  114. 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 114
  115. 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 statement if ( x is true ) { do y } • Nomi delle variabili: – I più semplici ed intuitivi possibili – ThisVarIsATempCounter: NO, cnt: SI 115
  116. 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; 116 }
  117. 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) 117
  118. 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 118
  119. 119. Gestione delle liste ... next ... next ... next null Lista a collegamento singolo null prev ... next prev ... next prev ... next null Lista a collegamento doppio 119
  120. 120. Gestione delle liste ... next ... next ... next Lista circolare a collegamento singolo prev ... next prev ... next prev ... next Lista circolare a collegamento doppio 120
  121. 121. Gestione delle liste • Il problema principale di tale struttura è che risulta essere definita insieme al tipo di dato che essa ospita struct 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 121
  122. 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 122
  123. 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? 123
  124. 124. Gestione delle liste • Un passo alla volta :-) • Il concatenatore viene inserito all'interno della struttura dati che si vuole concatenare struct 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 124
  125. 125. Gestione delle liste struct struct kool_list kool_list int to; int to; struct list_head struct list_head struct list_head list; list; list; Int from; Int from; Elemento Elemento Elemento sentinella lista lista 125
  126. 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 126
  127. 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; 127
  128. 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) } 128
  129. 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 ) 129
  130. 130. Gestione delle liste: inizializzazione struct list_head list; prev next 130
  131. 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 131
  132. 132. Gestione delle liste: inserimento struct kool_list int to; struct list_head struct list_head list; list; Int from; Elemento Elemento sentinella lista 132
  133. 133. Gestione delle liste: inserimento struct kool_list int to; struct list_head struct list_head list; list; struct Int from; kool_list int to; Elemento Elemento struct list_head list; sentinella lista Int from; Elemento da aggiungere in testa alla lista 133
  134. 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! 134
  135. 135. Gestione delle liste: cancellazione struct struct kool_list kool_list int to; int to; struct list_head struct list_head struct list_head list; list; list; Int from; Int from; Elemento Elemento Elemento sentinella da rimuovere lista 135
  136. 136. Gestione delle liste: cancellazione struct kool_list int to; struct list_head struct list_head list; list; Int from; struct kool_list Elemento Elemento int to; sentinella lista struct list_head list; =0 prev Int from; next Elemento =0 rimosso 136
  137. 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 137
  138. 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) 138
  139. 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) ); }) Indirizzo struct kool_list { 1000 int to; 1004 struct list_head list; 1012 int from; 1016 } my_kool_list; 139
  140. 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) ); }) Indirizzo struct kool_list { 1000 int to; 1004 struct list_head list; 1012 int from; 1016 } my_kool_list; 140
  141. 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) ); }) Indirizzo struct kool_list { Prendo l'indirizzo 0 e lo 1000 int to; vedo (cast) come puntatore 1004 struct list_head list; alla struttura kool_list. 1012 int from; Posso farlo perché non sto 1016 } my_kool_list; scrivendo in memoria. 141
  142. 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) ); }) Indirizzo struct kool_list { Con la dereferenziazione 1000 int to; (->)ottengo una variabile 1004 struct list_head list; di tipo “member” allo 1012 int from; indirizzo 4 (parto da 0!). 1016 } my_kool_list; Non la sto scrivendo; il sistema non crasha. 142
  143. 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) ); }) Indirizzo struct kool_list { Quale è il tipo della 1000 int to; variabile di tipo member 1004 struct list_head list; che ho appena derefe- 1012 int from; renziato? Ovviamente, 1016 } my_kool_list; member! 143
  144. 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) ); }) Indirizzo struct kool_list { Definiamo una variabile 1000 int to; puntatore di tipo member, 1004 struct list_head list; di nome __mptr. Il doppio 1012 int from; underscore “__” si usa 1016 } my_kool_list; per nomi di variabili interne (per convenzione). 144
  145. 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) ); }) Indirizzo struct kool_list { Const indica che il 1000 int to; puntatore punta ad una 1004 struct list_head list; area di memoria che 1012 int from; non deve essere mai 1016 } my_kool_list; modificata. Se provo a scriverci, il compilatore emette un errore. E' una misura di protezione contro codice che scriva il contenuto di __mptr accidentalmente. 145
  146. 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) ); }) Indirizzo struct kool_list { Assegniamo il puntatore 1000 int to; __mptr di tipo member, 1004 struct list_head list; protetto contro la scrittura 1012 int from; accidentale, al valore ptr, 1016 } my_kool_list; che rappresenta l'indirizzo iniziale di list. 146
  147. 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) ); }) Indirizzo struct kool_list { Prendiamo il puntatore 1000 int to; __mptr e vediamolo (cast) 1004 struct list_head list; come un puntatore a 1012 int from; caratteri. In questo modo, 1016 } my_kool_list; l'aritmetica sui puntatori diventa l'aritmetica sui byte. 147
  148. 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) ); }) Indirizzo struct kool_list { L'operatore offsetof 1000 int to; calcola l'offset (in byte) 1004 struct list_head list; di member all'interno 1012 int from; della struttura kool_list 1016 } my_kool_list; (4 byte). 148
  149. 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) ); }) Indirizzo struct kool_list { La differenza dei due 1000 int to; valori (indirizzo di list, 1004 struct list_head list; offset di list all'interno 1012 int from; di kool_list), interpretata 1016 } my_kool_list; in aritmetica dei char *, mi fornisce il valore richiesto, ossia l'indirizzo iniziale di my_kool_list. 149
  150. 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) ); }) Indirizzo struct kool_list { L'indirizzo finale viene 1000 int to; convertito ad un puntatore 1004 struct list_head list; di tipo “type” (kool_list). 1012 int from; 1016 } my_kool_list; 150
  151. 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 lista struct 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 ); ... } 151
  152. 152. Gestione delle liste: scorrimento struct kool_list ... struct list_head list; pos: pos: pos: tmp: tmp: tmp: Elemento Prima Seconda Ultima sentinella iterazione iterazione iterazione 152
  153. 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 elementi struct 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 ... } 153
  154. 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 elementi struct kool_list pos, my_kool_list; list_for_each(pos, &my_kool_list.list, list) { # pos punta ad un elemento di tipo kool_list ... } 154
  155. 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 elementi list_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) */ } 155
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×