[PHP]posix_getrlimit関数の使い方を完全解説!リソース制限を取得する方法

PHP

はじめに

PHPでシステムプログラミングを行う際、「このプロセスはどれだけのリソースを使用できるのか?」「なぜファイルが開けないのか?」といった疑問に直面することがあります。

Unixライクなシステムでは、プロセスが使用できるリソース(メモリ、ファイル記述子、プロセス数など)に制限が設けられています。これらの制限を超えようとすると、エラーが発生します。

その現在のリソース制限を取得するのが**posix_getrlimit関数**です。この関数を使えば、プロセスが利用可能なリソースの上限を確認し、適切なエラーハンドリングやリソース管理を実装できます。

この記事では、posix_getrlimitの基本から実践的な活用方法まで、詳しく解説します。

posix_getrlimitとは?

posix_getrlimitは、現在のプロセスのリソース制限(rlimit)を取得する関数です。

基本構文

posix_getrlimit(): array|false

パラメータ

この関数はパラメータを取りません。

戻り値

成功時は以下のようなリソース制限情報を含む連想配列:

キー説明
soft coreコアダンプファイルの最大サイズ(ソフトリミット)
hard coreコアダンプファイルの最大サイズ(ハードリミット)
soft dataデータセグメントの最大サイズ
hard dataデータセグメントの最大サイズ
soft stackスタックサイズの最大値
hard stackスタックサイズの最大値
soft rss常駐セットサイズの最大値
hard rss常駐セットサイズの最大値
soft nprocユーザーが作成できるプロセス数
hard nprocユーザーが作成できるプロセス数
soft nofileオープンできるファイル記述子の最大数
hard nofileオープンできるファイル記述子の最大数
soft memlockロックできるメモリの最大サイズ
hard memlockロックできるメモリの最大サイズ
soft cpuCPU時間の最大値(秒)
hard cpuCPU時間の最大値(秒)
soft filesize作成できるファイルの最大サイズ
hard filesize作成できるファイルの最大サイズ
soft openfilesオープンできるファイル数(macOS)
hard openfilesオープンできるファイル数(macOS)

失敗時: false

ソフトリミットとハードリミット

  • ソフトリミット: 通常の制限値。プロセスが自分で変更可能
  • ハードリミット: 最大の制限値。特権ユーザーのみ変更可能

ソフトリミットはハードリミットを超えることはできません。

対応環境

  • POSIX準拠システム(Linux、Unix、macOS)
  • Windows では利用不可
  • POSIX拡張モジュールが必要

対応バージョン

  • PHP 5.1.0 以降で使用可能

基本的な使い方

リソース制限の取得

<?php
$limits = posix_getrlimit();

if ($limits) {
    echo "=== リソース制限 ===\n\n";
    
    echo "【ファイル記述子】\n";
    echo "  ソフトリミット: {$limits['soft openfiles']}\n";
    echo "  ハードリミット: {$limits['hard openfiles']}\n\n";
    
    echo "【プロセス数】\n";
    echo "  ソフトリミット: {$limits['soft nproc']}\n";
    echo "  ハードリミット: {$limits['hard nproc']}\n\n";
    
    echo "【スタックサイズ】\n";
    echo "  ソフトリミット: {$limits['soft stack']} bytes\n";
    echo "  ハードリミット: {$limits['hard stack']} bytes\n";
} else {
    echo "リソース制限の取得に失敗しました\n";
}
?>

特定のリソース制限を確認

<?php
function checkFileDescriptorLimit() {
    $limits = posix_getrlimit();
    
    if (!$limits) {
        return null;
    }
    
    return [
        'soft' => $limits['soft openfiles'],
        'hard' => $limits['hard openfiles'],
        'unlimited' => $limits['soft openfiles'] === 'unlimited'
    ];
}

$fd_limit = checkFileDescriptorLimit();

if ($fd_limit) {
    echo "ファイル記述子の制限:\n";
    echo "  ソフトリミット: {$fd_limit['soft']}\n";
    echo "  ハードリミット: {$fd_limit['hard']}\n";
    
    if ($fd_limit['unlimited']) {
        echo "  状態: 無制限\n";
    }
}
?>

