C言語

fork()とexec()の間で余計なことをするな

2022年7月9日

はじめに

UNIX系のプログラミングで新しいプロセスを作成する場合、fork() システムコールを使用します。
その後、生成された子プロセスで execve() などを呼び出し、新たなプログラムを実行します。

この普通の流れの処理ですが、守らなければならないプログラミングルールが存在します。
今回は、このルールについてご紹介いたします。

参考

fork()execve() を知らない方は、まずこちらをどうぞ。

 

そもそもPOSIXの仕様は?

今回紹介する「プログラミングルール」とは、POSIX の仕様で明記されています。

A process shall be created with a single thread. If a multi-threaded process calls fork(), the new process shall contain a replica of the calling thread and its entire address space, possibly including the states of mutexes and other resources. Consequently, to avoid errors, the child process may only execute async-signal-safe operations until such time as one of the exec functions is called.

引用:IEEE Std 1003.1, 2004 Edition

そのルールとは、赤マーカーの1文です。和訳すると、

「子プロセスはexec関数が呼ばれるまで非同期シグナル安全な関数しか実行できないだろう」

です。

 

fork の使い方の注意ポイント①でも紹介している通り、fork を呼び出して生成された直後の子プロセスは、
親プロセスのページテーブル複製を持っています。
POSIX でも記載がある通り、状態や mutex、その他リソースの複製を子プロセスが持っている状態です。

 

そのような状態で親プロセスの続きを実行するため、

「危ないよね~。非同期シグナル安全な関数しか使えないよね~。」

と言っているわけです。

 

具体的な区間

具体的には、以下の区間で余計なこと(非同期シグナル安全な関数以外の実行)をしてはいけないのです。

fork()とexec()の間で余計なことをしてはいけない

 

余計なことをするとどうなる?

非同期シグナル安全な関数以外を使うとどのようなことが起きるのでしょうか。
非同期シグナル安全について纏めた「シグナルハンドラーの正しい書き方」にも記載していますが、
デッドロック不正メモリアクセスを起こします。

 

もし描画メモリやビデオバッファなどの物理メモリを扱うドライバーを使用している場合、
行儀の悪いドライバーだと先の区間で Kernel Panic を引き起こすかもしれません。
Copy-on-Write でページテーブル複製をコピーして子プロセスと実メモリを分離するため、
きちんと対応していないドライバの場合は、親プロセスのメモリを破壊します。

 

ここでは、シグナルハンドラの正しい書き方で紹介した Self-Pipe Trick を使う場合を絡めた
fork()exec*() に関する事例を紹介しましょう。

 

事例

fork() では、親プロセスのファイルディスクリプタ・リスト(fd List)が子プロセスに複製されます。
Self-Pipe Trick 方式を使用している場合、以下のようなケースで問題が起こります。

  1. Self-Pipe Trick を使っている親プロセスが fork() を呼び出す
  2. 子プロセスにシグナルが届く
  3. 子プロセスは親からシグナルハンドラを引き継いでいるため、pipe に write() する
  4. fd リストが複製されているため、親が pipe から read() する

この結果、子プロセスに届いたシグナルを、親プロセスに届いたかのように処理してしまいます。

 

サンプルプログラム

これまで紹介した事例や注意ポイントを回避するためのサンプルプログラム。

シグナルのブロックに pthread_sigmask を使用しています。
マルチスレッドなプロセスが多い昨今、動作が未定義な sigprocmask を使うのはナンセンスだからです。

 

こちらの記事もよく読まれています

  • この記事を書いた人
  • 最新記事
SANACHAN

SANACHAN

「生涯一エンジニア」を掲げ、大手グローバル企業でSE/PGとして8年勤め、キャリアアップ転職した現役のエンジニアです。世にあるメジャーな全プログラム言語(コボル除く)を自由に扱えます。一児の父。自分のため、家族のため、日々勉強してます。システムエンジニア、プログラミングに関する情報を蓄積している雑記帳です。

-C言語
-, ,