Linux/Unix : Les communications entre processus - IPC POSIX

14 janvier 2014 rdorigny 0 commentaires

Nous avons étudié dans le chapitre précédent la gestion des processus. Voyons maintenant comment les faire discuter entre eux.

Plusieurs mécanismes sont rassemblés sous le terme IPC (Inter Processus Communication) qui regroupe un ensemble de mécanismes permettant à des processus concurrents (ou distants) de communiquer.





Introduction

Il faut distinguer les IPC système V, plus ancien, ils offrent plus de possibilités mais sont plus complexes à utiliser; et les IPC POSIX (plus récents et plus simple à utiliser). Nous n'étudierons que les IPC POSIX car je considère qu'il faut toujours se tourner vers l'avenir.

Ces mécanismes peuvent être classés en trois catégories :
  • les outils permettant aux processus de s'échanger des données,
  • les outils permettant de synchroniser les processus, notamment pour gérer le principe de section critique,
  • les outils offrant directement les caractéristiques des deux premiers (ie : permettant d'échanger des données et de synchroniser des processus).

  • On distingue trois types d'IPC:
  • Les files de messages,
  • Les segments de mémoire partagée,
  • Les sémaphores.
  • 1)Les sémaphores

    Les sémaphores POSIX et System V fonctionnent selon le même principe, les POSIX sont plus simples et plus performants. On distingue deux types de sémaphores: nommés et non-nommés.

    Le principe des sémaphores est assez simple. Un sémaphore est un entier prenant la valeur 0 ou 1, auquel est associé deux opérations atomiques:
  • Opération P: cette opération abaisse le sémaphore (lock), en effet si l'entier est à 1,il est décrémenté, si l'entier est à 0, le processus est mis en sommeil,
  • Opération V: cette opération lève le sémaphore (unlock), l'entier est mis à 1, si l'entier est à 0 avant l'opération V, les processus endormis sont réveillés.

  • Les sémaphores constituent une solution pour résoudre le problème des accès multiples sur des ressources partagées entre activités. Globalement le concept est le suivant: le processus père créé un sémaphore qu'il initialise à 1, puis créé un processus fils. Ces deux processus seront concurrents. Aussi, pour pouvoir entrer en section critique, ils devront appliquer l'algorithme suivant : opération P sur le sémaphore, traitement de la section critique puis opération V.

    Avec Linux, on peut gérer un sémaphore pour plusieurs threads. Il existe une commande pour afficher les sémaphores et une autre pour les détruire:

    ipcs -s (affiche le tableau des sémaphores) ipcrm -s <semid> (détruit le sémaphore)

    1.1)Les sémaphores nommés

    Création :
    #include <semaphore.h> sem_t *sem_open(const char *name, int oflag); sem_t *sem_open(const char *name, int oflag,mode_t mode,unsigned int value);

    oflag spécifie le type d'accès (O_CREAT,O_EXCL,0), value repésente la valeur initiale.

    Destruction :
    #include <semaphore.h> sem_t *sem_close(sem_t *sem); //Ferme le sémaphore pointé par sem, sem_t *sem_unlink(const char *name); //On l'utilise lorsque l'utilisation du sémaphore est terminé //la fonction supprime la référence *name du système de fichiers.

    A noter que sous Linux, les sémaphores n'ont été implémentés que depuis la version 2.6 du noyau, Linux propose une pseudo système /dev/shm pour manipuler les objets POSIX.

    1.2)Les sémaphores non-nommés

    Création :
    #include <semaphore.h> int sem_init(sem_t *sem,int pshared,unsigned int value);

    sem_init() initialise le sémaphore non-nommé situé à l'adresse pointée par sem, retourne 0 en cas de succès et -1 sinon. Value initialise la valeur du sémaphore. L'argument pshared indique que le sémaphore sera partagé entre différents threads ou processus, si 0 le sémaphore sera partagé entre les activités.

    Destruction :
    #include <semaphore.h> int sem_destroy(sem_t *sem);

    1.3)Verrouiller/déverrouiller le sémaphore


    #include <semaphore.h> //Attente bloquante int sem_wait(sem_t *sem); //Attente non bloquante int sem_wait(sem_t *sem); //Déverrouiller le sémaphore int sem_post(sem_t *sem);

    La fonction sem_wait() décrémente le sémaphore s'il est positif, sinon il est reste en attente que le sémaphore soit disponible. La fonction sem_trywait() fait la même chose mais sans attente. Enfin, la fonction sem_post() incrémente le sémaphore pointé par sem et débloque l'attente des autres threads.

    Il est possible de disposer de disposer d'une attente limitée, si le sémaphore n'est toujours pas disponible, la fonction retourne -1 et rend la main.
    #include <semaphore.h> //Attente bornée int sem_timedwait(sem_t *sem); struct timespec{ time_t tv_sec; //pour les secondes long tv_nsec; //pour les nanosecondes };

    1.3)Exemples d'utilisation des sémaphores

    Le premier exemple pour une utilisation des sémaphores nommés. On créé deux thread qui sont mis en attentes immédiatement puis le processus père débloque le sémaphore pour chacun.
    #include <fcntl.h> #include <semaphore.h> #include <stdlib.h> #include <stdio.h> #define NBACTIVITE 2 sem_t * ptsema; void * fct(){ printf("Fils - TID : %dn",pthread_self()); //Attente du sémaphore sem_wait(ptsema); printf("Debut threadn"); sleep(10); printf("Fin threadn"); pthread_exit(NULL); } int main(){ pthread_t tid[2]; int i; printf("Pere - TID : %dn",pthread_self()); ptsema=sem_open("/mysema",O_CREAT,0600,0); for (i=0;i<NBACTIVITE;i++) pthread_create(tid+i,NULL,fct,NULL); sleep(5); sem_post(ptsema); //Débloque le premier thread sleep(5); sem_post(ptsema); //Débloque le second thread for (i=0;i<NBACTIVITE;i++) pthread_join(tid[i],NULL); sem_close(ptsema); system("ls -l /dev/shm"); sem_unlink("/mysema"); exit(0); }


    Un second exemple de code avec les sémaphore non-nommés ce coup-ci. On bloque le processus père après chaque création d'un fils puis on libère le père deux secondes après. Ce code permet au thread fils de ne pas être perturbé par le processus père.
    #include <fcntl.h> #include <semaphore.h> #include <stdlib.h> #include <stdio.h> #define NBACTIVITE 2 sem_t sema; void * fct(void * param){ int num; //Début de la section critique num=*((int *)param); printf("Fils n°%d - TID : %dn",num,pthread_self()); sleep(2); sem_post(&sema); //met le sémaphore à 1 et débloque le pére //Fin de section critique //Début du travail printf("Debut travail thread %dn",num); sleep(10); printf("Fin travail thread %dn",num); pthread_exit(NULL); } int main(){ pthread_t tid[2]; int i; sem_init(&sema,0,0); //Initialise le sémaphore à 0 for (i=0;i<NBACTIVITE;i++){ pthread_create(tid+i,NULL,fct,(void *)&i); sem_wait(&sema); // le processus pére est en attente } printf("Pere - TID : %dn",pthread_self()); for (i=0;i<NBACTIVITE;i++) pthread_join(tid[i],NULL); sem_destroy(&sema); exit(0); }


    2)Segments de mémoire partagée POSIX

    Le système Unix permet de partager la mémoire entre plusieurs processus grâce aux segments de mémoire partagée, c'est le moyen le plus rapide d'échange de données.

    2.1)Création et supression

    #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> int shm_open(const char *nom,int oflag,mode_t mode); int shm_unlink(const char *nom);

    La fonction shm_open() permet de créer un segment de mémoire partagée, avec nom pour le fichier, o_flag le type d'ouverture du segment (O_CREAT,O_EXCL,O_RDONLY,O_RDWR,O_TRUNC,0) et mode de création.

    2.2)Initialisation d'un segment d'interface

    #include <unistd.h> #include <sys/types.h> #include <fcntl.h> //desc le descripteur du fichier et length pour la taille int ftruncate(int desc,off_t length);

    Mais l'opération n'est pas terminée! Nous avons initialisé notre segment, ensuite il faut le projeter en mémoire grâce à la fonction mmap().

    2.3)Exemple de mise en oeuvre de segment d'interface

    Dans notre exemple, nous allons créer un processus père et un processus fils. Le père va charger un texte en mémoire et la fils va le lire puis l'afficher.
    //Pour compiler, faire gcc monfic.c -lrt #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <sys/mman.h> #include <sys/stat.h> #include <string.h> void erreur(char * message) { perror(message); exit(1); //Sortie du programme... } int main(){ pid_t fils; int fdmem; char * zone; fdmem=shm_open("/mymem",O_CREAT|O_RDWR,0600); if (fdmem==-1) erreur("Probleme lors du shm_open!"); if (ftruncate(fdmem,1024)==-1) erreur("Probleme lors du ftruncate!"); zone=mmap(NULL,1024,PROT_WRITE,MAP_SHARED,fdmem,0); if (zone==MAP_FAILED) erreur("Probleme lors du mmap!"); if ((fils=fork())==-1) erreur("Probleme lors du fork!"); if (fils==0) { //Travail du fils sleep(10); printf("zone : %sn",zone); exit(0); } printf("Le pere copie ds la zone memoire... n"); strcpy(zone,"Bonjour..."); wait(NULL); munmap(zone,1024); close(fdmem); system("ls -l /dev/shm"); shm_unlink("/mymem"); exit(0); }

    3)Files de messages POSIX

    3.1)création et suppression

    #include <mqueue.h> #include <sys/stat.h> #include <fcntl.h> //name le nom de la file, oflag le type d'ouverture (O_RDONLY,O_WRONLY, O_RDWR,O_NONBLOCK,O_CREAT,O_EXCL,0) //mode : le mode de la file créé, attr les attributs additionnels //mq_open() retourne un nouveau descripteur si l'appel est réussi et sinon retourne -1 mqd_t mq_open(const char *name, int oflag); mqd_t mq_open(const char *name, int oflag,mode_t mode,struct mq_attr *attr); //ferme la connexion à la file de message mqd_t mq_close(mqd_t mqds); //Supprime définitivement la file de message mqd_t mq_unlink(const char *name);

    3.2)Envoi d'un message

    La fonction mq_send() transmet le message, pointé par msg_ptr, de priorité msg_prio et de longueur msg_len, à la file de messages. La priorité est un entier entre 0 et 31.
    #include <mqueue.h> mqd_t mq_send(mqd_t mqds,const char *msgptr,size_t msg_len,unsigned msg_prio);

    3.3)Réception d'un message

    #include <mqueue.h> mqd_t mq_receive(mqd_t mqds,const char *msgptr,size_t msg_len,unsigned msg_prio);

    La chaîne pointée est mise à jour par la fonction mq_receive() avec le message le plus prioritaire dans la file de messages en attente.

    3.4)Linux et les files de messages

    Les files de messages ont été implémentées depuis la version 2.6.6 du noyau Linux. Elles sont accessibles via le pseudo système /proc par la commande ls /proc/sys/fs/mqueue.

    Conclusion

    Nous avons vu les trois types d'IPC POSIX Unix. Je ne vous cache pas que personnellement je préfère l'utilisation des sémaphores, qui présentent l'avantage de fonctionner sur toutes les plateformes.





    Pseudonyme (obligatoire) :
    Adresse mail (obligatoire) :
    Site web :

    Pour valider votre commentaire, écrivez le texte affiché sur l'image :



    © 2017 www.doritique.fr par Robert DORIGNY