現在の使用状況と比較

<?php
function checkResourceUsage() {
    $limits = posix_getrlimit();
    
    if (!$limits) {
        echo "リソース制限の取得に失敗\n";
        return;
    }
    
    echo "=== リソース使用状況 ===\n\n";
    
    // ファイル記述子
    $open_files = count(get_resources());
    $file_limit = $limits['soft openfiles'];
    
    echo "【ファイル記述子】\n";
    echo "  現在開いているリソース: {$open_files}\n";
    echo "  ソフトリミット: {$file_limit}\n";
    
    if (is_numeric($file_limit)) {
        $usage_percent = ($open_files / $file_limit) * 100;
        echo "  使用率: " . number_format($usage_percent, 2) . "%\n";
        
        if ($usage_percent > 80) {
            echo "  ⚠ 警告: 使用率が高くなっています\n";
        }
    }
    
    // メモリ使用量
    $memory_usage = memory_get_usage(true);
    $memory_limit_str = ini_get('memory_limit');
    
    echo "\n【メモリ使用量】\n";
    echo "  現在の使用量: " . formatBytes($memory_usage) . "\n";
    echo "  PHPメモリ制限: {$memory_limit_str}\n";
}

function formatBytes($bytes) {
    $units = ['B', 'KB', 'MB', 'GB'];
    $i = 0;
    
    while ($bytes >= 1024 && $i < count($units) - 1) {
        $bytes /= 1024;
        $i++;
    }
    
    return number_format($bytes, 2) . ' ' . $units[$i];
}

checkResourceUsage();
?>

実践的な使用例

例1: リソース制限の詳細表示

<?php
class ResourceLimitDisplay {
    /**
     * すべてのリソース制限を見やすく表示
     */
    public static function displayAll() {
        $limits = posix_getrlimit();
        
        if (!$limits) {
            echo "リソース制限の取得に失敗しました\n";
            return;
        }
        
        echo "=== システムリソース制限 ===\n";
        echo "日時: " . date('Y-m-d H:i:s') . "\n";
        echo "PID: " . posix_getpid() . "\n\n";
        
        $resources = [
            'openfiles' => ['名前' => 'ファイル記述子数', '単位' => '個'],
            'nproc' => ['名前' => 'プロセス数', '単位' => '個'],
            'stack' => ['名前' => 'スタックサイズ', '単位' => 'bytes'],
            'core' => ['名前' => 'コアダンプサイズ', '単位' => 'bytes'],
            'cpu' => ['名前' => 'CPU時間', '単位' => '秒'],
            'data' => ['名前' => 'データセグメント', '単位' => 'bytes'],
            'filesize' => ['名前' => 'ファイルサイズ', '単位' => 'bytes'],
            'memlock' => ['名前' => 'ロックメモリ', '単位' => 'bytes'],
            'rss' => ['名前' => '常駐セットサイズ', '単位' => 'bytes'],
        ];
        
        echo sprintf("%-25s %-20s %-20s\n", 
            "リソース", "ソフトリミット", "ハードリミット");
        echo str_repeat("-", 70) . "\n";
        
        foreach ($resources as $key => $info) {
            $soft_key = "soft {$key}";
            $hard_key = "hard {$key}";
            
            if (!isset($limits[$soft_key])) {
                continue;
            }
            
            $soft = self::formatValue($limits[$soft_key], $info['単位']);
            $hard = self::formatValue($limits[$hard_key], $info['単位']);
            
            echo sprintf("%-25s %-20s %-20s\n",
                $info['名前'],
                $soft,
                $hard
            );
        }
    }
    
    /**
     * 値をフォーマット
     */
    private static function formatValue($value, $unit) {
        if ($value === 'unlimited' || $value === -1) {
            return '無制限';
        }
        
        if ($unit === 'bytes' && is_numeric($value)) {
            return self::formatBytes($value);
        }
        
        return $value . ' ' . $unit;
    }
    
