Linux
n°91 — Juin 2003
Traduction française: Florence Cousin
Relecture de la version française : Deny
Copyright © 2008 Jeff Tranter
Copyright © 2008 Florence Cousin
Copyright © 2008 Deny
Article paru dans le n°91 de la Gazette Linux
de juin 2003.
Article publié sous Open Publication License. La Linux
Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.
Table des matières
L'appel système sendfile est un ajout récent au noyau Linux
. Il apporte des gains de performance significatifs aux applications qui nécessitent des transferts de fichiers efficaces. Dans cet article je vais étudier sendfile, ce qu'il fait, et comment l'utiliser. Ceci sera illustré par quelques exemples.
Une application de type serveur, par exemple un serveur web, passe la plus grande partie de son temps à transférer des fichiers stockés sur un disque vers une connexion réseau, connectée à un client sur lequel tourne un navigateur. Un pseudo-code simple pour le transfert de données ressemblerait à cela:
ouvrir la source (fichier disque) ouvrir la destination (connexion réseau) tant qu'il y a des données à transférer: lire les données de la source et le mettre dans un tampon écrire les données du tampon vers la destination fermer la source et la destination
La lecture et l'écriture des données utiliseront typiquement les appels système read et write, ou des fonctions de librairies construites sur ces appels.
Si nous suivons le cheminement des données du disque vers le réseau, elles sont copiées plusieurs fois. Chaque fois que l'appel système read est invoqué, les données doivent être transférées du disque physique vers un tampon noyau (en utilisant typiquement le DMA). Ensuite elles doivent être copiées dans la mémoire tampon utilisée par l'application. Quand write est appelé, les données dans la mémoire tampon de l'application doivent être transférées dans la mémoire du noyau et ensuite de la mémoire du noyau vers le périphérique matériel (par exemple la carte réseau). Chaque fois qu'un appel système est invoqué par un programme utilisateur, il se produit un changement de contexte entre le mode utilisateur et le mode noyau. Ceci est une opération relativement coûteuse. S'il y a beaucoup d'appels à read et write dans le programme, beaucoup de changements de contexte seront requis.
Le fait de copier des données entre la mémoire du noyau et la mémoire de l'application et réciproquement est redondant si les données n'ont pas besoin d'être modifiées. Beaucoup de systèmes d'exploitation, y compris Windows NT
, FreeBSD
et Solaris
proposent ce qui est appelé un appel zéro-copie qui peut effectuer un transfert en une seule opération. Les anciennes versions de Linux
étaient critiquées car elles n'offraient pas cette possibilité, jusqu'à ce que ceci soit implémenté dans les séries 2.2 du noyau. Cet appel est maintenant utilisé par des applications serveur populaires, telles Apache et Samba
L'implémentation de sendfile varie selon les systèmes d'exploitation. Dans la suite de cet article nous nous restreindrons à la version Linux
. Notez qu'il existe un outil de transfert de fichiers appelé sendfile : celui-ci n'a rien à voir avec l'appel système.
Pour utiliser sendfile, incluez le fichier d'en-tête <sys/sendfile.h>, qui déclare une fonction avec le prototype suivant:
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
Les paramètres sont les suivants:
out_fd : un descripteur de fichiers, ouvert en écriture, pour écrire les données.
in_fd : un descripteur de fichier ouvert en lecture, pour lire les données.
offset : le décalage dans le fichier d'entrée où on veut commencer le fichier (par exemple une valeur de 0 indique le début du fichier). Ce paramètre est passé à la fonction et mis à jour par la fonction en retour.
count : le nombre d'octets à transférer.
La fonction retourne le nombre d'octets écrits ou -1 si une erreur s'est produite.
Sous Linux
, les descripteurs de fichiers peuvent être de vrais fichiers ou des fichiers spéciaux, comme des interfaces de connexion réseau. L'implémentation de sendfile nécessite actuellement que le descripteur de fichier d'entrée corresponde à un vrai fichier ou à un périphérique qui supporte nmap. Cela signifie qu'il ne peut pas être socket réseau, par exemple. Le descripteur de fichier de sortie peut correspondre à une une interface de connexion, et c'est d'ailleurs habituellement dans ce contexte qu'il est utilisé.
Regardons un exemple simple qui illustre l'utilisation de sendfile. Le listing 1 présente fastcp.c, un programme simple de copie de fichiers qui utilise sendfile pour réaliser une copie de fichiers.
Le listing suivant est légèrement abrégé pour la clarté. Le listing complet disponible ici contient en plus des vérifications d'erreur supplémentaires et les directives d'include nécessaires à la compilation.
Listing 1: fastcp.c 1 int main(int argc, char **argv) { 2 int src; /* descripteur de fichier du fichier source */ 3 int dest; /* descripteur de fichier du fichier destination */ 4 struct stat stat_buf; /* conserve l'information sur le fichier d'entree */ 5 off_t offset = 0; /* decalage utilise par sendfile (en octets) */ 6 7 /* vérifie que le fichier source existe et peut être ouvert */ 8 src = open(argv[1], O_RDONLY); 9 /* récupère la taille et les permissions du fichier source */ 10 fstat(src, &stat_buf); 11 /* ouvre le fichier destination */ 12 dest = open(argv[2], O_WRONLY|O_CREAT, stat_buf.st_mode); 13 /* copie les fichiers avec sendfile */ 14 sendfile (dest, src, &offset, stat_buf.st_size); 15 /* nettoyage et sortie */ 16 close(dest); 17 close(src); 18 }
A la ligne 8 on ouvre le fichier destination, passé comme premier argument en ligne de commande. A la ligne 10 on récupère l'information sur le fichier en utilisant fstat, puisque nous aurons besoin plus tard de la taille du fichier et de ses permissions. A la ligne 12 on ouvre le fichier de sortie en écriture. La ligne 14 réalise l'appel à sendfile, en passant comme argument les descripteurs des fichiers d'entrée et de sortie, le décalage (zéro dans ce cas) et spécifiant le nombre d'octets à transférer, en utilisant la taille du fichier d'entrée. Ensuite on ferme les fichiers aux lignes 16 et 17.
Essayez de compiler ce programme (en utilisant la version complète ici). Je suggère de faire des expériences en l'utilisant pour copier différents types de fichiers, comme indiqué ci-dessous, et de regarder quels périphériques sources et destination supportent sendfile:
d'un fichier disque vers un autre fichier disque.
en utilisant des fichiers placés sur des disques ou des partitions différents.
d'un cédérom monté vers un fichier.
d'un fichier disque vers /dev/null
ou /dev/full
de /dev/zero
ou /dev/null
vers un fichier disque.
d'un fichier disque vers une disquette (/dev/fd0
)
Le premier exemple était simple, mais pas vraiment représentatif de l'utilisation typique de sendfile, utilisant une destination réseau. Le second exemple illustre l'envoi d'un fichier via socket réseau. Ce programme est plus long, principalement à cause de l'initialisation nécessaire pour utiliser les interfaces de connexion, c'est pourquoi je ne l'inclus pas en ligne. Vous pouvez voir le code source complet ici.
Le programme, appelé server, effectue les choses suivantes:
Ecoute sur socket réseau en attendant qu'un client se connecte.
Quand un client se connecte, attend que le client envoie un nom de fichier.
Envoie le fichier spécifié au client en utilisant sendfile
Déconnecte le client et attend une autre connexion.
Je suppose ici que les bases de la programmation des interfaces de connexion réseau vous sont familières. Si ce n'est pas le cas, il y a beaucoup de bons livres sur le sujet, tel UNIX Network Programming de Richard Stevens.
Le serveur utilise arbitrairement le port 1234 mais vous pouvez le spécifier comme option de la ligne de commande. Démarrez le serveur en le lançant (./server). Pour jouer le rôle du client, vous pouvez utiliser le programme telnet. Lancez-le d'une autre console pendant que le serveur tourne, en spécifiant le nom de l'hôte et le numéro de port (par exemple telnet localhost 1234). Une fois que telnet indique qu'il s'est connecté, entrez le nom d'un fichier existant, par exemple /etc/hosts
. Le serveur devrait renvoyer le contenu du fichier et ensuite fermer la connexion.
Le serveur devrait continuer à tourner de façon à ce que vous puissiez vous connecter à nouveau. Si vous utilisez quit
comme nom de fichier, alors le serveur s'arrêtera. Si vous avez une autre machine sur le réseau, vérifiez que vous pouvez vous connecter au serveur et transférer un fichier à partir d'une autre machine.
Notez que ceci est un exemple très simpliste d'un serveur : il supporte seulement un client à la fois et ne fait que peu de corrections d'erreurs, se terminant quand une erreur se produit. On peut également réaliser d'autres optimisation de performance au niveau de la couche TCP, qui sont hors du champ de ce qui peut être traité ici.
L'appel système sendfile facilite les performances des transferts de fichiers réseau, qui sont de mise pour les applications telles que les serveurs ftp et web. Si vous développez une application serveur, considérez sendfile comme un moyen d'accélérer l'exécution de votre code. Hors du contexte des serveurs, c'est une fonctionnalité intéressante en elle-même, et vous pourrez trouver d'autres moyens innovants pour l'utiliser.
Enfin, après tout ce discours sur sendfile, je vais vous laisser avec une question à méditer : pourquoi n'existe-t-il pas en retour d'appel système receivefile?
Les pages man de sendfile (2)
Les sources du noyau pour l'implémentation de sendfile.
Jeff a employé, écrit au sujet de, et contribué à
Linux
depuis 1992. Il travaille pour Xandros Corporation à Ottawa, au Canada.
L'adaptation française de ce document a été réalisée dans le cadre du Projet de traduction de la Gazette Linux.
Vous pourrez lire d'autres articles traduits et en apprendre plus sur ce projet en visitant notre site : http://wiki.traduc.org/Gazette_Linux.
Si vous souhaitez apporter votre contribution, n'hésitez pas à nous rejoindre, nous serons heureux de vous accueillir.