Page préc.
Systèmes de fichiers installés au moment du boot
Fin de page Page suiv.
Types de fichiers

Le système virtuel VFS

    VFS suppose de tout F.S. :     Pour des raisons de clarté, le superbloc et les i-nœuds de VFS seront appelés "superbloc VFS " et " i-nœud VFS " pour ne pas les confondre avec les superbloc et i-nœud du F.S. lui-même, ext2 par exemple. Il n'est pas surprenant de retrouver dans le VFS les caractéristiques d'ext2fs, le but du premier étant de généraliser les concepts du second à tous les autres F.S.

Les caches de VFS

Cache de données

    Comme tous les S.G.F., Linux utilise un cache de données (en fait un ensemble de caches, appelés tampons caches (cache buffers) ou simplements tampons, qui sont une image mémoire des données enregistrées sur disque. Tous les périphériques à structure de blocs (blocks devices) sont vus par Linux à travers une interface uniforme, généralement asynchrone (y compris les périphériques SCSI). Les transferts entre les tampons et le périphérique physique sont totalement transparents à l'utilisateur et se décomposent en plusieurs opérations :     Toutes les requêtes de lecture/écriture sont transmises aux pilotes de périphériques sous la forme d'une structure de données buffer_head par l'intermédiaire de fonctions standard du noyau. Elles contiennent toutes les informations dont le pilote de périphérique blocs a besoin. Tous les périphériques blocs sont vus comme des collections linéaires de blocs de même taille.

    Les tampons cache appartiennent logiquement à deux structures :

Cache d'i-nœuds

    Au fur et à mesure que les processus du système accèdent aux répertoires et aux fichiers, différentes fonctions sont appelées, qui traversent les i-nœuds VFS. Certains sont accédés très souvent. C'est pourquoi ils sont conservés dans un cache d'i-nœuds directement géré par VFS afin d'être retrouvés plus rapidement. On remarque par exemple que, lorsqu'on répète deux fois de suite la commande ls sur un répertoire qui n'a pas été accédé depuis longtemps, la seconde s'exécute beaucoup plus rapide que la première. Les i-nœuds VFS les moins souvent accédés finissent par être supprimés du cache.

    Le cache d'i-nœuds est organisé selon une table de hash-code classique, dont les entrées sont des pointeurs de listes de nœuds VFS ayant la même valeur de hachage. Celle-ci est calculée à partir de la valeur de l'i-nœud et de l'identificateur du périphérique. Si VFS trouve l'i-nœud dans la table, le compteur de ce dernier est incrémenté pour signifier qu'il est accédé par un nouvel utilisateur, puis le traitement continue. Si VFS ne trouve pas le couple {i-nœud,  périphérique} cherché, il doit trouver  un i-nœud libre. Pour ce faire, il dispose de plusieurs possibilités :

Cache de répertoires VFS

    Enfin, VFS gère aussi un cache de répertoires (directory entry cache, ou en abrégé dentry cache ou dcache) parcourus, afin de retrouver rapidement les i-nœuds des répertoires les plus fréquemment utilisés. Seules les entrées de répertoires courtes (moins de 16 caractères) sont stockées, ce qui correspond en principe aux répertoires les plus fréquemment parcourus.
Le cache des répertoires est lui aussi organisé en une table de hash-code, la fonction de calcul de la valeur de hachage utilisant le numéro de périphérique et le nom du répertoire. Afin de maintenir le cache valide et à jour, VFS gère deux listes LRU (Last Recently Used) pour chaque entrée de la table de hash-code : la première fois qu'un répertoire est entré dans la table, il est placé en queue de la première liste. Si les deux listes sont pleines, cela a pour effet de prélever l'élément de tête de la première liste et de le placer en queue. Si le répertoire est de nouveau accédé, il est placé à la queue de la seconde liste. Dans les deux cas, l'effet est de faire remonter les répertoires les moins utilisés en tête de liste. Lorsqu'un élément doit être prélevé pour ajouter un répertoire, c'est le répertoire le plus ancien qui est supprimé. De plus, les éléments de la seconde liste sont conservés plus longtemps que ceux de la première liste, ce qui est logique puisqu'ils ont été accédée au moins deux fois .

Les démons de VFS

Le noyau de Linux possède deux processus démons qui participent à la gestion des buffers :
- bdflush est un thread du noyau lancé au moment du démarrage du système, qui passe la majorité de son temps à dormir. Chaque fois qu'un tampon est alloué ou libéré, le nombre de tampon "sales" est incrémenté. Lorsque le pourcentage atteint une limite fixée (60 % par exemple) mais modifiable, bdflush est réveillé et appelle lui-même le processus kflush.
- update est un démon qui écrit périodiquement les tampons "sales" sur disque. Son action est à peu près identique à celle de bdflush.

Structures de VFS

Le rôle du module correspondant au F.S. (figure 3) est de simuler ces propriétés s'il ne les possède pas réellement : c'est le cas de msdos par exemple. Le VFS gère une table  des F.S. installés, qu'il reconnaît. Chaque élément de la table est de type file_system_type, défini  ainsi (figure 12) :
 
struct file_system_type {
    const char * name;
    int fs_flags;
    struct super_block * (*read_super) (struct super_block *, void *, int);
    struct file_system_type * next;
}; // file_system_type

figure 12
name  : nom sous lequel a été enregistré le F.S. (ext2, ntfs, etc...)
fs_flags  : différents indicateurs (par exemple F.S. en lecture seulement, etc...
read_super  :  pointeur vers la fonction que doit appeler VFS pour charger le super-bloc d'une arborescence de fichiers au moment où celle-ci est montée sous Linux,
next : pointeur vers le suivant dans la liste chaînée (l'initialiser à NULL)
Il est possible d'ajouter à Linux un F.S. personnel. Il suffit de remplir une structure de type file_system_type et de l'enregistrer auprès de VFS, soit, schématiquement (figure 13) :
 
struct super_block * MonReadSuperBloc (struct super_block *, void *, int)
{
    // ...

} // MonReadSuperBloc()

//                          name        fs_flags   read_super     next

file_system_type MonFs = {"MonFileSystem", ..., MonReadSuperBloc, NULL};
...
register_filesystem (MonFs);

figure 13
La fonction système register_filesystem() parcourt la liste des F.S. déjà installés, vérifie par son nom qu'il est nouveau, et l'ajoute éventuellement à la liste en modifiant le pointeur next.
L'ajout d'une sous-arborescence dans l'arborescence des fichiers est effectuée par la commande mount() à laquelle est indiqué le nœud sur laquelle elle doit être installée, et le nom du F.S. selon lequel elle est organisée (par défaut iso9660). VFS parcourt la liste des F.S. reconnus et, s'il le trouve, appelle la fonction read_super().
C'est donc à partir de la structure file_system_type que VFS apprend à lire le superbloc de l'arborescence installée ou à en simuler la lecture. Le superbloc de VFS est défini par la structure super_block suivante (figure 14) :
 
struct super_block {
 struct list_head s_list;  /* Keep this first */
 kdev_t   s_dev;
 unsigned long  s_blocksize;
 unsigned char  s_blocksize_bits;
 unsigned char  s_lock;
 unsigned char  s_rd_only;
 unsigned char  s_dirt;
 struct file_system_type *s_type;
 struct super_operations *s_op;
 struct dquot_operations *dq_op;
 unsigned long  s_flags;
 unsigned long  s_magic;
 unsigned long  s_time;
 struct dentry  *s_root;
 struct wait_queue       *s_wait;
 struct inode  *s_ibasket;
 short int        s_ibasket_count;
 short int        s_ibasket_max;
 struct list_head       s_dirty;          /* dirty inodes */

 union {
  struct minix_sb_info minix_sb;
  struct ext2_sb_info ext2_sb;

            // ...    :  tous les autres F.S. reconnaissables par VFS

 } u;
 /*
  * The next field is for VFS *only*. No filesystems have any business
  * even looking at it. You had been warned.
  */
 struct semaphore s_vfs_rename_sem; /* Kludge */

}; // super_block

figure 14
C'est par l'intermédiaire de ce superbloc qu'il apprend à manipuler les i-nœuds de VFS : le champ s_op est un pointeur sur une structure de type super_operations (figure 15) permettant d'accéder à toutes les fonctions de manipulation des i-nœud de l'arborescence montée :
 
struct super_operations {
 void (*read_inode)   (struct inode *);
 void (*write_inode)  (struct inode *);
 void (*put_inode)    (struct inode *);
 void (*delete_inode) (struct inode *);
 int  (*notify_change)(struct dentry *, struct iattr *);
 void (*put_super)    (struct super_block *);
 void (*write_super)  (struct super_block *);
 int  (*statfs)       (struct super_block *, struct statfs *, int);
 int  (*remount_fs)   (struct super_block *, int *, char *);
 void (*clear_inode)  (struct inode *);
 void (*umount_begin) (struct super_block *);

}; // super_operations

figure 15
read_inode, write_inode : lecture/écriture de l'i-nœud sur disque,
put_inode : suppression d'un i-nœud du cache des i-nœuds
delete_inode : suppression d'un i-nœud du fichier
notify_change : appelé lorsque les attributs d'un i-nœud sont modifiés
put_super : libération du superbloc (appelée lors de l'opération unmount()),
write_super : appelée lorsque le superbloc doit être écrit sur disque
statfs : appelée pour obtenir des statistiques du F.S.
remount_fs : appelée pour remonter le F.S.
clear_inode : appelée pour purger l'i-nœud
Un i-nœud VFS a la structure suivante (figure 16) :
 
struct inode {
 struct list_head i_hash;
 struct list_head i_list;
 struct list_head i_dentry;

 unsigned long i_ino;
 unsigned int i_count;
 kdev_t  i_dev;
 umode_t  i_mode;
 nlink_t  i_nlink;
 uid_t   i_uid;
 gid_t   i_gid;
 kdev_t  i_rdev;
 off_t   i_size;
 time_t  i_atime;
 time_t  i_mtime;
 time_t  i_ctime;
 unsigned long i_blksize;
 unsigned long i_blocks;
 unsigned long i_version;
 unsigned long i_nrpages;
 struct semaphore i_sem;
 struct semaphore i_atomic_write;
 struct inode_operations *i_op;
 struct super_block *i_sb;
 struct wait_queue *i_wait;
 struct file_lock *i_flock;
 struct vm_area_struct *i_mmap;
 struct page  *i_pages;
 struct dquot *i_dquot[MAXQUOTAS];

 unsigned long i_state;

 unsigned int i_flags;
 unsigned char i_pipe;
 unsigned char i_sock;

 int   i_writecount;
 unsigned int i_attr_flags;
 __u32   i_generation;
 union {
  struct pipe_inode_info  pipe_i;
  struct minix_inode_info  minix_i;
  struct ext2_inode_info  ext2_i;

            // ...    :  tous les autres F.S. reconnaissables par VFS

  void    *generic_ip;
 } u;
}; // inode

    On y reconnaît un certain nombre de champs qui ont été évoqués précédemment. Le champ i_op, de type inode_operations (figure 17), décrit comment le VFS peut manipuler les i-nœuds VFS :
 
struct inode_operations {
    struct file_operations * default_file_ops;
    int (*create)  (struct inode *, struct dentry *,int);
    struct dentry * (*lookup) (struct inode *, struct dentry *);
    int (*link)    (struct dentry *,struct inode *, struct dentry *);
    int (*unlink)  (struct inode *, struct dentry *);
    int (*symlink) (struct inode *,struct dentry *, const char *);
    int (*mkdir)   (struct inode *, struct dentry *, int);
    int (*rmdir)   (struct inode *, struct dentry *);
    int (*mknod)   (struct inode *, struct dentry *, int, int);
    int (*rename)  (struct inode *, struct dentry *,
                    struct inode *, struct dentry *);
    int (*readlink)(struct dentry *, char *,int);
    struct dentry *(*follow_link) (struct dentry *, struct dentry *,
                                   unsigned int);
    int (*readpage)  (struct file *, struct page *);
    int (*writepage) (struct file *, struct page *);
    int (*bmap)      (struct inode *,int);
    void (*truncate) (struct inode *);
    int (*permission)(struct inode *, int);
    int (*smap)      (struct inode *,int);
    int (*updatepage)(struct file *, struct page *, unsigned long, 
                      unsigned int, int);
    int (*revalidate)(struct dentry *);

}; // inode_operations

figure 17
default_file_ops : pointeur sur une structure qui décrit comment manipuler les fichiers ouverts (voir figure 18),
create : appelée par les fonctions système open() et creat(),
lookup : appelée lorsque VFS doit rechercher un i-nœud dans un répertoire parent.
link, unlink, symlink, mkdir, rmdir, mknod, rename, readlink : appelées par les fonctions système équivalentes
follow_link : appelée par VFS pour suivre un lien symbolique jusqu'à l'i-nœud qu'il désigne.
readpage :
writepage :
bmap :
truncate :
permission :
smap :
updatepage  :
revalidate :
    Les opérations sur les fichiers ouverts sont décrites dans la structure  suivante (figure 18) :
 
struct file_operations {
    loff_t (*llseek)   (struct file *, loff_t, int);
    ssize_t (*read)    (struct file *, char *, size_t, loff_t *);
    ssize_t (*write)   (struct file *, const char *, size_t, loff_t *);
    int (*readdir)     (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl)   (struct inode *, struct file *, unsigned int, 
                    unsigned long);
    int (*mmap)    (struct file *, struct vm_area_struct *);
    int (*open)    (struct inode *, struct file *);
    int (*flush)   (struct file *);
    int (*release) (struct inode *, struct file *);
    int (*fsync)   (struct file *, struct dentry *);
    int (*fasync)  (int, struct file *, int);
    int (*check_media_change) (kdev_t dev);
    int (*revalidate)         (kdev_t dev);
    int (*lock)    (struct file *, int, struct file_lock *);

}; // file_operations

figure 18
On y reconnaît les équivalents des fonctions système de même nom.


Page préc.
Systèmes de fichiers installés au moment du boot
Début de page Page suiv.
Types de fichiers
Dernière mise à jour : 12/07/2001