C++에서 포크로 프로세스 생성

Jinku Hu 2023년10월12일
  1. fork()를 사용하여 C++로 프로그램 내에서 두 개의 프로세스 생성
  2. fork()execve를 사용하여 C++에서 여러 프로세스 생성
  3. fork()execve를 사용하여 C++에서 자동 자식 정리 기능으로 여러 프로세스를 만듭니다
C++에서 포크로 프로세스 생성

이 기사에서는 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 개의 자식 프로세스가 생성되면 부모는wait 함수를 호출하는while 루프에서 계속됩니다. 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

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