Закрытие дочерних и главных процессов, pcntl_fork

Temp1ar

Новичок
Закрытие дочерних и главных процессов, pcntl_fork

Помогите пожалуйста с процессами разобраться. Ситуация следущая. Скрипт работает на ура, но при попытке завершить процесс в Linux командой kill, он ни как не реагирует и продолжает работу. По сути это многопоточный телнет сервер, создающий дочерний процесс для каждого клиента. При отключении клиента, созданный процесс получает стату Z (zombie) и приписку <defunct>. Читал документации по PCNTL и не понял как его убивать.

1. Как автоматически убивать подпроцесс при отключении клиента?
2. Как сделать главный процесс убиваемым(если имеются подпроцессы, они тоже должны быть убиты)?

Исходник(запускаю su; php script.php); Многое взято с примеров php.net.

PS: перечитал форум по форкам, но не могу выродить эти пару строчек проверки...


PHP:
#!/usr/local/bin/php -q
<?php

error_reporting (4);
set_time_limit (0);
ob_implicit_flush();

$pid_file = '/tmp/php_daemon.pid';
$underpriv_uid = '99'; // uid 99 == user nobody, at least on my system.
$underpriv_gid = '99';
$port = 10000;
$address = 0; // 0 binds to all addresses, may not work on fbsd
$quit = 0;

$logtofile = TRUE;

function sig_handler($signo) {
     switch($signo) {
         case SIGTERM:
             // handle shutdown tasks
             exit;
             break;
         case SIGHUP:
             // handle restart tasks
             break;
         case SIGUSR1:
             print "Caught SIGUSR1...\n";
             break;
         case SIGCHLD:
             while( pcntl_waitpid(-1, $status, WNOHANG) > 0 ) 
         		{ \* Вот суда что-то надо вставить, мне кажется *\};
             break;
         case SIGINT:
       exit;
         default:
             // not implemented yet...
             break;
     }
}

pcntl_signal(SIGCHLD, "sig_handler");
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGINT, "sig_handler");

function readtobuffer($msgsock) {
	if (false === ($buf = socket_read($msgsock, 2048, PHP_NORMAL_READ))) {
		logi ("Client disconnected" .
		socket_strerror(socket_last_error($msgsock)) . "\n");
		socket_shutdown($msgsock, 2);
		socket_close($msgsock);
		exit;
		break 3;
	}
	return $buf;
}

function logi($mess){
    global $obj;
	echo "[".date("d.m.Y H:i:s")."]: ".$mess."\n\r";
}

function shutdown(){
	logi("Shutdown command sended");
	unlink($pid_file);
	exit;
}

function interact($msgsock) {

	do {
		$msg = "Hello maaaan\n";
		socket_write($msgsock, $msg, strlen($msg));
		do {
		   $buf=readtobuffer($msgsock);
		   if (!$buf = trim($buf)) continue;
		   execute($buf,$msgsock);
		} while (true);
		socket_close($msgsock);
	} while (true);
}

function execute($buf, $msgsock) 
{
   # Обработка комманд
   if ($buf=="shutdown") exit;
}

function become_daemon() {
   $child = pcntl_fork();
   if($child) {
       exit; // kill parent
   }
   posix_setsid(); // become session leader
   chdir("/");
   umask(0); // clear umask
   return posix_getpid();
}

function open_pid_file($file) {
   if(file_exists($file)) {
       $fp = fopen($file,"r");
       $pid = fgets($fp,1024);
       fclose($fp);
       if(posix_kill($pid,0)) {
           print "Server already running with PID: $pid\n";
           exit;
       }
       print "Removing PID file for defunct server process $pid\n";
       if(!unlink($file)) {
           print "Cannot unlink PID file $file\n";
           exit;
       }
   }
   if($fp = fopen($file,"w")) {
       return $fp;
   } else {
       print "Unable to open PID file $file for writing...\n";
       exit;
   }
}

function change_identity($uid,$gid) {
   global $pid_file;
   if(!posix_setgid($gid)) {
       print "Unable to setgid to $gid!\n";
       unlink($pid_file);
       exit;
   }   
   if(!posix_setuid($uid)) {
       print "Unable to setuid to $uid!\n";
       unlink($pid_file);
       exit;
   }  
}

$fh = open_pid_file($pid_file);

if (($sock = socket_create (AF_INET, SOCK_STREAM, 0)) < 0) {
   print "socket_create() failed: reason: " . socket_strerror ($sock) . "\n";}
if (($ret = socket_bind ($sock, $address, $port)) < 0) {
   print "socket_bind() failed: reason: " . socket_strerror ($ret) . "\n";}