    /**
     * バイト数を人間が読める形式に変換
     */
    private static function formatBytes($bytes) {
        if ($bytes === 'unlimited' || $bytes === -1) {
            return '無制限';
        }
        
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $i = 0;
        
        while ($bytes >= 1024 && $i < count($units) - 1) {
            $bytes /= 1024;
            $i++;
        }
        
        return number_format($bytes, 2) . ' ' . $units[$i];
    }
}

ResourceLimitDisplay::displayAll();
?>

例2: ファイル記述子の制限チェック

<?php
class FileDescriptorMonitor {
    /**
     * ファイル記述子の使用状況を監視
     */
    public static function monitor() {
        $limits = posix_getrlimit();
        
        if (!$limits) {
            throw new RuntimeException("リソース制限の取得に失敗しました");
        }
        
        $soft_limit = $limits['soft openfiles'];
        $hard_limit = $limits['hard openfiles'];
        
        echo "=== ファイル記述子監視 ===\n\n";
        echo "制限:\n";
        echo "  ソフトリミット: {$soft_limit}\n";
        echo "  ハードリミット: {$hard_limit}\n\n";
        
        // 現在開いているリソースを取得
        $resources = get_resources();
        $open_count = count($resources);
        
        echo "現在の状況:\n";
        echo "  開いているリソース: {$open_count}\n";
        
        if (is_numeric($soft_limit)) {
            $usage_percent = ($open_count / $soft_limit) * 100;
            $remaining = $soft_limit - $open_count;
            
            echo "  使用率: " . number_format($usage_percent, 2) . "%\n";
            echo "  残り: {$remaining}\n\n";
            
            // 警告レベルの判定
            if ($usage_percent >= 90) {
                echo "🔴 危険: ファイル記述子がほぼ上限です\n";
            } elseif ($usage_percent >= 75) {
                echo "🟡 警告: ファイル記述子の使用率が高くなっています\n";
            } elseif ($usage_percent >= 50) {
                echo "🟢 注意: ファイル記述子を多く使用しています\n";
            } else {
                echo "✓ 正常: 十分な余裕があります\n";
            }
        } else {
            echo "  制限: 無制限\n";
        }
        
        // リソースの種類別集計
        echo "\nリソースタイプ別:\n";
        $type_counts = [];
        
        foreach ($resources as $resource) {
            $type = get_resource_type($resource);
            $type_counts[$type] = ($type_counts[$type] ?? 0) + 1;
        }
        
        arsort($type_counts);
        
        foreach ($type_counts as $type => $count) {
            echo "  {$type}: {$count}\n";
        }
    }
    
    /**
     * ファイル記述子が上限に達しそうか確認
     */
    public static function isNearLimit($threshold = 0.8) {
        $limits = posix_getrlimit();
        
        if (!$limits || !is_numeric($limits['soft openfiles'])) {
            return false;
        }
        
        $open_count = count(get_resources());
        $limit = $limits['soft openfiles'];
        
        return ($open_count / $limit) >= $threshold;
    }
    
    /**
     * ファイルを安全に開く
     */
    public static function safeOpen($filename, $mode, $warn_threshold = 0.8) {
        // 制限に近づいているか確認
        if (self::isNearLimit($warn_threshold)) {
            $limits = posix_getrlimit();
            $open_count = count(get_resources());
            $limit = $limits['soft openfiles'];
            
            error_log(sprintf(
                "Warning: File descriptor usage high: %d/%d (%.1f%%)",
                $open_count,
                $limit,
                ($open_count / $limit) * 100
            ));
        }
        
        $handle = @fopen($filename, $mode);
        
        if ($handle === false) {
            $errno = posix_get_last_error();
            
            if ($errno === EMFILE) {
                throw new RuntimeException(
                    "ファイル記述子の上限に達しました\n" .
                    "現在: " . count(get_resources()) . "\n" .
                    "制限: " . $limits['soft openfiles']
                );
            }
            
            throw new RuntimeException("ファイルを開けませんでした: {$filename}");
        }
        
        return $handle;
    }
}

// 使用例
FileDescriptorMonitor::monitor();

// 安全なファイルオープン
try {
    $fh = FileDescriptorMonitor::safeOpen('/tmp/test.txt', 'w');
    fwrite($fh, "テストデータ\n");
    fclose($fh);
} catch (RuntimeException $e) {
    echo "エラー: " . $e->getMessage() . "\n";
}
?>

