Communication Interprocessus en Linux : Pipes Nommés et Mémoire Partagée System V

Les pipes anonymes ne permettent la communication qu'entre des processus ayant un ancêtre commun. Pour échanger des données entre des processus non apparentés, on peut utiliser les FIFO, souvent appelés pipes nommés.

Création d'un Pipe Nommé

Un pipe nommé peut être créé via la ligne de commande avec la commande suivante :

$ mkfifo nom_du_pipe

Ou bien depuis un programme en utilisant la fonction mkfifo :

int mkfifo(const char *nom, mode_t mode);

Exemple de création d'un pipe nommé dans un programme :

int main() {
    mkfifo("pipe_nomme", 0644);
    return 0;
}

Différences Entre Pipes Anonymes et Pipes Nommés

Les pipes anonymes sont créés et ouverts par la fonction pipe, tandis que les pipes nommés sont créés par mkfifo et ouverts avec open. Une fois ces opérations réalisées, leur comportement est identique.

Règles d'Ouverture des Pipes Nommés

  • Lecture : si O_NONBLOCK est désactivé, le processus bloque jusqu'à ce qu'un autre processus ouvre le FIFO en écriture. Si O_NONBLOCK est activé, l'ouverture réussit immédiatement.
  • Écriture : si O_NONBLOCK est désactivé, le processus bloque jusqu'à ce qu'un autre processus ouvre le FIFO en lecture. Si O_NONBLOCK est activé, l'ouverture échoue immédiatement (erreur ENXIO).

Exemple 1: Copie de Fichier Utilisant un Pipe Nommé

Programme pour lire un fichier et écrire dans un pipe nommé :

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

#define ERR_EXIT(m) \
do { \
    perror(m); \
    exit(EXIT_FAILURE); \
} while(0)

int main() {
    mkfifo("mon_pipe", 0644);
    int infd = open("source.txt", O_RDONLY);
    if (infd == -1) ERR_EXIT("open");
    int outfd = open("mon_pipe", O_WRONLY);
    if (outfd == -1) ERR_EXIT("open");

    char buf[1024];
    int n;
    while ((n = read(infd, buf, 1024)) > 0) {
        write(outfd, buf, n);
    }

    close(infd);
    close(outfd);
    return 0;
}

Programme pour lire le pipe nommé et écrire dans un fichier cible :

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

#define ERR_EXIT(m) \
do { \
    perror(m); \
    exit(EXIT_FAILURE); \
} while(0)

int main() {
    int outfd = open("destination.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (outfd == -1) ERR_EXIT("open");
    int infd = open("mon_pipe", O_RDONLY);
    if (infd == -1) ERR_EXIT("open");

    char buf[1024];
    int n;
    while ((n = read(infd, buf, 1024)) > 0) {
        write(outfd, buf, n);
    }

    close(infd);
    close(outfd);
    unlink("mon_pipe");
    return 0;
}

Exemple 2: Communication Serveur-Client Utilisant un Pipe Nommé

Makefile pour compiler les programmes serveur et client :

.PHONY: all
all: serverPipe clientPipe

serverPipe: serverPipe.c
    gcc -o $@ $^

clientPipe: clientPipe.c
    gcc -o $@ $^

.PHONY: clean
clean:
    rm -f serverPipe clientPipe

Code du serveur (serverPipe.c) :

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define ERR_EXIT(m) \
do { \
    perror(m); \
    exit(EXIT_FAILURE); \
} while(0)

int main() {
    umask(0);
    if (mkfifo("pipe_comm", 0644) < 0) {
        ERR_EXIT("mkfifo");
    }
    int rfd = open("pipe_comm", O_RDONLY);
    if (rfd < 0) {
        ERR_EXIT("open");
    }

    char buf[1024];
    while (1) {
        buf[0] = 0;
        printf("En attente...\n");
        ssize_t s = read(rfd, buf, sizeof(buf) - 1);
        if (s > 0) {
            buf[s - 1] = 0;
            printf("Client dit: %s\n", buf);
        } else if (s == 0) {
            printf("Le client a quitté, fin du programme.\n");
            exit(EXIT_SUCCESS);
        } else {
            ERR_EXIT("read");
        }
    }

    close(rfd);
    return 0;
}

Code du client (clientPipe.c) :

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define ERR_EXIT(m) \
do { \
    perror(m); \
    exit(EXIT_FAILURE); \
} while(0)

int main() {
    int wfd = open("pipe_comm", O_WRONLY);
    if (wfd < 0) {
        ERR_EXIT("open");
    }

    char buf[1024];
    while (1) {
        buf[0] = 0;
        printf("Entrez votre message: ");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf) - 1);
        if (s > 0) {
            buf[s] = 0;
            write(wfd, buf, strlen(buf));
        } else if (s <= 0) {
            ERR_EXIT("read");
        }
    }

    close(wfd);
    return 0;
}