if (($ret = socket_listen ($sock, 0)) < 0) {
   print "socket_listen() failed: reason: " . socket_strerror ($ret) . "\n";
}

change_identity($underpriv_uid,$underpriv_gid);

print "Server ready. Waiting for connections.....\n";

$pid = become_daemon();
fputs($fh,$pid);
fclose($fh);

while (!$quit) {

   if (($connection = socket_accept($sock)) < 0) next;
  
   if( ($child = pcntl_fork()) == -1 ) {
       print  "Could not fork!!\n";
       print "Dying...\n";
       $quit++;
   }
   elseif($child == 0) {
       socket_close($sock);
       interact($connection);
       exit;
   }
   socket_close($connection);
}
if(posix_getpid() == $pid) {
   unlink($pid_file);
}
 

MiksIr

miksir@home:~$
1. Как автоматически убивать подпроцесс при отключении клиента?

Читаем http://ru2.php.net/manual/en/function.pcntl-wait.php
Судя по коду - все ок.

2. Как сделать главный процесс убиваемым(если имеются подпроцессы, они тоже должны быть убиты)?

http://ru2.php.net/manual/en/function.pcntl-signal.php
Cудя по коду, он убиваемый. Что бы дохли дети, пошли им TERM.
 

Temp1ar

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

tf, что именно вставлять подскажи
 

tf

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

MiksIr

miksir@home:~$
Автор оригинала: Temp1ar
Ну я все это читал, в упор непонимаю суть работы этих процессов, можете сказать строчку кода, я уже день сижу читаю, мозг кипит...
Смысл в том, что когда дите умирает, родитель получает SIGCHLD. Дите после этого висит как зомби, ожидая, что родитель "отпустит" его, считав код возврата. Делает это waitpid. Флаг WNOHANG делает его неблокирующим, т.е. если нет умерших детей, он не ждет ничего.
Т.е. у тебя все верно - стоит обработчик SIGCHLD, когда он срабатывает, делается необходимое количество вызовов waitpid, что бы освободить всех детей. Внутри while ничего не нужно - эта конструкция для того, что бы повторять waitpid.
Судя по описанию твоей проблемы - твой главный процесс не получает никаких сигналов, ни TERM для выхода, ни CHLD. Начинай копать в этом направлении и смотреть, вообще, как отрабатывается обработчик сигналов.
 

Temp1ar

Новичок
Автор оригинала: MiksIr
Смысл в том, что когда дите умирает, родитель получает SIGCHLD. Дите после этого висит как зомби, ожидая, что родитель "отпустит" его, считав код возврата. Делает это waitpid. Флаг WNOHANG делает его неблокирующим, т.е. если нет умерших детей, он не ждет ничего.
Т.е. у тебя все верно - стоит обработчик SIGCHLD, когда он срабатывает, делается необходимое количество вызовов waitpid, что бы освободить всех детей. Внутри while ничего не нужно - эта конструкция для того, что бы повторять waitpid.
Судя по описанию твоей проблемы - твой главный процесс не получает никаких сигналов, ни TERM для выхода, ни CHLD. Начинай копать в этом направлении и смотреть, вообще, как отрабатывается обработчик сигналов.
Спасибо, попробую.

Насчёт сигналов, я не проверял, приходят ли они, Тарас подскажи пожалуйста как проверить? echo в кейсах свитча поставить?.

-~{}~ 09.03.07 16:56:

