Créer des processus avec fork en C++

  1. Utilisez fork() pour créer deux processus dans le programme en C++
  2. Utilisez fork() et execve pour créer des processus multiples en C++
  3. Utilisez fork() et execve pour créer des processus multiples avec la fonction de nettoyage automatique des processus enfants en C++

Cet article explique plusieurs méthodes pour créer des processus avec un appel système fork() en C++.

Utilisez fork() pour créer deux processus dans le programme en C++

La fonction fork est l’appel système compatible POSIX disponible dans la plupart des systèmes d’exploitation basés sur Unix. La fonction crée un nouveau processus, qui est une duplication du programme d’appel original. Ce dernier processus est appelé parent et un processus nouvellement créé - child. Ces deux processus peuvent être considérés comme les deux threads s’exécutant dans des espaces mémoire séparés. Notez que l’implémentation actuelle de Linux n’a pas le concept de thread en interne, donc les threads sont des structures similaires aux processus sauf qu’ils partagent des régions de mémoire. La fonction fork peut implémenter une exécution simultanée dans le même programme ou exécuter un nouvel exécutable depuis le système de fichiers (démontré dans les exemples suivants).

Dans l’exemple suivant, nous utilisons fork pour démontrer le multitraitement au sein d’un même programme. fork ne prend aucun argument et retourne dans les deux processus. La valeur de retour est le PID du processus fils dans le processus parent, et 0 est renvoyé dans le processus fils. En cas d’échec de l’appel, -1 est renvoyé dans le processus parent. Par conséquent, nous pouvons construire des déclarations if basées sur l’évaluation de la valeur de retour, et chaque bloc if est exécuté par le processus correspondant, ce qui entraîne une exécution simultanée.

#include <iostream>
#include <sys/wait.h>
#include <unistd.h>

using std::cout; using std::endl;

int main() {
    pid_t c_pid = fork();

    if (c_pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (c_pid > 0) {
        cout << "printed from parent process " << getpid() << endl;
        wait(nullptr);
    } else {
        cout << "printed from child process " << getpid() << endl;
        exit(EXIT_SUCCESS);
    }

    return EXIT_SUCCESS;
}

Production :

printed from parent process 27295
printed from child process 27297

Utilisez fork() et execve pour créer des processus multiples en C++

Une utilisation plus pratique de l’appel de fonction fork consiste à créer de multiples processus et à exécuter différents programmes au sein de ces processus. Notez que, dans cet exemple, nous avons besoin de deux fichiers de code source : un pour le processus parent et l’autre pour les processus enfants. Le code du processus fils est la simple boucle infinie qui s’ajoute à l’entier unique et qui peut être arrêtée en envoyant le signal SIGTERM.

Le programme parent déclare un nom de fichier qui doit être exécuté par les processus enfants bifurqués et appelle ensuite la fonction spawnChild 6 fois. La fonction spawnChild enveloppe les appels fork/execve et retourne l’ID du processus nouvellement créé. Notez que execve nécessite un nom de programme et une liste d’arguments pour lancer un nouveau code de programme dans les processus fils. Une fois que les 6 processus fils sont créés, le parent continue dans la boucle while où il appelle la fonction wait. La fonction wait arrête le processus parent et attend que l’un des processus enfants se termine.

Notez que vous devez terminer chaque processus enfant pour que le parent se termine normalement. Si vous interrompez le processus parent, les processus enfants continueront à s’exécuter, et leurs parents deviendront un processus système.

#include <iostream>
#include <vector>
#include <sys/wait.h>
#include <unistd.h>
#include <atomic>
#include <filesystem>

using std::cout; using std::endl;
using std::vector; using std::string;
using std::filesystem::exists;

constexpr int FORK_NUM = 6;

pid_t spawnChild(const char* program, char** arg_list)
{
    pid_t ch_pid = fork();
    if (ch_pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (ch_pid > 0) {
        cout << "spawn child with pid - " << ch_pid << endl;
        return ch_pid;
    } else {
        execve(program, arg_list, nullptr);
        perror("execve");
        exit(EXIT_FAILURE);
    }
}

int main() {
    string program_name ("child");
    char *arg_list[] = {program_name.data(), nullptr};
    vector<int> children;
    children.reserve(FORK_NUM);

    if (!exists(program_name)){
        cout << "Program file 'child' does not exist in current directory!\n";
        exit(EXIT_FAILURE);
    }

    for (int i = 0; i < FORK_NUM; ++i)
        children[i] = spawnChild(program_name.c_str(), arg_list);
    cout << endl;

    pid_t child_pid;
    while ((child_pid = wait(nullptr)) > 0)
        cout << "child " << child_pid << " terminated" << endl;

    return EXIT_SUCCESS;
}

Code source du processus enfant (fichier différent) :

#include <iostream>
#include <sys/wait.h>
#include <unistd.h>

volatile sig_atomic_t shutdown_flag = 1;

void GracefulExit(int signal_number)
{
    shutdown_flag = 0;
}

int main()
{
    // Register SIGTERM handler
    signal(SIGTERM, GracefulExit);

    unsigned int tmp = 0;
    while (shutdown_flag) {
        tmp += 1;
        usleep(100);
    }

    exit(EXIT_SUCCESS);
}

Utilisez fork() et execve pour créer des processus multiples avec la fonction de nettoyage automatique des processus enfants en C++

Le code de l’exemple précédent a un comportement maladroit si le processus parent est terminé avant que tous les enfants ne soient sortis. Dans ce cas, nous ajoutons la fonction de traitement de signal au processus parent qui terminera automatiquement tous les processus enfants une fois que le signal SIGQUIT sera reçu. Utilisez la commande kill -SIGQUIT pid_num_of_parent pour envoyer le signal.

Notez que certaines des variables globales qui doivent être accédées dans le gestionnaire de signal sont déclarées comme des types std::atomic, ce qui est la condition stricte pour que le programme soit correct.

#include <iostream>
#include <sys/wait.h>
#include <unistd.h>
#include <atomic>
#include <filesystem>

using std::cout; using std::endl;
using std::string; using std::filesystem::exists;

constexpr std::atomic<int> FORK_NUM = 6;
constexpr std::atomic<int> handler_exit_code = 103;

std::atomic<int> child_pid;
std::atomic<int> *children;

void sigquitHandler(int signal_number)
{
    for (int i = 0; i < FORK_NUM; ++i) {
        kill(children[i], SIGTERM);
    }
    while ((child_pid = wait(nullptr)) > 0);
    _exit(handler_exit_code);
}

pid_t spawnChild(const char* program, char** arg_list)
{
    pid_t ch_pid = fork();
    if (ch_pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (ch_pid > 0) {
        cout << "spawn child with pid - " << ch_pid << endl;
        return ch_pid;
    } else {
        execve(program, arg_list, nullptr);
        perror("execve");
        exit(EXIT_FAILURE);
    }
}

int main() {
    string program_name ("child");
    char *arg_list[] = {program_name.data(), nullptr};

    if (!exists(program_name)){
        cout << "Program file 'child' does not exist in current directory!\n";
        exit(EXIT_FAILURE);
    }

    children = reinterpret_cast<std::atomic<int> *>(new int[FORK_NUM]);
    signal(SIGQUIT, sigquitHandler);

    for (int i = 0; i < FORK_NUM; ++i) {
        children[i] = spawnChild(program_name.c_str(), arg_list);
    }
    cout << endl;

    while ((child_pid = wait(nullptr)) > 0)
        cout << "child " << child_pid << " terminated" << endl;

    return EXIT_SUCCESS;
}