Table of Contents

Laborator 8 - Drivere de sisteme de fișiere (Linux) partea 1

Obiectivele laboratorului

Cuvinte cheie

Materiale ajutătoare

Virtual Filesystem

Virtual Filesystem (cunoscut și sub prescurtarea VFS) este o componentă a nucleului care se ocupă de tratarea tuturor apelurilor de sistem legate de fișiere și sisteme de fișiere. VFS este o interfață generică între utilizator și un sistem de fișiere particular. Acest lucru simplifică implementarea sistemelor de fișiere și oferă o integrare facilă a mai multor sisteme de fișiere. În acest fel, implementarea unui sistem de fișiere este realizată prin folosirea API-ului pus la dispoziție de VFS iar părțile generice de comunicație cu dispozitivul hardware și subsistemul de I/O sunt rezolvate de VFS.

Din punct de vedere funcțional sistemele de fișiere pot fi grupate în:

O instanță de nucleu Linux va folosi VFS pentru ierarhia (de tip arbore) de directoare și fișiere. Un nou sistem de fișiere va fi adăugat ca un subarbore a VFS prin operațiunea de montare. Fiecare sistem de fișiere este de obicei montat de pe mediul pentru care a fost construit (de pe un dispozitiv de tip bloc, de pe rețea etc.). În particular însă, VFS-ul poate folosi drept dispozitiv de tip bloc virtual un fișier normal, deci se pot monta sisteme de fișiere pentru disc peste fișiere normale. Astfel, se pot crea stive de sisteme de fișiere.

Ideea de bază a VFS-ului este de a oferi un singur model de fișier, care să poată reprezenta fișierele din orice sistem de fișiere. Driver-ul de sistem de fișiere este responsabil pentru aducerea la numitorul comun. Astfel se poate crea o singură structură de directoare care conține întreg sistemul. Va exista un sistem de fișiere care va fi rădăcina, restul fiind montate în diverse directoare ale acestuia.

Modelul general al sistemului de fișiere

Modelul general al sistemului de fișiere, la care trebuie să se reducă orice sistem de fișiere implementat, este format din mai multe entități cu rol bine definit: superbloc, inode, file și dentry. Aceste entități sunt metadatele sistemului de fișiere (conțin informații despre date sau despre alte metadate).

Entitățile modelului interacționează cu ajutorul unor subsisteme ale VFS sau ale nucleului: cache-ul de dentry-uri, cache-ul de inode-uri, buffer cache-ul. Fiecare entitate este tratată ca un obiect: are o structură de date asociată și un pointer la o tabelă de metode. Inducerea unui comportament particular al fiecărei componente este făcut prin înlocuirea metodelor asociate.

superbloc

Superblocul stochează informațiile necesare unui sistem de fișiere montat:

Localizare:

inode

Inode-ul (index node) menține informații despre un fișier în sensul general (abstractizare): fișier obișnuit (regular file), director, fișier special (pipe, fifo), dispozitiv de tip bloc, dispozitiv de tip caracter, link, sau orice poate fi abstractizat ca fișier.

Un inode menține informații precum:

În general, inode-ul nu deține numele fișierului. Numele este reținut de entitatea dentry. Astfel, un inode poate avea mai multe nume (hardlink-uri).

Localizare:

Fiecare inode este în general identificat de un număr. Pe Linux, argumentul -i la comanda ls precizează numărului inode-ului asociat fișierului:

razvan@valhalla:~/school/2008-2009/so2/wiki$ ls -i
1277956 lab10.wiki  1277962 lab9.wikibak  1277964 replace_lxr.sh
1277954 lab9.wiki   1277958 link.txt      1277955 tema4.wiki

file

File este componenta din modelul general al sistemului de fișiere care se apropie cel mai mult de utilizator. Structura există doar ca entitate VFS în memorie și nu are corespondent fizic pe disc.

În vreme ce inode-ul abstractizează un fișier situat pe disc, file-ul abtractizează un fișier deschis. Din punctul de vedere al procesului, entitatea file abstractizează fișierul. Din punctul de vedere al implementării sistemului de fișiere, însă, inode-ul este entitatea care abstractizează fișierul.

Structura file menține informații precum:

Localizare:

dentry

Dentry (directory entry) realizează asocierea între un inode și numele fișierului.

În general o structură dentry conține două câmpuri:

dentry reprezintă o componentă specifică dintr-o cale, care poate fi un director sau un fișier. Spre exemplu, pentru calea /bin/vi, vor fi create obiecte dentry pentru /, bin și vi (un total de 3 obiecte dentry).

Înregistrarea și deînregistrarea sistemelor de fișiere

În versiunea actuală, kernel-ul Linux are suport pentru un număr în jur de 50 de sisteme de fișiere, dintre care:

