Глубокое понимание обработки сигналов процесса PHP

PHP
Глубокое понимание обработки сигналов процесса PHP

задний план

В первые две недели начальник поставил задачу написать пакет для мониторинга сигнала. Поскольку проект нашей компании работает в контейнере, каждый раз, когда он выходит в сеть, образ необходимо переупаковывать, а затем запускать. Перед переупаковкой Dokcer сначала посылает сигнал контейнеру, затем ждет тайм-аут (по умолчанию 10 с), а затем посылает сигнал SIGKILL для завершения работы контейнера.

Теперь возникает ситуация, когда в контейнере находится резидентный процесс, и задача резидентного процесса — непрерывно потреблять сообщения в очереди. Предполагая, что вы сейчас выходите в сеть, вам нужно закрыть и убить контейнер.Докер отправляет сигнал резидентному процессу, работающему в контейнере, сообщая ему, что я закрою вас через 10 секунд.Предположим, что сейчас прошло 9 секунд, и резидентный процесс только что был взят из очереди.Для сообщения процесс был убит до того, как последующая логика была выполнена в течение 1 с.В это время сообщение теряется и могут быть сгенерированы грязные данные.

Вышеизложенное является предысторией этой миссии.Необходимо следить за сигналом, чтобы решить, как действовать дальше. В приведенной выше ситуации, когда резидентный процесс получает сигнал завершения работы, отправленный Docker, он может заблокировать процесс и заснуть, пока контейнер не будет уничтожен. Хорошо, после очистки фона я представлю сигналы на PHP ниже (позже я организую статью о том, как написать этот пакет, и опубликую пакет на https://packagist.org/ для тех, кому это нужно)

1. Что такое сигналы в операционной системе Linux

1. Кратко представить сигнал

Сигнал — это механизм уведомления для процесса, когда происходит событие, иногда называемоепрограммное прерывание. Процесс может послать сигнал другому процессу.Например, когда дочерний процесс завершается, он посылает SIGCHLD (сигнал 17) родительскому процессу, чтобы уведомить родительский процесс, поэтому иногда сигнал также используется как механизм для взаимодействия. - процесс общения.

В системе Linux мы обычно используем kill -9 XXPID для завершения процесса.На самом деле суть этой команды в том, чтобы послать процессу SIGKILL (сигнал № 9).Для процесса переднего плана мы обычно используем Ctrl+ c клавиша быстрого доступа для завершения операции.Суть этой клавиши быстрого доступа в том, чтобы отправить SIGINT (сигнал № 2) текущему процессу, а поведение по умолчанию процесса, получившего этот сигнал, заключается в завершении операции

2. Общие сигналы

Следующие сигналы можно просмотреть с помощью команды kill -lВот еще несколько важных и часто используемых сигналов:

имя сигнала Значение сигнала тип сигнала Описание сигнала
SIGHUP 1 Завершить процесс (терминальная линия зависает) Этот сигнал отправляется в конце соединения пользовательского терминала (нормального или ненормального, обычно, когда процесс управления терминалом завершается, уведомляя каждое задание в том же сеансе, а затем они больше не связаны с управляющим терминалом).
SIGQUIT 2 Завершить процесс (прервать процесс) Завершение программы (прерывание, сигнал, выдаваемый при вводе пользователем символа INTR (обычно Ctrl-C,
SIGQUIT 3 Создайте файл CORE, чтобы завершить процесс и сгенерировать файл CORE. процесс и генерирует файл CORE SIGQUIT похож на SIGINT, но управляется символом QUIT (обычно Ctrl-, ).Основной файл генерируется, когда процесс завершается после получения SIGQUIT, который аналогичен сигналу ошибки программы в этом смысл
SIGFPE 8 Построить файл CORE (исключение с плавающей запятой) SIGFPE выдается, когда происходит фатальная арифметическая ошибка. Не только ошибки с плавающей запятой, но и все другие арифметические ошибки, такие как переполнение и деление на 0.
SIGKILL 9 Завершить процесс (убить процесс) SIGKILL используется для немедленного завершения выполнения программы.Этот сигнал нельзя заблокировать, обработать или проигнорировать.
SIGSEGV 11 SIGSEGV попытался получить доступ к памяти, не выделенной для себя, или попытался записать данные в адрес памяти, который не имеет разрешения на запись
SIGALRM 14 Завершить процесс (таймер истекает) Сигнал синхронизации часов SIGALRM, который вычисляет фактическое время или время часов.Функция будильника использует этот сигнал
SIGTERM 15 Завершить процесс (сигнал программного уничтожения) Завершение программы SIGTERM (завершение, сигнал, в отличие от SIGKILL, сигнал может быть заблокирован и обработан. Обычно используется, чтобы потребовать нормального выхода программы. Команда оболочки kill генерирует этот сигнал по умолчанию
SIGCHLD 17 Игнорировать сигналы (уведомлять родительский процесс, когда дочерний процесс останавливается или завершается) SIGCHLD Когда дочерний процесс завершится, родительский процесс получит этот сигнал
SIGVTALRM 26 Завершить процесс (истечение фиктивного таймера) Виртуальный тактовый сигнал SIGVTALRM.Похож на SIGALRM, но вычисляет процессорное время, занимаемое процессом.
SIGIO 29 Игнорировать сигнал (ввод-вывод может выполняться на дескрипторе) Файловый дескриптор SIGIO готов к операциям ввода/вывода

2. Функции обработки сигналов в PHP

PHPpcntlрасширенный иposixРасширение предоставляет нам несколько методов работы с сигналами (если вы хотите использовать эти функции, вам необходимо сначала установить эти расширения)

Вот несколько конкретных методов, которые я использовал в этой задаче:

declare

Структура declare используется для установки команды выполнения фрагмента кода. Синтаксис объявления аналогичен другим структурам управления потоком.

declare (directive)
    statement

Раздел директив позволяет установить поведение раздела объявления. В настоящее время известны только две команды:тики и кодировка. Будет выполнена операторная часть сегмента кода объявления — как она выполняется и какие побочные эффекты возникают, зависит от директивы, установленной в директиве.

Ticks

Тик (тактовый цикл) — это время, которое может быть отсчитано каждый раз, когда интерпретатор выполняет N фрагментов в сегменте кода объявления.заявление низкого уровняЗначение события N, которое произойдет, указывается с помощью ticks=N в директивной части объявления. Не все заявления могут быть рассчитаны по времени. как правилоусловное выражениеа такжевыражение параметраНи то, ни другое не может быть рассчитано. События, которые появляются в каждом тике, определяются функцией register_tick_function().Обратите внимание, что в каждом тике может появиться несколько событий. Для получения более подробной информации вы можете проверить официальную документацию: https://www.php.net/manual/zh/control-structures.declare.php

<?php
declare(ticks=1);//每执行一条时,触发register_tick_function()注册的函数
$a=1;//在注册之前,不算
function test(){//定义一个函数
    echo "执行\n";
}
register_tick_function('test');//该条注册函数会被当成低级语句被执行
for($i=0;$i<=2;$i++){//for算一条低级语句
    $i=$i;//赋值算一条
}
输出:六个“执行”

pcntl_signal

pcntl_signal, устанавливает обработчик сигналов

pcntl_signal ( int $signo , callback $handler [, bool $restart_syscalls = true ] ) : bool

Функция pcntl_signal() устанавливает новый обработчик сигнала для сигнала, указанного в signo.

declare(ticks = 1);
pcntl_signal(SIGINT,function(){
    echo "你按了Ctrl+C".PHP_EOL;
});
while(1){
    sleep(1);//死循环运行低级语句
}
输出:当按Ctrl+C之后,会输出“你按了Ctrl+C”

posix_kill

posix_kill, посылает сигнал процессу

posix_kill ( int $pid , int $sig ) : bool

Первый параметр — это идентификатор процесса, а второй параметр — это сигнал, который вы хотите отправить.

a.php
<?php
declare(ticks = 1);
echo getmypid();//获取当前进程id
pcntl_signal(SIGINT,function(){
    echo "你给我发了SIGINT信号";
});
while(1){
    sleep(1);
}

b.php
<?php
posix_kill(执行1.php时输出的进程id, SIGINT);

pcntl_signal_dispatch

pcntl_signal_dispatch, вызвать процессор, ожидающий сигнала

pcntl_signal_dispatch ( void ) : bool

Функция pcntl_signal_dispatch() вызывает каждый обработчик, ожидающий сигнала, установленного через pcntl_signal().

<?php
echo "安装信号处理器...\n";
pcntl_signal(SIGHUP,  function($signo) {
     echo "信号处理器被调用\n";
});
echo "为自己生成SIGHUP信号...\n";
posix_kill(posix_getpid(), SIGHUP);
echo "分发...\n";
pcntl_signal_dispatch();
echo "完成\n";
?>

输出:
安装信号处理器...
为自己生成SIGHUP信号...
分发...
信号处理器被调用
完成

pcntl_async_signals()

Асинхронная обработка сигналов, чтобы включить асинхронную обработку сигналов без тактов (что приводит к большим дополнительным накладным расходам). (PHP>=7.1)

<?php
pcntl_async_signals(true); // turn on async signals

pcntl_signal(SIGHUP,  function($sig) {
    echo "SIGHUP\n";
});

posix_kill(posix_getpid(), SIGHUP);

输出:
SIGHUP

3. Как работать с семафором в PHP

Ранее мы знали, что можем отслеживать сигналы, комбинируя declare(ticks=1) и pcntl_signal, то есть каждый низкоуровневый PHP-оператор будет проверять, есть ли в текущем процессе необработанные сигналы, что на самом деле очень требовательно к производительности.

Принцип реализации pcntl_signal заключается в добавлении сигнала в очередь после запуска сигнала. Затем проверьте, есть ли сигнал в функции обратного вызова тиков PHP, если есть сигнал, выполните функцию обратного вызова, указанную в PHP, если нет, выпрыгните из функции.

PHP_MINIT_FUNCTION(pcntl)
{
 php_register_signal_constants(INIT_FUNC_ARGS_PASSTHRU);
 php_pcntl_register_errno_constants(INIT_FUNC_ARGS_PASSTHRU);
 php_add_tick_function(pcntl_signal_dispatch TSRMLS_CC);

 return SUCCESS;
}

После PHP5.3 появилась функция pcntl_signal_dispatch. На данный момент объявлять больше не нужно, просто добавьте эту функцию в цикл, и сигнал можно будет вызывать через:

<?php
echo getmypid();//获取当前进程id
pcntl_signal(SIGUSR1,function(){
    echo "触发信号用户自定义信号1";
});
while(1){
    pcntl_signal_dispatch();
    sleep(1);//死循环运行低级语句
}

Все знают, что PHP ticks=1 означает, что эта функция вызывается каждый раз, когда выполняется строка PHP-кода. На самом деле большую часть времени сигнал не генерируется, но функция тиков всегда выполняется. Если серверная программа получает 1000 запросов за 1 секунду, в среднем каждый запрос будет выполнять 1000 строк PHP-кода. Затем PHP pcntl_signal добавляет дополнительные 1000 * 1000, что составляет 1 миллион пустых вызовов функций. Это приведет к потере большого количества ресурсов процессора. Лучше избавиться от тиков и вместо этого использовать pcntl_signal_dispatch для самостоятельной обработки сигналов в цикле кода. Реализация функции pcntl_signal_dispatch:

void pcntl_signal_dispatch()
{
 //.... 这里略去一部分代码,queue即是信号队列
 while (queue) {
  if ((handle = zend_hash_index_find(&PCNTL_G(php_signal_table), queue->signo)) != NULL) {
   ZVAL_NULL(&retval);
   ZVAL_LONG(&param, queue->signo);

   /* Call php signal handler - Note that we do not report errors, and we ignore the return value */
   /* FIXME: this is probably broken when multiple signals are handled in this while loop (retval) */
   call_user_function(EG(function_table), NULL, handle, &retval, 1, &param TSRMLS_CC);
   zval_ptr_dtor(&param);
   zval_ptr_dtor(&retval);
  }
  next = queue->next;
  queue->next = PCNTL_G(spares);
  PCNTL_G(spares) = queue;
  queue = next;
 }
}

Но у вышеупомянутого также есть отвратительное место, которое должно быть помещено в бесконечный цикл. После PHP7.1 вышла функция, завершающая асинхронный прием и обработку сигнала: pcntl_async_signals

<?php
//a.php
echo getmypid();
pcntl_async_signals(true);//开启异步监听信号
pcntl_signal(SIGUSR1,function(){
    echo "触发信号";
    posix_kill(getmypid(),SIGSTOP);
});
posix_kill(getmypid(),SIGSTOP);//给进程发送暂停信号

//b.php
posix_kill(文件1进程, SIGCONT);//给进程发送继续信号
posix_kill(文件1进程, SIGUSR1);//给进程发送user1信号

Благодаря методу pcntl_async_signals нет необходимости писать бесконечный цикл.

Пакет, который слушает сигнал:

https://github.com/Rain-Life/monitorSignal