Les opérations sur le système de fichiers

Voici un petit interlude pour parler un peu d'une structure importante dans le noyau: la structure file_operations. Cette structure est utilisée pour décrire l'interface entre un système de fichiers (ext3, reiserfs, fat, ntfs etc...) et l'utilisateur. En fait c'est simple, un système de fichier donne tous les outils à l'utilisateur pour exploiter les fichiers et les dossiers de son système par le biais de quelques fonctions. Un fichier, on peut l'ouvrir, le fermer, le lire, écrire dedans, le parcourir. Un pilote de système de fichier doit donc fournir une interface pour les fonctions suivante: open pour ouvrir, release pour fermer, read pour lire, write pour fermer, llseek pour se déplacer dedans etc...

Par exemple dans un programme utilisateur, lorsque vous appelez la fonction open sur un fichier, votre noyau va chercher le pilote de système de fichier qui prend en charge ce fichier (ext3 par exemple). Lorsqu'il l'a trouvé, il va demander au pilote d'appeler la fonction qui prend en charge l'appel open approprié pour ce système de fichier.

Et en pratique, comment ça se passe?

En pratique, lorsque vous écrivez votre pilote de système de fichiers, vous devez créer une variable de type "struct file_operations" dans laquelle vous mettez les adresses de vos fonctions qui prendront en charge les opérations sur votre système de fichiers. Cette structure n'est pas seulement utilisée pour les pilotes de système de fichiers mais aussi pour d'autres drivers comme les drivers char par exemple. Je vais vous montrer cette structure mais avant, vous allez me promettre de ne pas fuir en vomissant. Si la syntaxe vous paraît incompréhensible, c'est normal. Je vous montrerai quelques exemples d'utilisation. Voici donc la structure file_operations, celle-ci se trouve dans les sources de votre noyau (/include/linux/fs.h):

struct file_operations {
	struct module *owner;
	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 *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, 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);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*dir_notify)(struct file *filp, unsigned long arg);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
};

Voilà le mammouth! Pour explication, si vous ne comprenez pas la syntaxe de cette structure, c'est peut être parce que vous ne savez pas encore ce qu'est un pointeur de fonction. En ce cas, je vous conseille fortement de regarder vite fait sur le site que vous voulez pour voir de quoi il en retourne. Le premier champ de cette structure représente un pointeur vers le module courant, typiquement on lui met la constante THIS_MODULE. Les autres champs sont des pointeurs de fonction. Vous devez donc écrire vos fonctions d'interface pour read/write etc.. puis passer leur adresse dans les champs appropriés.

Voici un petit exemple bidon de structure file_operations pour vous donner un petit aperçu de son utilisation

#include <linux/fs.h> //
#include <linux/module.h> //Pour utiliser THIS_MODULE

//Fonction open
int my_open (struct inode *ino, struct file *fp)
{
    printk("On vient d'appeller la fonction open() sur un fichier de mon système de fichier!\n");
	return 0; // Tout s'est bien passé
}

//Fonction de libération (close)
int my_release (struct inode *ino, struct file *fp)
{
	printk("On vient d'appeller la fonction close() sur un fichier de mon système de fichier!\n");
	return 0; // Tout s'est bien passé
}

/*
 * Fonction de lecture
 * buf: le buffer utilisateur où il faudra enregistrer le contenu à lire
 * size: nombre d'octets à lire
 * pos: la position du pointeur de fichier (lire à partir du pos ième octet dans le fichier)
 */
ssize_t my_read (struct file *fp, char __user *buf, size_t size, loff_t * pos)
{
	printk("On vient de me demander de lire size octets dans le fichier fp à la position pos pour les mettre dans le buffer buf\n");
	return size; //On a lu tout ce qu'il faut
}

//On crée notre structure avec les pointeurs vers nos fonctions
struct file_operations fops = {
	.owner = THIS_MODULE;
    .open = my_open;
	.release = my_release;
	.read = my_read;
};

/* On enregistre notre structure d'opérations pour notre système de fichier. Cette fonction n'existe pas, c'est juste pour l'image.
   Selon votre type de driver (système de fichier, driver char) vous devrez utiliser des fonctions spécifiques pour enregistrer votre structure
*/

register_file_operations(&fops);

Et voilà! En pratique c'est plus complexe que ça. C'était surtout pour vous donner la signification, l'utilité et le rôle de cette structure ainsi qu'une vue grossière de son utilisation.

Sources