Crea processi con fork in C++

Jinku Hu 12 ottobre 2023
  1. Usa fork() per creare due processi all’interno del programma in C++
  2. Usa fork() e execve per creare più processi in C++
  3. Usa fork() ed execve per creare più processi con la funzione di pulizia automatica dei bambini in C++
Crea processi con fork in C++

Questo articolo spiegherà diversi metodi su come creare processi con una chiamata di sistema fork() in C++.

Usa fork() per creare due processi all’interno del programma in C++

La funzione fork è la chiamata di sistema conforme a POSIX disponibile nella maggior parte dei sistemi operativi basati su Unix. La funzione crea un nuovo processo, che è un duplicato del programma chiamante originale. Quest’ultimo processo è chiamato genitore e uno di nuova creazione - figlio. Questi due processi possono essere visti come i due thread in esecuzione in spazi di memoria separati. Si noti che l’attuale implementazione di Linux non ha alcun concetto di thread internamente, quindi i thread sono strutture simili ai processi tranne per il fatto che condividono regioni di memoria. La funzione fork può implementare l’esecuzione simultanea all’interno dello stesso programma o eseguire un nuovo eseguibile dal filesystem (dimostrato negli esempi successivi).

Nell’esempio seguente, utilizziamo fork per dimostrare il multiprocessing all’interno di un programma. fork non accetta argomenti e restituisce in entrambi i processi. Il valore di ritorno è il PID del figlio nel processo genitore e 0 viene restituito nel processo figlio. Nel caso in cui la chiamata fallisca, viene restituito -1 nel processo genitore. Quindi, possiamo costruire istruzioni if basate sulla valutazione del valore restituito, e ogni blocco if viene eseguito dal processo corrispondente, risultando in esecuzione simultanea.

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

Produzione:

printed from parent process 27295
printed from child process 27297

Usa fork() e execve per creare più processi in C++

L’uso più pratico della chiamata alla funzione fork consiste nel creare più processi ed eseguire diversi programmi all’interno di questi processi. Nota che, in questo esempio, abbiamo bisogno di due file di codice sorgente: uno per il processo genitore e l’altro per i processi figli. Il codice del processo figlio è il semplice bucle infinito che si aggiunge al singolo intero e può essere interrotto inviando il segnale SIGTERM.

Il programma genitore dichiara un nome di file che deve essere eseguito dai processi figlio biforcati e quindi chiama la funzione spawnChild 6 volte. La funzione spawnChild avvolge le chiamate fork / execve e restituisce l’ID del processo appena creato. Si noti che execve richiede un nome di programma e una lista di argomenti come argomenti per lanciare un nuovo codice di programma all’interno dei processi figli. Una volta che i 6 processi figli sono stati creati, il genitore continua nel cicli while dove chiama la funzione wait. wait interrompe il processo genitore e attende fino al termine di uno qualsiasi dei processi figli.

Notare che è necessario terminare ogni processo figlio affinché il genitore esca normalmente. Se interrompi il processo genitore, i processi figli continueranno a essere eseguiti e i loro genitori diventeranno un processo di 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;
}

Codice sorgente del processo figlio (file diverso):

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

Usa fork() ed execve per creare più processi con la funzione di pulizia automatica dei bambini in C++

Il codice di esempio precedente ha un comportamento goffo se il processo genitore è stato terminato prima che tutti i figli fossero terminati. In questo caso, aggiungiamo la funzione di gestore del segnale al processo genitore che terminerà automaticamente tutti i processi figli una volta ricevuto il segnale SIGQUIT. Usa il comando kill -SIGQUIT pid_num_of_parent per inviare il segnale.

Si noti che alcune delle variabili globali a cui è necessario accedere nel gestore del segnale sono dichiarate come tipi std::atomic, che è il requisito rigoroso per la correttezza del programma.

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