例3: プロセス数の制限チェック

<?php
class ProcessLimitChecker {
    /**
     * プロセス数の制限を確認
     */
    public static function checkProcessLimit() {
        $limits = posix_getrlimit();
        
        if (!$limits) {
            echo "リソース制限の取得に失敗\n";
            return;
        }
        
        $soft_limit = $limits['soft nproc'];
        $hard_limit = $limits['hard nproc'];
        
        echo "=== プロセス数制限 ===\n\n";
        echo "制限:\n";
        echo "  ソフトリミット: {$soft_limit}\n";
        echo "  ハードリミット: {$hard_limit}\n\n";
        
        // 現在のユーザーのプロセス数を取得(Linux)
        $uid = posix_getuid();
        $user = posix_getpwuid($uid);
        
        $ps_output = shell_exec("ps -u {$user['name']} --no-headers | wc -l");
        $current_processes = (int)trim($ps_output);
        
        echo "現在のプロセス数:\n";
        echo "  ユーザー '{$user['name']}': {$current_processes}\n";
        
        if (is_numeric($soft_limit)) {
            $usage_percent = ($current_processes / $soft_limit) * 100;
            $remaining = $soft_limit - $current_processes;
            
            echo "  使用率: " . number_format($usage_percent, 2) . "%\n";
            echo "  残り: {$remaining}\n\n";
            
            if ($usage_percent >= 90) {
                echo "⚠ 警告: プロセス数が上限に近づいています\n";
            } else {
                echo "✓ 正常範囲内です\n";
            }
        }
    }
    
    /**
     * 子プロセスをforkする前にチェック
     */
    public static function canFork() {
        $limits = posix_getrlimit();
        
        if (!$limits || !is_numeric($limits['soft nproc'])) {
            return true; // 制限が取得できない場合は許可
        }
        
        $uid = posix_getuid();
        $user = posix_getpwuid($uid);
        
        $ps_output = shell_exec("ps -u {$user['name']} --no-headers | wc -l");
        $current_processes = (int)trim($ps_output);
        
        $limit = $limits['soft nproc'];
        
        // 90%を超えていたらfork不可
        return ($current_processes / $limit) < 0.9;
    }
    
    /**
     * 安全にfork
     */
    public static function safeFork() {
        if (!self::canFork()) {
            throw new RuntimeException(
                "プロセス数が上限に近づいているためforkできません"
            );
        }
        
        $pid = pcntl_fork();
        
        if ($pid === -1) {
            $errno = posix_get_last_error();
            $errmsg = posix_strerror($errno);
            
            throw new RuntimeException(
                "forkに失敗しました: {$errmsg}"
            );
        }
        
        return $pid;
    }
}

// 使用例
ProcessLimitChecker::checkProcessLimit();
?>

例4: スタックサイズの確認

<?php
class StackMonitor {
    /**
     * スタックサイズの制限を確認
     */
    public static function checkStackLimit() {
        $limits = posix_getrlimit();
        
        if (!$limits) {
            echo "リソース制限の取得に失敗\n";
            return;
        }
        
        $soft_limit = $limits['soft stack'];
        $hard_limit = $limits['hard stack'];
        
        echo "=== スタックサイズ制限 ===\n\n";
        echo "制限:\n";
        echo "  ソフトリミット: " . self::formatBytes($soft_limit) . "\n";
        echo "  ハードリミット: " . self::formatBytes($hard_limit) . "\n\n";
        
        // 再帰の深さをテスト
        echo "再帰深さテスト:\n";
        
        $max_depth = 0;
        
        try {
            self::testRecursion($max_depth);
        } catch (Error $e) {
            echo "  最大再帰深さ: {$max_depth}\n";
            echo "  エラー: " . $e->getMessage() . "\n";
        }
    }
    
    /**
     * 再帰テスト
     */
    private static function testRecursion(&$depth, $max_test = 100000) {
        $depth++;
        
        if ($depth >= $max_test) {
            return;
        }
        
        self::testRecursion($depth, $max_test);
    }
    
