GNU/Linux >> Tutoriels Linux >  >> Linux

Communication inter-processus sous Linux :stockage partagé

Ceci est le premier article d'une série sur la communication interprocessus (IPC) sous Linux. La série utilise des exemples de code en C pour clarifier les mécanismes IPC suivants :

  • Fichiers partagés
  • Mémoire partagée (avec sémaphores)
  • Tuyaux (nommés et sans nom)
  • Files d'attente de messages
  • Prises
  • Signaux

Cet article passe en revue certains concepts de base avant de passer aux deux premiers de ces mécanismes :les fichiers partagés et la mémoire partagée.

Concepts de base

Un processus est un programme en cours d'exécution, et chaque processus a son propre espace d'adressage, qui comprend les emplacements de mémoire auxquels le processus est autorisé à accéder. Un processus a un ou plusieurs threads d'exécution, qui sont des séquences d'instructions exécutables :un single-thread processus n'a qu'un seul thread, alors qu'un processus multi-thread processus a plus d'un thread. Les threads au sein d'un processus partagent diverses ressources, en particulier l'espace d'adressage. En conséquence, les threads au sein d'un processus peuvent communiquer directement via la mémoire partagée, bien que certains langages modernes (par exemple, Go) encouragent une approche plus disciplinée telle que l'utilisation de canaux thread-safe. Ce qui est intéressant ici, c'est que différents processus, par défaut, ne le font pas partager la mémoire.