PHP:
function become_daemon() {
   $child = pcntl_fork();
   if($child) {
       exit; // kill parent
   }
А сдесь точно нужен exit? :confused:? Правда и без него тоже самое, но в чем смысл убийства предка сдесь?

При попытке убийства баш говорит Operation not permitted.

И ещё, в мануале написано:

pcntl_wait
(PHP 5)

У меня 4.4.6, это не критично? Ошибок не выдаёт.
 

MiksIr

miksir@home:~$
А сдесь точно нужен exit? :confused:? Правда и без него тоже самое, но в чем смысл убийства предка сдесь?
Смысл в том, что когда ты запустил скрипт, он форкается, и тот процесс (родитель), что был запущен из шела, убивается, дабы вернуть управление шелу, а дите уже начинает фоновую работу. Это и называется - запуск демона, в общем, стандартная схема.
А запускаешь ты все это и киляешь из под рута? Возможно, это даже какие-то настройки OC виноваты.
 

Temp1ar

Новичок
Вот я тоже подозреваю, всё делаю под рутом, какие настройки могут повлиять?

OS: Mandriva 2006 под VMWare установлена.

-~{}~ 09.03.07 17:50:

PHP:
function sig_handler($signo) {
     switch($signo) {
         case SIGTERM:
             echo "TERM\n";
             // handle shutdown tasks
             exit;
             break;
         case SIGHUP:
             echo "HUP\n";
             // handle restart tasks
             break;
         case SIGUSR1:
             echo "USR1\n";
             print "Caught SIGUSR1...\n";
             break;
         case SIGCHLD:
             echo "CHLD\n";
             while( pcntl_waitpid(-1, $status, WNOHANG) > 0 ) {};
             break;
         case SIGINT:
         echo "INT\n";
       exit;
         default:
             echo "Default\n";
             // not implemented yet...
             break;
     }
}
С такой структурой, с эхами, ничего из указанного не выдаёт на экран, сигналы не доходят?

-~{}~ 09.03.07 18:06:

И ещё вопрос, зачем нужна функция
PHP:
change_identity($uid,$gid)
Смена юзера и группы на определённую? С какой целью? Чтобы от лица рута не запускать? Нельзя ли запускать от обычного пользователя сразу?
 

MiksIr

miksir@home:~$
Смена юзера и группы на определённую? С какой целью? Чтобы от лица рута не запускать? Нельзя ли запускать от обычного пользователя сразу?
Да, сразу от юзера можно - попробуйте, кстати. От рута есть только один важный момент - если нужно слушать порт до 1024 - это только рут может. Но так, как порт у Вас 10000, то можно и от юзера.
kill: Operation not permitted говорит о том, что сигнал послан процессу, которому нет прав посылать сигналы ;) Для рута - это странно, но возможно. К сожалению больше не помогу в этом направлении - просто не владею информацией.
 

Temp1ar

Новичок
Operation not permitted это я ошибся, не от рута было, от обычного юзера. От рута ничо не пишет, просто не завершает процесс и всё.

А эхи должны что-нибудь выводить или как ещё можно проверить доходимость сигналов?
 

Сергей Тарасов

Профессор
PHP собран с --with-signals ?

-~{}~ 09.03.07 21:38:

declare(ticks = 1); прописан?

-~{}~ 09.03.07 21:41:

как минимум нужно

--enable-pcntl --enable-posix --enable-sigchild

-~{}~ 09.03.07 21:43:

PS Эхи должны конечно выводить... :)
 

Temp1ar

Новичок
declare(ticks = 1); где прописывать в скрипте в любом месте??
Я с примеров взял, там без декларов, я так понял это обновляющаяся функция?

Пересобрал PHP, прописал в начало скрипта деклар, без изменений, щас качаю 5 php, кто нибудь может попробовать демона из примера, во исх код: просто запустите, законнектесь на 10000 порт, телнет отрубит и если в процессах php <defunct>, то пример изначально неверен.

PHP:
<?PHP

/*

   PHP forked daemon
   Standalone PHP binary must be compiled with --enable-sockets and --enable-pcntl
   Dave M. -2002
       Online Services USA
*/

function sig_handler($signo) {

     switch($signo) {
         case SIGTERM:
             // handle shutdown tasks
             exit;
             break;
         case SIGHUP:
             // handle restart tasks
             break;
         case SIGUSR1:
             print "Caught SIGUSR1...\n";
             break;
         case SIGCHLD:
             while( pcntl_waitpid(-1,$status,WNOHANG)>0 ) {
             }
             break;
         case SIGINT:
       exit;
         default:
             // not implemented yet...
             break;
     }

}

function interact($sock) {

   // Custom code goes here... e.g: socket_read() socket_write()...

}

function become_daemon() {

   $child = pcntl_fork();
   if($child) {
       exit; // kill parent
   }
   posix_setsid(); // become session leader
   chdir("/");
   umask(0); // clear umask
   return posix_getpid();

}

function open_pid_file($file) {

   if(file_exists($file)) {
       $fp = fopen($file,"r");
       $pid = fgets($fp,1024);
       fclose($fp);
       if(posix_kill($pid,0)) {
           print "Server already running with PID: $pid\n";
           exit;
       }
       print "Removing PID file for defunct server process $pid\n";
       if(!unlink($file)) {
           print "Cannot unlink PID file $file\n";
           exit;
       }
   }
   if($fp = fopen($file,"w")) {
       return $fp;
   } else {
       print "Unable to open PID file $file for writing...\n";
       exit;
   }
}

function change_identity($uid,$gid) {
   global $pid_file;
   if(!posix_setgid($gid)) {
       print "Unable to setgid to $gid!\n";
       unlink($pid_file);
       exit;
   }   
   if(!posix_setuid($uid)) {
       print "Unable to setuid to $uid!\n";
       unlink($pid_file);
       exit;
   }
  
  
}

error_reporting (4);

set_time_limit (0);

ob_implicit_flush ();

$pid_file = '/tmp/php_daemon.pid';