Mémoire Partagée System V

La mémoire partagée est la forme d'IPC la plus rapide. Une fois mappée dans l'espace d'adressage des processus, la transmission de données se fait sans intervention du noyau.

Structure de la Mémoire Partagée

struct shmid_ds {
    struct ipc_perm shm_perm; /* permissions */
    int shm_segsz; /* taille du segment (octets) */
    __kernel_time_t shm_atime; /* dernière connexion */
    __kernel_time_t shm_dtime; /* dernière déconnexion */
    __kernel_time_t shm_ctime; /* dernière modification */
    __kernel_ipc_pid_t shm_cpid; /* PID du créateur */
    __kernel_ipc_pid_t shm_lpid; /* PID du dernier opérateur */
    unsigned short shm_nattch; /* nombre de connexions actuelles */
    unsigned short shm_unused; /* compatibilité */
    void *shm_unused2; /* utilisé par DIPC */
    void *shm_unused3; /* inutilisé */
};

Fonctions de la Mémoire Partagée

  • shmget: Crée une zone de mémoire partagée.
  • shmat: Attache la mémoire partagée à l'espace d'adressage du processus.
  • shmdt: Détache la mémoire partagée de l'espace d'adressage du processus.
  • shmctl: Contrôle la mémoire partagée.

Exemple 1: Communication Via la Mémoire Partagée

Structure du projet :

# ls
client.c comm.c comm.h Makefile server.c
# cat Makefile
.PHONY: all
all: server client

client: client.c comm.c
    gcc -o $@ $^

server: server.c comm.c
    gcc -o $@ $^

.PHONY: clean
clean:
    rm -f client server

Header (comm.h) :

#ifndef _COMM_H_
#define _COMM_H_

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PATHNAME "."
#define PROJ_ID 0x6666

int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);

#endif

Implémentation (comm.c) :

#include "comm.h"

static int commShm(int size, int flags) {
    key_t key = ftok(PATHNAME, PROJ_ID);
    if (key < 0) {
        perror("ftok");
        return -1;
    }
    int shmid = shmget(key, size, flags);
    if (shmid < 0) {
        perror("shmget");
        return -2;
    }
    return shmid;
}

int destroyShm(int shmid) {
    if (shmctl(shmid, IPC_RMID, NULL) < 0) {
        perror("shmctl");
        return -1;
    }
    return 0;
}

int createShm(int size) {
    return commShm(size, IPC_CREAT | IPC_EXCL | 0666);
}

int getShm(int size) {
    return commShm(size, IPC_CREAT);
}

Code du serveur (server.c) :

#include "comm.h"

int main() {
    int shmid = createShm(4096);
    char* addr = (char*) shmat(shmid, NULL, 0);
    sleep(2);
    int i = 0;
    while (i++ < 26) {
        printf("Client: %s\n", addr);
        sleep(1);
    }
    shmdt(addr);
    sleep(2);
    destroyShm(shmid);
    return 0;
}

Code du cleint (client.c) :

#include "comm.h"

int main() {
    int shmid = getShm(4096);
    sleep(1);
    char* addr = (char*) shmat(shmid, NULL, 0);
    sleep(2);
    int i = 0;
    while (i < 26) {
        addr[i] = 'A' + i;
        i++;
        addr[i] = 0;
        sleep(1);
    }
    shmdt(addr);
    sleep(2);
    return 0;
}

