Mécanismes d'Entrées/Sorties sous Linux : Descripteurs de Fichiers et Redirection

Sous les systèmes d'exploitation de type Unix, la gestion des entrées/sorties (E/S) repose sur une abstraction fondamentlae où "tout est fichier". Comprendre comment le noyau Linux gère ces opérations nécessite d'explorer la relation entre les processus, les descripteurs de fichiers et les appels système.

Abstraction des Fichiers et Appels Système

Un fichier n'est pas seulement une collection de données sur un disque ; il est défini par son contenu et ses métadonnées (attributs). Toute interaction avec un fichier implique une manipulation de ces deux aspects. Cependant, un fichier sur le disque ne peut pas être directement manipulé par un processus utilisateur. Il doit d'abord être chargé en mémoire via une opération d'ouverture.

Les langages de haut niveau comme le C, le C++ ou Java fournissent leurs propres bibliothèques d'E/S (par exemple, fopen, fread en C). Ces fonctions sont des envelopeps (wrappers) autour des appels système du noyau. Puisque le noyau est l'unique entité autorisée à interagir avec le matériel, toutes les opérations de fichiers transitent inévitablement par des appels système tels que open, read, write et close. Maîtriser ces appels système permet de comprendre le comportement sous-jacent, indépendament du langage de programmation utilisé.

Descripteurs de Fichiers et Table d'Allocation

Lorsqu'un processus ouvre un fichier, le noyau crée une structure de données interne (généralement struct file) pour représenter ce fichier ouvert. Pour permettre au processus d'y accéder, le noyau retourne un entier non négatif appelé descripteur de fichier (fd).

Le descripteur de fichier n'est rien d'autre qu'un index dans la table des descripteurs de fichiers du processus (un tableau de pointeurs vers les structures du noyau). Par défaut, chaque processus hérite de trois descripteurs standard :

  • 0 : Entrée standard (stdin)
  • 1 : Sortie standard (stdout)
  • 2 : Erreur standard (stderr)

Par conséquent, tout nouveau fichier ouvert se verra attribuer le plus petit descripteur disponible, commençant généralement à 3.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int descriptors[5];
    const char *target_file = "system_log.txt";

    // Ouverture de plusieurs instances du même fichier
    for (int i = 0; i < 5; i++) {
        descriptors[i] = open(target_file, O_WRONLY | O_CREAT | O_APPEND, 0644);
        if (descriptors[i] < 0) {
            perror("Erreur lors de l'ouverture");
            return 1;
        }
        printf("Instance %d - Descripteur alloué : %d\n", i + 1, descriptors[i]);
    }

    // Nettoyage des ressources
    for (int i = 0; i < 5; i++) {
        close(descriptors[i]);
    }

    return 0;
}

Règles d'Allocation des Descripteurs

Le noyau alloue les descripteurs en suivant une règle stricte : le plus petit indice disponible. Si un descripteur standard est fermé, le prochain appel à open réutilisera cet indice.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    // Fermeture explicite de la sortie standard
    close(STDOUT_FILENO);

    // Le prochain open récupérera le descripteur 1
    int log_fd = open("output_capture.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);

    if (log_fd < 0) {
        perror("open");
        return 1;
    }

    // Cette instruction ne s'affichera pas sur le terminal,
    // car stdout (fd 1) pointe désormais vers le fichier.
    printf("Descripteur alloué : %d\n", log_fd);

    close(log_fd);
    return 0;
}

Dans cet exemple, l'appel à printf écrit sur le descripteur 1. Puisque 1 a été réassigné à output_capture.txt, le texte est écrit dans le fichier au lieu du terminal.

Mécanismes de Redirection

La redirection consiste à modifier la cible d'un descripteur de fichier standard sans changer le code de l'application. Au niveau du noyau, cela revient à modifier le pointeur dans la table des descripteurs du processus pour qu'il pointe vers une autre structure de fichier.

L'appel système dup2(int oldfd, int newfd) est l'outil principal pour cette opération. Il ferme newfd s'il est ouvert, puis le fait pointer vers la même structure de fichier que oldfd.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int custom_fd = open("redirected_output.log", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (custom_fd < 0) {
        perror("open");
        return 1;
    }

    // Redirection : stdout (1) pointe maintenant vers le même fichier que custom_fd
    if (dup2(custom_fd, STDOUT_FILENO) < 0) {
        perror("dup2");
        return 1;
    }

    // L'écriture sur stdout va maintenant dans le fichier
    printf("Ce message est capturé dans le fichier journal.\n");
    fprintf(stdout, "Ceci est une autre ligne de log.\n");

    close(custom_fd);
    return 0;
}

Pour une redirection en mode ajout (append), il suffit de modifier les drapeaux lors de l'ouverture du fichier en utilisant O_APPEND au lieu de O_TRUNC.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int input_fd = open("input_data.txt", O_RDONLY);
    if (input_fd < 0) {
        perror("open");
        return 1;
    }

    // Redirection de l'entrée standard (0) vers notre fichier
    if (dup2(input_fd, STDIN_FILENO) < 0) {
        perror("dup2");
        return 1;
    }

    char buffer[128];
    // La lecture sur stdin lira désormais depuis input_data.txt
    while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
        printf("Lu depuis le fichier : %s", buffer);
    }

    close(input_fd);
    return 0;
}

Étiquettes: Linux system-calls file-descriptors io-redirection c-programming

Publié le 17 juillet à 21h47