Il existe différentes manières de lancer des processus qui communiquent ensuite, et deux manières dominent dans les exemples qui suivent :

  • Un terminal est utilisé pour démarrer un processus, et peut-être qu'un terminal différent est utilisé pour en démarrer un autre.
  • La fonction système fork est appelé dans un processus (le parent) pour générer un autre processus (l'enfant).

Les premiers exemples adoptent l'approche terminale. Les exemples de code sont disponibles dans un fichier ZIP sur mon site Web.

Fichiers partagés

Les programmeurs ne sont que trop familiers avec l'accès aux fichiers, y compris les nombreux pièges (fichiers inexistants, mauvaises autorisations de fichiers, etc.) qui assaillent l'utilisation de fichiers dans les programmes. Néanmoins, les fichiers partagés peuvent être le mécanisme IPC le plus élémentaire. Prenons le cas relativement simple dans lequel un processus (producteur ) crée et écrit dans un fichier, et un autre processus (consommateur ) lit à partir de ce même fichier :

         writes  +-----------+  reads
producer-------->| disk file |<-------consumer
                 +-----------+

Le défi évident dans l'utilisation de ce mécanisme IPC est qu'une condition de concurrence pourrait survenir :le producteur et le consommateur pourraient accéder au fichier exactement au même moment, rendant ainsi le résultat indéterminé. Pour éviter une condition de concurrence, le fichier doit être verrouillé de manière à éviter un conflit entre une écriture opération et toute autre opération, qu'il s'agisse d'une lecture ou un écrire . L'API de verrouillage dans la bibliothèque système standard peut être résumée comme suit :

  • Un producteur doit obtenir un verrou exclusif sur le fichier avant d'y écrire. Une exclusivité le verrou peut être détenu par un processus au maximum, ce qui exclut une condition de concurrence car aucun autre processus ne peut accéder au fichier tant que le verrou n'est pas libéré.
  • Un consommateur doit obtenir au moins un verrou partagé sur le fichier avant de lire à partir du fichier. Plusieurs lecteurs peut contenir un partagé verrouiller en même temps, mais pas de writer peut accéder à un fichier quand même un seul lecteur détient un verrou partagé.

Une serrure partagée favorise l'efficacité. Si un processus ne fait que lire un fichier et ne modifie pas son contenu, il n'y a aucune raison d'empêcher d'autres processus de faire de même. L'écriture, cependant, exige clairement un accès exclusif à un fichier.

La bibliothèque d'E/S standard inclut une fonction utilitaire nommée fcntl qui peut être utilisé pour inspecter et manipuler les verrous exclusifs et partagés sur un fichier. La fonction fonctionne à travers un descripteur de fichier , une valeur entière non négative qui, dans un processus, identifie un fichier. (Différents descripteurs de fichiers dans différents processus peuvent identifier le même fichier physique.) Pour le verrouillage de fichiers, Linux fournit la fonction de bibliothèque flock , qui est une fine enveloppe autour de fcntl . Le premier exemple utilise le fcntl fonction pour exposer les détails de l'API.

Exemple 1. Le producteur programme

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FileName "data.dat"
#define DataString "Now is the winter of our discontent\nMade glorious summer by this sun of York\n"

void report_and_exit(const char* msg) {
  perror(msg);
  exit(-1); /* EXIT_FAILURE */
}

int main() {
  struct flock lock;
  lock.l_type = F_WRLCK;    /* read/write (exclusive versus shared) lock */
  lock.l_whence = SEEK_SET; /* base for seek offsets */
  lock.l_start = 0;         /* 1st byte in file */
  lock.l_len = 0;           /* 0 here means 'until EOF' */
  lock.l_pid = getpid();    /* process id */

  int fd; /* file descriptor to identify a file within a process */
  if ((fd = open(FileName, O_RDWR | O_CREAT, 0666)) < 0)  /* -1 signals an error */
    report_and_exit("open failed...");

  if (fcntl(fd, F_SETLK, &lock) < 0) /** F_SETLK doesn't block, F_SETLKW does **/
    report_and_exit("fcntl failed to get lock...");
  else {
    write(fd, DataString, strlen(DataString)); /* populate data file */
    fprintf(stderr, "Process %d has written to data file...\n", lock.l_pid);
  }

  /* Now release the lock explicitly. */
  lock.l_type = F_UNLCK;
  if (fcntl(fd, F_SETLK, &lock) < 0)
    report_and_exit("explicit unlocking failed...");

  close(fd); /* close the file: would unlock if needed */
  return 0;  /* terminating the process would unlock as well */
}

Les grandes étapes du producteur programme ci-dessus peut être résumé comme suit :

  • Le programme déclare une variable de type struct flock , qui représente un verrou, et initialise les cinq champs de la structure. La première initialisation :
    lock.l_type = F_WRLCK; /* exclusive lock */

    rend le verrou exclusif (lecture-écriture ) plutôt qu'un partage (lecture seule ) fermer à clé. Si le producteur obtient le verrou, alors aucun autre processus ne pourra écrire ou lire le fichier jusqu'à ce que le producteur libère le verrou, soit explicitement avec l'appel approprié à fcntl ou implicitement en fermant le fichier. (Lorsque le processus se termine, tous les fichiers ouverts seront automatiquement fermés, libérant ainsi le verrou.)

  • Le programme initialise ensuite les champs restants. L'effet principal est que la toute le fichier doit être verrouillé. Cependant, l'API de verrouillage n'autorise le verrouillage que des octets désignés. Par exemple, si le fichier contient plusieurs enregistrements de texte, un seul enregistrement (ou même une partie d'un enregistrement) peut être verrouillé et le reste laissé déverrouillé.
  • Le premier appel à fcntl :
    if (fcntl(fd, F_SETLK, &lock) < 0)

    essaie de verrouiller le fichier en mode exclusif, en vérifiant si l'appel a réussi. En général, le fcntl la fonction renvoie -1 (donc inférieur à zéro) pour indiquer un échec. Le deuxième argument F_SETLK signifie que l'appel à fcntl n'est pas block :la fonction revient immédiatement, soit en accordant le verrou, soit en indiquant un échec. Si le drapeau F_SETLKW (le W à la fin est pour attendre ) ont été utilisés à la place, l'appel à fcntl bloquerait jusqu'à ce qu'il soit possible d'obtenir le verrou. Dans les appels à fcntl , le premier argument fd est le descripteur de fichier, le deuxième argument spécifie l'action à entreprendre (dans ce cas, F_SETLK pour définir le verrou), et le troisième argument est l'adresse de la structure du verrou (dans ce cas, &lock ).

  • Si le producteur obtient le verrou, le programme écrit deux enregistrements de texte dans le fichier.
  • Après avoir écrit dans le fichier, le producteur modifie le l_type de la structure de verrouillage champ pour déverrouiller valeur :
    lock.l_type = F_UNLCK;

    et appelle fcntl pour effectuer l'opération de déverrouillage. Le programme se termine en fermant le fichier et en quittant.

