Главная > Блог > Фоновые PHP-процессы в Linux

Фоновые PHP-процессы в Linux

Процессы в Linux

В программирование бывает необходимость писать программы, которые должны работать непрерывно, причины тому могут быть: непрерывное отслеживание изменений на сайте, длительное выполнение какой-либо операции, веб-сокет сервер и другие. И для небольших программ язык PHP, как никогда подходит лучше всего. Здесь я попытаюсь описать возможные шаги, которые нужно предпринять чтобы написать свою программу работающую в среде Linux. Я предполагаю что Вы уже знакомы с Линукс и слова «консоль» и «ssh» Вас уже не пугают. Примеры команд будут запущены в среде Debian Linux 8.4, с PHP 7. Сам PHP 7 установлен как CLI.

Оглавление

Процесс в Linux

Запуская программу в Unix вы создаёте «процесс», т.е. процессор начинает выполнять инструкции программы. Чтобы не запутаться в названиях «программа» и «процесс» можно представить, что «программа» это некоторая бумага с текстом приказа, а «процесс» это робот который читает приказ и выполняет его. Вполне может быть что процесс может удалить программу, которая его запустила или что процесс запустил другие программы, т.е. вызвал другие процессы.

Запуская программу в Unix процессу присваиваются некоторые параметры, а именно:

- PID - уникальный номер процесса. С помощью PID можно отправить сигнал процессу, узнать о его состоянии. PID всегда уникален и предсказать этот номер не представляется возможным. Если выполнить в консоле команду то увидим ID процесса.

$ nano sleep.php
<?php sleep(100); ?>
$ php sleep.php &
[1] 6178

* Запуская программу в консоли я использую на конце символ «&», это я делаю для того чтобы после запуска в консоли можно было запускать другие программы, т.е. не быть заблокированным своим процессом.

- Имя - имя процесса формируется из имени программы и параметры его запуска. Т.е. если выполнить в консоли «php sleep.php &» процесс будет называться «php sleep.php» если же выполнить «/usr/local/bin/php sleep.php &», то процесс будет называться «/usr/local/bin/php sleep.php». Но так бывает не всегда, при желании процесс может задать себе новое какое пожелает. Пример:

$ nano custom_name_process.php
<?php 
cli_set_process_title("Это имя процесс присвоил себе сам");	
sleep(100);
?>
$ php custom_name_process.php &
$ ps -ef | grep "[Э]то"

Вывод команды ps будет напоминать что-то вроде этого.

example     8214  6235  0 12:03 pts/0    00:00:00 Это имя процесс присвоил себе сам

Я всё же рекомендую не использовать в имени процесса UTF-8 символы и спец символы, во избежания проблем с операционными системами Unix, без поддержки Unicode на уровне ядра.

- PPID - PID родительского процесса. У каждого процесса есть родитель, т.е. процесс который его запустил. У родительского процесса также может присутстовать родитель и так вверх по иерархии. На самом верху иерархии находится процесс «init» или «systemd», он является родоначальников всех процессов и его запускает само ядро Linux. У процесса всегда есть родитель, если родительский процесс закрывается все потомки родительского процесса «усыновляются» процессом «init» или «systemd». Если вы запускаете программу через консоль, то родителем процесса будет консольная программа, вероятно «tcsh», «bash» или «zsh». Посмотреть иерархию процессов можно так:

$ htop t
$ pstree

- UID - UID пользователя, который запустил процесс. После запуска процессу ещё присваивается владелец и группа (почти как у файла) и с правами этого пользователя процесс будет работать. К примеру если мы запустим программу от пользователя «user1» находящегося в группе «group1», то процесс сможет выполнять действия доступные пользователю «user1», и не сможет скажем зайти в папку «/root» или удалить файл, владелец которого является пользователь «user2».

- Аргументы программы - строки указанные в консоле при запуске программы после имени программы через пробел. Например если в консоли запустить программу: «php example.php a b -p1 --test=2», аргументами будут строки «a», «b», «-p1», «--test=2». В PHP аргументы программы можно найти в глобальном массиве $argv, а кол-во аргументов можно найти в глобальной переменной «$argc». Пример:

$ nano arguments.php
<?php 
print_r($argv);
?>
$ php arguments.php --param1=1 --param2=2
Array
(
    [0] => arg.php
    [1] => --param1=1
    [2] => --param2=2
)

Закрытие процесса

В большинстве случаев процесс сам знает когда ему закончить работу, т.е. выполнил программу и завершился, но бывают случаи, когда процесс необходимо завершить принудительно. Для того чтобы обратиться к процесс нужно знать его PID или хотя бы имя процесса, чтобы по нему потом определить PID. После того как узнали PID процесса можно отправить сигнал закрытия, это делается командой «kill». Пример:

$ nano sleep.php
<?php 
sleep(100);
?>
$ php simple.php &
$ ps -ef | grep "simple.php" 
$ kill 12111
$ ps -ef | grep "simple.php"

Бывает такое что процесс не может завершиться, то тогда ему нужно отправить сигнал «принудительное закрытие», это делается командой «kill -9 PID». Ещё процесс можно завершить по имени процесса, для этого используйте команду «killall».

Если вы запустили программу и программа заблокировала консоль используйте сочетание клавиш «Ctrl + C» и программа завершится. Или если просто хотите вернуться консоль не закрывая программу, то нажмите «Ctrl + Z», а далее «bg».

Программы Linux для работы с процессами

Программ для работы с процессами множество, я же расскажу о программах которые мне нравятся больше и популярные примеры их использования. Речь пойдёт о программах: ps, pgrep, kill, htop.

- ps - показывает запущенные процессы и сведения по ним. Примеры:

$ ps							# Показать свои запущенные процессы
$ ps -ef						# Показать все запущенные процессы
$ ps -ef | grep "[s]imple.php"	# Найти процесс в имени которого встречается «simple.php» *

* - Если написать просто «ps -ef | grep "simple.php"», то отобразиться не только нужный нам процесс и сведения о самом нашем процессе «ps» и это может запутать.

- pgrep - ищет процесс по имени и возвращает PID процесса. В отличии от ps не возвращает PID самого себя, а только нужный нам. В большинстве случаев результат программы pgrep используется другими программами. Примеры:

$ pgrep -u example                      # Найти PID всех процессов пользователя «example»
$ pgrep -f "simple.php" | xargs kill	# Найти все программы, в имени которого присутствует «simple.php» и завершить

- kill - отправить сигнал процессу. Не стоит пугаться названия этой программы, она не только завершает процесс, но и может отправить разные сигналы процессу. В основном используется чтобы принудительно закрыть процесс.

$ kill 11111            # Закрыть процесс с PID «11111»
$ kill -9 11111         # Принудительно закрыть процесс с PID «11111»
$ kill -s USR1 11111	# Отправить пользовательский сигнал процессу с PID «11111» *
$ kill -l               # Показать список доступных сигналов

* Если процесс не настроен на «ловлю» этого сигнала процесс завершиться.

- htop - программа для мониторинга процессов в реальном времени. Программа позволяет узнать загруженность системы, а также узнать какие процессы используют большую часть процессорного времени или оперативной памяти. Удобно запускать в отдельном окне и заглядывать туда.

$ htop                          # Анализ загруженности процессора и оперативной памяти
$ htop --sort-key=PERCENT_MEM	# Показать статистику по процессам и отсортировать по использованию оперативной памяти
$ htop -p 11111                 # Показать статистику по процессу с PID «11111»
$ htop t                        # Показать иерархию процессов
$ htop -d 5                     # Обновлять статистику каждые 0.5 секунд

- pstree - показать дерево процессов. Можно посмотреть всю иерархию процессов или всех «предков» процесса.

$ pstree            # Иерархия всех процессов
$ pstree -as 11111	# Показать всех предков процесса с PID «11111»

Непрерывный процесс