    /**
     * バイト数をフォーマット
     */
    private static function formatBytes($bytes) {
        if ($bytes === 'unlimited' || $bytes === -1) {
            return '無制限';
        }
        
        $units = ['B', 'KB', 'MB', 'GB'];
        $i = 0;
        
        while ($bytes >= 1024 && $i < count($units) - 1) {
            $bytes /= 1024;
            $i++;
        }
        
        return number_format($bytes, 2) . ' ' . $units[$i];
    }
}

StackMonitor::checkStackLimit();
?>

例5: リソース制限の診断ツール

<?php
class ResourceDiagnostics {
    /**
     * 包括的なリソース診断を実行
     */
    public static function diagnose() {
        echo "=== システムリソース診断 ===\n";
        echo "日時: " . date('Y-m-d H:i:s') . "\n";
        echo "ホスト: " . gethostname() . "\n";
        echo "PID: " . posix_getpid() . "\n\n";
        
        $limits = posix_getrlimit();
        
        if (!$limits) {
            echo "エラー: リソース制限の取得に失敗しました\n";
            return;
        }
        
        $issues = [];
        
        // 1. ファイル記述子
        $fd_check = self::checkFileDescriptors($limits);
        if (!$fd_check['ok']) {
            $issues[] = $fd_check['message'];
        }
        
        // 2. プロセス数
        $proc_check = self::checkProcessCount($limits);
        if (!$proc_check['ok']) {
            $issues[] = $proc_check['message'];
        }
        
        // 3. メモリ制限
        $mem_check = self::checkMemoryLimits($limits);
        if (!$mem_check['ok']) {
            $issues[] = $mem_check['message'];
        }
        
        // 4. CPU時間
        $cpu_check = self::checkCPULimit($limits);
        if (!$cpu_check['ok']) {
            $issues[] = $cpu_check['message'];
        }
        
        // 結果表示
        echo "\n【診断結果】\n";
        
        if (empty($issues)) {
            echo "✓ すべて正常です\n";
        } else {
            echo "⚠ 以下の問題が検出されました:\n\n";
            foreach ($issues as $i => $issue) {
                echo ($i + 1) . ". {$issue}\n";
            }
        }
    }
    
    private static function checkFileDescriptors($limits) {
        $soft_limit = $limits['soft openfiles'];
        
        if (!is_numeric($soft_limit)) {
            return ['ok' => true];
        }
        
        $open_count = count(get_resources());
        $usage = ($open_count / $soft_limit) * 100;
        
        echo "【ファイル記述子】\n";
        echo "  制限: {$soft_limit}\n";
        echo "  使用中: {$open_count}\n";
        echo "  使用率: " . number_format($usage, 2) . "%\n";
        
        if ($usage >= 80) {
            return [
                'ok' => false,
                'message' => "ファイル記述子の使用率が高い ({$usage}%)"
            ];
        }
        
        echo "  状態: ✓ 正常\n\n";
        return ['ok' => true];
    }
    
    private static function checkProcessCount($limits) {
        $soft_limit = $limits['soft nproc'];
        
        if (!is_numeric($soft_limit)) {
            return ['ok' => true];
        }
        
        $uid = posix_getuid();
        $user = posix_getpwuid($uid);
        
        $ps_output = shell_exec("ps -u {$user['name']} --no-headers 2>/dev/null | wc -l");
        $current = (int)trim($ps_output);
        $usage = ($current / $soft_limit) * 100;
        
        echo "【プロセス数】\n";
        echo "  制限: {$soft_limit}\n";
        echo "  現在: {$current}\n";
        echo "  使用率: " . number_format($usage, 2) . "%\n";
        
        if ($usage >= 80) {
            return [
                'ok' => false,
                'message' => "プロセス数が上限に近い ({$usage}%)"
            ];
        }
        
        echo "  状態: ✓ 正常\n\n";
        return ['ok' => true];
    }
    