Exemple 2. Le consommateur programme

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define FileName "data.dat"

void report_and_exit(const char* msg) {
  perror(msg);
  exit(-1); /* EXIT_FAILURE */
}

int main() {
  struct flock lock;
  lock.l_type = F_WRLCK;    /* read/write (exclusive) lock */
  lock.l_whence = SEEK_SET; /* base for seek offsets */
  lock.l_start = 0;         /* 1st byte in file */
  lock.l_len = 0;           /* 0 here means 'until EOF' */
  lock.l_pid = getpid();    /* process id */

  int fd; /* file descriptor to identify a file within a process */
  if ((fd = open(FileName, O_RDONLY)) < 0)  /* -1 signals an error */
    report_and_exit("open to read failed...");

  /* If the file is write-locked, we can't continue. */
  fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */
  if (lock.l_type != F_UNLCK)
    report_and_exit("file is still write locked...");

  lock.l_type = F_RDLCK; /* prevents any writing during the reading */
  if (fcntl(fd, F_SETLK, &lock) < 0)
    report_and_exit("can't get a read-only lock...");

  /* Read the bytes (they happen to be ASCII codes) one at a time. */
  int c; /* buffer for read bytes */
  while (read(fd, &c, 1) > 0)    /* 0 signals EOF */
    write(STDOUT_FILENO, &c, 1); /* write one byte to the standard output */

  /* Release the lock explicitly. */
  lock.l_type = F_UNLCK;
  if (fcntl(fd, F_SETLK, &lock) < 0)
    report_and_exit("explicit unlocking failed...");

  close(fd);
  return 0;
}

Le consommateur programme est plus compliqué que nécessaire pour mettre en évidence les fonctionnalités de l'API de verrouillage. En particulier, le consommateur programme vérifie d'abord si le fichier est exclusivement verrouillé et essaie seulement ensuite d'obtenir un verrou partagé. Le code correspondant est :

lock.l_type = F_WRLCK;
...
fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */
if (lock.l_type != F_UNLCK)
  report_and_exit("file is still write locked...");

Le F_GETLK opération spécifiée dans le fcntl l'appel recherche un verrou, dans ce cas, un verrou exclusif donné sous la forme F_WRLCK dans la première déclaration ci-dessus. Si le verrou spécifié n'existe pas, alors le fcntl call change automatiquement le champ du type de verrou en F_UNLCK pour indiquer ce fait. Si le fichier est exclusivement verrouillé, le consommateur se termine. (Une version plus robuste du programme pourrait avoir le consommateur dormir un peu et réessayez plusieurs fois.)

Si le fichier n'est pas actuellement verrouillé, alors le consommateur essaie d'obtenir un partage (lecture seule ) verrouiller (F_RDLCK ). Pour raccourcir le programme, le F_GETLK appelez fcntl pourrait être supprimé car le F_RDLCK l'appel échouerait si un lecture-écriture le verrou était déjà détenu par un autre processus. Rappelez-vous qu'un lecture seule lock empêche tout autre processus d'écrire dans le fichier, mais permet aux autres processus de lire à partir du fichier. Bref, un partagé le verrou peut être détenu par plusieurs processus. Après avoir obtenu un verrou partagé, le consommateur programme lit les octets un par un à partir du fichier, imprime les octets sur la sortie standard, libère le verrou, ferme le fichier et se termine.

