Guida da adottare! Bannermv.png


Premessa

Spero di fare cosa gradita nel condividere alcuni appunti del filesystem del kernel 0.0.1 che sto studiando. Tuttavia, non posso garantire che quanto scritto qui sia perfettamente esatto, ma solo deduzioni da quanto ho appreso fino ad oggi. La guida è in fase di creazione e di correzione nel tempo . Grazie per la vostra comprensione e per le correzioni che chiunque potrà fare.

Che cos'è un inode?

Un inode è una struttura che viene utilizzata quando un filesystem viene creato per salvare le informazioni dei file, quali i diritti di accesso, la dimensione, l'uid e il gid ecc... (http://en.wikipedia.org/wiki/Inode) quando questi vengono aperti .

Nel kernel un inode è una struttura (una tabella di inode) che si trova a livello del virtual file system (http://www.makelinux.info/system/poster). Il virtual file system arriverà in Linux dalla versione 0.96b, tuttavia per dimostrare l'uso degli inode, ho riportato in seguito per semplificare (non conosco il codice del VFS) il codice del fs minix usato da Linus in Linux-0.0.1 .

Un esempio pratico

Linux 0.0.1 e minixfs

Bisogna premettere che il filesystem di minix che Linus ha utilizzato per il suo primo os (che ancora non era maturo ma funzionante come kernel, bisognerà aspettare qualche versione successiva) è molto diverso dalla versione 3 dei nostri giorni (Lo stesso Linus cambierà in seguito codice per adattarlo alla versione del fs minix 1.6 ), per questo nel paragrafo che segue alcuni campi che troveremo andando ad analizzare il contenuto nel disco non saranno esatti.

Il filesystem minix (ma non solo questo, ext2 è nato in un certo senso da minix fs e quindi non sorprendentemente ha gli stessi concetti di base) è diviso in blocchi da 1k (come vedremo in seguito quando lo creeremo).

Il primo blocco è vuoto (per salvare le informazioni di boot immagino). Il secondo blocco contiene il super block (vedere più avanti). Il terzo contiene la bit map dei blocchi allocati mentre il quarto la bit map degli inode e dal quinto in poi (il campo unsigned short s_firstdatazone del super block contiene questa informazione) iniziano i dati del filesystem che includono i file, le tabelle dei file (cioè il contenuto delle directory).

Seppur abbastanza semplice come kernel rispetto a quello dei giorni nostri è impossibile purtroppo evitare di presentare anche altre strutture o di sconfinare in campi che non appartengono all'oggetto di questa piccola guida poiché altrimenti si perderebbe di vista lo scopo ultimo del codice e cioè come tutto lavora in armonia con gli altri componenti per far si che alla fine determinate cose vengano eseguite in un sistema che è realmente multitasking (vedere le release note di Linus).

Da un punto di vista pratico dando una lettura al codice sorgente di Linux 0.0.1 (www.kernel.org/pub/kernel/historic/) nella directory degli include, l'header che definisce le strutture del fs include/linux/fs.h contiene la struttura dell'inode salvata su disco è questa:

struct d_inode {
        unsigned short i_mode; /* mask bit della modalita' d'accesso */
        unsigned short i_uid; /* user id */
        unsigned long i_size; /* dimensione */
        unsigned long i_time; /* time stamp */
        unsigned char i_gid; /* group id */
        unsigned char i_nlinks; /* hardware link, se questo valore non è zero non potrete rimuovere il file */
        unsigned short i_zone[9]; /* blocchi occupati dai dati */
};

oltre a questa struttura possiamo subito introdurre quella più completa ed utilizzata dal kernel (non ho ripetuto le note già dette) :

struct m_inode {
        unsigned short i_mode;
        unsigned short i_uid;
        unsigned long i_size;
        unsigned long i_mtime;
        unsigned char i_gid;
        unsigned char i_nlinks;
        unsigned short i_zone[9];
/* these are in memory also */
        struct task_struct * i_wait; /* la task che ha in carico questo inode*/
        unsigned long i_atime; /* access time */
        unsigned long i_ctime; /* change time ? */
        unsigned short i_dev; /* device su cui punta questo inode */
        unsigned short i_num;
        unsigned short i_count;
        unsigned char i_lock; /* accesso esclusivo, solitamente quando si ha un accesso esclusivo la task viene messa in TASKINTERRUPTABLE mode */
        unsigned char i_dirt; /* i valori contenuti nella struttura sono cambiati */
        unsigned char i_pipe; /* pipe ? */
        unsigned char i_mount;
        unsigned char i_seek;
        unsigned char i_update;
};

come si può vedere ci sono due diverse strutture . La prima è quella che fisicamente viene salvata su disco e identifica le entry che esistono fisicamente nel fs (filesystem), mentre la seconda ha una parte in comune con la prima e inoltre avrà altri campi che si troveranno solamente in memoria organizzati in una tabella di inode. Prima di spiegare meglio alcuni campi che possono essere poco chiari e legati strettamente al funzionamento di minixfs, tutte le operazioni che il nostro filesystem esegue sugli inode si trovano nel file sorgente fs/inode.c . Meglio quindi dargli una lettura almeno parziale .

Il superblock

Il superblock è il blocco che descrive il nostro filesystem. Per sicurezza viene copiato su più blocchi in modo da essere ridondante (il filesystem di cui parliamo è un insieme di blocchi). Contiene informazioni legate al tipo di filesystem.

struct super_block {
        unsigned short s_ninodes; /* qui dentro vengono salvati il numero massimo di inode alla creazione del filesystem. Nei filesystem moderni e' possibile cambiarlo quando si va in run out of inodes. */
        unsigned short s_nzones;
        unsigned short s_imap_blocks;
        unsigned short s_zmap_blocks;
        unsigned short s_firstdatazone; /* E' il primo blocco che contiene i dati */
        unsigned short s_log_zone_size;
        unsigned long s_max_size;
        unsigned short s_magic;
/* These are only in memory */
        struct buffer_head * s_imap[8]; /* buffers che contengono i blocchi degli inode */
        struct buffer_head * s_zmap[8]; /* buffers che contengono i blocchi delle zone */
        unsigned short s_dev;
        struct m_inode * s_isup;
        struct m_inode * s_imount;
        unsigned long s_time;
        unsigned char s_rd_only; /* il filesystem è in read only mode */
        unsigned char s_dirt; /* il super block è stato cambiato */
};

's_inodes' : il numero di inode è fisso ma solitamente nei filesystem è possibile cambiare questo numero nel superblock. Alcuni si saranno accorti che su alcuni filesystem quando gli inode sono esauriti bisogna aumentarne il numero e riservare ulteriore spazio altrimenti non sarà più possibile salvare alcun file anche se c'è dello spazio disponibile.

Per curiosità il campo s_magic definisce il tipo di filesystem . Un altra cosa a cui dobbiamo dare attenzione sono i campi unsigned short s_imap_blocks : questo campo definisce il blocco che contiene la bitmap degli inode e cioè dove si trovano i blocchi che contengono delle strutture di inode .

Il codice del superblock si tova in linux/fs/super.c . In questo file si trovano le routine per fare il mount del superblock (e quindi la sua descrizione completa) e del device di root.

Creiamo il nostro filesystem minix e spiamolo

Per mettere in pratica i concetti appena esposti creaimo un piccolo filesystem minix (serve mkfs.minix e che si abbia almeno un device di loop). Come detto prima il fs in questione non è esattamente come quello che Linus ha usato, ma la versione 3, per cui alcune cose saranno lievemente diverse (per esempio il magic number).

Bisognera' usare root per montare in lodevice il file cha andreamo a creare, per cui attenzione a non fare danni.

 $ dd if=/dev/zero of=minixfs count=10 bs=1024
   10+0 records in                 
   10+0 records out                
   10240 bytes (10 kB) copied, 0.000333843 seconds, 30.7 MB/s

 # losetup /dev/loop0 minixfs
 # mkfs.minix /dev/loop0 
   32 inodes
   10 blocks
   Firstdatazone=5 (5)
   Zonesize=1024
   Maxsize=268966912

questi valori ci serviranno dopo quando andremo a vedere cosa c'è dentro il nostro filesystem .
 # mount -t minix /dev/loop0 /mnt/data/
 $

Andremo ora a fare delle semplici prove per vedere come cambiano i dati nel nostro filesystem appena creato e come cambiano gli inode su disco (fare riferimento al paragrafo precedente per una descrizione degli inode contenuti in memoria dal kernel TODO) usando hexdump come tool.

Come detto il primo blocco è vuoto, per cui partiamo dal secondo blocco che contiene il superblock:

 $ hexdump minixfs -C  -n 1024 -s 0x400
   00000400  20 00 0a 00 01 00 01 00  05 00 00 00 00 1c 08 10  | ...............|
   00000410  8f 13 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   00000420  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   *
   00000800

Se confrontate i primi campi del superblock e i dati contenuti su disco potrete ritrovare molte cose stampate quando abbiamo creato il minix fs .

Se ora andiamo a vedere il blocco 5 possiamo vedere la tabella delle entry nel fs . Per comprenderene i campi fate riferimento alla struttura presentata nel paragrafo precedente (struct d_inode).

 $ ls -la /mnt/data/
   total 2
   drwxr-xr-x 2 root root   64 Nov 15 12:31 .
   drwxr-xr-x 5 root root 1024 Sep 28 15:16 ..
 
 $ hexdump minixfs -C  -n 1024 -s 0x1000
   00001000  ed 41 00 00 40 00 00 00  83 15 1f 49 00 02 05 00  |.A..@......I....|
   00001010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   *
   00001400

i primi due byte 41 ed (su macchine Intel) identificano i permessi di accesso, Gli altri due byte l'uid, mentre gli altri 4 la dimensione (00 00 00 40) che infatti in decimale 0x40 è 64 . Gli altri byte identificano l'ora (49 1f 15 83), il gid (notare che a differenza dell'uid occupa un solo byte), un contatore del numero dei link che utilizzano questo inode entry ? (in questo caso 02) e da qui una serie di byte che ci dicono quali blocchi occupano i dati di questo inode, in questo caso essendo "." la dir corrente indica il primo blocco disponibile che e' il numero 5 .

 $ hexdump minixfs -C  -n 1024 -s 0x1400
   00001400  01 00 2e 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   00001410  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   00001420  01 00 2e 2e 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   00001430  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   00001440  00 00 2e 62 61 64 62 6c  6f 63 6b 73 00 00 00 00  |...badblocks....|
   00001450  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   *
   00001800

Questo e' il contenuto dei dati dove l'inode di "." punta .

Vediamo cosa succede quando creiamo un file .

 $ echo "Hello World" > /mnt/data/hello.asc
 $ ls -la /mnt/data/
   total 3
   drwxr-xr-x 2 root root   96 Nov 15 15:33 .
   drwxr-xr-x 5 root root 1024 Sep 28 15:16 ..
   -rw-r--r-- 1 root root   12 Nov 15 15:33 hello.asc

 $ hexdump minixfs -C  -n 1024 -s 0x1000
   00001000  ed 41 00 00 60 00 00 00  24 40 1f 49 00 02 05 00  |.A..`...$@.I....|
   00001010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   00001020  a4 81 00 00 0c 00 00 00  24 40 1f 49 00 01 06 00  |........$@.I....|
   00001030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   *
   00001400

 $ hexdump minixfs -C  -n 1024 -s 0x1400
   00001400  01 00 2e 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   00001410  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   00001420  01 00 2e 2e 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   00001430  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   00001440  02 00 68 65 6c 6c 6f 2e  61 73 63 00 00 00 00 00  |..hello.asc.....|
   00001450  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
   *
   00001800

Come si vede, la tabella degli inode è stata aggiornata e nel blocco in cui punta l'inode di ".", che contiene la lista dei file presenti, è stato aggiornato con il nome del nostro nuovo file. Notare come il primo blocco occupato dai dati nell'inode del file hello.asc è il numero 6 . Se andiamo a vedere questo blocco vedremo i dati del file . Potete provare se interessati a creare dei link hardware tra gli inode e delle directory, che altro non sono che normali file come "." che contengono una lista di file .

Gli inode ai giorni nostri

Come acennato prima all'inizio di questa guida, gli inode oggi si trovano a livello di astrazione del VFS (fate riferimento alla mappa del kernel), guardando nel codice di un kernel moderno, ovviamente saranno un po' più complicati e dipendono anche dal tipo di fs che il vostro kernel supporterà in fase di configurazione .