III. Structures élémentaires▲
III-A. File System Type▲
struct
file_system_type {
//...
Cette structure décrit un filesystem pour qu'il puisse être enregistré dans la liste des FS disponibles.
Voici un exemple simple :
//FS type
static
struct
file_system_type yfs_fs_type =
{
.name =
"
yfs
"
,
.get_sb =
yfs_get_sb, //Get superblock (mount)
.kill_sb =
yfs_kill_sb, //Kill superblock (umount)
.owner =
THIS_MODULE, //The filesystem owner(this module)
.fs_flags =
FS_REQUIRES_DEV, //Tell we need a block device
}
;
III-B. Inode▲
struct
inode {
//...
La structure inode contient toutes les informations relatives à l'état d'un fichier(3). Voici le détail des champs les plus usuels.
• i_ino : numéro identificateur propre à chaque inode. Pour certains systèmes de fichiers qui n'ont pas d'inode fixe (FAT32) ce numéro est généré dynamiquement par exemple par iunique. Pour des systèmes de fichiers comme ext2/3, ce numéro est propre à chaque fichier, les ids 0 et 1 ne sont pas utilisables, la racine (/) se voyant alors attribuer l'inode numéro 2. (les valeurs 0 et 1 sont respectivement, une valeur invalide et une inode utilisée pour lier tous les blocks considérés comme défectueux du disque).
• i_count : nombre de contextes d'utilisation de l'inode. Incrémenté à chaque ouverture d'un fichier, et décrémenté lors de sa fermeture.
• i_mode : il s'agit du chmod du fichier/répertoire ainsi que du type de fichier. Ce peut être un fichier,
un répertoire, un lien dur, un lien symbolique, un char device, un block device, un point
de montage... Généralement on se contente de gérer les cas qui relèvent du filesystem (Lien,
fichier et répertoire).
La liste des valeurs est disponible dans include/linux/stat.h
• i_nlink : nombre de liens physiques pointant sur la même inode. Dans le cas d'un système de
fichiers comme ext3, c'est cette valeur qui permet de savoir lors de la suppression d'un
fichier si l'inode et le contenu associé doivent être détruits ou bien conservés pour un autre
fichier.
Un lien physique (ln src_file link_name) incrémente cette valeur, et la suppression d'un des
liens (Il n'y a pas « d'original » ou de « premier fichier », toutes les entrées vers ce
fichier/cette inode se valent) entrainera la décrémentation de cette valeur. Quand i_nlink
atteint 0, le système sait que le fichier peut être considéré comme « détruit » et les données
libérées (c'est le filesystem qui se chargera de cette tâche).
• i_uid && i_gid : ce sont respectivement l'identifiant de l'utilisateur possédant le fichier et le groupe auquel appartient le fichier. La correspondance peut parfois être faite en observant les fichiers /etc/passwd et /etc/group.
• i_size : la taille (en octets) du fichier/répertoire considéré. Par exemple, les répertoires ont
"souvent" une taille multiple de 4096 octets (correspondant à une page mémoire sous certaines
architectures)
Il s'agit bien de la taille des données contenues dans le fichier, et non de l'espace utilisé sur
le périphérique de stockage.
• i_atime && i_mtime && i_ctime, : ce sont respectivement les derniers timestamp :
- du dernier accès à un fichier (lecture)
- de la dernière modification d'un fichier
- du dernier changement de statut du fichier (par exemple, chmod)
• i_blocks : le nombre de blocs utilisés pour stocker les données du fichier/répertoire associé à l'inode. Notez toutefois qu'un filesystem peut fonctionner en se passant de cette valeur. Maintenir sa valeur à jour rend les informations fournies par stat et d'autres commandes plus exactes.
• i_op : un lien vers la structure « inode operations » liée à ce fichier. (cf: inode operations)
• i_fop : un lien vers la structure « file operations » liée à ce fichier. (cf: file operations)
• i_aops : un lien vers la structure « address_space_operations » liée à ce fichier. (cf: address_space_operations)
(3) A noter qu'il sera parfois nécessaire d'adjoindre des données supplémentaires à une inode, par exemple la place des premiers blocs du fichier correspondant. On créera alors une superstructure qui contient les données et l'inode, et on utilisera les macros fournies par le kernel comme container_of. Ceci sera abordé à nouveau plus tard dans ce document.
III-C. Superblock▲
struct
super_block {
//...
Cette structure que nous avons déjà évoquée contient les informations cruciales du
filesystem monté.
Toutefois, il n'y a que deux éléments essentiels que vous devez impérativement définir
vous même lors de l'appel de fill_super :
• s_op : un lien vers la structure « super opérations » liée au superblock. Cette structure est cruciale puisque c'est elle qui fournira les fonctions élémentaires qui permettent de parcourir l'arborescence. (cf: super opérations)
• s_root : la dentry liée à l'inode décrivant le répertoire / (dentry pouvant être allouée avec int d_alloc_root(struct *inode) une fois la structure inode de / construite par vos soins).
III-D. Super Operations▲
struct
super_operations {
//...
Cette structure est capitale. Elle contient les pointeurs sur les fonctions qui permettront la dés-allocation du superblock, l'écriture d'une inode sur le disque, sa suppression, sa libération, la création d'une inode, ou encore remonter le filesystem. Les noms des champs sont on ne peut plus explicites.
Voici les fonctions essentielles, la liste complète et documentée figure dans vfs.txt fourni dans la documentation du kernel.
//Super operations
static
struct
super_operations yourfs_super_operations =
{
.alloc_inode =
yfs_alloc_inode, //Allocate inode's memory
.destroy_inode =
yfs_destroy_inode, //Free inode's memory
.write_inode =
yfs_write_inode, //Write inode's data on device
.delete_inode =
yfs_delete_inode, //Inode Destruction
.put_super =
yfs_put_super, //Free superblock
.statfs =
simple_statfs /* simple statfs */
,
.remount_fs =
NULL
, //Remount the filesystem
}
;
III-E. Inode Operations▲
Cette structure fournit les pointeurs de fonctions qui permettent d'agir sur le fichier/répertoire. On distinguera donc le cas d'un fichier contenant simplement des données d'un répertoire contenant une liste d'inodes nommées.
Voici deux exemples simples pour ces deux cas. Vous trouverez aussi un exemple pour les liens symboliques. N'hésitez pas à consulter la documentation et les headers des structures pour avoir la liste complète des champs disponibles.
//Repertoires
struct
inode_operations yfs_dir_iops =
{
//Permet de resoudre un chemin
.lookup =
yfs_lookup,
//Crée un fichier, peut se contenter d'appeler mknod
.create =
yfs_create,
//Crée un noeud (fichier, répertoire, char/block device, ...)
.mknod =
yfs_mknod,
//Crée un repertoire, peut se contenter d'adapter i_nlink et appeler mknod
.mkdir =
yfs_mkdir,
//Supprime un repertoire du filesystem
.rmdir =
yfs_rmdir,
//Lien "dure"
.link =
yfs_link,
//Supprime un fichier
.unlink =
yfs_unlink,
//Crée un lien symbolique. On pourra utiliser page_symlink
.symlink =
yfs_symlink,
}
;
//Fichiers
struct
inode_operations yfs_file_iops =
{
//Libère les bloques de l'inode suite à un redimensionnement
.truncate =
sfs_truncate,
//Permet de récupérer les attributs du fichier
.getattr =
yfs_getattr,
}
;
//Liens symbolique
struct
inode_operations yfs_symlink_iops =
{
.readlink =
generic_readlink,
.follow_link =
page_follow_link_light,
.put_link =
page_put_link,
.getattr =
yfs_getattr,
}
;
III-F. File Operations▲
A nouveau, les opérations qui peuvent être effectuées sur un nœud dépendent de l'inode considérée. De plus, vous pouvez choisir de rendre la lecture d'un répertoire comme d'un fichier possible ou impossible, etc...
Voici quelques exemples minimaux :
struct
file_operations yfs_file_ops =
{
.llseek =
generic_file_llseek,
.read =
do_sync_read,
.aio_read =
generic_file_aio_read,
.write =
do_sync_write,
.aio_write =
generic_file_aio_write,
.mmap =
generic_file_mmap,
.splice_read =
generic_file_splice_read,
}
;
struct
file_operations yfs_dir_ops =
{
.read =
generic_read_dir,
.readdir =
yfs_readdir,
}
;
On constate que le kernel fournit de quoi assurer nombre d'opérations élémentaires. Ceci sous réserve de fournir une structure dans le champ a_ops valide.
III-G. Adresse Space Operations▲
La structure « Address Space Operations » permet de faire abstraction de la discontinuité
des données enregistrées sur le périphérique et de les représenter comme une succession de
pages mémoire connexe. La compréhension de cet outil est essentielle.
Dans l'exemple suivant, nous présenterons une utilisation simple où nous nous
contenterons de renvoyer le numéro du block contenant la Nième page mémoire d'un
fichier. Ceci n'est qu'un cas particulier, et vous retrouverez cette structure dans les
filesystem virtuels, qui ne requièrent pas de périphérique.
//Maping virtual connex memory of a file into physical blocks
struct
address_space_operations sfs_address_space_ops =
{
.readpage =
yfs_readpage,
.writepage =
yfs_writepage,
.sync_page =
block_sync_page,
.write_begin =
yfs_write_begin,
.write_end =
generic_write_end,
.bmap =
sfs_bmap,
}
;
//Read page with yfs_get_block
static
int
yfs_readpage
(
struct
file *
file, struct
page *
page)
{
printk
(
KERN_DEBUG "
yfs_readpage
\n
"
);
return
block_read_full_page
(
page, yfs_get_block);
}
//Write page with yfs_get_block
static
int
yfs_writepage
(
struct
page *
page, struct
writeback_control *
wbc)
{
printk
(
KERN_DEBUG "
yfs_writepage
\n
"
);
return
block_write_full_page
(
page, yfs_get_block, wbc);
}
//Prepare Write page
int
__yfs_write_begin
(
struct
file *
file, struct
address_space *
mapping,
loff_t pos, unsigned
len, unsigned
flags,
struct
page **
pagep, void
**
fsdata)
{
printk
(
KERN_DEBUG "
__yfs_write_begin
\n
"
);
//Called by us, so pagep is initialised
return
block_write_begin
(
file, mapping, pos, len, flags, pagep, fsdata, yfs_get_block);
}
//Prepare Write page with yfs_get_block
static
int
yfs_write_begin
(
struct
file *
file, struct
address_space *
mapping,
loff_t pos, unsigned
len, unsigned
flags,
struct
page **
pagep, void
**
fsdata)
{
printk
(
KERN_DEBUG "
yfs_write_begin
\n
"
);
//Called by kernel, *pagep is uninitialised!
*
pagep =
NULL
;
return
block_write_begin
(
file, mapping, pos, len, flags, pagep, fsdata, yfs_get_block);
}
La fonction yfs_get_block associe une partie d'un fichier au block où se situent les données de cette partie. Voici un aperçu de ce à quoi peut ressembler cette fonction :
//Associate logical block to physical block
int
yfs_get_block
(
struct
inode *
inode, sector_t iblock, struct
buffer_head *
bh_result, int
create)
{
//...
int
err =
-
EIO;
//Invalid negativ block number!
if
(
iblock <
0
)
{
printk
(
"
YFS-warning: In get_block, invalid iblock = %u
\n
"
, (
unsigned
int
)iblock);
return
err;
}
//Search the grate block from inode's data and store result in blk
if
((
err =
yfs_find_block
(
inode, /* ... */
&
blk)) <
0
)
goto
err;
//Block found
if
(
blk)
goto
map;
//Get a new block and mark it used in fs block's list.
if
((
err =
yfs_alloc_block
(
inode, /* .. */
&
blk) <
0
))
goto
err;
//Map block into bh
map
:
map_bh
(
bh_result, inode->
i_sb, blk);
return
0
;
err
:
return
err;
}
Ici, tout le travail est relégué à yfs_find_block et yfs_alloc_block.