在 C++ 中用 Fork 建立程序

Jinku Hu 2023年10月12日
  1. 使用 fork() 在 C++ 程式中建立兩個程序
  2. 使用 fork()execve 在 C++ 中建立多個程序
  3. 使用 fork()execve 在 C++ 中建立多個程序的自動子程序清理功能
在 C++ 中用 Fork 建立程序

本文將為大家講解幾種在 C++ 中使用 fork() 系統呼叫建立程序的方法。

使用 fork() 在 C++ 程式中建立兩個程序

fork 函式是大多數基於 Unix 的作業系統中可用的 POSIX 相容系統呼叫。該函式建立了一個新的程序,它是原始呼叫程式的副本。後一個程序稱為 parent,新建立的程序-child。這兩個程序可以看作是在不同記憶體空間執行的兩個執行緒。需要注意的是,目前 Linux 的實現內部沒有執行緒的概念,所以執行緒除了共享記憶體區域外,其他結構與程序類似。fork 函式可以實現同一程式內的併發執行,也可以從檔案系統中執行一個新的可執行檔案(在後面的例子中演示)。

在下面的例子中,我們利用 fork 來演示一個程式內的多程序。fork 不接受引數,並在兩個程序中返回。返回值是父程序中子程序的 PID,子程序中返回 0。如果呼叫失敗,在父程序中返回 -1。因此,我們可以根據返回值的評估來構造 if 語句,每個 if 塊都會被相應的程序執行,從而實現併發執行。

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

輸出:

printed from parent process 27295
printed from child process 27297

使用 fork()execve 在 C++ 中建立多個程序

fork 函式呼叫更實際的用途是建立多個程序,並在這些程序中執行不同的程式。需要注意的是,在這個例子中,我們需要兩個原始碼檔案:一個是父程序,另一個是子程序。子程序程式碼是簡單的無限迴圈,加到單整數,可以通過傳送 SIGTERM 訊號來停止。

父程式宣告一個需要被分叉的子程序執行的檔名,然後呼叫 spawnChild 函式 6 次。spawnChild 函式封裝了 fork/execve 的呼叫,並返回新建立的程序 ID。注意,execve 需要一個程式名和引數列表作為引數,才能在子程序中啟動新的程式程式碼。一旦 6 個子程序建立完畢,父程序繼續在 while 迴圈中呼叫 wait 函式。wait 停止父程序並等待任何一個子程序終止。

注意,需要終止每個子程序,父程序才能正常退出。如果中斷父程序,子程序將繼續執行,其父程序成為一個系統程序。

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

子程序的原始碼(不同的檔案):

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

使用 fork()execve 在 C++ 中建立多個程序的自動子程序清理功能

如果父程序在所有子程序退出之前就被終止,那麼前面的示例程式碼就會出現笨拙的行為。在這種情況下,我們在父程序中新增訊號處理函式,一旦收到 SIGQUIT 訊號,就會自動終止所有子程序。使用 kill -SIGQUIT pid_num_of_parent 命令傳送訊號。

注意,訊號處理程式中需要訪問的一些全域性變數被宣告為 std::atomic 型別,這是對程式正確性的嚴格要求。

#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;
}
作者: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

DelftStack.com 創辦人。Jinku 在機器人和汽車行業工作了8多年。他在自動測試、遠端測試及從耐久性測試中創建報告時磨練了自己的程式設計技能。他擁有電氣/ 電子工程背景,但他也擴展了自己的興趣到嵌入式電子、嵌入式程式設計以及前端和後端程式設計。

LinkedIn Facebook