SYSTÈME D’EXPLOITATION I SIF-1015
Contenu du cours 8 Serveur itératifs et leurs faiblesses Serveurs concurrents Implémenter avec des processus Implémenter par des threads Implémenter avec la gestion des événements LECTURES: Chapitre 14 (Mitchell) Chapitres 5 et 8 (Beck) Chapitre 16 (Johnson) http://csapp.cs.cmu.edu/
Serveurs itératifs Les serveurs itératifs traitent une requête à la fois client 1 server client 2 call connect call connect call accept ret connect ret accept call read write ret read close close call accept ret connect ret accept call read write ret read close close
Serveurs itératifs: Faiblesses client 1 server client 2 call accept call connect ret connect ret accept call fgets call read Server blocks waiting for data from Client 1 call connect User goes out to lunch Client 1 blocks waiting for user to type in data Client 2 blocks waiting to complete its connection request until after lunch! Solution: Utilisation de serveurs concurrents Les serveurs concurrents démarrent plusieurs flots de traitement concurrents pour servir plusieurs clients simultanément
Serveurs concurrents Les serveurs concurrents traitent plusieurs requêtes concurremment client 1 server client 2 call accept call connect call connect ret connect ret accept call fgets child 1 fork call accept call read User goes out to lunch Client 1 blocks waiting for user to type in data ret connect call fgets ret accept fork child 2 write call read call read ... write end read close close
Serveurs concurrents: Trois mécanismes possibles 1. Processus Le noyau offre la possibilité de créer des flots de traitement avec des espaces d’adresses distincts Contrôle des processus et signaux par des appels système standards d’Unix 2. Threads Le noyau offre la possibilté de démarrer plusieurs flots d’instruction dans un processus Chaque thread a sa pile et son environnement volatile (registres) Les threads partagent le même espace d’adresses et les fichiers ouverts Interface Pthreads est utilisée
Serveurs concurrents: Trois mécanismes possibles 3. Multiplexage d’I/O avec l’appel système select() Traitement manuelle entrelacée de plusieurs connexions Utilisation de l’appel système Unix select() pour déterminer si un socket est en activité Forme de la concurrence au niveau des applications Populaire pour la conception de serveurs hautes performances
Serveurs concurrents: Par processus
Serveurs concurrents: Par processus Ces serveurs doivent éliminer les processus enfants restés zombie Permet d’éviter des fuites de mémoire fatales Ces serveurs doivent fermer (close) leurs copies de connfd. Le noyau garde une référence de chaque socket Après un fork(), refcnt(connfd) = 2 Une connexion n’est pas fermée tant que refcnt(connfd)!=0
Serveurs concurrents: Par processus + Supportent plusieurs connexions concurrentes + Modèle de partage propre descripteurs (non) table des fichiers (oui) variables globales(non) + Simple - Contrôle de processus plus lourd - Partage de données entre processus pas évident Requiert les IPC (interprocess communication) FIFO (named pipes), mémoires partagées et sémaphores Les threads offrent des flots de traitement plus afficaces et permettent le partage de variables
Rappel sur l’interface Pthreads Création et eliminations de threads pthread_create pthread_join Déterminer l’id du thread courant pthread_self Terminaison d’un threads pthread_cancel pthread_exit exit [terminer tous les threads] , ret [terminer le thread courant] Synchroniser l’accès aux variables partagées pthread_mutex_init pthread_mutex_[un]lock pthread_cond_init pthread_cond_[timed]wait
Serveurs concurrents: Par threads
Serveurs concurrents: Par threads Doivent appeler Pthread_detach() pour éviter les fuites de mémoire En tout temps, un thread est soit joinable ou detached Un thread joinable peut être tué ou éliminé par d’autres threads Doivent être éliminés (reaped avec pthread_join) pour libérer la mémoire Un thread Detached ne peut être tué ou éliminé par d’autres threads Les ressources sont automatiquement libérées à la terminaison du thread Un thread est joinable par défaut Attention aux partages non voulues Par exemple, qu’arrive-t-il si nous passons l’adresse de connfd au thread ? Pthread_create(&tid, NULL, thread, (void *)&connfd); Les fonctions appelées par un thread doivent être thread-safe
Serveurs concurrents: Par threads + Facile d’échanger des informations entre threads Ex: Information de logging, cache de fichiers. + Les threads sont plus efficaces --- Le partage non intentionnel peut introduire des erreurs subtiles La facilité de partage des threads est à la fois leur grande force et aussi leur grande faiblesse
Serveurs concurrents: Par événement Approche basée sur l’occurrence d’événements: Maintient d’un pool de descripteurs de socket connectés Repétion de cette suite d’opérations pour toujours: Utiliser l’appel système select() d’Unix qui attend tant que: (a) qu’une nouvelle requête de connexion arrive sur le socket listener (b) des données nouvelles arrivent sur un socket déjà connecté SI (a), ajouter une nouvelle connexion dans le pool de connexions SI (b), lire les données disponibles à partir de la connexion Fermer la connexion au EOF et la sortir du pool
Appel système select() select() dort jusqu’à ce qu’un ou plusieurs descripteurs de fichier dans l’ensemble readset soient prêts à être lus #include <sys/select.h> int select(int maxfdp1, fd_set *readset, NULL, NULL, NULL); readset vecteur de bits (max FD_SETSIZE bits) qui indique l’appartenance à un ensemble de descripteurs SI le bit k est à 1, alors le descripteur k est un membre de l’ensemble de descripteurs maxfdp1 nombre maximum de descripteurs dans l’ensemble de descripteurs plus 1. testez les descripteurs 0, 1, 2, ..., maxfdp1 – 1 pour connaître leur appartenance select() retourne le nombre de descripteurs prêts et initialise (à 1) chaque bit du readset pour indiquer l’état prêt des descripteurs correspondant
Appel système select() readset Ensemble de descripteurs vide Ensemble de descripteurs: stdin et un socket select() retourne un vecteur avec des descripteurs prêts en lecture Activité au clavier
Fonctions de manipulation d’ensemble de descripteurs void FD_ZERO(fd_set *fdset); Met à 0 tous les bits dans le fdset void FD_SET(int fd, fd_set *fdset); Met à 1 le bit fd dans le fdset void FD_CLR(int fd, fd_set *fdset); Met à 0 le bit fd dans le fdset int FD_ISSET(int fd, *fdset); Le bit fd dans le fdset est-il à 1?
Exemple d’utilisation du select(): Lecture au clavier et TIMEOUT #include <sys/select.h> int select(int maxfdp1, fd_set *readset, NULL, NULL, struct timeval *timeout); Exécution: >./select timeout hello read 6 from keyboard: hello SIF-1015 read 9 from keyboard: SIF-1015 ^D keyboard done
Exemple d’utilisation du select(): Lecture au clavier et TIMEOUT
Exemple d’utilisation du select(): Serveur simple
Exemple d’utilisation du select(): Serveur supportant plusieurs clients
Exemple d’utilisation du select(): Serveur supportant plusieurs clients
Exemple d’utilisation du select(): Serveur supportant plusieurs clients
Exemple d’utilisation du select(): Serveur supportant plusieurs clients > server5 & [7] 1670 server waiting >client3 & client3 & client3 & ps –ax [8] 1671 [9] 1672 [10] 1673 adding client on fd 4 adding client on fd 5 adding client on fd 6 PID TTY STAT TIME COMMAND 1670 pp0 S 0:00 server5 1671 pp0 S 0:00 client3 1672 pp0 S 0:00 client3 1673 pp0 S 0:00 client3 1674 pp0 S 0:00 ps -ax serving client on fd 4 server waiting char from server = B serving client on fd 5 serving client on fd 6 removing client on fd 4 removing client on fd 5 removing client on fd 6 [8] Done client3 [9]- Done client3 [10]+ Done client3
Serveur concurrent: Par événement Déclaration des structures et en-tête des fonctions
Serveur concurrent: Par événement
Serveur concurrent: Par événement
Serveur concurrent: Par événement
Serveur concurrent: Par événement
Serveur concurrent: Par événement + Un seul flot de traitement et de contrôle + Peut être plus facilement corrigé avec un debugger + Pas de surcharge de contrôle des threads et des processus Conception idéale pour les serveurs WEB haute performance et les moteurs de recherche - Plus difficile à coder que les approches par processus ou par threads - Vulnérable aux attaques du type: denial of service