[PHP]posix_getpgid関数とは?プロセスグループIDを取得する方法【完全解説】

PHP

こんにちは!今回はPHPのPOSIX拡張機能の中でも、プロセス管理に関わる重要なposix_getpgid関数について詳しく解説していきます。

posix_getpgid関数とは?

posix_getpgidは、指定したプロセスIDのプロセスグループID(PGID)を取得する関数です。プロセスグループは、複数のプロセスをまとめて管理するためのUNIX/Linuxの仕組みです。

基本的な構文

posix_getpgid(int $process_id): int|false
  • 引数: プロセスID(PID)
  • 戻り値: プロセスグループID(PGID)、または失敗時はfalse

プロセスグループとは?

プロセスグループは、関連するプロセスをひとまとめにして管理するための仕組みです。

プロセスグループの特徴

  • 🔹 シグナルの一括送信: グループ全体にシグナルを送信可能
  • 🔹 ジョブ制御: シェルでのフォアグラウンド/バックグラウンド管理
  • 🔹 パイプライン: cmd1 | cmd2 | cmd3のような連続実行で同じグループに
例: シェルでのパイプライン
$ cat file.txt | grep "pattern" | sort

この場合、cat、grep、sortは同じプロセスグループに属します

基本的な使い方

シンプルな例

<?php
// 現在のプロセスのプロセスグループIDを取得
$pid = posix_getpid();
$pgid = posix_getpgid($pid);

if ($pgid !== false) {
    echo "プロセスID (PID): {$pid}\n";
    echo "プロセスグループID (PGID): {$pgid}\n";
    
    if ($pid === $pgid) {
        echo "このプロセスはプロセスグループリーダーです\n";
    } else {
        echo "このプロセスはグループメンバーです\n";
    }
} else {
    echo "プロセスグループIDの取得に失敗しました\n";
}
?>

出力例:

プロセスID (PID): 12345
プロセスグループID (PGID): 12340
このプロセスはグループメンバーです

親プロセスとの関係を調査

<?php
function analyzeProcessHierarchy() {
    // 現在のプロセス情報
    $pid = posix_getpid();
    $ppid = posix_getppid();
    $pgid = posix_getpgid($pid);
    $sid = posix_getsid($pid);
    
    echo "=== プロセス階層分析 ===\n\n";
    
    // 現在のプロセス
    echo "現在のプロセス:\n";
    echo "  PID  (プロセスID):        {$pid}\n";
    echo "  PPID (親プロセスID):      {$ppid}\n";
    echo "  PGID (プロセスグループID): {$pgid}\n";
    echo "  SID  (セッションID):      {$sid}\n\n";
    
    // 親プロセスの情報
    $parent_pgid = posix_getpgid($ppid);
    $parent_sid = posix_getsid($ppid);
    
    if ($parent_pgid !== false) {
        echo "親プロセス:\n";
        echo "  PID:  {$ppid}\n";
        echo "  PGID: {$parent_pgid}\n";
        echo "  SID:  {$parent_sid}\n\n";
    }
    
    // 関係性の分析
    echo "関係性:\n";
    
    if ($pid === $pgid) {
        echo "  ✓ このプロセスはプロセスグループリーダー\n";
    } else {
        echo "  - プロセスグループリーダーのPIDは {$pgid}\n";
    }
    
    if ($pid === $sid) {
        echo "  ✓ このプロセスはセッションリーダー\n";
    }
    
    if ($pgid === $parent_pgid) {
        echo "  ✓ 親プロセスと同じプロセスグループ\n";
    } else {
        echo "  - 親プロセスとは別のプロセスグループ\n";
    }
}

analyzeProcessHierarchy();
?>

実践的な活用例

1. プロセスグループ監視ツール

<?php
class ProcessGroupMonitor {
    public function getGroupInfo(int $pid): ?array {
        $pgid = posix_getpgid($pid);
        
        if ($pgid === false) {
            return null;
        }
        
        $sid = posix_getsid($pid);
        
        return [
            'pid' => $pid,
            'pgid' => $pgid,
            'sid' => $sid,
            'is_group_leader' => ($pid === $pgid),
            'is_session_leader' => ($pid === $sid)
        ];
    }
    
    public function findGroupMembers(int $pgid): array {
        $members = [];
        
        // /procディレクトリからプロセス情報を取得
        $procDirs = glob('/proc/[0-9]*', GLOB_ONLYDIR);
        
        foreach ($procDirs as $procDir) {
            $pid = (int)basename($procDir);
            
            // プロセスのPGIDを確認
            $processPgid = posix_getpgid($pid);
            
            if ($processPgid === $pgid) {
                $members[] = $pid;
            }
        }
        
        return $members;
    }
    