Pe un singur sistem, însă, este puțin probabil să existe mai mult de 5-6 sisteme de fișiere. Din acest motiv, sistemele de fișiere (sau, mai corect, tipurile de sisteme de fișiere) sunt implementate ca module și pot fi încărcate sau descărcate oricând.

Pentru a putea încărca / descărca în mod dinamic un modul de sistem de fișiere este necesar un API de înregistrare / deînregistrare a tipului sistemului de fișiere în / din sistem. Structura care descrie un anumit sistem de fișiere este struct file_system_type:

#include <linux/fs.h>
 
struct file_system_type {
         const char *name;
         int fs_flags;
         struct dentry *(*mount) (struct file_system_type *, int,
                                   const char *, void *);
         void (*kill_sb) (struct super_block *);
         struct module *owner;
         struct file_system_type * next;
         struct hlist_head fs_supers;
         struct lock_class_key s_lock_key;
         struct lock_class_key s_umount_key;
         //...
};

Înregistrarea unui sistem de fișiere în sistem se realizează, în general, în funcția de inițializare a modulului. Pentru înregistrare, programatorul va trebui să

  1. inițializeze o structură de tipul struct file_system_type cu numele, flag-urile, funcția care implementează operația de citire a superblocului și referința la structura ce identifică modulul curent
  2. apeleze funcția register_filesystem.

La descărcarea modulului trebuie să se deînregistreze sistemul de fișiere prin apelarea funcției unregister_filesystem.

Un exemplu de înregistrare a unui sistem de fișiere virtual se găsește în codul pentru ramfs:

static struct file_system_type ramfs_fs_type = {
        .name           = "ramfs",
        .mount          = ramfs_mount,
        .kill_sb        = ramfs_kill_sb,
        .fs_flags       = FS_USERNS_MOUNT,
};
 
static int __init init_ramfs_fs(void)
{
        if (test_and_set_bit(0, &once))
                return 0;
        return register_filesystem(&ramfs_fs_type);
}

Funcțiile mount, kill_sb

La montarea sistemului de fișiere, nucleul apelează funcția mount definită în cadrul structurii struct file_system_type. Funcția face un set de inițializări și returnează un dentry (structura struct dentry) ce reprezinta directorul punctului de mount. De obicei, mount este o funcție simplă care apelează una din funcțiile:

Aceste funcții primesc ca parametru un pointer spre o funcție fill_super care va fi apelată după inițializarea superblocului pentru terminarea inițializării acestuia de către driver. Un exemplu de o astfel de funcție găsiți în secțiunea fill_super.

La demontare nucleul apelează kill_sb, care face operații de tip cleanup și apelează una din funcțiile:

Un exemplu pentru un sistem de fișiere fără suport pe disc este funcția ramfs_mount din sistemul de fișiere ramfs:

struct dentry *ramfs_mount(struct file_system_type *fs_type,
        int flags, const char *dev_name, void *data)
{
        return mount_nodev(fs_type, flags, data, ramfs_fill_super);
}

Un exemplu pentru un sistem de fișiere pentru disc este funcția minix_mount din sistemul de fișiere minix:

struct dentry *minix_mount(struct file_system_type *fs_type,
        int flags, const char *dev_name, void *data)
{
         return mount_bdev(fs_type, flags, dev_name, data, minix_fill_super);
}

Superblocul în VFS

Superblocul există atât ca entitate fizică (entitate pe disc) cât și ca entitate VFS (în cadrul structurii struct super_block). Superblocul conține numai metainformație și este folosit pentru scrierea și citirea de metainformații de pe disc (inode-uri, directory entries). Un superbloc (și implicit structura struct super_block) va conține informații despre dispozitivul utilizat, lista de inode-uri, pointer-ul la inode-ul rădăcină al sistemului de fișiere și un pointer la operațiile de superbloc.

Structura struct super_block

O parte din definiția structurii struct super_block este prezentată mai jos:

struct super_block {
        //...
        dev_t                   s_dev;              /* identifier */
        unsigned char           s_blocksize_bits;   /* block size in bits */        
        unsigned long           s_blocksize;        /* block size in bytes */
        unsigned char           s_dirt;             /* dirty flag */
        loff_t                  s_maxbytes;         /* max file size */
        struct file_system_type *s_type;            /* filesystem type */
        struct super_operations *s_op;              /* superblock methods */
        //...
        unsigned long           s_flags;            /* mount flags */
        unsigned long           s_magic;            /* filesystem’s magic number */
        struct dentry           *s_root;            /* directory mount point */
        //...
        char                    s_id[32];           /* informational name */
        void                    *s_fs_info;         /* filesystem private info */
};

Superblocul memorează informația globală pentru o instanță a unui sistem de fișiere:

În plus, un pointer generic (void *) memorează date private sistemului de fișiere. Superblocul poate fi privit ca un obiect abstract căruia îi sunt adăugate date proprii în momentul în care există o implementare concretă.

