Communication interprocessus Linux

À propos de cet article

Présentation d'IPC (Interprocess Communication) pour Linux.

Qu'est-ce que la communication inter-processus?

La communication inter-processus (IPC) fait référence à l'échange de données entre les processus, qui sont les unités d'exécution d'un programme. Les dépendances de processus sont gérées par le système d'exploitation pour être couplées de manière aussi lâche que possible. Par conséquent, IPC doit être effectué via les fonctionnalités du système d'exploitation Linux. Le système d'exploitation peut échanger des données pour les processus de plusieurs manières. Nous offrons une variété de méthodes uniques. Les cinq suivants sont présentés ici.

  1. Mémoire partagée
  2. Semafo
  3. Mémoire mappée
  4. Tuyau
  5. Communication par socket

(Si vous avez d'autres questions, veuillez me le faire savoir dans les commentaires.)

Nous allons jeter un coup d'oeil.

la memoire partagée

Partagez la même mémoire entre les processus. Le plus grand avantage de la mémoire partagée est sa vitesse d'accès. Une fois la mémoire partagée créée, elle peut être accédée sans utiliser les fonctions du noyau, donc elle peut être accédée à la même vitesse que la mémoire ordinaire dans le processus. Cette méthode est souvent utilisée pour les logiciels qui nécessitent des performances de traitement qui nécessitent le bénéfice de cette vitesse d'accès. En revanche, si vous écrivez à partir de deux processus en même temps, un conflit se produira. La mémoire partagée n'a pas de mécanisme d'exclusion mutuelle intégré pour éviter ce conflit. Vous devez le préparer vous-même.

Voici un exemple de code qui utilise la mémoire partagée. Allouez la mémoire partagée avec process_a et écrivez la chaîne de caractères "Hello World!" Dans la mémoire partagée. Les données écrites dans la mémoire partagée sont lues par process_b et imprimées sur la console.

process_a.cpp


#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/ipc.h>

int main ()
{
    /*Générer un identifiant de segment*/
    const std::string file_path("./key_data.dat");
    const int id = 42;
    const auto key = ftok(file_path.c_str(), id);
    if(-1 == key) {
        std::cerr << "Failed to acquire key" << std::endl;
        return EXIT_FAILURE;
    }

    /*Répartition sectorielle*/
    const int shared_segment_size = 0x6400;
    const auto segment_id = shmget(key, shared_segment_size,
                                   IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
    if(-1==segment_id) {
        std::cerr << segment_id << " :Failed to acquire segment" << std::endl;
        return EXIT_FAILURE;
    }

    /*Afficher la clé et l'ID de segment*/
    std::cout << "Clé:" << key << std::endl;
    std::cout << "ID de segment:" << segment_id << std::endl;

    /*Joindre à la mémoire partagée*/
    char* const shared_memory = reinterpret_cast<char*>(shmat(segment_id, 0, 0));
    printf ("shared memory attached at address %p\n", shared_memory);

    /*Écrire dans la mémoire partagée*/
    sprintf (shared_memory, "Hello, world.");


    std::cout << "Hit any key when ready to close shared memory" << std::endl;
    std::cin.get();

    /*Détacher la mémoire partagée*/
    shmdt(shared_memory);
    /*Libérer la mémoire partagée*/
    shmctl (segment_id, IPC_RMID, 0);

    return EXIT_SUCCESS;
}

process_b.cpp


#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string>
#include <sys/shm.h>
#include <sys/stat.h>

int main ()
{
    /*Générer un identifiant de segment*/
    const std::string file_path("./key_data.dat");
    const int id = 42;
    const auto key = ftok(file_path.c_str(), id);
    if(-1 == key) {
        std::cerr << "Failed to acquire key" << std::endl;
        return EXIT_FAILURE;
    }


    /*Joindre à la mémoire partagée*/
    const auto segment_id = shmget(key, 0, 0);
    const char* const shared_memory = reinterpret_cast<char*>(shmat(segment_id, 0, 0));
    printf ("shared memory attached at address %p\n", shared_memory);

    /*Lire la mémoire partagée*/
    printf ("%s\n", shared_memory);

    /*Détacher la mémoire partagée*/
    shmdt(shared_memory);

    return EXIT_SUCCESS;
}

Commandes Linux liées à la communication inter-processus

Présentation des commandes requises lors de la création d'un programme pour la communication inter-processus. La première est la commande ipcs. Affiche l'état de la communication inter-processus.

kei@Glou-Glou:~/src/ipc/shared_memory$ ipcs

------File d'attente des messages--------
Clé msqid Autorisation du propriétaire Nombre d'octets utilisés Message

------Segment de mémoire partagée--------
Clé shmid Owner Privilege Byte État
0x00000000 884736     kei        600        16777216   2                       
0x00000000 1409025 kei 600 268435456 2 Cible
0x00000000 819202 kei 600 524288 2 Cible
       
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~réduction~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~    

------Tableau Semapho--------
Autorité de propriétaire semi-semi-clé nsems

Ensuite, je présenterai la commande ipcrm. Libérez les ressources partagées réservées.

kei@Glou-Glou:~/src/ipc/shared_memory$ ipcrm shm <shmid>

sémaphore

Semafo partage des données de type entier entre des processus qui n'ont pas de relation parent-enfant. Il dispose d'un mécanisme pour contrôler l'accès simultané de plusieurs processus, mais l'inconvénient est qu'il ne peut gérer que des données de type entier. L'utilisation principale est l'exclusion mutuelle de la mémoire partagée. Vous trouverez ci-dessous l'exemple de code utilisé par Semafo.

process_a.cpp


#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/types.h>
#include<cstdlib>
#include<iostream>

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short int *array;
    struct seminfo *__buf;
};

enum SEMAPHORE_OPERATION
{
    UNLOCK = -1,
    WAIT = 0,
    LOCK = 1,
};

int main()
{
    /*Sécuriser le semapho*/
    const key_t key = 112;
    int sem_flags = 0666;
    int sem_id = semget(key, 1, sem_flags | IPC_CREAT);
    if(-1 == sem_id)
    {
        std::cerr << "Failed to acquire semapore" << std::endl;
        return EXIT_FAILURE;
    }

    /*Initialisation du semapho*/
    union semun argument;
    unsigned short values[1];
    values[0] = 1;
    argument.array = values;
    semctl(sem_id, 0, SETALL, argument);

    /*Attendez que le processus B s'exécute*/
    std::cout << "Waiting for post operation..." << std::endl;
    sembuf operations[1];
    operations[0].sem_num = 0;
    operations[0].sem_op = WAIT;
    operations[0].sem_flg = SEM_UNDO;
    semop(sem_id, operations, 1);

    /*Sortie de semapho*/
    auto result = semctl(sem_id, 1, IPC_RMID, NULL);
    if(-1 == result)
    {
        std::cerr << "Failed to close semaphore" << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

process_b.cpp


#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/types.h>
#include<cstdlib>
#include<iostream>

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short int *array;
    struct seminfo *__buf;
};

enum SEMAPHORE_OPERATION
{
    UNLOCK = -1,
    WAIT = 0,
    LOCK = 1,
};

int main()
{
    /*Sécuriser le semapho*/
    const key_t key = 112;
    int sem_flags = 0666;
    int sem_id = semget(key, 1, sem_flags);
    if(-1 == sem_id)
    {
        std::cerr << "Failed to acquire semapore" << std::endl;
        return EXIT_FAILURE;
    }

    std::cout << "Unlock semaphore" << std::endl;

    /*Publier sur Semafo*/
    sembuf operations[1];
    operations[0].sem_num = 0;
    operations[0].sem_op = UNLOCK;
    operations[0].sem_flg = SEM_UNDO;
    semop(sem_id, operations, 1);

    return EXIT_SUCCESS;
}

Mémoire mappée

Plusieurs processus communiquent sur le fichier. Le mappage de la mémoire est effectué lors de l'accès à un fichier pour accélérer le processus. Le mappage de mémoire fait référence au mappage de fichiers, de périphériques, etc. à un espace d'adressage virtuel afin qu'ils soient accessibles comme s'il s'agissait de mémoire. Cela vous permet de placer des données dans un fichier sans sérialisation.

Voici un exemple de code pour la communication inter-processus à l'aide d'une carte mémoire.

process_a.cpp


#include<cstdlib>
#include<cstdio>
#include<string>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<unistd.h>

const unsigned int FILE_LENGTH = 0x100;
const std::string FILE_NAME("./data.dat");

int main()
{
    /* Prepare a file large enough to hold an unsigned integer. */
    auto fd = open(FILE_NAME.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    lseek(fd, FILE_LENGTH+1, SEEK_SET);
    write(fd, "", 1);
    lseek(fd, 0, SEEK_SET);
    /* Create the memory mapping. */
    char* const file_memory = reinterpret_cast<char*>(mmap(0, FILE_LENGTH, PROT_WRITE, MAP_SHARED, fd, 0));
    close(fd);
    /* Write a random integer to memory-mapped area. */
    sprintf(file_memory, "%s", "Hello World!");
    /* Release the memory (unnecessary because the program exits). */
    munmap (file_memory, FILE_LENGTH);

    return EXIT_SUCCESS;
}

process_b.cpp


#include<cstdlib>
#include<cstdio>
#include<string>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<unistd.h>

const unsigned int FILE_LENGTH = 0x100;
const std::string FILE_NAME("./data.dat");

int main()
{
    /* Open the file. */
    auto fd = open(FILE_NAME.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
    /* Create the memory mapping. */
    char* const file_memory = reinterpret_cast<char*>(mmap(0, FILE_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
    close(fd);
    /* Read the integer, print it out, and double it. */
    printf("%s\n", file_memory);

    /* Release the memory (unnecessary because the program exits). */
    munmap(file_memory, FILE_LENGTH);

    return EXIT_SUCCESS;
}

tuyau

Les tuyaux fournissent une communication unidirectionnelle entre les processus parent-enfant. Vous le connaîtrez sur la ligne de commande.

$ ps aux | grep apache

Le résultat de l'exécution de ps aux est passé par le tube | avec la commande grep. Mais la partie délicate de ce tube est qu'il nécessite une relation parent-enfant entre les processus. Le programme process_a a démarré le programme process_b et a essayé de créer un exemple de programme qui échange des données via un tube, mais cela n'a pas fonctionné. Si quelqu'un peut le faire, veuillez nous le faire savoir dans les commentaires.

FIFO Aussi connu sous le nom de named piped. Un tube avec un nom sur le système de fichiers. Tous les processus peuvent créer, accéder et supprimer des FIFO sans relation parent-enfant. Ce FIFO peut également être créé à partir de la console avec la commande mkfifo.

$ mkfifo ./fifo.tmp

Le FIFO créé est accessible comme un fichier normal.

Écrire dans FIFO


$ cat > fifo.tmp

Lire FIFO


$ cat fifo.tmp

Voici un exemple de code pour l'échange de données avec FIFO.

process_a.cpp


#include <cstdio>
#include <cstdlib>
#include <string>
#include <iostream>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

const size_t BUFFER_SIZE(80);

int main()
{
    //Descripteur de fichier
    int fd;

    //Créer un FIFO
    // mkfifo(<pathname>, <permission>)
    mkfifo("/tmp/myfifo", 0666);

    std::string message("Hello World!");
    //Ouvrez FIFO pour écriture uniquement
    fd = open("/tmp/myfifo", O_WRONLY);

    //Rédaction d'un message
    write(fd, message.c_str(), message.size() + 1);
    close(fd);

    return EXIT_SUCCESS;
}

process_b.cpp


#include <cstdio>
#include <cstdlib>
#include <string>
#include <iostream>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

const size_t BUFFER_SIZE(80);

int main()
{
    //Descripteur de fichier
    int fd;

    //Créer un FIFO
    // mkfifo(<pathname>, <permission>)
    mkfifo("/tmp/myfifo", 0666);

    char str[BUFFER_SIZE];
    //Ouvrir FIFO en lecture seule
    fd = open("/tmp/myfifo", O_RDONLY);
    read(fd, str, BUFFER_SIZE);
    const std::string message(str);

    //Affichage du contenu lu
    std::cout << message << std::endl;
    close(fd);

    return EXIT_SUCCESS;
}

Le FIFO peut être lu et écrit par plusieurs processus. Les accès simultanés multiples sont traités automatiquement de manière exclusive.

Communication par socket

Les sockets assurent la communication entre les processus indépendants. En outre, il existe des avantages que les autres méthodes n'ont pas. Il peut communiquer avec des processus sur d'autres machines. Vous trouverez ci-dessous un exemple de code utilisant un socket.

process_a.cpp


#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cassert>
#include<sys/socket.h>
#include<sys/un.h>
#include<unistd.h>

int server (const int& client_socket)
{
    while (1) {
        const size_t MAX_SIZE = 128;
        char buffer[MAX_SIZE];
        read(client_socket, buffer, MAX_SIZE);
        const std::string message(buffer);
        if(message.size() == 0) {
            return 0;
        } else {
            std::cout << message << std::endl;
            return 1;
        }
    }

    assert(!"This line must not be executed.");
}

int main()
{
    const std::string socket_name("my_socket");
    int socket_fd;
    sockaddr_un name;
    int client_sent_quit_message;
    /*Créer une prise*/
    socket_fd = socket(PF_LOCAL, SOCK_STREAM, 0);
    /*Définir comme serveur*/
    name.sun_family = AF_LOCAL;
    strcpy(name.sun_path, socket_name.c_str());
    bind(socket_fd, reinterpret_cast<sockaddr*>(&name), SUN_LEN (&name));
    /*Prise ouverte*/
    listen(socket_fd, 5);
    /*Attendez un message une fois connecté*/
    do {
        sockaddr_un client_name;
        socklen_t client_name_len;
        int client_socket_fd;
        /*Attendez qu'il y ait une connexion*/
        client_socket_fd = accept(socket_fd, reinterpret_cast<sockaddr*>(&client_name), &client_name_len);
        /*Recevoir un message*/
        client_sent_quit_message = server(client_socket_fd);
        /*Déconnecter*/
        close(client_socket_fd);
    } while (!client_sent_quit_message);
    /*Fermer la prise*/
    close(socket_fd);
    unlink(socket_name.c_str());

    return EXIT_SUCCESS;
}

process_b.cpp


#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<sys/socket.h>
#include<sys/un.h>
#include<unistd.h>

/* Write TEXT to the socket given by file descriptor SOCKET_FD. */
int write_text(const int& socket_fd, const std::string& message)
{
    /* Write the string. */
    write(socket_fd, message.c_str(), message.size() + 1);
    return 0;
}

int main ()
{
    const std::string socket_name("my_socket");
    const std::string message = "Hello World!!";
    int socket_fd;
    sockaddr_un name;
    /*Créer une prise*/
    socket_fd = socket(PF_LOCAL, SOCK_STREAM, 0);
    /*Définir le nom du socket*/
    name.sun_family = AF_LOCAL;
    strcpy(name.sun_path, socket_name.c_str());
    /*Connecter la prise*/
    connect(socket_fd, reinterpret_cast<sockaddr*>(&name), SUN_LEN (&name));
    /*Envoyer le message*/
    write_text(socket_fd, message);
    close(socket_fd);
    return EXIT_SUCCESS;
}

Conclusion

J'ai brièvement présenté la méthode IPC fournie par Linux. S'il y a tant de méthodes, vous vous demandez peut-être laquelle utiliser. C'est là que les compétences du programmeur sont montrées. Chaque méthode présente des avantages et des inconvénients. Envisagez et sélectionnez la meilleure méthode sous les contraintes du logiciel.

Les références

Recommended Posts

Communication interprocessus Linux
Types de communication inter-processus
Communication inter-processus ~ mémoire partagée ~
Communication interprocessus pour DL embarquée
Linux
mémorandum Linux
Commande Linux n ° 4
Commande Linux
Commande Linux n ° 3
Présentation de Linux
Bases de Linux
Organisation Linux
Pratique Linux
Ubuntu Linux 20.04
Résumé Linux
Processus Linux
Autorisations Linux
Outils utilisés pour vérifier la communication réseau Linux
Commande Linux n ° 5
À propos de Linux
Bases de Linux
Oubliez Linux
À propos de Linux
Commande Linux
À propos de Linux
À propos de Linux
Redirection Linux