Erzeugen von Prozessen mit Fork in C++

  1. Verwenden von fork() zum Erzeugen von zwei Prozessen innerhalb eines Programms in C++
  2. Verwendung von fork() und execve zur Erzeugung mehrerer Prozesse in C++
  3. Verwenden Sie fork() und execve, um mehrere Prozesse mit automatischer Aufräumfunktion für Kinder in C++ zu erstellen

Dieser Artikel erklärt verschiedene Methoden, wie man Prozesse mit einem fork()-Systemaufruf in C++ erzeugen kann.

Verwenden von fork() zum Erzeugen von zwei Prozessen innerhalb eines Programms in C++

Die Funktion fork ist der POSIX-konforme Systemaufruf, der in den meisten Unix-basierten Betriebssystemen verfügbar ist. Die Funktion erzeugt einen neuen Prozess, der ein Duplikat des ursprünglich aufrufenden Programms ist. Der letztere Prozess wird parent genannt und der neu erzeugte - child. Diese beiden Prozesse können als zwei Threads angesehen werden, die in getrennten Speicherbereichen ausgeführt werden. Beachten Sie, dass die aktuelle Linux-Implementierung intern kein Konzept für Threads hat. Threads sind also ähnliche Strukturen wie Prozesse, nur dass sie sich Speicherbereiche teilen. Die Funktion fork kann die gleichzeitige Ausführung innerhalb desselben Programms implementieren oder eine neue ausführbare Datei aus dem Dateisystem ausführen (wie in den späteren Beispielen gezeigt wird).

Im folgenden Beispiel verwenden wir fork, um Multiprocessing innerhalb eines Programms zu demonstrieren. fork nimmt keine Argumente entgegen und kehrt in beiden Prozessen zurück. Der Rückgabewert ist die PID des Kindprozesses im Elternprozess, und im Kindprozess wird 0 zurückgegeben. Falls der Aufruf fehlschlägt, wird im Elternprozess -1 zurückgegeben. Daher können wir “if”-Anweisungen basierend auf der Auswertung des Rückgabewerts konstruieren, und jeder “if”-Block wird vom entsprechenden Prozess ausgeführt, was zu einer gleichzeitigen Ausführung führt.

#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;
}

Ausgabe:

printed from parent process 27295
printed from child process 27297

Verwendung von fork() und execve zur Erzeugung mehrerer Prozesse in C++

Eine praktischere Verwendung des Funktionsaufrufs fork besteht darin, mehrere Prozesse zu erzeugen und innerhalb dieser Prozesse verschiedene Programme auszuführen. Beachten Sie, dass wir in diesem Beispiel zwei Quellcodedateien benötigen: eine für den Elternprozess und die andere für die Kindprozesse. Der Code des Kindprozesses ist die einfache Endlosschleife, die sich zu einer einzigen Ganzzahl addiert und durch das Senden des SIGTERM-Signals gestoppt werden kann.

Das Elternprogramm deklariert einen Dateinamen, der von den gegabelten Kindprozessen ausgeführt werden soll und ruft dann die Funktion spawnChild 6 mal auf. Die Funktion spawnChild umhüllt die Aufrufe von fork/execve und gibt die neu erstellte Prozess-ID zurück. Beachten Sie, dass execve einen Programmnamen und eine Argumentliste als Argumente benötigt, um einen neuen Programmcode innerhalb der Kindprozesse zu starten. Sobald die 6 Kindprozesse erstellt sind, fährt der Elternprozess in der while-Schleife fort, wo er die Funktion wait aufruft. wait stoppt den Elternprozess und wartet, bis einer der Kindprozesse beendet wird.

Beachten Sie, dass Sie jeden Kindprozess beenden müssen, damit der Elternprozess normal beendet werden kann. Wenn Sie den Elternprozess unterbrechen, laufen die Kindprozesse weiter, und der Elternprozess wird zum Systemprozess.

#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;
}

Quellcode des Kindprozesses (andere Datei):

#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);
}

Verwenden Sie fork() und execve, um mehrere Prozesse mit automatischer Aufräumfunktion für Kinder in C++ zu erstellen

Der vorherige Beispielcode verhält sich ungeschickt, wenn der Elternprozess beendet wurde, bevor alle Kinder beendet wurden. In diesem Fall fügen wir dem Elternprozess eine Signalhandlerfunktion hinzu, die automatisch alle Kindprozesse beendet, sobald das Signal SIGQUIT empfangen wird. Verwenden Sie den Befehl kill -SIGQUIT pid_num_of_parent, um das Signal zu senden.

Beachten Sie, dass einige der globalen Variablen, auf die im Signal-Handler zugegriffen werden muss, als std::atomic-Typen deklariert sind, was die strikte Voraussetzung für die Korrektheit des Programms ist.

#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;
}