$underpriv_uid = '99'; // uid 99 == user nobody, at least on my system.
$underpriv_gid = '99';

$port = 10000;
$address = 0; // 0 binds to all addresses, may not work on fbsd

$quit = 0;

pcntl_signal(SIGCHLD, "sig_handler");
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGINT, "sig_handler");

$fh = open_pid_file($pid_file);

if (($sock = socket_create (AF_INET, SOCK_STREAM, 0)) < 0) {
   print "socket_create() failed: reason: " . socket_strerror ($sock) . "\n";
}

if (($ret = socket_bind ($sock, $address, $port)) < 0) {
   print "socket_bind() failed: reason: " . socket_strerror ($ret) . "\n";
}

if (($ret = socket_listen ($sock, 0)) < 0) {
   print "socket_listen() failed: reason: " . socket_strerror ($ret) . "\n";
}

change_identity($underpriv_uid,$underpriv_gid);

print "Server ready. Waiting for connections.....\n";

$pid = become_daemon();
fputs($fh,$pid);
fclose($fh);

while(!$quit) {

   if (($connection = socket_accept($sock)) < 0) {
       next;
   }
  
   if( ($child = pcntl_fork()) == -1 ) {
       print  "Could not fork!!\n";
       print "Dying...\n";
       $quit++;
   }
   elseif($child == 0) {
       socket_close($sock);
       interact($connection);
       exit;
   }
  
   socket_close($connection);
  
}

if(posix_getpid() == $pid) {
   unlink($pid_file);
}
?>
Заранее спасибо.
 

Temp1ar

Новичок
Вставил в самом начале, собираю так:
./configure --with-mysql --enable-sockets --with-zlib --enable-pcntl --enable-shmop --enable-sigchild --enable-posix --enable-signals
 

Temp1ar

Новичок
PHP:
declare(ticks=1);

function sig_handler($signo) {
     switch($signo) {
         case SIGTERM:
		echo "SIGTERM\n";
         	exit;
         break;
         case SIGUSR1:
            	 echo "SIGUSR1\n";
         break;
         case SIGCHLD:
		echo "SIGCHLD\n";
             	while( pcntl_waitpid(-1, $status, WNOHANG) > 0 ) {};
	 break;
     }

}
pcntl_signal(SIGCHLD, "sig_handler");
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGUSR1, "sig_handler");




function interact($msgsock) {

	do {
		$msg = "hello\n\r";
		socket_write($msgsock, $msg, strlen($msg));
		
		do {
		
		if (false === ($buf = socket_read($msgsock, 2048, PHP_NORMAL_READ))) {
		logi ("client disconnected: " .
		socket_strerror(socket_last_error($msgsock)) . "\n");
		socket_shutdown($msgsock, 2);
		socket_close($msgsock);
		posix_kill(posix_getpid(), SIGCHLD);
		break 2;
		}
		   if (!$buf = trim($buf)) continue;
		   
		   execute($buf,$msgsock);
		
		} while (true);
		
		socket_close($msgsock);
	} while (true);
}
Частично проблема решена, при такой небольшой переделке можно обходными путями убить процесс главный. Как работает:
Запускаю демон, телнечусь, отключаюсь, появляется сообщение SIGCHLD, в списке процессов главный + зомби, телнечусь опять, зомби исчезает, и появляется рабочий процесс. Так же и с главным, пишу килл, и только после телнетовского соединения он умирает, получается он обрабатывает сигналы только при подключении клиента? Можно ли зделать обработку на лету??
 

hermit_refined

Отшельник
posix_kill(posix_getpid(), SIGCHLD);
а это ещё зачем?..
получается он обрабатывает сигналы только при подключении клиента?
судя по всему, он у вас блокируется в socket_accept, и поскольку $restart_syscalls в pcntl_signal у вас установлен в true, а обработчики сигналов вызываются с помощью механизма ticks (а не так, как в С) - да, ему ничего другого не остается, как ждать нового подключения.
 

Temp1ar

Новичок
Вообщем линукс сам не плохо обрабатывает сигналы, как выяснилось, я убрал хендлер и перенаправление на хендлер и всё работает как часы, т.е без

PHP:
function sig_handler($signo) {
     switch($signo) {
         case SIGTERM:
        echo "SIGTERM\n";
             exit;
         break;
         case SIGUSR1:
                 echo "SIGUSR1\n";
         break;
         case SIGCHLD:
        echo "SIGCHLD\n";
                 while( pcntl_waitpid(-1, $status, WNOHANG) > 0 ) {};
     break;
     }

}
pcntl_signal(SIGCHLD, "sig_handler");
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGUSR1, "sig_handler");
И детишек убивает, и сам прекрасно умирает.
Тему можно закрывать.
 
Сверху