Criar processos com fork em C++

  1. Utilize fork() para criar dois processos dentro do programa em C++
  2. Utilize fork() e execve para Criar Processos Múltiplos em C++
  3. Utilize fork() e execve para criar processos múltiplos com a função de limpeza automática de crianças em C++

Este artigo explicará vários métodos de como criar processos com uma chamada de sistema fork() em C++.

Utilize fork() para criar dois processos dentro do programa em C++

A função fork é a chamada de sistema compatível com POSIX disponível na maioria dos sistemas operativos baseados em Unix. A função cria um novo processo, que é uma duplicata do programa de chamada original. Este último processo chama-se parent e um recentemente criado - child. Estes dois processos podem ser vistos como os dois threads executados em espaços de memória separados. Note-se que a actual implementação do Linux não tem qualquer conceito de thread internamente, pelo que os threads são estruturas semelhantes aos processos, excepto que partilham regiões de memória. A função fork pode implementar execução simultânea dentro do mesmo programa ou executar um novo executável a partir do sistema de ficheiros (demonstrado nos exemplos posteriores).

No exemplo seguinte, utilizamos o fork para demonstrar o multiprocessamento dentro do mesmo programa. O fork não aceita argumentos e retornos em ambos os processos. O valor de retorno é o PID da criança no processo dos pais, e 0 é devolvido no processo da criança. No caso da chamada falhar, -1 é devolvido no processo dos pais. Assim, podemos construir declarações if baseadas na avaliação do valor de retorno, e cada bloco if é executado pelo processo correspondente, resultando na execução simultânea.

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

Resultado:

printed from parent process 27295
printed from child process 27297

Utilize fork() e execve para Criar Processos Múltiplos em C++

Uma utilização mais prática da chamada à função fork é criar múltiplos processos e executar diferentes programas dentro destes processos. Note-se que, neste exemplo, precisamos de dois ficheiros de código fonte: um para o processo dos pais e outro para os processos dos filhos. O código de processo infantil é o simples loop infinito que se acrescenta ao inteiro único e pode ser parado enviando o sinal SIGTERM.

O programa pai declara um nome de ficheiro que precisa de ser executado pelos processos de criança bifurcada e depois chama a função spawnChild 6 vezes. A função spawnChild envolve as chamadas fork/execve e devolve o ID do processo recentemente criado. Note que o execve requer um nome de programa e uma lista de argumentos como argumentos para lançar um novo código de programa dentro dos processos da criança. Uma vez criados os processos de 6 filhos, o pai continua no ciclo while onde chama a função wait. A função wait pára o processo dos pais e espera até que qualquer um dos processos da criança termine.

Note que é necessário terminar cada processo de criança para que o pai saia normalmente. Se interromper o processo dos pais, os processos dos filhos continuarão a correr, e os seus pais tornam-se um processo do sistema.

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

O código fonte do processo infantil (ficheiro diferente):

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

Utilize fork() e execve para criar processos múltiplos com a função de limpeza automática de crianças em C++

O código do exemplo anterior tem um comportamento desajeitado se o processo dos pais foi terminado antes de todas as crianças terem saído. Neste caso, adicionamos a função de manipulador de sinal ao processo dos pais que terminará automaticamente todos os processos das crianças assim que o sinal SIGQUIT for recebido. Utilize o comando kill -SIGQUIT pid_num_of_parent para enviar o sinal.

Note-se que algumas das variáveis globais que precisam de ser acedidas no manipulador do sinal são declaradas como tipos std::atomic, que é o requisito rigoroso para a correcção do programa.

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