Multiplexage des entrées/sorties
Introduction
Il est usuel que des applications doivent attendre sur des périphériques d’entrées/sorties, tels que clavier, souris, interface de communication, etc.
Ces périphériques sont représentés dans l’application par un descripteur de
fichier, lequel offre généralement un accès au périphérique par un des appels
système read
ou write
Mécanismes à disposition :
- Utilisation des services non bloquants
- Implémentation d’un thread par entrée/sortie
- Utilisation de services offrant un multiplexage des entrées/sorties
Mécanismes - Services non bloquants
Un chemin possible pour solutionner cette problématique est l’utilisation de services non bloquants pour scruter chaque entrée/sortie séquentiellement.
Ces services permettent de tester le périphérique si son accès est possible ou pas. Si l’accès est disponible, alors l’entrée/sortie est traitée.
Carences
- Usage excessif du processeur pour la scrutation
- Complexité du logiciel
Mécanismes - Multi-threading
Une deuxième voie possible consiste à utiliser les services bloquants et à créer un thread par entrée/sortie
Cette technique permet d’éviter que l’indisponibilité d’une entrée/sortie bloque le traitement des autres
Carences :
- Nombre de threads nécessaires pour réaliser l’application
- Complexité du logiciel pour synchroniser les différentes données
- Complexité lors du debugging du logiciel
Mécanismes - Multiplexage des entrées/sorties
Les systèmes Unix, et plus particulièrement Linux, proposent des services autorisant le multiplexage des entrées/sorties. Ces services permettent de sélectionner un catalogue d’entrées/sorties à traiter et de le passer au noyau. Ce dernier informe le processus sur ceux disponibles pour traitement.
Linux propose 3 services :
select()
(compatible avec les systèmes UNIX)poll()
(pas traité ici)epoll()
(Linux spécifique)
select - Service
Linux propose avec l’appel système select()
une interface simple pour le
multiplexage d’entrées/sorties.
#include <sys/select.h>
#include <sys/time.h>
int select (int n,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
FD_CLR(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_ZERO(fd_set *set)
- La fonction
select()
permet d’attendre jusqu’à ce qu’un des descripteurs de fichiers soit prêt pour effectuer l’opération de lecture ou d’écriture souhaitée. En cas de succès, la fonction retourne le nombre de descripteurs de fichiers disponibles. - Linux man page: https://man.cx/select(2)
Arguments
- L’argument
n
spécifie le numéro du plus grand descripteur de fichiers plus un. - Les arguments
readfds
,writefds
etexceptfds
contiennent la liste des descripteurs de fichiers en lecture, écriture respectivement exception sur lesquels la méthodeselect()
doit attendre. En retour, ces arguments indiquent les descripteurs disponibles pour le service demandé. - L’argument
timeout
permet d’attendre sur un événement avec un temps limite. Sitimeout
est nonNULL
, lastruct timeval
permet de spécifier le temps en microsecondes, par exemple pour attendre 10 seconds au maximum:struct timeval timeout; timeout.tv_sec = 10; timeout.tv_usec = 0;
Macros
FD_ZERO
initialise la liste des descripteurs de fichiersFD_CLR
enlève un descripteur de fichiers de la listeFD_SET
ajoute un descripteur de fichiers dans la listeFD_ISSET
teste si un descripteur de fichiers est contenu dans liste
select - Exemple
fd_set fd_in, fd_out;
FD_ZERO(&fd_in);
FD_ZERO(&fd_out);
// monitor fd1 for input events and fd2 for output events
FD_SET(fd1, &fd_in);
FD_SET(fd2, &fd_out);
// find out which fd has the largest numeric value
int largest_fd = (fd1 > fd2) ? fd1 : fd2;
// wait up to 5 seconds
struct timeval tv = {
.tv_sec = 5,
.tv_usec = 0,
};
// wait for events
int ret = select(largest_fd + 1, &fd_in, &fd_out, NULL, &tv);
// check if select actually succeed
if (ret == ‐1) {
// report error and abort
} else if (ret == 0) {
// timeout; no event detected
} else {
if (FD_ISSET(fd1, &fd_in)) {
// input event on sock1
}
if (FD_ISSET(fd2, &fd_out)) {
// output event on sock2
}
}
Service epoll
Le service epoll
a été introduit sur Linux pour pallier les carences du
service select()
et pour offrir une interface performante lorsqu’un grand
nombre de descripteurs de fichiers doit être traité.
operation | syscall |
---|---|
Création d’un contexte epoll |
epoll_create1 |
Contrôle du contexte epoll |
epoll_ctl |
Attente sur des événements | epoll_wait |
Création d’un contexte epoll
La création d’un contexte epoll
est obtenue à l’aide de l’appel système
epoll_creat1()
#include <sys/epoll.h>
int epoll_create1(int flags);
Exemple
int epfd = epoll_create1(0);
if (epfd == -1)
/* error*/
Comportement
- La fonction
epoll_create1()
permet de créer un nouveau contexteepool
pour le multiplexage des entrées/sorties. Les erreurs pouvant survenir lors de la création d’un contexte epoll sontEINVAL
,EMFILE
,ENFILE
ouENOMEM
.
Contrôle d’un contexte epoll
Le contrôle d’un contexte epoll
est obtenu à l’aide de l’appel système
epoll_ctl()
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
Exemple
struct epoll_event event = {
.events = EPOLLIN | EPOLLOUT,
.data.fd = fd,
};
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
if (ret == -1)
/* error*/
Comportement
- La fonction
epoll_ctl()
permet d’ajouter ou de retirer un descripteur de fichiers au contexteepool
. Il est également possible de modifier les événements que l’on souhaite surveiller pour un descripteur de fichiers donné.
Arguments
- L’argument
epfd
spécifie le contexteepoll
à contrôler - L’argument
op
indique l’opération que l’on souhaite exécuter avec le contexteepoll
pour le descripteur de fichiersfd
.EPOLL_CTL_ADD
ajouter un nouveau descripteur de fichiersEPOLL_CTL_DEL
retirer un descripteur de fichiersEPOLL_CTL_MOD
modifier les événements à surveiller
- L’argument
event
, à l’aide de lastruct epoll_event
, permet de spécifier les événements (attributevents
) que l’on souhaite surveiller pour le descripteur de fichiersfd
. Cette même structure permet également d’attacher un paramètre supplémentaire (attributdata
) pour identifier le descripteur de fichiers ayant levé un événement.struct epoll_event { __u32 events; union { void *ptr; int fd; __u32 u32; __u64 u64; } data; };
- Les événements principaux que l’on peut surveiller sont
EPOLLERR
une condition d’erreur a été levéeEPOLLIN
un fichier virtuel est disponible lectureEPOLLOUT
un ifchier virtuel est disponible en écritureEPOLLPRI
des données prioritaires out-of-band sont diponibles
epoll - Attente sur la levée d’événements
L’attente sur des événements pour un contexte epoll
est réalisée à l’aide de
l’appel système epoll_wait()
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
Exemple
struct epoll_event events[2];
int nr = epoll_wait(epfd, events, 2, -1);
if (nr == -1)
/* error*/
for (int i=0; i<nr; i++) {
printf ("event=%ld on fd=%d\n", events[i].events, events[i].data.fd);
// operation on events[i].data.fd can be performed without blocking...
}
Comportement
- La fonction
epoll_wait()
permet d’attendre jusqu’à ce que un ou plusieurs événements soient levés pour les descripteurs de fichiers attachés au contexteepoll
.
Arguments
- L’argument
epfd
spécifie le contexteepoll
à surveiller - L’argument
events
spécifie l’adresse d’un tableau pour stocker la liste d’événements ayant été levés et pouvant être traités. Si le nombre d’événements levés dépasse la taille du tableau, des appels successifs au serviceepoll_wait
fournissent les événements restants. - L’argument
maxevents
indique la taille maximale du tableau - L’argument
timeout
spécifie l’intervalle de temps maximal en millisecondes à attendre sur un événement. Si la valeur detimeout
est-1
, la fonction ne retournera que si un événement a été levé.