PID в языке PHP: получение и управление процессами

Раздел:

Идентификатор процесса (PID) в PHP: основные подходы

Основной способ получить идентификатор текущего процесса в PHP - использовать функцию getmypid(). Она доступна во всех версиях PHP и не требует дополнительных расширений. Функция возвращает целое число - PID процесса, в котором выполняется скрипт.


<?php
$pid = getmypid();
echo "Текущий PID: " . $pid;
?>
Текущий PID: 31415

Возможные проблемы:

  • При работе через веб-сервер (например, Apache с mod_php) несколько запросов могут выполняться в одном и том же процессе, поэтому PID не уникален для каждого запроса. В CLI-режиме каждый запуск даёт новый PID.
  • Функция getmypid() возвращает PID только текущего процесса. Для получения PID родительского процесса нужно использовать posix_getppid().

Как узнать идентификатор родительского процесса?

Для получения PID родительского процесса применяется функция posix_getppid(). Она доступна, если установлено расширение POSIX (обычно включено по умолчанию в CLI).


<?php
if (function_exists('posix_getppid')) {
    $ppid = posix_getppid();
    echo "PID родительского процесса: " . $ppid;
} else {
    echo "Расширение POSIX не установлено.";
}
?>
PID родительского процесса: 1

Типичные ошибки:

  • Функция posix_getppid() отсутствует, если PHP собран без расширения POSIX. Решение: установить пакет php-posix или пересобрать PHP с поддержкой POSIX.
  • В веб-окружении (например, под Apache с mod_php) родительским процессом может быть серверный процесс, а не PID родительского скрипта.

Как получить PID через командную оболочку, если функции PHP недоступны?

Иногда возникают ситуации, когда стандартные функции не работают (например, в окружении с ограничениями). Альтернативный способ - выполнить команду оболочки echo $$. Однако этот метод работает только в CLI-режиме и не всегда корректен.


<?php
$pid = trim(shell_exec('echo $$'));
echo "PID через shell: " . $pid;
?>
PID через shell: 31415

Проблемы и ограничения:

  • Использование shell_exec() требует отключения директивы shell_exec в php.ini, если она запрещена.
  • В веб-окружении переменная $$ возвращает PID интерпретатора оболочки, запущенного для выполнения команды, а не PHP-процесса. Результат может быть неверным.
  • Команда может быть заблокирована в безопасном режиме (deprecated).

Как получить PID дочернего процесса после создания форка?

При работе с многозадачностью через pcntl_fork() родительский процесс получает PID дочернего как возвращаемое значение. Дочерний процесс получает 0. Пример:


<?php
$child_pid = pcntl_fork();
if ($child_pid == -1) {
    die('Ошибка при создании процесса');
} elseif ($child_pid) {
    // в родительском процессе
    echo "Родитель. PID дочернего: " . $child_pid;
} else {
    // в дочернем процессе
    echo "Дочерний. Мой PID: " . getmypid();
    exit;
}
?>
Родитель. PID дочернего: 12345

Частые ошибки:

  • Расширение pcntl доступно только в CLI-режиме и не работает в веб-окружении. Попытка использования вызовет ошибку.
  • После вызова pcntl_fork() обе ветки продолжают выполнение. Необходимо правильно разделить логику, иначе код может выполниться дважды.
  • Для избежания зомби-процессов родитель должен вызвать pcntl_wait().

Как проверить, существует ли процесс с заданным PID?

Проверка существования процесса выполняется с помощью функции posix_kill() с сигналом 0. Сигнал 0 не отправляется процессу, но проверяет возможность отправки. Если процесс существует, функция возвращает true.


<?php
$pid = 12345;
if (@posix_kill($pid, 0)) {
    echo "Процесс с PID $pid существует.";
} else {
    echo "Процесс с PID $pid не найден.";
}
?>
Процесс с PID 12345 существует.

Потенциальные проблемы:

  • Проверка с сигналом 0 не требует прав на отправку сигнала, но требует права на определение наличия процесса. Для процессов других пользователей может вернуть false.
  • Функция posix_kill() генерирует предупреждение, если процесс не существует. Рекомендуется подавлять ошибки с помощью оператора @.
  • Метод не подходит для проверки процессов в системе, которые не принадлежат текущему пользователю, без соответствующих прав.

Как завершить процесс по PID?

Принудительное завершение процесса выполняется с помощью posix_kill() с сигналом SIGKILL (9). Альтернативно можно использовать команду kill -9 через shell_exec.


<?php
$pid = 12345;
if (posix_kill($pid, 9)) {
    echo "Процесс $pid завершен.";
} else {
    echo "Не удалось завершить процесс $pid.";
}
?>
Процесс 12345 завершен.

