C++ で fork を使ってプロセスを作成する
-
fork()
を用いて C++ でプログラム内に 2つのプロセスを作成する -
fork()
とexecve
を用いて C++ で複数のプロセスを作成する -
fork()
とexecve
を用いて C++ で子プロセスの自動クリーンアップ関数を用いて複数のプロセスを作成する
この記事では、C++ で fork()
システムコールを使ってプロセスを作成する方法をいくつか説明します。
fork()
を用いて C++ でプログラム内に 2つのプロセスを作成する
fork
関数はほとんどの Unix ベースのオペレーティングシステムで利用できる POSIX 準拠のシステムコールです。この関数は元の呼び出しプログラムの複製である新しいプロセスを生成します。後者のプロセスは parent
と呼ばれ、新たに作成されたプロセスは child
と呼ばれる。これら 2つのプロセスは、別々のメモリ空間で実行されている 2つのスレッドと見ることができます。現在の 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
関数呼び出しのより実用的な使い方は、複数のプロセスを作成し、それらのプロセス内で異なるプログラムを実行することです。この例では、親プロセス用と子プロセス用の 2つのソースコードファイルが必要であることに注意してください。子プロセスのコードは、単一の整数に加算する単純な無限ループであり、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;
}