Voici la sortie des deux programmes lancés depuis le même terminal avec % comme invite de ligne de commande :

% ./producer
Process 29255 has written to data file...

% ./consumer
Now is the winter of our discontent
Made glorious summer by this sun of York

Dans ce premier exemple de code, les données partagées via IPC sont du texte :deux lignes de la pièce de Shakespeare Richard III . Pourtant, le contenu du fichier partagé peut être volumineux, des octets arbitraires (par exemple, un film numérisé), ce qui fait du partage de fichiers un mécanisme IPC incroyablement flexible. L'inconvénient est que l'accès aux fichiers est relativement lent, que l'accès implique la lecture ou l'écriture. Comme toujours, la programmation s'accompagne de compromis. L'exemple suivant présente l'avantage d'IPC grâce à la mémoire partagée, plutôt qu'aux fichiers partagés, avec une amélioration correspondante des performances.

Mémoire partagée

Les systèmes Linux fournissent deux API distinctes pour la mémoire partagée :l'ancienne API System V et la plus récente POSIX. Cependant, ces API ne doivent jamais être mélangées dans une seule application. Un inconvénient de l'approche POSIX est que les fonctionnalités sont encore en développement et dépendent de la version du noyau installée, ce qui a un impact sur la portabilité du code. Par exemple, l'API POSIX, par défaut, implémente la mémoire partagée en tant que fichier mappé en mémoire  :pour un segment de mémoire partagée, le système conserve un fichier de sauvegarde avec le contenu correspondant. La mémoire partagée sous POSIX peut être configurée sans fichier de sauvegarde, mais cela peut avoir un impact sur la portabilité. Mon exemple utilise l'API POSIX avec un fichier de sauvegarde, qui combine les avantages de l'accès à la mémoire (vitesse) et du stockage de fichiers (persistance).

L'exemple de mémoire partagée a deux programmes, nommés memwriter et memreader , et utilise un sémaphore coordonner leur accès à la mémoire partagée. Chaque fois que la mémoire partagée entre en scène avec un écrivain , que ce soit en multi-traitement ou en multi-threading, il en va de même pour le risque d'une condition de concurrence basée sur la mémoire ; par conséquent, le sémaphore est utilisé pour coordonner (synchroniser) l'accès à la mémoire partagée.

Le memwriter programme doit être démarré en premier dans son propre terminal. Le lecteur de mémoire programme peut alors être démarré (en une douzaine de secondes) dans son propre terminal. La sortie du memreader est :

This is the way the world ends...

Plus de ressources Linux

  • Aide-mémoire des commandes Linux
  • Aide-mémoire des commandes Linux avancées
  • Cours en ligne gratuit :Présentation technique de RHEL
  • Aide-mémoire sur le réseau Linux
  • Aide-mémoire SELinux
  • Aide-mémoire sur les commandes courantes de Linux
  • Que sont les conteneurs Linux ?
  • Nos derniers articles Linux

Chaque fichier source a une documentation en haut expliquant les drapeaux de lien à inclure lors de la compilation.

Commençons par un examen du fonctionnement des sémaphores en tant que mécanisme de synchronisation. Un sémaphore général est aussi appelé un sémaphore de comptage , car il a une valeur (généralement initialisée à zéro) qui peut être incrémentée. Considérez un magasin qui loue des vélos, avec une centaine d'entre eux en stock, avec un programme que les employés utilisent pour faire les locations. A chaque fois qu'un vélo est loué, le sémaphore est incrémenté de un; lorsqu'un vélo est rendu, le sémaphore est décrémenté de un. Les locations peuvent continuer jusqu'à ce que la valeur atteigne 100, mais doivent ensuite s'arrêter jusqu'à ce qu'au moins un vélo soit rendu, ce qui décrémente le sémaphore à 99.