Теперь нам нужно добиться того чтобы наш скрипт работал постоянно (а не выключался после запуска), для этого нам достаточно написать бесконечный цикл, к примеру вот так:

$ nano background.php
<?php 
while (true) {}
?>
$ php background.php &              # Запускаем скрипт в фоне
$ pgrep -f background.php			# Определяем PID процесса
$ htop -p 11111                     # Мониторим наш процесс
  PID USER      PRI  NI  VIRT   RES   SHR S CPU% MEM%   TIME+  Command
11111 root       20   0 50472 19004 13452 R 99.9  0.5  0:05.27 php background.php

Если мы запустим этот скрипт, у нас действительно процесс будет запущен постоянно, но при этом мы получим почти 100% загрузку процессора, т.е. итерации цикла будут выполняться постоянно со 100% скоростью. Чтобы этого избежать нагрузку процессора, но при этом получать отклик необходимо по окончании каждой итерации цикла останавливать скрипт на некоторое количество времени. Для этого можно использовать функции «sleep» или «usleep», при «sleep» указывается время в секундах, а при «usleep» время в микросекундах, т.е. «sleep(2) = usleep(2000000)». Переделаем наш скрипт:

$ nano background.php
<?php 
while (true) 
{
    /* Некоторые комнады */

    /* Ожидаем 0.01 секунды */
    usleep(10000); 
}
?>
$ php background.php &         # Запускаем скрипт в фоне
$ pgrep -f background.php      # Определяем PID процесса
$ htop -p 11111                # Мониторим наш процесс
  PID USER      PRI  NI  VIRT   RES   SHR S CPU% MEM%   TIME+  Command
11111 root       20   0 50472 19004 13452 R 00.0  0.5  0:05.27 php background.php

Здесь «usleep» будет означать, время отклика ваше программы на внешние раздражители (сигналы, сокеты и прочее).Теперь вместо почти 100% загруженности получим почти 0% загруженность процессора при простаивании процесса. Вероятно программа, которая работает в фоне будет не просто выполняться в бесконечном цикле, а ждать поступления определённого события (сигналы, таймер, поступление данных на канал или сокет, изменения в файлах и прочее), здесь же скрипт предоставлен в качестве примера.

Отвязываемся от консоли

Мы научились запускать процессы в режиме ожидания, теперь необходимо чтобы процесс смог самостоятельно работать и не быть «привязанным к консоли». Запуская свои программы в консоли мы можем заметить пока программа выполняется, другие программы запускать в консоли не получается. Есть несколько способов запустить программу в фоновом режиме.

Первый способ:

Используйте символ «&» (Амперса́нд) после набора команды. Пример:

$ php background.php &

Консоли бывают разные (bash, tcsh, zsh и др.) и некоторые консоли (но не tcsh) при закрытии отправляют процессам специальный сигнал «SIGHUP», после чего программы завершают своё выполнение. Чтобы этого избежать используйте программу «nohup», она гарантирует что после закрытия консоли программы не закроются.

$ nohup php background.php &

Второй способ:

После запуска программы нажмите сочетание клавиш «Ctrl + Z» и далее команду «bg». Сочетаниями клавиш мы ставим процесс на «паузу» и командой «bg» «будим» процесс и он продолжает работать работать в фоне.

Третий способ:

Ещё один способ подразумевает что запущенный процесс (родительский процесс) «склонирует» себе подобный процесс (дочерний процесс) т.е. «форкнется» и завершиться. Родительский процесс завершиться, а дочерний процесс продолжит работу, к тому же он усыновляется процессом «init» и перестаёт быть зависимым от консоли. Я считаю этот способ более предпочтительным, т.к. нам теперь нет необходимости на конце писать символ «&» и следить не закроется ли он после закрытия консоли.

$ nano bg_fork.php
<?php 
$pid = pcntl_fork();

if ($pid !== 0)
{
	/* Здесь выполняется родитель */
	exit();
}

/* Здесь выполняется дочерний процесс */
while (true)
{
	usleep(10000); 
}
?>
$ php bg_fork.php