Риски и предостережения:

  • Завершение процесса работает только если PHP-скрипт выполняется от имени того же пользователя, что и целевой процесс, или от root.
  • Применение SIGKILL не даёт процессу корректно завершиться, возможна потеря данных. Рекомендуется сначала отправлять SIGTERM (15) и при отсутствии реакции - SIGKILL.
  • Ошибка "Operation not permitted" означает недостаток прав.

Расширенные примеры работы с PID в PHP

Создание нескольких дочерних процессов и управление ими

В следующем примере родительский процесс порождает трёх дочерних процессов, каждый из которых выводит свой PID и случайное число. Родитель ожидает завершения всех потомков через pcntl_wait().

Пример

<?php
for ($i = 1; $i <= 3; $i++) {
    $pid = pcntl_fork();
    if ($pid == -1) {
        die("Невозможно создать процесс");
    } elseif ($pid) {
        // родитель продолжает цикл
    } else {
        // дочерний процесс
        echo "Дочерний $i: PID " . getmypid() . ", случайное число " . rand(1,100) . PHP_EOL;
        exit; // важно завершить дочерний процесс
    }
}

// Родитель ожидает всех детей
while (true) {
    $status = null;
    $pid = pcntl_wait($status);
    if ($pid == -1) break;
    echo "Завершился дочерний процесс с PID $pid" . PHP_EOL;
}
?>
Дочерний 1: PID 20001, случайное число 42
Дочерний 2: PID 20002, случайное число 73
Дочерний 3: PID 20003, случайное число 15
Завершился дочерний процесс с PID 20001
Завершился дочерний процесс с PID 20002
Завершился дочерний процесс с PID 20003

Создание демона с записью PID в файл

Демон обычно записывает свой PID в файл, чтобы другие процессы могли управлять им (например, завершить). Пример демона с циклической работой и сохранением PID:

Пример

<?php
// Файл для хранения PID
$pidFile = '/var/run/myapp.pid';

// Первый fork: отсоединение от терминала
$pid = pcntl_fork();
if ($pid == -1) {
    die('Ошибка первого форка');
} elseif ($pid) {
    // Родитель завершается
    exit;
}

// Становимся лидером сессии
posix_setsid();

// Второй fork: гарантирует, что не станет лидером сессии
$pid = pcntl_fork();
if ($pid == -1) {
    die('Ошибка второго форка');
} elseif ($pid) {
    // Первый дочерний процесс завершается, основной остается
    exit;
}

// Теперь это демон
$currentPid = getmypid();
file_put_contents($pidFile, $currentPid);
echo "Демон запущен с PID $currentPid" . PHP_EOL;

// Бесконечный цикл работы демона
while (true) {
    // полезная работа
    sleep(60);
}
?>
Демон запущен с PID 30001

Использование proc_open и proc_get_status для внешней команды

Функция proc_open() позволяет выполнить внешнюю программу и получить её PID через proc_get_status(). Пример запуска утилиты sleep в фоне:

Пример

<?php
$descriptorspec = array(
    0 => array('pipe', 'r'),
    1 => array('pipe', 'w'),
    2 => array('pipe', 'w')
);

$process = proc_open('sleep 10', $descriptorspec, $pipes);

if (is_resource($process)) {
    $status = proc_get_status($process);
    $pid = $status['pid'];
    echo "Внешний процесс запущен с PID: " . $pid . PHP_EOL;

    // Можно проверить статус позже
    sleep(5);
    $status = proc_get_status($process);
    if ($status['running']) {
        echo "Процесс все еще работает." . PHP_EOL;
    } else {
        echo "Процесс завершен." . PHP_EOL;
    }

    // Закрыть процесс
    proc_close($process);
} else {
    echo "Ошибка запуска процесса.";
}
?>
Внешний процесс запущен с PID: 40001
Процесс все еще работает.

Проверка существования процесса через цикл с таймаутом

Иногда необходимо дождаться завершения другого процесса, проверяя его статус периодически. Пример проверки существования процесса с PID 10000 каждые 2 секунды в течение 30 секунд:

Пример

<?php
$targetPid = 10000;
$timeout = 30;
$start = time();

while (time() - $start < $timeout) {
    if (@!posix_kill($targetPid, 0)) {
        echo "Процесс $targetPid завершился." . PHP_EOL;
        break;
    }
    echo "Еще работает..." . PHP_EOL;
    sleep(2);
}

if (time() - $start >= $timeout) {
    echo "Таймаут: процесс $targetPid не завершился." . PHP_EOL;
}
?>
Еще работает...
Еще работает...
Процесс 10000 завершился.

Идентификатор процесса (PID) в PHP - comments

En
Php pid (php)