こんにちは!今回はPHPのPOSIX関数の中から、posix_kill関数について詳しく解説していきます。プロセス間通信の基本となる「シグナル送信」の方法を、実践的なサンプルコードと共にお伝えします。
posix_kill関数とは?
posix_killは、指定したプロセスに**シグナル(signal)**を送信する関数です。名前に「kill」とありますが、プロセスを終了させるだけでなく、様々な通知や制御を行うことができます。
基本構文
posix_kill(int $process_id, int $signal): bool
- 第1引数: プロセスID(PID)
- 第2引数: シグナル番号
- 戻り値: 成功時に
true、失敗時にfalse
シグナルとは?
シグナルは、プロセスに送信される非同期通知です。プロセスの制御、状態変更、通信などに使用されます。
主要なシグナル一覧
| シグナル | 番号 | 意味 | デフォルト動作 |
|---|---|---|---|
| SIGTERM | 15 | 終了要求 | プロセス終了 |
| SIGKILL | 9 | 強制終了 | 即座に終了 |
| SIGINT | 2 | 割り込み(Ctrl+C) | プロセス終了 |
| SIGHUP | 1 | ハングアップ | プロセス終了/設定再読込 |
| SIGUSR1 | 10 | ユーザー定義1 | プロセス終了 |
| SIGUSR2 | 12 | ユーザー定義2 | プロセス終了 |
| SIGCHLD | 17 | 子プロセス状態変化 | 無視 |
| SIGCONT | 18 | 一時停止解除 | 実行再開 |
| SIGSTOP | 19 | 一時停止 | プロセス停止 |
SIGTERMとSIGKILLの違い
<?php
// SIGTERM (15): 正常終了を要求
// - プロセスはシグナルをキャッチして後処理を実行できる
// - クリーンアップ処理が可能
posix_kill($pid, SIGTERM);
// SIGKILL (9): 強制終了
// - プロセスはシグナルをキャッチできない
// - 即座に終了、クリーンアップ不可
posix_kill($pid, SIGKILL);
?>
実践的な使い方
例1: 基本的なシグナル送信
<?php
// 子プロセスを作成
$pid = pcntl_fork();
if ($pid == -1) {
die("フォークに失敗しました\n");
} elseif ($pid == 0) {
// 子プロセス: 無限ループで待機
echo "子プロセス起動 (PID: " . posix_getpid() . ")\n";
while (true) {
sleep(1);
echo "実行中...\n";
}
} else {
// 親プロセス: 5秒後に子プロセスを終了
echo "親プロセス (PID: " . posix_getpid() . ")\n";
echo "5秒後に子プロセスを終了します\n";
sleep(5);
// SIGTERMを送信
if (posix_kill($pid, SIGTERM)) {
echo "子プロセス (PID: {$pid}) に終了シグナルを送信しました\n";
} else {
echo "シグナル送信に失敗しました\n";
}
// 子プロセスの終了を待つ
pcntl_wait($status);
echo "子プロセスが終了しました\n";
}
?>
例2: プロセスの存在確認
<?php
function isProcessRunning($pid) {
// シグナル0を送信してプロセスの存在を確認
// シグナル0は実際には何もしないが、プロセスの存在チェックに使える
return posix_kill($pid, 0);
}
// 使用例
$pid = 12345; // チェックしたいPID
if (isProcessRunning($pid)) {
echo "プロセス {$pid} は実行中です\n";
} else {
echo "プロセス {$pid} は存在しないか、アクセス権限がありません\n";
}
?>
例3: シグナルハンドラの実装
<?php
// シグナルを受け取った際の処理を定義
$shutdown_requested = false;
function signalHandler($signal) {
global $shutdown_requested;
switch ($signal) {
case SIGTERM:
echo "\nSIGTERMを受信しました。終了処理を開始します...\n";
$shutdown_requested = true;
break;
case SIGINT:
echo "\nSIGINTを受信しました (Ctrl+C)。終了します...\n";
$shutdown_requested = true;
break;
case SIGHUP:
echo "\nSIGHUPを受信しました。設定を再読み込みします...\n";
// 設定ファイルの再読み込み処理
break;
case SIGUSR1:
echo "\nSIGUSR1を受信しました。ステータスを表示します...\n";
// ステータス情報の出力
break;
}
}
// シグナルハンドラを登録
pcntl_signal(SIGTERM, 'signalHandler');
pcntl_signal(SIGINT, 'signalHandler');
pcntl_signal(SIGHUP, 'signalHandler');
pcntl_signal(SIGUSR1, 'signalHandler');
echo "プロセス起動 (PID: " . posix_getpid() . ")\n";
echo "終了するにはSIGTERMまたはCtrl+Cを送信してください\n";
echo "別のターミナルから試してみてください:\n";
echo " kill -TERM " . posix_getpid() . "\n";
echo " kill -USR1 " . posix_getpid() . "\n";
// メインループ
$counter = 0;
while (!$shutdown_requested) {
// シグナルの処理
pcntl_signal_dispatch();
echo "処理中... ({$counter})\n";
$counter++;
sleep(2);
}
echo "クリーンアップ処理を実行中...\n";
sleep(1);
echo "終了しました\n";
?>
例4: プロセス管理クラス
<?php
class ProcessManager {
private $processes = [];
public function start($command, $name = null) {
$pid = pcntl_fork();
if ($pid == -1) {
throw new Exception("フォークに失敗しました");
} elseif ($pid == 0) {
// 子プロセス
exec($command);
exit(0);
} else {
// 親プロセス
$this->processes[$pid] = [
'name' => $name ?? "Process-{$pid}",
'command' => $command,
'started_at' => time()
];
return $pid;
}
}
public function stop($pid, $graceful = true) {
if (!isset($this->processes[$pid])) {
throw new Exception("プロセス {$pid} は管理されていません");
}
$signal = $graceful ? SIGTERM : SIGKILL;
$result = posix_kill($pid, $signal);
if ($result) {
echo "プロセス {$pid} に" .
($graceful ? "SIGTERM" : "SIGKILL") .
"を送信しました\n";
}
return $result;
}
public function restart($pid) {
if (!isset($this->processes[$pid])) {
throw new Exception("プロセス {$pid} は管理されていません");
}
$process_info = $this->processes[$pid];
// 既存プロセスを停止
$this->stop($pid);
pcntl_waitpid($pid, $status);
unset($this->processes[$pid]);
// 新しいプロセスを起動
return $this->start($process_info['command'], $process_info['name']);
}
public function stopAll($graceful = true) {
foreach (array_keys($this->processes) as $pid) {
$this->stop($pid, $graceful);
}
// すべてのプロセスの終了を待つ
foreach (array_keys($this->processes) as $pid) {
pcntl_waitpid($pid, $status);
}
$this->processes = [];
}
public function isRunning($pid) {
return posix_kill($pid, 0);
}
public function getStatus($pid = null) {
if ($pid !== null) {
if (!isset($this->processes[$pid])) {
return null;
}
return array_merge(
$this->processes[$pid],
['running' => $this->isRunning($pid)]
);
}
// すべてのプロセスの状態を返す
$status = [];
foreach ($this->processes as $pid => $info) {
$status[$pid] = array_merge(
$info,
['running' => $this->isRunning($pid)]
);
}
return $status;
}
}
// 使用例
$manager = new ProcessManager();
// プロセスを起動
$pid1 = $manager->start('sleep 100', 'worker-1');
$pid2 = $manager->start('sleep 100', 'worker-2');
echo "起動したプロセス:\n";
print_r($manager->getStatus());
// 5秒待機
sleep(5);
// 1つのプロセスを停止
$manager->stop($pid1);
// 残りのプロセスを確認
sleep(1);
echo "\n現在のプロセス:\n";
print_r($manager->getStatus());
// すべて停止
$manager->stopAll();
echo "\nすべてのプロセスを停止しました\n";
?>
例5: デーモンプロセスの制御
<?php
class Daemon {
private $pid_file;
private $running = false;
public function __construct($pid_file = '/tmp/daemon.pid') {
$this->pid_file = $pid_file;
}
public function start() {
// 既に実行中かチェック
if ($this->isRunning()) {
throw new Exception("デーモンは既に実行中です");
}
// デーモン化
$pid = pcntl_fork();
if ($pid == -1) {
throw new Exception("フォークに失敗しました");
} elseif ($pid) {
// 親プロセスは終了
exit(0);
}
// 子プロセスでデーモンを実行
posix_setsid();
// PIDファイルに書き込み
file_put_contents($this->pid_file, posix_getpid());
// シグナルハンドラを設定
$this->setupSignalHandlers();
// メインループ
$this->run();
}
public function stop() {
$pid = $this->getPid();
if ($pid === null) {
throw new Exception("PIDファイルが見つかりません");
}
if (!posix_kill($pid, 0)) {
echo "プロセスは既に停止しています\n";
unlink($this->pid_file);
return;
}
// SIGTERMを送信
echo "デーモン (PID: {$pid}) に終了シグナルを送信中...\n";
posix_kill($pid, SIGTERM);
// 終了を待つ(最大10秒)
$timeout = 10;
$elapsed = 0;
while (posix_kill($pid, 0) && $elapsed < $timeout) {
sleep(1);
$elapsed++;
echo ".";
}
echo "\n";
// まだ動いている場合は強制終了
if (posix_kill($pid, 0)) {
echo "強制終了します...\n";
posix_kill($pid, SIGKILL);
sleep(1);
}
unlink($this->pid_file);
echo "デーモンを停止しました\n";
}
public function restart() {
echo "デーモンを再起動中...\n";
if ($this->isRunning()) {
$this->stop();
}
sleep(1);
$this->start();
}
public function status() {
$pid = $this->getPid();
if ($pid === null) {
echo "デーモンは停止しています(PIDファイルなし)\n";
return;
}
if (posix_kill($pid, 0)) {
echo "デーモンは実行中です (PID: {$pid})\n";
} else {
echo "デーモンは停止しています (古いPIDファイル: {$pid})\n";
}
}
public function reload() {
$pid = $this->getPid();
if ($pid === null || !posix_kill($pid, 0)) {
throw new Exception("デーモンが実行されていません");
}
echo "設定を再読み込みします (PID: {$pid})...\n";
posix_kill($pid, SIGHUP);
}
private function isRunning() {
$pid = $this->getPid();
return $pid !== null && posix_kill($pid, 0);
}
private function getPid() {
if (!file_exists($this->pid_file)) {
return null;
}
return (int)file_get_contents($this->pid_file);
}
private function setupSignalHandlers() {
pcntl_signal(SIGTERM, function() {
$this->running = false;
});
pcntl_signal(SIGHUP, function() {
echo "設定を再読み込みしました\n";
// 設定再読み込み処理
});
}
private function run() {
$this->running = true;
while ($this->running) {
pcntl_signal_dispatch();
// デーモンの処理
// この例では単にログを出力
file_put_contents(
'/tmp/daemon.log',
date('Y-m-d H:i:s') . " - デーモン実行中\n",
FILE_APPEND
);
sleep(5);
}
// クリーンアップ
unlink($this->pid_file);
}
}
// 使用例(コマンドライン引数で制御)
if ($argc < 2) {
echo "使用方法: php {$argv[0]} {start|stop|restart|status|reload}\n";
exit(1);
}
$daemon = new Daemon();
$command = $argv[1];
try {
switch ($command) {
case 'start':
$daemon->start();
break;
case 'stop':
$daemon->stop();
break;
case 'restart':
$daemon->restart();
break;
case 'status':
$daemon->status();
break;
case 'reload':
$daemon->reload();
break;
default:
echo "不明なコマンド: {$command}\n";
exit(1);
}
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
exit(1);
}
?>
よくある使用シーン
1. グレースフルシャットダウン
<?php
function gracefulShutdown($pid, $timeout = 10) {
// まずSIGTERMで正常終了を試みる
if (!posix_kill($pid, SIGTERM)) {
return false;
}
echo "正常終了を要求しました (PID: {$pid})...\n";
// 指定時間待つ
$elapsed = 0;
while (posix_kill($pid, 0) && $elapsed < $timeout) {
sleep(1);
$elapsed++;
echo ".";
}
echo "\n";
// まだ動いている場合は強制終了
if (posix_kill($pid, 0)) {
echo "タイムアウト。強制終了します...\n";
posix_kill($pid, SIGKILL);
sleep(1);
return posix_kill($pid, 0) === false;
}
echo "正常に終了しました\n";
return true;
}
?>
2. プロセスグループへのシグナル送信
<?php
// プロセスグループ全体にシグナルを送る
function killProcessGroup($pgid, $signal = SIGTERM) {
// 負の値を指定するとプロセスグループに送信される
return posix_kill(-$pgid, $signal);
}
// 使用例: 自分のプロセスグループにシグナル送信
$pgid = posix_getpgid(posix_getpid());
killProcessGroup($pgid, SIGUSR1);
?>
3. ワーカープロセスの監視
<?php
class WorkerPool {
private $workers = [];
private $max_workers = 4;
public function start() {
for ($i = 0; $i < $this->max_workers; $i++) {
$this->spawnWorker($i);
}
// 監視ループ
while (true) {
$this->checkWorkers();
sleep(5);
}
}
private function spawnWorker($id) {
$pid = pcntl_fork();
if ($pid == 0) {
// ワーカープロセス
$this->workerTask($id);
exit(0);
} else {
$this->workers[$pid] = [
'id' => $id,
'started' => time()
];
echo "ワーカー {$id} を起動しました (PID: {$pid})\n";
}
}
private function checkWorkers() {
foreach ($this->workers as $pid => $info) {
// プロセスが生きているか確認
if (!posix_kill($pid, 0)) {
echo "ワーカー {$info['id']} (PID: {$pid}) が停止しています。再起動します...\n";
unset($this->workers[$pid]);
$this->spawnWorker($info['id']);
}
}
}
private function workerTask($id) {
echo "ワーカー {$id} が処理を開始しました\n";
// ワーカーの処理
while (true) {
sleep(1);
// 何か仕事をする
}
}
}
?>
注意点とトラブルシューティング
権限の問題
<?php
// 他のユーザーのプロセスにはシグナルを送れない
$result = posix_kill($other_user_pid, SIGTERM);
if (!$result) {
$error = posix_get_last_error();
$error_msg = posix_strerror($error);
echo "エラー: {$error_msg}\n";
// 通常は "Operation not permitted"
}
?>
SIGKILLとSIGSTOPは無視できない
<?php
// これらのシグナルはキャッチできない
// SIGKILL (9): 強制終了
// SIGSTOP (19): 一時停止
// ハンドラを設定しようとしても無効
pcntl_signal(SIGKILL, function() {
// この関数は呼ばれない
});
?>
Windows環境での制限
<?php
if (DIRECTORY_SEPARATOR === '\\') {
die("posix_killはWindows環境では使用できません\n");
}
?>
シグナルの遅延
<?php
// シグナルは即座に処理されない場合がある
// pcntl_signal_dispatch()を定期的に呼ぶ必要がある
while ($running) {
pcntl_signal_dispatch(); // これを忘れずに!
// 処理
doWork();
sleep(1);
}
?>
関連する便利な関数
シグナル関連関数
pcntl_signal(): シグナルハンドラを設定pcntl_signal_dispatch(): 保留中のシグナルを処理pcntl_alarm(): アラームシグナルを設定pcntl_sigprocmask(): シグナルマスクを操作pcntl_sigwaitinfo(): シグナルを待つ
プロセス関連関数
posix_getpid(): 現在のプロセスIDを取得posix_getppid(): 親プロセスIDを取得posix_getpgid(): プロセスグループIDを取得pcntl_fork(): 子プロセスを作成pcntl_wait(): 子プロセスの終了を待つ
まとめ
posix_kill関数は以下の特徴があります。
✅ プロセスにシグナルを送信 ✅ プロセスの終了だけでなく様々な制御が可能 ✅ グレースフルシャットダウンに必須 ✅ プロセス監視と管理に活用 ✅ デーモンプロセスの制御に最適
シグナルはUnix/Linuxのプロセス間通信の基本です。適切にシグナルを使いこなすことで、堅牢で管理しやすいアプリケーションを構築できます。
セキュリティとベストプラクティス
- 常にSIGTERMから試す: SIGKILLは最後の手段
- タイムアウトを設定: 無限待機を避ける
- 権限を確認: 他のユーザーのプロセスは制御できない
- エラーハンドリング: シグナル送信の失敗に対応
- シグナル0で存在確認: プロセスの状態をチェック
安全なプロセス管理を! この記事が役に立ったら、ぜひシェアしてください。PHPのプロセス制御について、他にも知りたいことがあればコメントで教えてくださいね。
