UN fichier désigne un ensemble d’informations stockées sur le disque. Le système de fichiers est la partie du système d’exploitation qui se charge de gérer les fichiers. La gestion consiste en la création (identification, allocation d’espace sur disque), la suppression, les accès en lecture et en écriture, le partage de fichiers et leur protection en contrôlant les accès.
Pour le système d’exploitation, un fichier est une suite d’octets. Par contre, les utilisateurs peuvent donner des significations différentes au con- tenu d’un fichier (suites d’octets, suite d’enregistrements, arbre, etc.). Cha- que fichier est identifié par un nom auquel on associe un emplacement sur le disque (une référence) et possède un ensemble de propriétés : ses attri- buts.
Le nom est en général, composé de deux parties séparées par un point. La partie qui suit le point est appelée extension (prog.c, entete.h, fich.doc, archivo.pdf, etc.). L’extension peut être de taille fixe, co- mme dans MS-DOS ou variable comme c’est le cas d’Unix/Linux; obliga- toire ou non. L’extension est nécessaire dans certains cas. Par exemple, le compilateur C rejettera le fichier prog.txt même si son contenu est un programme C. Le nom d’un fichier peut être sensible à la typographie : ainsi en Unix/Linux Archivo archivo.
Les fichiers — comme bien d’autres composants — ont un cycle de vie. Ils sont crées (ou ouverts), on les fait modifier (écrire), on lit à partir d’eux, et finalement, peut être, ils meurent (sont effacés). Ceci est illustré par la fi- gure 11.1, avec des appels système qu’on étudiera dans le présent Chapitre.
Dans un système, il existe plusieurs types de fichiers. Unix/Linux et MS-DOS ont des fichiers ordinaires, des répertoires, des fichiers spéciaux caractère et des fichiers spéciaux bloc.
Les fichiers ordinaires contiennent les informations des utilisateurs. Ils sont en général des fichiers ASCII ou binaires. Les répertoires sont des fi- chiers système qui maintiennent la structure du système de fichiers. Les fichiers spéciaux caractère sont liés aux Entrées/Sorties et permettent de modéliser les périphériques d’E/S série tels que les terminaux, les impri- mantes et les réseaux. Les fichiers spéciaux bloc modélisent les disques.
Dans le système Unix/Linux, "-" désigne les fichiers ordinaires, "d" les répertoires, "c" les périphériques à caractères, "b" les périphériques à blocs, et "p" les tubes avec nom (named pipe). Les périphériques sont des fichiers désignés par des références.
Les fichiers spéciaux bloc et caractère vont identifier les dispositifs physiques du matériel : les disques, les rubans magnétiques, les terminaux, les réseaux, etc. Chaque type de dispositif a un contrôleur responsable de sa communication. Dans le système d’exploitation il y a une table qui pointe vers les différents contrôleurs de dispositifs. Tous les dispositifs seront alors traités comme de fichiers.
Dans Unix/Linux les périphériques comme les terminaux sont des fi- chiers spéciaux qui se trouvent sous le répertoire /dev. On copie par exem- ple le texte : "abcd" tapé au clavier, dans un fichier par la commande :
leibnitz> cat > fichier abcd
^D
et sur un terminal, par la commande :
leibnitz> cat > /dev/tty abcd
^D
Pour accéder à un fichier il faut fournir au système de fichiers les in- formations nécessaires pour le localiser sur le disque, c’est-à-dire lui four- nir un chemin d’accès. Les systèmes modernes permettent aux utilisateurs d’accéder directement à une donnée d’un fichier, sans le parcourir depuis le début du chemin.
Les attributs des fichiers diffèrent d’un système à un autre. Cependant ils peuvent être regroupés en deux catégories :
Dans le système Unix/Linux toutes les informations des fichiers sont rassemblées dans une structure associée au fichier, appelée nœud d’infor- mation, i-nœud ou i-node. L’i-nœud contient les informations suivantes : le type du fichier (régulier, répertoire, caractère, bloc ou tube) ; le code de protection sur 9 bits ; l’identificateur et groupe du propriétaire ; les dates de création, du dernier accès et de la dernière modification ; le compteur de références ; la taille et finalement la table d’index composée de 13 numéros de blocs et de pointeurs. La figure 11.2 montre la structure typique d’un i-nœud.
Les fichiers permettent de stocker des informations et de les rechercher ultérieurement. Les systèmes fournissent un ensemble d’appels système re- latifs aux fichiers. Dans le cas d’Unix/Linux, les principaux appels système Posix relatifs aux fichiers sont :
Ouverture d’un fichier
#include <unistd.h> #include <fcntl.h>
int open(char * filename, int mode);
int open(char *filename, int mode, int permission); int creat(char * filename, int permission);
En cas de succès, open() retourne un descripteur du fichier. En cas d’échec, il retourne 0. Chaque fichier ouvert a un pointeur qui pointe sur un élément du fichier. Ce pointeur sert à parcourir le fichier. A l’ouverture du fichier, le pointeur de fichier pointe sur le premier élément du fichier, sauf si l’option O_APPEND a été spécifiée. Les lectures et les écritures se font à partir de la position courante du pointeur. Chaque lecture et chaque écriture entraînent la modification de la position du pointeur.
L’appel système creat()1 est de moins en moins utilisée. Son usage est équivalent à celui de :
open(char *filename, O_WRONLY|O_CREAT|O_TRUNC, int mode) Le mode est une combinaison de plusieurs élements assemblés par un OU logique. Il faut utiliser de façon obligée l’une de trois constantes :
On peut ensuite utiliser d’autres constantes qui permettent de mieux pré- ciser l’usage :
Les permissions sont utilisées lors de la création d’un fichier. Elles ser- vent à signaler les autorisations d’accès au nouveau fichier crée. On peut les fournir en représentation octale directement (précédées d’un 0) ou bien on peut utiliser les constantes symboliques les plus fréquentes de la table 11.1.
Ainsi si l’on utilise la combinaison :
"S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH" = 0644 = rw-r-r-
on donne les droits de lecture à tous et les droits de lecture et écriture seule- ment au propriétaire du fichier.
Exemple : Cet appel système crée, puis ouvre un fichier nommé archivo pour un accès en lecture/écriture. Le descripteur de fichier est fd. Le code de protection du fichier est 0600 ce qui correspond à (wr-------------------------------------------------------------------- ).
int fd;
fd = open("archivo", O_CREAT | O_RDWR, 0600);
Exemple : Cet appel système ouvre le fichier archivo en mode écriture et positionne le pointeur à la fin du fichier. Les écritures dans le fichier ajouteront donc des données à la fin de celui-ci.
int fd;
fd = open("archivo", O_WRONLY | O_APPEND);
Exemple : Les appels suivants sont équivalents :
fd = creat("datos.txt", 0751);
fd = open("datos.txt", O_WRONLY | O_CREAT | O_TRUNC, 0751);
Fermeture des fichiers
L’appel système close() permet de libérer un descripteur de fichier fd. Le fichier pointé par le descripteur libéré est fermé s’il n’a plus aucun descripteur associé au fichier.
int close (int fd);
Lecture d’un fichier
#include <unistd.h>
int read(int fd, char* buf, int count);
L’appel système read() lit, à partir de la position courante du pointeur, count octets au maximum et les copie dans le tampon buf. En cas de suc- cès, il retourne le nombre d’octets réellement lus. L’appel système read() retourne 0 si la fin de fichier est atteinte. Il retourne -1, en cas d’erreur.
Écriture dans un fichier
#include <unistd.h>
int write(int fd, void* buf, int count);
L’appel système write() copie count octets du tampon buf vers le fi- chier fd, à partir de la position pointée par le pointeur de fichier. En cas de succès, il retourne le nombre d’octets réellement écrits. Sinon, il renvoie -1. Il faut retenir que read() et write() manipulent des octets, et que le programmeur a la responsabilité de les interpréter d’une ou autre forme. Par exemple, regardez les lectures de types différents de données à par- tir d’un descripteur du fichier fd (les écritures seraient tout à fait équiva-
lentes) :
char c, s[N];
int i, v[N];
// 1 char :
read(fd, &c, sizeof(char));
// N char’s:
read(fd, s, N*sizeof(char));
// 1 int :
read(fd, &i, sizeof(int));
// N int’s :
read(fd, v, N*sizeof(int));