    public function printGroupReport(int $targetPid): void {
        $info = $this->getGroupInfo($targetPid);
        
        if ($info === null) {
            echo "プロセス {$targetPid} の情報を取得できませんでした\n";
            return;
        }
        
        echo "=== プロセスグループレポート ===\n\n";
        echo "対象プロセス (PID): {$info['pid']}\n";
        echo "プロセスグループ (PGID): {$info['pgid']}\n";
        echo "セッション (SID): {$info['sid']}\n";
        
        if ($info['is_group_leader']) {
            echo "\n✓ このプロセスはグループリーダーです\n";
        }
        
        if ($info['is_session_leader']) {
            echo "✓ このプロセスはセッションリーダーです\n";
        }
        
        // 同じグループのメンバーを検索
        echo "\n同じグループのプロセス:\n";
        $members = $this->findGroupMembers($info['pgid']);
        
        foreach ($members as $memberPid) {
            $isLeader = ($memberPid === $info['pgid']) ? ' [リーダー]' : '';
            $isCurrent = ($memberPid === $targetPid) ? ' [現在]' : '';
            echo "  PID {$memberPid}{$isLeader}{$isCurrent}\n";
        }
    }
}

// 使用例
$monitor = new ProcessGroupMonitor();
$monitor->printGroupReport(posix_getpid());
?>

2. 子プロセス管理システム

<?php
class ProcessGroupManager {
    private array $children = [];
    
    public function spawnChildProcess(callable $callback): ?int {
        $pid = pcntl_fork();
        
        if ($pid === -1) {
            // fork失敗
            error_log('プロセスのforkに失敗しました');
            return null;
        }
        
        if ($pid === 0) {
            // 子プロセス
            // 新しいプロセスグループを作成
            posix_setpgid(0, 0);
            
            $childPid = posix_getpid();
            $childPgid = posix_getpgid($childPid);
            
            echo "[子プロセス] PID: {$childPid}, PGID: {$childPgid}\n";
            
            // コールバック実行
            $callback();
            exit(0);
        }
        
        // 親プロセス
        $this->children[] = $pid;
        
        $childPgid = posix_getpgid($pid);
        echo "[親プロセス] 子プロセス {$pid} を起動 (PGID: {$childPgid})\n";
        
        return $pid;
    }
    
    public function waitForChildren(): void {
        foreach ($this->children as $childPid) {
            echo "子プロセス {$childPid} の終了を待機中...\n";
            pcntl_waitpid($childPid, $status);
            
            if (pcntl_wifexited($status)) {
                $exitCode = pcntl_wexitstatus($status);
                echo "子プロセス {$childPid} が終了しました (終了コード: {$exitCode})\n";
            }
        }
        
        $this->children = [];
    }
    
    public function killProcessGroup(int $pgid): bool {
        // プロセスグループ全体にSIGTERMを送信
        // マイナスのPGIDを指定することでグループ全体にシグナル送信
        return posix_kill(-$pgid, SIGTERM);
    }
}

// 使用例
if (function_exists('pcntl_fork')) {
    $manager = new ProcessGroupManager();
    
    // 子プロセスを3つ起動
    for ($i = 1; $i <= 3; $i++) {
        $manager->spawnChildProcess(function() use ($i) {
            echo "子プロセス #{$i} が実行中...\n";
            sleep(2);
            echo "子プロセス #{$i} が完了\n";
        });
    }
    
    // 全ての子プロセスの終了を待つ
    $manager->waitForChildren();
    echo "全ての子プロセスが終了しました\n";
} else {
    echo "pcntl拡張が利用できません\n";
}
?>

3. デーモンプロセスの作成

<?php
class DaemonProcess {
    private string $pidFile;
    private string $logFile;
    
    public function __construct(string $pidFile, string $logFile) {
        $this->pidFile = $pidFile;
        $this->logFile = $logFile;
    }
    