Одна программа - один процесс

Запуская нашу программу несколько раз мы будем постоянно «плодить» одинаковые процессы, нам же нужно чтобы программа порождала только один процесс. Чтобы программа запускалась только один раз, нужно чтобы она перед запуском опрашивала систему на поиск себе подобных процессов. Один из способов узнать запущенна ли уже программа это использовать «pid-файл», т.е. файл в котором записан PID запущенного процесса. Определить какой PID текущего процесса можно функцией posix_getpid. Создаётся pid-файл вот так:

<?php 
file_put_contents("my.pid", posix_getpid());
?>

Далее при каждом запуске программы, программа будет читать этот файл и на указанный PID будет отправлять сигнал, если ответ придёт утвердительный значит программа уже запущена и запуск повторный не потребуется, если же ответа нет значит можно запускаться. Проверить запущен ли процесс с определённым PID можно функцией «posix_kill», которая отправит сигнал процессу. В функции указать сигнал с номером «0».

<?php 
if (posix_kill(12111, 0))
{
	echo "Процесс с PID = 12111 запущен.";
}
?>

Теперь напишем программу, которая будет запускаться только один раз:

$ nano once.php
<?php 
/* Проверяем запущен ли процесс указанный в pid файле */
if (is_file("my.pid"))
{
	$pid = file_get_contents("my.pid");
	if (posix_kill($pid, 0))
	{
		exit();
	}
}

/* Создаём PID-фал */
file_put_contents("my.pid", posix_getpid());

/* Зацикливаемся */
while (true)
{
	usleep(100000);
}
?>
$ php once.php &
$ php once.php &
$ php once.php &
$ php once.php &
$ ps -ef | grep "[o]nce.php"
example    11111     1  0 14:42 pts/0    00:00:00 php once.php

Программа запустилась только один раз.

PHP-программа как Unix скрипт

Чтобы запускать php-программу, как обычный Unix-скрипт (т.е. вместо «php script.php» писать «./script.php»), необходимо в начале файла (перед «<?php») программы написать путь к интерпритатору, который будет обрабатывать этот файл (в нашем случае это php). Путь к интерпритатору будет начинаться с символов «#!». Также нужно задать файлу права на выполнение к примеру 755, что будет означать - разрешено запускать скрипт всем.

$ whereis php		# определяем путь к php-интерпритатору
php: /usr/bin/php /usr/share/php /usr/share/man/man1/php.1.gz
$ nano script
#!/usr/bin/php
<?php
echo "Мой скрипт.";
?>
$ chmod 755 script.php	# Даём право на выполнение
$ ./script              # Запускаем через «./» чтобы не указывать абсолютный путь к файлу.

Теперь если запустить файл без указания интерпритатора, то программа запуститься через «/usr/bin/php», если запустить с указанием интерпритатора, то программа запустить через указнный в консоли интерпритатор. Пример:

$ nano version
#!/usr/bin/php
<?php
echo PHP_VERSION . "\n";
?>
$ chmod 755 version
$ ./version
7.0.9
$ /opt/php5/bin/php ./version
5.6.17

Если же ваш скрипт будет использоваться в системах, где php может находиться в разных местах используйте такой путь к интерпритатору «#!/usr/bin/env php»

$ nano uscript
#!/usr/bin/env php
<?php
echo PHP_VERSION . "\n";
?>
7.0.9
php, процессы, unix-скрипт, pid, ppid, kill, ps, pgrep, htop, pstree, pcntl_fork, форк, pid-файл

Комментарии:

Геннадий
от
12.08.2016 - 18:46:24
Валять дурака бесценно, для всего остального есть PHP.
Комментировать
Роман
от
14.02.2017 - 03:37:58
Круто, всё думал можно ли такое сделать или нет. А как дела со стабильностью? Хочу в своём приложении заменить cron sheduler запускающий служебный cron.php на как раз такого "демона". Или я может не правильно понял принцип работы подобного php-процесса?
Комментировать

Добавить комментарий

x

Добавить