Pourquoi ce document ?
Ce document sert de rangement fourre-tout à toutes les définitions des termes accessoires rencontrés au long du cours d'Unix et qui auraient considérablement alourdi la lecture de ce dernier en retardant inutilement le lecteur.
Voici, grosso modo, le plan retenu :
Les données utilisées par un programme peuvent être rangées en 3 grandes catégories en fonction de leur classe de stockage c'est à dire de leur emplacement de stockage et de leur pérennité dans le programme :
La gestion de la pile peut varier considérablement d'un système Unix à un autre. Dans la plupart des cas, la taille maximale de la pile est fixée en dur dans l'exécutable. Une option du lieur permet de la modifier. Dans d'autres cas, la taille de la pile est variable, celle-ci pouvant croître au cours de l'exécution.
La pile est utilisée dans plusieurs cas :
Le tas (heap) est l'espace mémoire du système non encore alloué. Il est manipulable par deux grandes catégories de primitives : celles de bas niveau ... et celles de haut niveau !
Afin de comprendre la différence, introduisons la notion de point de rupture (breakpoint). Celui-ci représente la plus grande valeur de pointeur valide dans l'espace d'adressage de votre processus. Toute modification de la taille de votre espace d'adressage passe par une augmentation du point de rupture, ce qui pourra se faire directement par l'intermédiaire des primitives de bas niveau ou indirectement par celles de haut niveau.
Elles permettent de modifier directement la valeur du point de rupture, soit en lui affectant une valeur (setbrk) soit en lui ajoutant un increment (sbrk). L'utilisation de la première forme est particulièrement ardue et doit être réservée aux utilisateurs les plus avertis. La deuxième forme est moins délicate et vous pouvez jouer avec pour en étudier les effets.
Ce sont toutes les fonctions de la famille [m|c|re]alloc ainsi que l'opérateur new du C++. Elles permettent une gestion plus efficace de la mémoire en implémentant, en particulier un mécanisme de prise en compte de la fragmentation. Lorsqu'une opération de haut niveau nécessite une augmentation de la taille de l'espace d'adressage, la fonction de bas niveau setbrk est appelée.
Vous connaissez tous le mécanisme d'édition des liens. Vous avez plusieurs fichiers source, tous sont compilés vers des fichiers objet qui sont ensuite rassemblés dans un exécutable. Bien, ceci concerne votre code personnel, mais qu'en est il, par exemple des fonctions standard du C, des fonctions mathématiques standard ou encore des primitives X11 que vous invoquez ? Vous ajoutez à la fin de votre ligne de commande des options genre -lm ou -lX11 pour indiquer que le code des fonctions utilisées est à chercher dans une bibliothèque de fichiers objets.
Par défaut, les bibliothèques sont liées statiquement, ce qui signifie qu'à l'instar du code de vos propres fonctions, celui des fonctions de bibliothèque est ajouté dans votre exécutable. Les pointeurs d'appel sont alors reliés à l'emplacement des fonctions dans l'exécutable par ld. Les fichiers de bibliothèques utilisés ont l'extension .a.
La liaison dynamique des bibliothèques repose sur l'existence d'une forme spéciale du fichier de bibliothèque. Cette forme particulière est en fait constituée de deux fichiers, le premier (.sa) est utilisé lors de l'édition de liens ; le second (.so) lors de l'exécution.
L'édition se déroule alors en deux phases :
Soit dit au passage, vous avez tout à fait la possibilité de créer vos propres bibliothèques, pour la version statiques, il vous suffit d'utiliser les utilitaires standard ar et ranlib. Pour la version dynamique, c'est beaucoup plus compliqué ... et très dépendant de la version du système que vous utilisez !
Ici encore, il existe des mécanismes de haut et bas niveau. Les mécanismes de bas niveau s'appuient directement sur la notion de descripteur de fichier alors que les mécanismes de haut niveau utilisent des structures spéciales de type FILE.
Toutes les commandes de traitement de fichier utilisent la constante EOF pour marquer la fin des fichiers.
Nous traiterons de manière séparée les fonctions d'accès à l'inode dont le nom et la syntaxe sont quelque peu confondantes.
Bien que ces mécanismes ne soient pas normalisés ANSI, on les retrouve sur la majorité des plateformes de développement. En outre, sous Unix, ils sont indispensables car seuls à même de réaliser certaines opérations. Ils s'appuient directement sur la notion de handle de fichier. Pour mémoire, rappelons que chaque fichier ouvert est associé à un descripteur. Il y a autant de descripteurs associé au fichier que d'ouvertures. Les descripteurs sont rangés dans une table, chacun contient des renseignements importants sur le type d'acces accordé sur le fichier en question. Le handle n'est ni plus ni moins que l'index dans la table.
Si la conformité au standard ANSI est une obsession pour vous, et, à mon avis, vous avez entièrement raison, vous ne voudrez probablement utiliser ques les primitives de haut niveau. Toutefois, il est rassurant de savoir que l'on peut toujours compter sur des mécanismes de plus bas niveau parfois !
int open(const char *path, int access [, mode_t mode]); | Ouverture du fichier de nom path, avec les options spécifiées dans access et mode |
int creat (const char * path, mode_t mode); | Crée un nouveau fichier de nom path avec les options mode et renvoie son handle |
int read(int handle, void *buf, unsigned len); | Lecture de len octets depuis le fichier spéficié par handle vers l'emplacement de mémoire pointé par buf. Il doit y avoir au moins len octets de disponibles à l'emplacement pointé par buf |
int write(int handle, void *buf, unsigned len); | Ecrit len octets depuis l'emplacement mémoire pointé par buf vers le fichier de descripteur handle |
int dup(int handle); | Duplique le descripteur associé à handle et renvoie un nouveau handle |
int fcntl (int handle, int, ...); | Permet de modifier le comportement des opérations sur le fichier de descripteur handle |
int eof(int handle); | Teste si la fin du fichier est atteinte |
long tell(int handle); | Indique la position courante dans le fichier associé au handle passé en paramètre. La position est toujours donnée relativement au début du fichier. |
long lseek(int handle, long offset, int fromwhere); | Déplace le pointeur courant du fichier associé à handle de offset octets à partir de la position fromwhere. En fonction de la valeur de fromwhere vous pouvez indique une position à partir du début du fichier, de sa fin, ou de la position courante. |
int close(int handle); | Fermeture du descripteur associé au handle passé en paramètre |
Il est important de noter que les opérations de bas niveau ne sont pas bufferisées ! Toute opération d'écriture est immédiatement répercutée sur le fichier concerné. Réciproquement, toute opération de lecture nécessite un accès disque. De ce fait l'utilisation ces primitives peut s'avérer plus lent que celui des primitives de haut niveau.
Dernière précaution à respecter, le nom des constantes numériques utilisées par open fcntl etc. est normalisé POSIX, mais pas la valeur de chacune de ces constante. Aussi, plus que jamais, il convient d'utiliser les constantes plutôt que des valeurs bien que cela soit parfois tentant (utiliser 4 pour un accès en lecture, par exemple !).
Petit détail merdique qui peut avoir son importance, il faut inclure le fichier <io.h> pour que tout ca marche !
Contrairement aux mécanismes de bas niveau, ceux de haut niveau sont normalisés ANSI, et donc, disponibles, virtuellement partout ... Ils ne s'appuient plus sur la notion de handle (tous les systèmes d'exploitation ne sont pas sensés gérer une table de descripteurs) mais sur une structure sensée pouvoir représenter l'état d'un fichier dans n'importe quel système : FILE. On ne manipule jamais directement celle-ci mais plutôt des pointeurs alloués dynamiquement par la fonction d'ouverture (fopen) et désalloués par la fonction de fermeture (fclose).
Vous noterez (nons sans sourire, j'en suis bien convaincu) que la plupart de ces primitives ont pour nom celui d'une primitive de bas niveau précédé de la lettre «f». Marrant non ?
FILE *fopen(const char *filename, const char *mode); | Ouverture de fichier, par opposition à open qui utilise des constantes numériques, fopen utilise une chaîne de caractères « mnémoniques » Toutes les autres opérations utilisent le FILE * renvoyé par fopen |
int fclose(FILE *stream); | Fermeture de fichier |
int fflush(FILE *stream); | Vide le buffer (lecture/écriture) associé au fichier |
int feof(FILE *stream) | Teste si la fin du fichier est atteinte |
size_t fread(void *ptr, size_t size, size_t n, FILE *stream); | Lecture de n éléments de taille size octets depuis le fichier stream. Les éléments sont logés à l'emplacement mémoire désigné par ptr Attention, verifiez que la mémoire libre est suffisante ! La valeur de retour indique le nombre d'octets réellement lus. |
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream); | Ecrit dans le fichier stream de n elements de taille size localisés en mémoire par ptr.La valeur de retour indique le nombre d'octets réellement lus. |
int fputs(const char *s, FILE *stream); | Ecrit la chaîne s dans le fichier stream. Ajoute automatiquement un saut de ligne ! |
char *fgets(char *s, int n, FILE *stream); | Assurément la commande de lecture la plus utile pour les fichiers en mode texte ! Lit une ligne depuis stream jusqu'à concurrence de n-1 caractères et les loge dans la chaîne s. Supprime le caractère de saut de ligne si une ligne complète est lue.Notez que s doit être dimensionnée à n caractères à cause du caractère 0 ajouté automatiquement. |
int fscanf(FILE *stream, const char *format, ...) | Bon, ça, vous connaissez tous :
cette immonde fonction permet de lire des données sur le fichier
stream gràce à la chaîne de format
format. Toutefois, n'oubliez jamais que :
|
int fprintf(FILE *stream, const char *format, ...) | Encore une fonction que vous connaissez bien ! fprintf permet de faire des écritures formatées dans le fichier stream conformément à la chaîne de format format |
long int ftell(FILE *stream); | Renvoie la position du pointeur courant (relativement au début du fichier) du fichier stream |
int fseek(FILE *stream, long offset, int fromwhere); | Permet de déplacer le pointeur
courant du fichier stream de offset octets à partir du
point de référence fromwhere qui peut être :
|
int getc(FILE *stream); | Lit le prochain caractère dans le fichier stream. Notez que le type renvoyé est int et non pas char. |
int ungetc(int c, FILE *stream); | Place le caractère c dans le tampon de lecture du fichier stream de manière à ce qu'il soit renvoyé par la prochaine commande de lecture. Le caractère n'est pas placé physiquement dans le fichier mais uniquement dans le buffer de lecture, aussi certaines opérations risquent de le supprimer. |
Vous noterez également l'existence des fonctions fgetpos et fsetpos qui permettent de placer des marqueurs sur un fichier puis d'y revenir ultérieurement.
On ne le dira jamais assez, la seule commande sure de lecture dans un fichier texte (en particulier, le clavier ... ) est fgets qui permet :
N'oubliez pas que vous pouvez utiliser fgets sur la console avec stdin en paramètre !
Si vous avez besoin d'extraire des éléments de la chaîne lue, vous pouvez utiliser :
Les fonctions fstat et stat (toutes deux non normalisées ANSI mais reconnues par le standard POSIX) permettent d'obtenir les informations contenues dans l'inode. Notons également que ces fonctions sont disponibles sous DOS et Windows, bien que ces deux systèmes n'aient aucune structure de donnée comparable à un inode.
Il est impératif de note que le fichier doit être ouvert pour lui appliquer ces fonctions.
int fstat(int handle, struct stat *statbuf); | Stockent dans la structure statbuf les informations relatives au fichier précédemment ouvert et désigné soit par un descripteurhandle soit par son chemin path |
int stat(const char *path, struct stat *statbuf); |
Voici, tel que fourni par l'examen du fichier sys/stat.h le prototype (POSIX) de la structure stat. Comme vous pouvez le constater, la structure de type BSD est plus complète que celle de System V. Le texte original a été commenté en Français pour une compréhension plus aisée.
struct stat { dev_t st_dev; /* si le fichier est associé à un périphérique, réf de celui-ci */ ino_t st_ino; /* Numéro d'inode */ mode_t st_mode; /* Mode d'ouverture du fichier */ nlink_t st_nlink; /* Nombre de liens physiques (entrées de répertoires) associés au fichier */ uid_t st_uid; /* Numéro du propriétaires */ gid_t st_gid; /* Numéro du groupe */ dev_t st_rdev; /* Identique st_dev */ off_t st_size; /* Taille du fichier (consultez man pour l'unite, habituellement en nb de blocs) */ /* SysV/sco doesn't have the rest... But Solaris, eabi does. */ #if defined(__svr4__) && !defined(__PPC__) && !defined(__sun__) time_t st_atime; /* Date de dernier acces */ time_t st_mtime; /* Date de derniere modification du contenu */ time_t st_ctime; /* Date de dernier changement (changement attributs et ou modification du contenu*/ #else time_t st_atime; /* Date de dernier acces */ long st_spare1; /* Spécial SUN */ time_t st_mtime; /* Date de derniere modification du contenu */ long st_spare2; /* Spécial SUN */ time_t st_ctime; /* Date de dernier changement (changement attributs et ou modification du contenu*/ long st_spare3; /* Spécial SUN */ long st_blksize; /* Taille de chaque bloc */ long st_blocks; /* Taille en nb de blocs */ long st_spare4[2]; /* Spécial SUN */ #endif };
Les deux fonctions suivantes permettent respectivement d'obtenir le handle d'un fichier connu par un FILE * ou une structure FILE à partir de son descripteur.
int fileno(FILE *stream); | Renvoie un descripteur associé au fichier attaché à stream Si plusieurs descripteurs sont associés à ce fichier, le premier créé est renvoyé. |
FILE *fdopen(int handle, char *type); | Crée une nouvelle structure FILE associée au descripteur de fichier handle Le mode d'ouverture spécifié dans type doit correspondre à celui déjà fourni lors de l'obtention du descripteur. |
monstyle