Un sémaphore binaire est un cas particulier ne nécessitant que deux valeurs :0 et 1. Dans cette situation, un sémaphore agit comme un mutex :un construit d'exclusion mutuelle. L'exemple de mémoire partagée utilise un sémaphore comme mutex. Lorsque la valeur du sémaphore est 0, le memwriter seul peut accéder à la mémoire partagée. Après l'écriture, ce processus incrémente la valeur du sémaphore, permettant ainsi au memreader pour lire la mémoire partagée.

Exemple 3. Code source pour le memwriter processus

/** Compilation: gcc -o memwriter memwriter.c -lrt -lpthread **/
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include "shmem.h"

void report_and_exit(const char* msg) {
  perror(msg);
  exit(-1);
}

int main() {
  int fd = shm_open(BackingFile,      /* name from smem.h */
                    O_RDWR | O_CREAT, /* read/write, create if needed */
                    AccessPerms);     /* access permissions (0644) */
  if (fd < 0) report_and_exit("Can't open shared mem segment...");

  ftruncate(fd, ByteSize); /* get the bytes */

  caddr_t memptr = mmap(NULL,       /* let system pick where to put segment */
                        ByteSize,   /* how many bytes */
                        PROT_READ | PROT_WRITE, /* access protections */
                        MAP_SHARED, /* mapping visible to other processes */
                        fd,         /* file descriptor */
                        0);         /* offset: start at 1st byte */
  if ((caddr_t) -1  == memptr) report_and_exit("Can't get segment...");

  fprintf(stderr, "shared mem address: %p [0..%d]\n", memptr, ByteSize - 1);
  fprintf(stderr, "backing file:       /dev/shm%s\n", BackingFile );

  /* semaphore code to lock the shared mem */
  sem_t* semptr = sem_open(SemaphoreName, /* name */
                           O_CREAT,       /* create the semaphore */
                           AccessPerms,   /* protection perms */
                           0);            /* initial value */
  if (semptr == (void*) -1) report_and_exit("sem_open");

  strcpy(memptr, MemContents); /* copy some ASCII bytes to the segment */

  /* increment the semaphore so that memreader can read */
  if (sem_post(semptr) < 0) report_and_exit("sem_post");

  sleep(12); /* give reader a chance */

  /* clean up */
  munmap(memptr, ByteSize); /* unmap the storage */
  close(fd);
  sem_close(semptr);
  shm_unlink(BackingFile); /* unlink from the backing file */
  return 0;
}