    public function daemonize(): bool {
        // 第1段階: 親プロセスから分離
        $pid = pcntl_fork();
        
        if ($pid === -1) {
            return false;
        }
        
        if ($pid > 0) {
            // 親プロセスは終了
            exit(0);
        }
        
        // 子プロセスがセッションリーダーになる
        if (posix_setsid() === -1) {
            return false;
        }
        
        // 第2段階: 制御端末から完全に切り離す
        $pid = pcntl_fork();
        
        if ($pid === -1) {
            return false;
        }
        
        if ($pid > 0) {
            // 第1子プロセスも終了
            exit(0);
        }
        
        // これでデーモンプロセスになった
        $myPid = posix_getpid();
        $myPgid = posix_getpgid($myPid);
        $mySid = posix_getsid($myPid);
        
        // PIDファイルに書き込み
        file_put_contents($this->pidFile, $myPid);
        
        // ログに記録
        $this->log("デーモン起動: PID={$myPid}, PGID={$myPgid}, SID={$mySid}");
        
        // 作業ディレクトリを変更
        chdir('/');
        
        // ファイルマスクをクリア
        umask(0);
        
        // 標準入出力を閉じる
        fclose(STDIN);
        fclose(STDOUT);
        fclose(STDERR);
        
        return true;
    }
    
    private function log(string $message): void {
        $timestamp = date('Y-m-d H:i:s');
        $logEntry = "[{$timestamp}] {$message}\n";
        file_put_contents($this->logFile, $logEntry, FILE_APPEND);
    }
    
    public function run(): void {
        $this->log("デーモンプロセス実行中");
        
        // メインループ
        while (true) {
            // 実際の処理をここに記述
            $this->log("定期処理実行");
            sleep(60); // 60秒ごと
        }
    }
}

// 使用例
if (php_sapi_name() === 'cli') {
    $daemon = new DaemonProcess('/tmp/mydaemon.pid', '/tmp/mydaemon.log');
    
    if ($daemon->daemonize()) {
        $daemon->run();
    } else {
        echo "デーモン化に失敗しました\n";
        exit(1);
    }
}
?>

4. プロセスツリーの可視化

<?php
class ProcessTreeVisualizer {
    public function getProcessInfo(int $pid): ?array {
        $pgid = posix_getpgid($pid);
        
        if ($pgid === false) {
            return null;
        }
        
        $ppid = $this->getParentPid($pid);
        $sid = posix_getsid($pid);
        
        return [
            'pid' => $pid,
            'ppid' => $ppid,
            'pgid' => $pgid,
            'sid' => $sid,
            'command' => $this->getCommand($pid)
        ];
    }
    
    private function getParentPid(int $pid): ?int {
        $statusFile = "/proc/{$pid}/status";
        
        if (!file_exists($statusFile)) {
            return null;
        }
        
        $status = file_get_contents($statusFile);
        
        if (preg_match('/PPid:\s+(\d+)/', $status, $matches)) {
            return (int)$matches[1];
        }
        
        return null;
    }
    
    private function getCommand(int $pid): string {
        $cmdlineFile = "/proc/{$pid}/cmdline";
        
        if (!file_exists($cmdlineFile)) {
            return '(unknown)';
        }
        
        $cmdline = file_get_contents($cmdlineFile);
        $cmdline = str_replace("\0", ' ', $cmdline);
        $cmdline = trim($cmdline);
        
        return $cmdline ?: '(unknown)';
    }
    
    public function printProcessTree(int $rootPid, int $depth = 0): void {
        $info = $this->getProcessInfo($rootPid);
        
        if ($info === null) {
            return;
        }
        
        $indent = str_repeat('  ', $depth);
        $marker = $depth > 0 ? '└─ ' : '';
        
        printf("%s%sPID: %d, PGID: %d, SID: %d\n",
               $indent,
               $marker,
               $info['pid'],
               $info['pgid'],
               $info['sid']);
        
        printf("%s   Command: %s\n",
               $indent,
               substr($info['command'], 0, 60));
        
        // 子プロセスを検索
        $children = $this->findChildren($rootPid);
        
        foreach ($children as $childPid) {
            $this->printProcessTree($childPid, $depth + 1);
        }
    }
    
    private function findChildren(int $parentPid): array {
        $children = [];
        $procDirs = glob('/proc/[0-9]*', GLOB_ONLYDIR);
        
        foreach ($procDirs as $procDir) {
            $pid = (int)basename($procDir);
            $ppid = $this->getParentPid($pid);
            
            if ($ppid === $parentPid) {
                $children[] = $pid;
            }
        }
        
        return $children;
    }
}

// 使用例
$visualizer = new ProcessTreeVisualizer();
echo "=== プロセスツリー ===\n\n";
$visualizer->printProcessTree(posix_getpid());
?>

よくある使用パターン

パターン1: プロセスグループの状態確認

