
Salut, habrozhiteli! Nous avons déjà écrit sur le livre de Michael Kerrisk
«Linux API. Guide complet .
" Nous avons maintenant décidé de publier un extrait du livre "Gestion de la mise en mémoire tampon des E / S des fichiers gérés dans le noyau"
La réinitialisation de la mémoire tampon du noyau pour les fichiers de sortie peut être forcée. Parfois, cela est nécessaire si l'application, avant de continuer à fonctionner (par exemple, une base de données de journalisation des processus change), doit garantir que la sortie est réellement écrite sur le disque (ou au moins dans le cache du disque matériel).
Avant de considérer les appels système utilisés pour contrôler la mise en mémoire tampon du noyau, il serait utile de considérer plusieurs définitions liées de SUSv3.
E / S synchronisées avec intégrité des données et des fichiersDans SUSv3, le concept d'achèvement des E / S synchronisées signifie "une opération d'E / S qui a conduit à un transfert de données réussi [sur le disque] ou a été diagnostiquée comme infructueuse".
SUSv3 définit deux types différents de terminaisons d'E / S synchronisées. La différence entre les types concerne les métadonnées («données sur les données») décrivant le fichier. Le noyau les stocke avec les données du fichier lui-même. Les détails des métadonnées de fichier seront abordés dans la section 14.4 lors de l'examen des inodes de fichier. En attendant, il suffira de noter que les métadonnées du fichier incluent des informations telles que des informations sur le propriétaire du fichier et son groupe, les droits d'accès au fichier, la taille du fichier, le nombre de liens durs vers le fichier, les horodatages indiquant l'heure à laquelle le fichier a été accédé pour la dernière fois, l'heure de sa dernière modification et l'heure du dernier changement de métadonnées, ainsi que des pointeurs vers des blocs de données.
Le premier type d'achèvement d'E / S synchronisé dans SUSv3 est l'achèvement de l'intégrité des données. Lors de la mise à jour des données du fichier, le transfert d'informations suffisant pour permettre une extraction ultérieure de ces données pour continuer à fonctionner doit être assuré.
- pour une opération de lecture, cela signifie que les données du fichier demandé ont été transférées (du disque) vers le processus. S'il y a des opérations d'écriture en attente qui peuvent affecter les données demandées, les données seront transférées sur le disque avant la lecture.
- pour une opération d'écriture, cela signifie que les données spécifiées dans la demande d'écriture ont été transférées (sur disque), comme toutes les métadonnées de fichier requises pour extraire ces données. Le point clé à prendre en compte: pour s'assurer que les données sont extraites du fichier modifié, il n'est pas nécessaire de transférer tous les fichiers medaten. Un exemple de l'attribut de métadonnées d'un fichier modifié qui doit être migré est sa taille (si l'opération d'écriture augmente la taille du fichier). En revanche, les horodatages du fichier à modifier n'auront pas besoin d'être transférés sur le disque avant que la récupération de données ultérieure ne se produise.
Le deuxième type d'achèvement d'E / S synchronisées défini dans SUSv3 est l'achèvement de l'intégrité des fichiers. Il s'agit d'une option avancée pour effectuer des E / S synchronisées avec l'intégrité des données. La différence entre ce mode est que lors de la mise à jour du fichier, toutes ses métadonnées sont transférées sur le disque, même si cela n'est pas nécessaire pour l'extraction ultérieure des données du fichier.
Appels système pour contrôler la mise en mémoire tampon du noyau pendant les E / S de fichiersL'appel système fsync () réinitialise toutes les données mises en mémoire tampon et toutes les métadonnées associées à un fichier ouvert doté d'un descripteur fd. L'appel de fsync () place le fichier dans un état d'intégrité (fichier) après la fin des E / S synchrones.
L'appel fsync () renvoie le contrôle uniquement une fois le transfert des données vers le périphérique de disque (ou au moins vers son cache) terminé.
#include <unistd.h> int fsync(int fd);
Retourne en cas de succès 0 ou -1 en cas d'erreur
L'appel système fdatasync () fonctionne exactement comme fsync (), mais place le fichier dans un état d'intégrité (données) après la fin des E / S synchrones.
#include <unistd.h> int fdatasync(int fd);
Retourne en cas de succès 0 ou -1 en cas d'erreur
L'utilisation de fdatasync () réduit potentiellement le nombre d'opérations sur disque de deux requises par l'appel système fsync () à une seule. Par exemple, si les données du fichier ont changé, mais que la taille reste la même, l'appel à fdatasync () force uniquement la mise à jour des données. (Il a déjà été noté ci-dessus que pour terminer une opération d'E / S synchrone avec l'intégrité des données, il n'est pas nécessaire de transférer les modifications apportées aux attributs tels que l'heure de la dernière modification du fichier.) En revanche, l'appel à fsync () forcera également le transfert des métadonnées sur le disque.
Une telle réduction du nombre d'opérations d'E / S disque sera utile pour les applications individuelles pour lesquelles les performances et la mise à jour précise de métadonnées spécifiques (par exemple, les horodatages) jouent un rôle décisif. Cela peut conduire à des améliorations significatives des performances pour les applications qui produisent plusieurs mises à jour de fichiers à la fois. Étant donné que les données de fichier et les métadonnées sont généralement situées dans différentes parties du disque, la mise à jour des deux nécessitera des recherches répétées en avant et en arrière sur le disque.
Sur Linux 2.2 et versions antérieures, fdatasync () est implémenté en tant qu'appel à fsync (), donc il ne donne aucune amélioration des performances.
À partir de la version 2.6.17 du noyau, Linux fournit un appel système non standard sync_file_range (). Il vous permet de contrôler plus précisément le processus de vidage des données de fichier sur le disque que fdatasync (). Lors de l'appel, vous pouvez spécifier la zone à supprimer dans le fichier et définir des indicateurs qui définissent les conditions de blocage de cet appel. Voir la page de manuel sync_file_range (2) pour plus de détails.
L'appel système sync () provoque le vidage de tous les tampons du noyau contenant les informations de fichier mises à jour (c'est-à-dire les blocs de données, les blocs de pointeur, les métadonnées, etc.).
#include <unistd.h> void sync(void);
Dans l'implémentation Linux, la fonction sync () ne retourne le contrôle qu'après que toutes les données ont été transférées vers le périphérique de disque (ou au moins vers son cache). Mais dans SUSv3, il est autorisé que sync () introduise simplement le transfert de données pour l'opération d'E / S dans le plan et renvoie le contrôle jusqu'à ce que le transfert soit terminé.
Un thread du noyau exécuté en continu vide les tampons du noyau modifiés sur le disque s'ils n'ont pas été explicitement synchronisés pendant 30 secondes. Cette opération est effectuée afin d'éviter que les tampons de données ne soient désynchronisés avec le fichier disque correspondant pendant de longues périodes (et non pour les exposer au risque de perte en cas de défaillance du système). Sous Linux 2.6, cette tâche est effectuée par le thread du noyau pdflush. (Sous Linux 2.4, il a été exécuté par le thread du noyau kupdated.)
La période (en centièmes de seconde) après laquelle le tampon modifié doit être vidé sur le disque par le code de flux pdflush est définie dans le fichier / proc / sys / vm / dirty_expire_centisecs. Des fichiers supplémentaires dans le même répertoire contrôlent d'autres fonctionnalités de l'opération effectuée par le flux pdflush.
Activer le mode de synchronisation pour tous les enregistrements: O_SYNCLa spécification de l'indicateur O_SYNC lors de l'appel à open () entraîne l'exécution de toutes les opérations de sortie suivantes en mode synchrone:
fd = open(pathname, O_WRONLY | O_SYNC);
Après cet appel à open (), chaque opération d'écriture () effectuée sur un fichier vide automatiquement les données et les métadonnées de fichier sur le disque (c'est-à-dire que les écritures sont effectuées en tant qu'opérations d'écriture synchronisées avec l'intégrité du fichier).
Dans les anciennes versions du système BSD, l'indicateur O_FSYNC était utilisé pour fournir les fonctionnalités incluses avec l'indicateur O_SYNC. Dans la glibc, l'indicateur O_FSYNC est défini comme synonyme d'O_SYNC.
Impact sur les performances de l'indicateur O_SYNCL'utilisation de l'indicateur O_SYNC (ou des appels fréquents à fsync (), fdatasync () ou sync ()) peut considérablement affecter les performances. Dans le tableau. La figure 13.3 montre le temps nécessaire pour écrire 1 million d'octets dans un fichier qui vient d'être créé (dans le système de fichiers ext2) pour différentes tailles de tampon avec l'indicateur O_SYNC défini et décoché. Les résultats ont été obtenus (en utilisant le programme filebuff / write_bytes.c fourni dans le code source du livre) en utilisant la version 2.6.30 du noyau «vanilla» et le système de fichiers ext2 avec une taille de bloc de 4096 octets. Chaque ligne contient la valeur moyenne obtenue après 20 démarrages pour une taille de tampon donnée.
Tableau 13.3. L'effet de l'indicateur O_SYNC sur une vitesse d'écriture de 1 million d'octets
Comme vous pouvez le voir, la spécification de l'indicateur O_SYNC entraîne une augmentation monstrueuse du temps passé lors de l'utilisation d'un tampon de 1 octet plus de 1000 fois. Veuillez également noter la grande différence qui se produit lors de l'exécution des enregistrements avec l'indicateur O_SYNC entre le temps écoulé et le temps d'utilisation du processeur. C'est une conséquence du blocage de l'exécution du programme lorsque le contenu réel de chaque tampon est vidé sur le disque.
Dans les résultats indiqués dans le tableau. 13.3, un autre facteur affectant les performances lors de l'utilisation de O_SYNC n'est pas pris en compte. Les lecteurs de disque modernes ont un grand cache interne et, par défaut, le paramètre O_SYNC transfère simplement les données vers ce cache. Si vous désactivez la mise en cache du disque (à l'aide de la commande hdparm –W0), l'impact sur les performances d'O_SYNC deviendra encore plus important. Avec une taille de tampon de 1 octet, le temps écoulé passera de 1030 secondes à environ 16 000 secondes. Avec une taille de tampon de 4096 octets, le temps écoulé passera de 0,34 seconde à 4 secondes. Par conséquent, si vous devez forcer le vidage des tampons du noyau sur le disque, vous devez déterminer si vous pouvez concevoir votre application à l'aide de tampons plus grands pour write () ou envisager d'utiliser des appels périodiques fsync () ou fdatasync () au lieu de l'indicateur O_SYNC.
Drapeaux O_DSYNC et O_RSYNCSUSv3 définit deux indicateurs d'état de fichier ouvert supplémentaires liés aux E / S synchronisées: O_DSYNC et O_RSYNC.
L'indicateur O_DSYNC entraîne des opérations d'écriture synchronisées ultérieures avec l'intégrité des données des E / S terminées (similaire à l'utilisation de fdatasync ()). L'effet de son fonctionnement est différent de l'effet provoqué par l'indicateur O_SYNC, dont l'utilisation conduit à des opérations d'écriture synchronisées ultérieures avec l'intégrité du fichier (comme fsync ()).
L'indicateur O_RSYNC est spécifié avec O_SYNC ou O_DSYNC et conduit à une extension du comportement associé à ces indicateurs lors des opérations de lecture. La spécification des indicateurs O_RSYNC et O_DSYNC lors de l'ouverture du fichier entraîne des opérations de lecture synchronisées ultérieures avec l'intégrité des données (c'est-à-dire qu'avant la fin de la lecture, toutes les entrées de fichier en attente sont terminées en raison de la présence de O_DSYNC). La spécification des indicateurs O_RSYNC et O_SYNC lors de l'ouverture du fichier entraîne des opérations de lecture synchronisées ultérieures avec l'intégrité du fichier (c'est-à-dire qu'avant la fin de la lecture, toutes les entrées de fichier en attente sont terminées en raison de la présence d'O_SYNC).
Avant la sortie de la version 2.6.33 du noyau, les indicateurs O_DSYNC et O_RSYNC n'étaient pas implémentés sous Linux et ces constantes étaient définies dans les fichiers d'en-tête glibc comme définissant l'indicateur O_SYNC. (Dans le cas de O_RSYNC, ce n'était pas vrai, car O_SYNC n'affecte aucune caractéristique fonctionnelle des opérations de lecture.)
À partir de la version 2.6.33 du noyau, Linux implémente l'indicateur O_DSYNC, et l'implémentation de l'indicateur O_RSYNC sera probablement ajoutée dans les futures versions du noyau.
Avant la sortie du noyau 2.6.33 sous Linux, il n'y avait pas d'implémentation complète de la sémantique O_SYNC. Au lieu de cela, l'indicateur O_SYNC a été implémenté en tant que O_DSYNC. Dans les applications liées aux anciennes versions de la bibliothèque GNU C pour les noyaux plus anciens, sur les versions Linux 2.6.33 et ultérieures, l'indicateur O_SYNC se comporte toujours comme O_DSYNC. Cela est fait pour maintenir le comportement familier de ces programmes. (Pour préserver la compatibilité binaire en amont dans le noyau 2.6.33, le drapeau O_DSYNC a reçu l'ancien drapeau O_SYNC et le nouveau drapeau O_SYNC comprend le drapeau O_DSYNC (respectivement 04010000 et 010000 sur l'une des machines). Cela permet aux applications compilées avec de nouveaux fichiers d'en-tête. , obtenez au moins la sémantique O_DSYNC dans les noyaux publiés avant la version 2.6.33.)
13.4. Présentation de la mise en mémoire tampon des E / S
Dans la fig. La figure 13.1 montre le schéma de mise en mémoire tampon utilisé (pour les fichiers de sortie) par la bibliothèque stdio et le noyau, ainsi que les mécanismes de contrôle de chaque type de mise en mémoire tampon. Si vous descendez le graphique au milieu, vous verrez le transfert des données utilisateur par les fonctions de la bibliothèque stdio vers le tampon stdio, qui fonctionne dans l'espace mémoire de l'utilisateur. Lorsque ce tampon est plein, la bibliothèque stdio a recours à l'appel système write (), qui transfère les données vers le cache de tampon du noyau (situé dans la mémoire du noyau). En conséquence, le noyau lance une opération de disque pour transférer des données sur le disque.
Dans la partie gauche du circuit de la fig. 13.1 montre les appels qui peuvent être utilisés à tout moment pour forcer explicitement le vidage de n'importe quel tampon. La partie de droite montre les appels qui peuvent être utilisés pour effectuer automatiquement une réinitialisation en désactivant la mise en mémoire tampon dans la bibliothèque stdio ou en activant la sortie de fichier d'un mode d'exécution synchrone pour les appels système afin que chaque appel write () soit immédiatement vidé sur le disque.
13.5. Notification d'E / S du noyau
L'appel système posix_fadvise () permet au processus d'informer le noyau de sa méthode préférée d'accès aux données de fichier.
Le noyau peut (mais n'est pas obligé) d'utiliser les informations fournies par l'appel système posix_fadvise () pour optimiser son utilisation du cache de tampon, augmentant ainsi les performances d'E / S pour le processus et pour le système dans son ensemble. L'appel à posix_fadvise () n'affecte pas la sémantique du programme.
#define _XOPEN_SOURCE 600 #include <fcntl.h> int posix_fadvise(int fd, off_t offset, off_t len, int advice);
Renvoie en cas de succès 0 ou un nombre d'erreur positif lorsqu'il se produit
L'argument fd est un descripteur de fichier qui identifie le fichier pour lequel le noyau doit être contacté. Les arguments offset et len identifient la zone du fichier à laquelle se rapporte la notification: offset indique l'offset initial de la zone et len indique sa taille en octets. La définition de len à 0 signifie que tous les octets sont signifiés, commençant par offset et se terminant par la fin du fichier. (Dans les versions du noyau antérieures à 2.6.6, la valeur 0 pour len était interprétée littéralement comme 0 octet.)
L'argument de conseil montre la nature prévue de l'accès du processus au fichier. Il est défini avec l'une des valeurs suivantes.
POSIX_FADV_NORMAL - le processus n'a pas de notification spéciale concernant les modèles de traitement. Il s'agit du comportement par défaut si aucune notification n'est donnée pour le fichier. Sous Linux, cette opération définit la fenêtre pour lire de manière proactive les données d'un fichier à leur taille d'origine (128 Ko).
POSIX_FADV_SEQUENTIAL - le processus implique la lecture séquentielle des données des décalages plus petits vers les plus grands. Sous Linux, cette opération définit la fenêtre pour lire de manière proactive les données d'un fichier pour doubler sa valeur d'origine.
POSIX_FADV_RANDOM - le processus implique l'accès aux données dans un ordre aléatoire. Sous Linux, cette option désactive la lecture proactive des données d'un fichier.
POSIX_FADV_WILLNEED - le processus implique d'accéder à la zone spécifiée du fichier dans un avenir proche. Le noyau lit les données de manière préventive pour remplir le cache de tampon avec des données de fichier dans la plage spécifiée par les arguments offset et len. Les appels read () ultérieurs au fichier ne bloquent pas les E / S disque, mais récupèrent simplement les données du cache de tampon. Le noyau ne fait aucune garantie quant à la durée pendant laquelle les données extraites du fichier sont dans le cache de tampon. Si pendant le fonctionnement d'un autre processus ou noyau il y a un besoin particulier de mémoire, la page sera éventuellement réutilisée. En d'autres termes, si la mémoire est en forte demande, nous devons garantir un petit intervalle de temps entre l'appel à posix_fadvise () et l'appel (ou les appels) suivant à read (). (La fonctionnalité équivalente à l'opération POSIX_FADV_WILLNEED est fournie par l'appel système readahead () spécifique à Linux.)
POSIX_FADV_DONTNEED - le processus n'implique pas d'appels à la zone de fichiers spécifiée dans un avenir proche. De cette façon, le noyau est informé qu'il peut libérer les pages de cache correspondantes (le cas échéant). Sous Linux, cette opération s'effectue en deux étapes. Premièrement, si la file d'attente d'écriture sur le périphérique hôte n'est pas remplie d'une série de demandes, le noyau rejette toutes les pages de cache modifiées dans la zone spécifiée. Le noyau tente alors de libérer toutes les pages de cache de la zone spécifiée. Pour les pages modifiées dans cette zone, la deuxième étape ne se terminera avec succès que si elles ont été enregistrées sur le périphérique de base au cours de la première étape, c'est-à-dire que la file d'attente d'enregistrement sur le périphérique n'est pas pleine. Étant donné que l'application ne peut pas vérifier l'état de la file d'attente sur le périphérique, vous pouvez garantir que les pages de cache sont libérées en appelant fsync () ou fdatasync () sur le descripteur fd avant d'appliquer POSIX_FADV_DONTNEED.
POSIX_FADV_NOREUSE - le processus implique un accès unique aux données dans la zone spécifiée du fichier, sans les réutiliser. Ainsi, le noyau est informé qu'il peut libérer des pages après un seul accès à celles-ci. Sous Linux, cette opération est actuellement ignorée.
La spécification posix_fadvise () n'apparaissait que dans SUSv3 et cette interface n'est pas prise en charge par toutes les implémentations UNIX. Sous Linux, l'appel posix_fadvise () est fourni depuis la version 2.6 du noyau.
13.6. Contournement du cache de tampon: E / S directes
À partir de la version 2.4 du noyau, Linux permet à une application de contourner le cache de tampon lors de l'exécution des E / S disque en déplaçant les données directement de l'espace mémoire de l'utilisateur vers un fichier ou un périphérique de disque. Parfois, ce mode est appelé E / S directes ou non traitées.
Les informations fournies ici concernent uniquement Linux et ne sont pas standardisées dans SUSv3. Cependant, certaines options d'accès direct aux E / S pour les périphériques ou les fichiers sont fournies par la plupart des implémentations UNIX.
Parfois, les E / S directes sont mal comprises comme moyen d'atteindre des performances d'E / S élevées. Mais pour la plupart des applications, l'utilisation d'E / S directes peut réduire considérablement les performances. Le fait est que le noyau effectue plusieurs optimisations pour augmenter les performances d'E / S grâce à l'utilisation d'un cache de tampon, y compris la lecture séquentielle proactive des données, l'exécution d'E / S dans des grappes de blocs de disques et la possibilité pour les processus d'accéder au même volume le même fichier, partagez les tampons dans le cache. Tous ces types d'optimisation lors de l'utilisation d'E / S directes sont perdus. Il est destiné uniquement aux applications ayant des exigences d'E / S spécialisées, par exemple, des systèmes de gestion de base de données qui effectuent leur propre mise en cache et optimisation d'E / S, et qui n'ont pas besoin du noyau pour perdre du temps CPU et de la mémoire pour effectuer les mêmes tâches.
L'entrée-sortie directe peut être effectuée soit par rapport à un seul fichier, soit par rapport à un périphérique de bloc (par exemple, un disque). Pour ce faire, lors de l'ouverture d'un fichier ou d'un périphérique à l'aide de l'appel open (), l'indicateur O_DIRECT est spécifié.
L'indicateur O_DIRECT fonctionne depuis la version 2.4.10 du noyau. L'utilisation de cet indicateur n'est pas prise en charge par tous les systèmes de fichiers et versions du noyau Linux. La plupart des systèmes de fichiers de base prennent en charge l'indicateur O_DIRECT, mais de nombreux systèmes de fichiers non UNIX (tels que VFAT) ne le font pas. Vous pouvez tester la prise en charge de cette fonctionnalité en testant le système de fichiers sélectionné (si le système de fichiers ne prend pas en charge O_DIRECT, l'appel à open () échouera avec une erreur EINVAL) ou en examinant le code source du noyau pour cela.
Si un processus a ouvert le fichier avec l'indicateur O_DIRECT et l'autre de la manière habituelle (c'est-à-dire en utilisant le cache de tampon), il n'y a pas de cohérence entre le contenu du cache de tampon et les données lues ou écrites via les E / S directes. Une telle évolution doit être évitée.
Des informations sur la méthode obsolète (désormais déconseillée) pour obtenir un accès brut à un périphérique de disque peuvent être trouvées sur la page de manuel raw (8).
Restrictions d'alignement pour les E / S directesÉtant donné que les E / S directes (à la fois sur les périphériques de disque et par rapport aux fichiers) impliquent un accès direct au disque, certaines limitations doivent être respectées lors de l'exécution des E / S.
- le tampon de données portable doit être aligné sur la bordure de la mémoire, un multiple de la taille du bloc.
- Le décalage dans le fichier ou dans l'appareil à partir duquel les données transférées commencent doit être un multiple de la taille du bloc.
- La longueur des données transférées doit être un multiple de la taille du bloc.
Le non-respect de l'une de ces restrictions entraînera une erreur EINVAL. Dans la liste ci-dessus, la taille de bloc fait référence à la taille du bloc physique de l'appareil (généralement 512 octets).
Lors de l'exécution d'E / S directes sous Linux 2.4, plus de restrictions sont imposées que sous Linux 2.6: l'alignement, la longueur et le décalage doivent être un multiple de la taille du bloc logique du système de fichiers utilisé. (En règle générale, la taille des blocs logiques dans un système de fichiers est de 1024, 2048 ou 4096 octets.)
Exemple de programme13.1 O_DIRECT . , ( ) , , , , , , , read(). 4096 .
, :
$ ./direct_read /test/x 512 512 0 Read 512 bytes $ ./direct_read /test/x 256 ERROR [EINVAL Invalid argument] read 512 $ ./direct_read /test/x 512 1 ERROR [EINVAL Invalid argument] read 512 $ ./direct_read /test/x 4096 8192 512 Read 4096 bytes $ ./direct_read /test/x 4096 512 256 ERROR [EINVAL Invalid argument] read 512
13.1 , , , memalign(). memalign() 7.1.4.
#define _GNU_SOURCE /* O_DIRECT <fcntl.h> */ #include <fcntl.h> #include <malloc.h> #include "tlpi_hdr.h" int main(int argc, char *argv[]) { int fd; ssize_t numRead; size_t length, alignment; off_t offset; void *buf; if (argc < 3 || strcmp(argv[1], "–help") == 0) usageErr("%s file length [offset [alignment]]\n", argv[0]); length = getLong(argv[2], GN_ANY_BASE, "length"); offset = (argc > 3) ? getLong(argv[3], GN_ANY_BASE, "offset") : 0; alignment = (argc > 4) ? getLong(argv[4], GN_ANY_BASE, "alignment") : 4096; fd = open(argv[1], O_RDONLY | O_DIRECT); if (fd == -1) errExit("open"); /* memalign() , , . 'buf' , 'alignment', . , , , , 256 , , 512- . '(char *)' ( 'void *', memalign(). */ buf = (char *) memalign(alignment * 2, length + alignment) + alignment; if (buf == NULL) errExit("memalign"); if (lseek(fd, offset, SEEK_SET) == -1) errExit("lseek"); numRead = read(fd, buf, length); if (numRead == -1) errExit("read"); printf("Read %ld bytes\n", (long) numRead); exit(EXIT_SUCCESS); } _______________________________________________________________filebuff/direct_read.c
»
»
Contenu»
Extrait20% —
Linux