    private static function checkMemoryLimits($limits) {
        $memory_usage = memory_get_usage(true);
        $memory_limit_str = ini_get('memory_limit');
        
        echo "【メモリ】\n";
        echo "  PHPメモリ制限: {$memory_limit_str}\n";
        echo "  現在の使用量: " . self::formatBytes($memory_usage) . "\n";
        
        // メモリ制限をバイトに変換
        $memory_limit = self::parseSize($memory_limit_str);
        
        if ($memory_limit > 0) {
            $usage = ($memory_usage / $memory_limit) * 100;
            echo "  使用率: " . number_format($usage, 2) . "%\n";
            
            if ($usage >= 80) {
                return [
                    'ok' => false,
                    'message' => "メモリ使用率が高い ({$usage}%)"
                ];
            }
        }
        
        echo "  状態: ✓ 正常\n\n";
        return ['ok' => true];
    }
    
    private static function checkCPULimit($limits) {
        $cpu_limit = $limits['soft cpu'];
        
        echo "【CPU時間制限】\n";
        echo "  制限: " . ($cpu_limit === 'unlimited' ? '無制限' : "{$cpu_limit}秒") . "\n";
        echo "  状態: ✓ 正常\n\n";
        
        return ['ok' => true];
    }
    
    private static function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $i = 0;
        
        while ($bytes >= 1024 && $i < count($units) - 1) {
            $bytes /= 1024;
            $i++;
        }
        
        return number_format($bytes, 2) . ' ' . $units[$i];
    }
    
    private static function parseSize($size) {
        $units = ['K' => 1024, 'M' => 1048576, 'G' => 1073741824];
        
        if (preg_match('/^(\d+)([KMG]?)$/i', $size, $matches)) {
            $value = (int)$matches[1];
            $unit = strtoupper($matches[2]);
            
            return $value * ($units[$unit] ?? 1);
        }
        
        return (int)$size;
    }
}

// 使用例
ResourceDiagnostics::diagnose();
?>

まとめ

posix_getrlimitは、プロセスのリソース制限を取得する関数です。

主な特徴:

  • ✅ プロセスが使用できるリソースの上限を確認
  • ✅ ソフトリミットとハードリミットの両方を取得
  • ✅ ファイル記述子、プロセス数、メモリなど様々なリソースに対応
  • ✅ リソース枯渇の予防に有用

主なリソース:

  • ファイル記述子(openfiles)
  • プロセス数(nproc)
  • スタックサイズ(stack)
  • CPU時間(cpu)
  • ファイルサイズ(filesize)
  • コアダンプサイズ(core)
  • メモリロック(memlock)

使用場面:

  • リソース枯渇の事前検知
  • ファイル記述子不足のデバッグ
  • プロセス制限の確認
  • システム監視とアラート

関連関数:

  • posix_setrlimit() – リソース制限を設定(PHP 7.0.0以降)
  • memory_get_usage() – メモリ使用量を取得
  • get_resources() – 開いているリソースを取得

重要なポイント:

  • ソフトリミットは通常のプロセスが変更可能
  • ハードリミットは特権ユーザーのみ変更可能
  • ソフトリミット ≤ ハードリミット
  • “unlimited”または-1は無制限を意味する
  • システムやシェルの設定で制限値が決まる

ベストプラクティス:

  • リソース使用率が80%を超えたら警告
  • ファイルを開く前に制限をチェック
  • 長時間実行するプロセスは定期的に監視
  • エラーハンドリングでEMFILE(ファイル記述子不足)を考慮
  • ログにリソース使用状況を記録

制限値の変更方法:

# ulimitコマンドで一時的に変更
ulimit -n 4096  # ファイル記述子

# /etc/security/limits.confで恒久的に変更
username soft nofile 4096
username hard nofile 8192

よくあるエラーと対処:

  1. ファイル記述子不足(EMFILE)
    • 原因: ファイルを閉じ忘れ、制限値が低い
    • 対処: 不要なファイルをclose、ulimitで制限値を上げる
  2. プロセス数超過(EAGAIN)
    • 原因: 子プロセスの作りすぎ
    • 対処: プロセスプールを使う、古いプロセスをkill
  3. スタックオーバーフロー
    • 原因: 深い再帰、大きなローカル変数
    • 対処: 再帰を反復に変換、ulimit -s で制限値を上げる

この関数を理解して、より安定したリソース管理を実現しましょう!

参考リンク


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

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