<?php
function checkProcessGroup(): void {
    $pid = posix_getpid();
    $pgid = posix_getpgid($pid);
    $ppid = posix_getppid();
    $parent_pgid = posix_getpgid($ppid);
    
    echo "現在のプロセス: PID={$pid}, PGID={$pgid}\n";
    echo "親プロセス: PID={$ppid}, PGID={$parent_pgid}\n";
    
    if ($pgid === $parent_pgid) {
        echo "状態: 親プロセスと同じグループに所属\n";
    } else {
        echo "状態: 独立したプロセスグループ\n";
    }
}

checkProcessGroup();
?>

パターン2: グループリーダーの特定

<?php
function findGroupLeader(int $pid): ?int {
    $pgid = posix_getpgid($pid);
    
    if ($pgid === false) {
        return null;
    }
    
    // PGIDはグループリーダーのPIDと同じ
    return $pgid;
}

$pid = posix_getpid();
$leader = findGroupLeader($pid);

if ($leader !== null) {
    echo "プロセスグループリーダーのPID: {$leader}\n";
    
    if ($pid === $leader) {
        echo "このプロセスがグループリーダーです\n";
    }
}
?>

注意点とベストプラクティス

⚠️ 重要な注意事項

  1. POSIX環境限定: Windows環境では使用できません
  2. 権限の制限: 他のユーザーのプロセスの情報は取得できない場合があります
  3. プロセスの存在: 存在しないPIDを指定するとfalseを返します

エラーハンドリング

<?php
function safeGetPgid(int $pid): ?int {
    if (!function_exists('posix_getpgid')) {
        error_log('POSIX拡張が利用できません');
        return null;
    }
    
    // プロセスが存在するか確認
    if (!posix_kill($pid, 0)) {
        error_log("プロセス {$pid} は存在しません");
        return null;
    }
    
    $pgid = posix_getpgid($pid);
    
    if ($pgid === false) {
        error_log("プロセス {$pid} のPGIDを取得できませんでした");
        return null;
    }
    
    return $pgid;
}

// 使用例
$pid = posix_getpid();
$pgid = safeGetPgid($pid);

if ($pgid !== null) {
    echo "PGID: {$pgid}\n";
}
?>

関連関数との関係

関数説明関係性
posix_getpgid()プロセスグループIDを取得
posix_setpgid()プロセスグループIDを設定新しいグループの作成
posix_getpid()現在のプロセスIDを取得PGIDと比較してリーダー判定
posix_getsid()セッションIDを取得より上位のグループ化
posix_kill()シグナルを送信グループ全体への送信に使用

関連関数の活用例

<?php
function demonstrateProcessFunctions(): void {
    $pid = posix_getpid();
    $ppid = posix_getppid();
    $pgid = posix_getpgid($pid);
    $sid = posix_getsid($pid);
    
    echo "=== プロセス関数の活用 ===\n\n";
    
    echo "識別子:\n";
    echo "  プロセスID (PID):           {$pid}\n";
    echo "  親プロセスID (PPID):        {$ppid}\n";
    echo "  プロセスグループID (PGID):  {$pgid}\n";
    echo "  セッションID (SID):         {$sid}\n\n";
    
    echo "階層構造:\n";
    echo "  セッション [{$sid}]\n";
    echo "    └─ プロセスグループ [{$pgid}]\n";
    echo "         └─ プロセス [{$pid}]\n\n";
    
    echo "役割:\n";
    
    if ($pid === $pgid) {
        echo "  ✓ プロセスグループリーダー\n";
    }
    
    if ($pid === $sid) {
        echo "  ✓ セッションリーダー\n";
    }
    
    if ($pid !== $pgid && $pid !== $sid) {
        echo "  - 通常のプロセス\n";
    }
}

demonstrateProcessFunctions();
?>

まとめ

posix_getpgid関数は、プロセスのグループID(PGID)を取得し、プロセス間の関係性を理解するための重要な関数です。

主な用途:

  • プロセス管理: 関連プロセスのグループ化
  • シグナル制御: グループ単位でのシグナル送信
  • デーモン化: バックグラウンドプロセスの作成
  • ジョブ制御: フォアグラウンド/バックグラウンド管理

重要なポイント:

  • プロセスグループリーダーはPID === PGID
  • posix_setpgid()と組み合わせて新しいグループを作成
  • マイナスのPGIDでグループ全体にシグナル送信可能
  • セッション > プロセスグループ > プロセス の階層構造

Linux/Unix環境でのプロセス管理やデーモン開発に必須の知識です!


参考リンク:

この記事が役立ったら、ぜひシェアしてください!🚀

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