[PHP]posix_kill関数とは?プロセスにシグナルを送る方法を徹底解説

PHP

こんにちは!今回はPHPのPOSIX関数の中から、posix_kill関数について詳しく解説していきます。プロセス間通信の基本となる「シグナル送信」の方法を、実践的なサンプルコードと共にお伝えします。

posix_kill関数とは?

posix_killは、指定したプロセスに**シグナル(signal)**を送信する関数です。名前に「kill」とありますが、プロセスを終了させるだけでなく、様々な通知や制御を行うことができます。

基本構文

posix_kill(int $process_id, int $signal): bool
  • 第1引数: プロセスID(PID)
  • 第2引数: シグナル番号
  • 戻り値: 成功時にtrue、失敗時にfalse

シグナルとは?

シグナルは、プロセスに送信される非同期通知です。プロセスの制御、状態変更、通信などに使用されます。

主要なシグナル一覧

シグナル番号意味デフォルト動作
SIGTERM15終了要求プロセス終了
SIGKILL9強制終了即座に終了
SIGINT2割り込み(Ctrl+C)プロセス終了
SIGHUP1ハングアッププロセス終了/設定再読込
SIGUSR110ユーザー定義1プロセス終了
SIGUSR212ユーザー定義2プロセス終了
SIGCHLD17子プロセス状態変化無視
SIGCONT18一時停止解除実行再開
SIGSTOP19一時停止プロセス停止

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のプロセス間通信の基本です。適切にシグナルを使いこなすことで、堅牢で管理しやすいアプリケーションを構築できます。

セキュリティとベストプラクティス

  1. 常にSIGTERMから試す: SIGKILLは最後の手段
  2. タイムアウトを設定: 無限待機を避ける
  3. 権限を確認: 他のユーザーのプロセスは制御できない
  4. エラーハンドリング: シグナル送信の失敗に対応
  5. シグナル0で存在確認: プロセスの状態をチェック

安全なプロセス管理を! この記事が役に立ったら、ぜひシェアしてください。PHPのプロセス制御について、他にも知りたいことがあればコメントで教えてくださいね。

タイトルとURLをコピーしました