Voici un aperçu de la façon dont le memwriter et memreader les programmes communiquent via la mémoire partagée :

  • Le memwriter programme, illustré ci-dessus, appelle le shm_open pour obtenir un descripteur de fichier pour le fichier de sauvegarde que le système coordonne avec la mémoire partagée. À ce stade, aucune mémoire n'a été allouée. L'appel suivant à la fonction nommée de manière trompeuse ftruncate :
    ftruncate(fd, ByteSize); /* get the bytes */

    alloue ByteSize octets, dans ce cas, un modeste 512 octets. Le memwriter et memreader les programmes accèdent uniquement à la mémoire partagée, pas au fichier de sauvegarde. Le système est responsable de la synchronisation de la mémoire partagée et du fichier de sauvegarde.

  • Le memwriter appelle ensuite le mmap fonction :
    caddr_t memptr = mmap(NULL,       /* let system pick where to put segment */
                          ByteSize,   /* how many bytes */
                          PROT_READ | PROT_WRITE, /* access protections */
                          MAP_SHARED, /* mapping visible to other processes */
                          fd,         /* file descriptor */
                          0);         /* offset: start at 1st byte */

    pour obtenir un pointeur vers la mémoire partagée. (Le lecteur de mémoire fait un appel similaire.) Le type de pointeur caddr_t commence par un c pour calloc , une fonction système qui initialise à zéro le stockage alloué dynamiquement. Le memwriter utilise le memptr pour le plus tard écrivez opération, en utilisant la bibliothèque strcpy (copie de chaîne).

  • À ce stade, le memwriter est prêt pour l'écriture, mais il crée d'abord un sémaphore pour assurer un accès exclusif à la mémoire partagée. Une condition de concurrence se produirait si le memwriter écrivaient pendant que le memreader était en train de lire. Si l'appel à sem_open réussit :
    sem_t* semptr = sem_open(SemaphoreName, /* name */
                             O_CREAT,       /* create the semaphore */
                             AccessPerms,   /* protection perms */
                             0);            /* initial value */

    alors l'écriture peut continuer. Le SemaphoreName (tout nom unique non vide fera l'affaire) identifie le sémaphore à la fois dans le memwriter et le lecteur de mem . La valeur initiale de zéro donne le créateur du sémaphore, dans ce cas, le memwriter , le droit de procéder, dans ce cas, à l'écriture opération.

  • Après avoir écrit, le memwriter incrémente la valeur du sémaphore à 1 :
    if (sem_post(semptr) < 0) ..

    avec un appel au sem_post une fonction. L'incrémentation du sémaphore libère le verrou mutex et active le memreader pour effectuer sa lecture opération. Pour faire bonne mesure, le memwriter démappe également la mémoire partagée du memwriter espace d'adressage :

    munmap(memptr, ByteSize); /* unmap the storage *

    Cela interdit le memwriter d'un accès ultérieur à la mémoire partagée.

Exemple 4. Code source pour le memreader processus

/** Compilation: gcc -o memreader memreader.c -lrt -lpthread **/
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include "shmem.h"

void report_and_exit(const char* msg) {
  perror(msg);
  exit(-1);
}

int main() {
  int fd = shm_open(BackingFile, O_RDWR, AccessPerms);  /* empty to begin */
  if (fd < 0) report_and_exit("Can't get file descriptor...");

  /* get a pointer to memory */
  caddr_t memptr = mmap(NULL,       /* let system pick where to put segment */
                        ByteSize,   /* how many bytes */
                        PROT_READ | PROT_WRITE, /* access protections */
                        MAP_SHARED, /* mapping visible to other processes */
                        fd,         /* file descriptor */
                        0);         /* offset: start at 1st byte */
  if ((caddr_t) -1 == memptr) report_and_exit("Can't access segment...");

  /* create a semaphore for mutual exclusion */
  sem_t* semptr = sem_open(SemaphoreName, /* name */
                           O_CREAT,       /* create the semaphore */
                           AccessPerms,   /* protection perms */
                           0);            /* initial value */
  if (semptr == (void*) -1) report_and_exit("sem_open");

  /* use semaphore as a mutex (lock) by waiting for writer to increment it */
  if (!sem_wait(semptr)) { /* wait until semaphore != 0 */
    int i;
    for (i = 0; i < strlen(MemContents); i++)
      write(STDOUT_FILENO, memptr + i, 1); /* one byte at a time */
    sem_post(semptr);
  }

  /* cleanup */
  munmap(memptr, ByteSize);
  close(fd);
  sem_close(semptr);
  unlink(BackingFile);
  return 0;
}

Dans le memwriter et memreader programmes, les fonctions de mémoire partagée les plus intéressantes sont shm_open et mmap :en cas de succès, le premier appel renvoie un descripteur de fichier pour le fichier de sauvegarde, que le second appel utilise ensuite pour obtenir un pointeur vers le segment de mémoire partagée. Les appels à shm_open sont similaires dans les deux programmes sauf que le memwriter programme crée la mémoire partagée, alors que le memreader n'accède qu'à cette mémoire déjà créée :

