Criar processos com fork em C++

Jinku Hu 12 outubro 2023
  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++
Criar processos com fork 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 <sys/wait.h>
#include <unistd.h>

#include <iostream>

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 cicli 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 <sys/wait.h>
#include <unistd.h>

#include <atomic>
#include <filesystem>
#include <iostream>
#include <vector>

using std::cout;
using std::endl;
using std::string;
using std::vector;
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 <sys/wait.h>
#include <unistd.h>

#include <iostream>

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 <sys/wait.h>
#include <unistd.h>

#include <atomic>
#include <filesystem>
#include <iostream>

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;
}
Autor: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn Facebook