Exemple 2: Contrôle d'Accès à la Mémoire Partagée Via un Pipe

Header (Comm.hpp) :

#pragma once
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cassert>
#include <cstdio>
#include <ctime>
#include <cstring>
#include <iostream>
using namespace std;

#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3

const string msg[] = {"Debug", "Notice", "Warning", "Error"};

ostream& Log(string message, int level) {
    cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
    return cout;
}

#define PATH_NAME "/home/user"
#define PROJ_ID 0x66
#define SHM_SIZE 4096
#define FIFO_NAME "./fifo"

class Init {
public:
    Init() {
        umask(0);
        int n = mkfifo(FIFO_NAME, 0666);
        assert(n == 0);
        Log("Fifo créé avec succès", Notice) << "\n";
    }
    ~Init() {
        unlink(FIFO_NAME);
        Log("Fifo supprimé avec succès", Notice) << "\n";
    }
};

#define READ O_RDONLY
#define WRITE O_WRONLY

int OpenFIFO(string pathname, int flags) {
    int fd = open(pathname.c_str(), flags);
    assert(fd >= 0);
    return fd;
}

void CloseFifo(int fd) {
    close(fd);
}

void Wait(int fd) {
    Log("En attente...", Notice) << "\n";
    uint32_t temp = 0;
    ssize_t s = read(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
}

void Signal(int fd) {
    uint32_t temp = 1;
    ssize_t s = write(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    Log("Signal envoyé...", Notice) << "\n";
}

string TransToHex(key_t k) {
    char buffer[32];
    snprintf(buffer, sizeof buffer, "0x%x", k);
    return buffer;
}

Code du serveur (ShmServer.cc) :

#include "Comm.hpp"
Init init;

int main() {
    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);
    Log("Clé créée", Debug) << " Clé : " << TransToHex(k) << endl;

    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }
    Log("Mémoire partagée créée", Debug) << " ID : " << shmid << endl;

    char* shmaddr = (char*) shmat(shmid, nullptr, 0);
    Log("Mémoire partagée attachée", Debug) << " ID : " << shmid << endl;

    int fd = OpenFIFO(FIFO_NAME, O_RDONLY);
    while (true) {
        Wait(fd);
        printf("%s\n", shmaddr);
        if (strcmp(shmaddr, "quit") == 0)
            break;
    }
    CloseFifo(fd);

    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("Mémoire partagée détachée", Debug) << " ID : " << shmid << endl;

    n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    Log("Mémoire partagée supprimée", Debug) << " ID : " << shmid << endl;

    return 0;
}

Code du cliant (ShmClient.cc) :

#include "Comm.hpp"

int main() {
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if (k < 0) {
        Log("Échec de la création de la clé", Error) << " Clé : " << TransToHex(k) << endl;
        exit(1);
    }
    Log("Clé créée", Debug) << " Clé : " << TransToHex(k) << endl;

    int shmid = shmget(k, SHM_SIZE, 0);
    if (shmid < 0) {
        Log("Échec de la création de la mémoire partagée", Error) << " Clé : " << TransToHex(k) << endl;
        exit(2);
    }
    Log("Mémoire partagée créée", Error) << " Clé : " << TransToHex(k) << endl;

    char* shmaddr = (char*) shmat(shmid, nullptr, 0);
    if (shmaddr == nullptr) {
        Log("Échec de l'attachement de la mémoire partagée", Error) << " Clé : " << TransToHex(k) << endl;
        exit(3);
    }
    Log("Mémoire partagée attachée", Error) << " Clé : " << TransToHex(k) << endl;

    int fd = OpenFIFO(FIFO_NAME, O_WRONLY);
    while (true) {
        ssize_t s = read(0, shmaddr, SHM_SIZE - 1);
        if (s > 0) {
            shmaddr[s - 1] = 0;
            Signal(fd);
            if (strcmp(shmaddr, "quit") == 0)
                break;
        }
    }
    CloseFifo(fd);

    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("Mémoire partagée détachée", Error) << " Clé : " << TransToHex(k) << endl;

    return 0;
}

Étiquettes: Linux IPC Pipes Nommés Mémoire partagée System V

Publié le 10 octobre à 14h35