int fd = shm_open(BackingFile, O_RDWR | O_CREAT, AccessPerms); /* memwriter */
int fd = shm_open(BackingFile, O_RDWR, AccessPerms);           /* memreader */

Avec un descripteur de fichier en main, les appels à mmap sont les mêmes :

caddr_t memptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

Le premier argument de mmap est NULL , ce qui signifie que le système détermine où allouer la mémoire dans l'espace d'adressage virtuel. Il est possible (mais délicat) de spécifier une adresse à la place. Le MAP_SHARED indique que la mémoire allouée est partageable entre les processus, et le dernier argument (dans ce cas, zéro) signifie que le décalage de la mémoire partagée doit être le premier octet. La taille l'argument spécifie le nombre d'octets à allouer (dans ce cas, 512), et l'argument protection indique que la mémoire partagée peut être écrite et lue.

Quand le memwriter programme s'exécute avec succès, le système crée et gère le fichier de sauvegarde ; sur mon système, le fichier est /dev/shm/shMemEx , avec shMemEx comme mon nom (donné dans le fichier d'en-tête shmem.h ) pour le stockage partagé. Dans la version actuelle du memwriter et memreader programmes, la déclaration :

shm_unlink(BackingFile); /* removes backing file */

supprime le fichier de sauvegarde. Si le dissocier est omise, le fichier de sauvegarde persiste après la fin du programme.

Le lecteur de mémoire , comme le memwriter , accède au sémaphore par son nom dans un appel à sem_open . Mais le memreader passe ensuite dans un état d'attente jusqu'à ce que le memwriter incrémente le sémaphore, dont la valeur initiale est 0 :

if (!sem_wait(semptr)) { /* wait until semaphore != 0 */

Une fois l'attente terminée, le memreader lit les octets ASCII de la mémoire partagée, nettoie et termine.

L'API de mémoire partagée inclut explicitement des opérations pour synchroniser le segment de mémoire partagée et le fichier de sauvegarde. Ces opérations ont été omises de l'exemple pour réduire l'encombrement et garder l'accent sur le partage de mémoire et le code sémaphore.

Le memwriter et memreader les programmes sont susceptibles de s'exécuter sans induire de condition de concurrence même si le code du sémaphore est supprimé :le memwriter crée le segment de mémoire partagée et y écrit immédiatement ; le lecteur de mémoire ne peut même pas accéder à la mémoire partagée tant qu'elle n'a pas été créée. Cependant, la meilleure pratique exige que l'accès à la mémoire partagée soit synchronisé chaque fois qu'un écriture l'opération est dans le mix, et l'API sémaphore est suffisamment importante pour être mise en évidence dans un exemple de code.

Conclusion

Les exemples de fichiers partagés et de mémoire partagée montrent comment les processus peuvent communiquer via le stockage partagé , des fichiers dans un cas et des segments de mémoire dans l'autre. Les API pour les deux approches sont relativement simples. Ces approches ont-elles un inconvénient commun ? Les applications modernes traitent souvent des données en continu, en effet, avec des flux de données massivement volumineux. Ni les approches de fichiers partagés ni celles de mémoire partagée ne sont bien adaptées aux flux de données massifs. Les canaux d'un type ou d'un autre sont mieux adaptés. La partie 2 présente donc les canaux et les files d'attente de messages, toujours avec des exemples de code en C.

[Télécharger le guide complet de la communication inter-processus sous Linux]


Linux
  1. Présentation du guide de communication inter-processus sous Linux

  2. Communication inter-processus sous Linux :Sockets et signaux

  3. Linux - Tout est un fichier ?

  4. Recommandation de communication inter-processus

  5. Ubuntu Linux - VHDX partagé

Moins de commande sous Linux

Commande Gzip sous Linux

Commande Gunzip sous Linux

Commande Stat sous Linux

Qu'est-ce qu'umask sous Linux ?

Comment créer un lien symbolique vers un fichier sous Linux