Operațiile pe superbloc

Operațiile pe superbloc sunt descrise de structura struct super_operations:

struct super_operations {
       //...
       int (*write_inode) (struct inode *, struct writeback_control *wbc);
       struct inode *(*alloc_inode)(struct super_block *sb);
       void (*destroy_inode)(struct inode *);
 
       void (*put_super) (struct super_block *);
       int (*statfs) (struct dentry *, struct kstatfs *);
       int (*remount_fs) (struct super_block *, int *, char *);
       //...
};

Câmpurile structurii sunt pointeri de funcții cu următoarele semnificații:

Funcția fill_super

După cum s-a specificat, funcția fill_super este apelată pentru terminarea inițializării superblocului. Această inițializare presupune completarea câmpurilor structurii struct super_block și inițializarea inode-ului rădăcină.

Un exemplu de implementare este funcția ramfs_fill_super apelată pentru inițializarea câmpurile din superbloc care au mai rămas de inițializat:

#include <linux/pagemap.h>
 
#define RAMFS_MAGIC     0x858458f6
 
static const struct super_operations ramfs_ops = {
        .statfs         = simple_statfs,
        .drop_inode     = generic_delete_inode,
        .show_options   = ramfs_show_options,
};
 
static int ramfs_fill_super(struct super_block *sb, void *data, int silent)
{
        struct ramfs_fs_info *fsi;
        struct inode *inode;
        int err;
 
        save_mount_options(sb, data);
 
        fsi = kzalloc(sizeof(struct ramfs_fs_info), GFP_KERNEL);
        sb->s_fs_info = fsi;
        if (!fsi)
                return -ENOMEM;
 
        err = ramfs_parse_options(data, &fsi->mount_opts);
        if (err)
                return err;
 
        sb->s_maxbytes          = MAX_LFS_FILESIZE;
        sb->s_blocksize         = PAGE_SIZE;
        sb->s_blocksize_bits    = PAGE_SHIFT;
        sb->s_magic             = RAMFS_MAGIC;
        sb->s_op                = &ramfs_ops;
        sb->s_time_gran         = 1;
 
        inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
        sb->s_root = d_make_root(inode);
        if (!sb->s_root)
                return -ENOMEM;
 
        return 0;
}

În kernel sunt disponibile funcții generice pentru implementarea operațiilor cu structurile sistemelor de fișiere. Funcțiile generic_drop_inode și simple_statfs, folosite în codul de mai sus, sunt astfel de funcții și pot fi folosite în implementarea driverelor, daca funcționalitatea acestora este suficientă.

În mare, funcția ramfs_fill_super din codul de mai sus setează câteva câmpuri din superbloc, apoi citește inode-ul rădăcină și alocă dentry-ul rădăcină. Citirea inode-ului radăcină se face în funcția ramfs_get_inode, și constă din alocarea unui nou inode folosind new_inode și inițializarea acestuia. Pentru eliberarea inode-ului se folosește iput, iar pentru alocarea dentry-ului rădăcină d_make_root.

Un exemplu de implementare pentru un sistem de fișiere pentru disc este funcția minix_fill_super din sistemul de fișiere minix. Funcționalitatea pentru sistemul de fișiere pentru disc este similară cu cea a sistemului de fișiere virtual, cu deosebirea folosirii buffer cache-ului. De asemenea, sistemul de fișiere minix păstrează date private de forma struct minix_sb_info. Mare parte a acestei funcții se ocupa cu inițializarea acestor date private (care nu sunt incluse în extrasul de cod de mai sus, pentru claritate). Datele private sunt alocate folosind funcția kzalloc și sunt păstrate în câmpul s_fs_info al superblocului.

Funcțiile VFS-ului primesc de obicei ca parametru superblocul, un inode sau/și un dentry, care conțin un pointer către superbloc, astfel încât aceste date private pot fi accesate ușor.

Buffer cache-ul

Buffer cache-ul este un subsistem în kernel care se ocupă caching-ul (atât la citire cât și la scriere) blocurilor de pe dispozitivele de tip bloc. Entitatea de bază cu care lucrează buffer cache-ul este struct buffer_head. Câmpurile mai importante din această structură sunt:

Există câteva funcții importante ce lucrează cu astfel de structuri:

Funcții și macro-uri folositoare

Superblocul conține de obicei o hartă a blocurilor ocupate (de inode-uri, dentry, date) sub forma unui bitmap (vector de biți). Pentru lucrul cu aceste hărți se recomandă folosirea următoarelor funcții:

Pentru verificarea tipului unui inode se pot folosi următoarele macrodefiniții:

Resurse utile

  1. Robert Love – Linux Kernel Development, Second Edition – Chapter 12. The Virtual Filesystem
  2. Understanding the Linux Kernel, 3rd edition - Chapter 12. The Virtual Filesystem