PCNTL 函数
在线手册:中文  英文

pcntl_signal

(PHP 4 >= 4.1.0, PHP 5)

pcntl_signal安装一个信号处理器

说明

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

函数 pcntl_signal()signo指定的信号安装一个新 的信号处理器。

参数

signo

信号编号。

handler

信号处理器可以是用户创建的函数或方法的名字,也可以是系统常量 SIG_IGN(译注:忽略信号处理程序)或SIG_DFL(默认信号处理程序).

Note:

注意当你使用一个对象方法的时候,该对象的引用计数回增加使得它在你改变为其他处理或脚本结束之前是持久存在的。

restart_syscalls

指定当信号到达时系统调用重启是否可用。(译注:经查资料,此参数意为系统调用被信号打断时,系统调用是否从 开始处重新开始,但根据http://bugs.php.net/bug.php?id=52121,此参数存在bug无效。)

返回值

成功时返回 TRUE, 或者在失败时返回 FALSE

更新日志

版本 说明
4.3.0 增加参数restart_syscalls
4.3.0 对象方法可以作为回调被使用。
4.3.0 PCNTL现在使用了ticks作为信号处理的回调机制,ticks在速度上远远超过了之前的处理机制。 这个变化与“用户ticks”遵循了相同的语义。您可以使用 declare() 语句在程序中指定允许发生回调的位置。这使得我们对异步事件处理的开销最小化。在编译PHP时 启用pcntl将始终承担这种开销,不论您的脚本中是否真正使用了pcntl。 PHP 4.3.0使用ticks作为信号处理回调机制,这比以前的机制快了很多。这个变化与 "用户ticks" 遵循了相同的语义。您可以使用declare() 语句在程序中指定允许发生回调的位置。

范例

Example #1 pcntl_signal()示例

<?php
//使用ticks需要PHP 4.3.0以上版本
declare(ticks 1);

//信号处理函数
function sig_handler($signo)
{

     switch (
$signo) {
         case 
SIGTERM:
             
// 处理SIGTERM信号
             
exit;
             break;
         case 
SIGHUP:
             
//处理SIGHUP信号
             
break;
         case 
SIGUSR1:
             echo 
"Caught SIGUSR1...\n";
             break;
         default:
             
// 处理所有其他信号
     
}

}

echo 
"Installing signal handler...\n";

//安装信号处理器
pcntl_signal(SIGTERM"sig_handler");
pcntl_signal(SIGHUP,  "sig_handler");
pcntl_signal(SIGUSR1"sig_handler");

// 或者在PHP 4.3.0以上版本可以使用对象方法
// pcntl_signal(SIGUSR1, array($obj, "do_something");

echo "Generating signal SIGTERM to self...\n";

//向当前进程发送SIGUSR1信号
posix_kill(posix_getpid(), SIGUSR1);

echo 
"Done\n"

?>

参见


PCNTL 函数
在线手册:中文  英文

用户评论:

Zsolt Szilagyi (2013-02-19 09:04:41)

Remember that declaring a tick handler can become really expensive in terms of CPU cycles: Every n ticks the signal handling overhead will be executed.
So instead of declaring tick=1, test if tick=100 can do the job. If so, you are likely to gain fivefold speed.
As your script might always might miss some signals due to blocking operations like cURL downloads, call pcntl_signal_dispatch() on vital spots, e.g. at the beginning of your main loop.

S. Kr@cK (2012-05-31 12:25:10)

static class method to get the name of a process signal as string:

(self::$processSignalDescriptions is used to cache the results)

<?php
    
public static function getPOSIXSignalText($signo) {

        try {
            if (
is_null(self::$processSignalDescriptions)) {

                
self::$processSignalDescriptions = array();

                
$signal_list explode(" "trim(shell_exec("kill -l")));
                foreach (
$signal_list as $key => $value) {
                    
self::$processSignalDescriptions[$key+1] = "SIG".$value;
                }
            }

            return isset(
self::$processSignalDescriptions[$signo])?self::$processSignalDescriptions[$signo]:"UNKNOWN";

        } catch (
Exception $e) {}

        return 
"UNKNOWN";
    }
?>

Aurelien Marchand (2011-03-04 07:22:34)

I was having some issues with processing a signal using an object method I use for something else as well. In particular, I wanted to handle SIGCHLD with my own method "do_reap()" which I also call after a stream_select timeout and that uses a non-blocking pcntl_waitpid function.

The method was called when the signal was received but it couldn't reap the child.

The only way it worked was by creating a new handler that itself called do_reap().

So in other words, the following does not work:

<?php
class Parent {
  
/* ... */
  
private function do_reap(){
    
$p pcntl_waitpid(-1,$status,WNOHANG);
    if(
$p 0){
      echo 
"\nReaped zombie child " $p;
    }

   public function 
run(){
    
/* ... */
    
pcntl_signal(SIGCHLD,array(&$this,"do_reap"));
    
$readable = @stream_select($read,$null,$null,5); // 5 sec timeout
    
if($readable === 0){
      
// we got a timeout
      
$this->do_reap();
   }
}
?>

But this work:

<?php
class Parent {
  
/* ... */
  
private function do_reap(){
    
$p pcntl_waitpid(-1,$status,WNOHANG);
    if(
$p 0){
      echo 
"\nReaped zombie child " $p;
    }

   public function 
run(){
    
/* ... */
    
pcntl_signal(SIGCHLD,array(&$this,"child_died"));
    
$readable = @stream_select($read,$null,$null,5); // 5 sec timeout
    
if($readable === 0){
      
// we got a timeout
      
$this->do_reap();
   }

    private function 
child_died(){
      
$this->do_reap();
    }
}
?>

webmaster at ajeux dot com (2009-08-10 06:56:04)

For PHP >= 5.3.0, instead of declare(ticks = 1), you should now use pcntl_ signal_ dispatch().

benjamin at josefus dot /NO+SPAM/ net (2009-07-24 13:25:29)

Since php >= 5.3 handles Closures, you are now able to define the Callback directly.
Try this:

<?php
declare(ticks 1);

pcntl_signal(SIGUSR1, function ($signal) {
            echo 
'HANDLE SIGNAL ' $signal PHP_EOL;
});

posix_kill(posix_getpid(), SIGUSR1);

die;
?>

ewout dot NOSPAM dot zigterman at custosys dot nl (2009-04-21 08:32:09)

I found out then when you use pcntl_signal in a 'deamon' script and you run it before you fork childs it does not work as expected.
instead you need to use pcntl_signal in the child code of the child you are forking
and if you want to cach signals for the 'deamon' part you should use pcntl_signal in the parent code.

seong at respice dot net (2009-02-14 08:48:48)

Be careful, when using an object in your callback. It seems this elevates the reference count. You may not want it to happen in repeated child processes.

rbotzer at yahoo dot com (2008-06-21 23:50:37)

You cannot assign a signal handler for SIGKILL (kill -9).

Holger Hees (2008-06-09 00:49:24)

you should use following code to prevent situation described by anxious2006 (children exit near simultaneously)
public function sig_handler($signo){
switch ($signo) {
case SIGCLD:
while( ( $pid = pcntl_wait ( $signo, WNOHANG ) ) > 0 ){
$signal = pcntl_wexitstatus ( $signo );
}
break;
}
}

Anonymous (2007-12-20 08:44:35)

Multiple children return less than the number of children exiting at a given moment SIGCHLD signals is normal behavior for Unix (POSIX) systems. SIGCHLD might be read as "one or more children changed status -- go examine your children and harvest their status values". Signals are implemented as bit masks in most Unix systems, so there can only be 1 SIGCHLD bit set in any given kernel tick for a process.

anxious2006 (2007-10-30 21:35:20)

Under my setup (FreeBSD 6.2-RELEASE / PHP 5.2.4 CLI) I've noticed that when a child exits the SIGCHLD handler in the parent is not always invoked. It seems to happen when two children exit near simultaneously.
In this instance the child prints "EXIT" and the parent prints "SIGCHLD received":
- EXIT
- SIGCHLD received
This works as expected, but now look what happens when three exit in quick succession:
- EXIT
- EXIT
- EXIT
- SIGCHLD received
- SIGCHLD received
Because of this quirk any code which tries to limit the maximum number of children by incrementing on fork and decrementing on SIGCHLD will eventually end up with a single child (or no further forks), since the "active children" count is always above the maximum. I've noticed similar behaviour with using decrement after pcntl_wait(). Hopefully there's a workaround for this.

rob at robertjohnkaper dot com (2006-11-16 03:00:05)

Tip: when using objects, don't set the signal handler from the constructor or even a method called from the constructor - your internal variables will be uninitialised.

nate at example dot com (2006-10-29 04:10:37)

If you have a script that needs certain sections to not be interrupted by a signal (especially SIGTERM or SIGINT), but want to make your script ready to process that signal ASAP, there's only one way to do it. Flag the script as having received the signal, and wait for your script to say its ready to process it.

Here's a sample script:

<?
    $allow_exit = true;  // are we permitting exit?
    $force_exit = false; // do we need to exit?

    declare(ticks = 1);
    register_tick_function('check_exit');
    pcntl_signal(SIGTERM, 'sig_handler');
    pcntl_signal(SIGINT, 'sig_handler');

    function sig_handler () {
        global $allow_exit, $force_exit;

        if ($allow_exit)
            exit;
        else
            $force_exit = true;
    }

    function check_exit () {
        global $allow_exit, $force_exit;

        if ($force_exit && $allow_exit)
            exit;
    }

    $allow_exit = false;

    $i = 0;
    while (++$i) {
        echo "still going (${i})\n";
        if ($i == 10)
            $allow_exit = true;

        sleep(2);
    }
?>

You set $allow_exit to true at all times when it is perfectly acceptable that your script could exit without warning. In sections where you really need the script to continue running, you set $allow_exit to false. Any signals received while $allow_exit is false will not take effect until you set $allow_exit to true.

<?
    $allow_exit = true;

    // unimportant stuff here. exiting will not harm anything

    $allow_exit = false;

    // really important stuff not to be interrupted

    $allow_exit = true;

    // more unimportant stuff. if signal was received during
    // important processing above, script will exit here
?>

imslushie at hotmaildotcom dot com (2006-10-04 19:16:15)

I have been having trouble reaping my child process. It seemed most of the time, children were reaped properly but *sometimes* they would stay as zombies. I was catching the CHLD signal to reap child processes with the following code:

<?php

function childFinished($signal)
{
  global 
$kids;
  
$kids--;
  
pcntl_waitpid(-1$status);
}

$kids 0;
pcntl_signal(SIGCHLD"childFinished");
for (
$i 0$i 1000$i++)
{
  while (
$kids >= 50sleep(1);
  
  
$pid pcntl_fork();
  if (
$pid == -1) die('failed to fork :(');

  
/* child process */
  
if ($pid == 0
  {
    
/* do something */
    
exit(0);
  }

  
/* parent */
  
else { $kids++; }
}

/* when finished, just clean up the children */
print "Reaping $kids children...\n";
while (
$kidssleep(1);

print 
"Finished.\n";
?>

The problem was, $kids never became zero so it would effectively wait forever. After wracking my brains (UNIX forks are new to me) I finally read the Perl IPC docs and viola, a solution! It turns out that because signal handlers are not re-entrant, my handler will not be called again while it is in use. The scenario that caused me trouble was that one child would exit and call the signal handler, which would pcntl_waitpid() it and decrement the counter. Meanwhile, another child would exit while the first child was still being reaped,  so the second would never get to notify the parent! 

The solution was to continually reap children from the SIGCHLD handler, so long as there were children to reap. Here is the *fixed* childFinished function:

<?php

function childFinished($signal)
{
  global 
$kids;

  while( 
pcntl_waitpid(-1$statusWNOHANG) > )
    
$kids--;
}

?>

aeolianmeson at NOSPAXM dot blitzeclipse dot com (2006-06-09 01:19:15)

This issue occurs in at least PHP 5.1.2.
When a SIGINT is sent via CTRL+C or CTRL+BREAK, the handler is called. If this handler sends a SIGTERM to other children, the signals are not received.
SIGINT can be sent via posix_kill() and it work exactly as expected-- This only applies when initiated via a hard break.

aeolianmeson at NOSPAM dot blitzeclipse dot com (2006-05-29 20:43:30)

In at least version 5.1.4, the parameter passed to the handler is not a strict integer.

I have had such problems as trying to add the signal to an array, but the array is completely screwed up when viewed (but not viewed immediately after being added). This occurs when the handler is a method (array($this, 'methodname')), or a traditional functions.

To avoid this bug, typecast the parameter to an integer:
(note that each newline may appear to just be 'n'.)

<?php
print("pid= " posix_getpid() . "\n");
declare(
ticks=1);
$arrsignals = array();

function 
handler($nsig)
{
    global 
$arrsignals;
    
$arrsignals[] = (int)$nsig;
    print(
"Signal caught and registered.\n");
    
var_dump($arrsignals);
}

pcntl_signal(SIGTERM'handler');

// Wait for signals from the command-line (just a simple 'kill (pid)').
$n 15;
while(
$n)
{
    
sleep(1);
    
$n--;
}

print(
"terminated.\n\n");
var_dump($arrsignals);
?>

Dustin

zenyatta22 at hotmail (2006-03-28 14:26:00)

Process handling is not available when using a blocking socket! Bear this in mind.

sezer yalcin (2006-01-12 00:29:12)

You should pay attention to this issue.
I realized "some" files need specific declare() where some do not.
It is good idea to include in all "main" files, not in include files.
junkmail at konvergencia dot hu
06-May-2003 04:21
It seems like the scope of declare() when used in it's new global form e.g.
declare(ticks = 1);
is restricted to the file it is used in and the one's include() -ed or require() -ed by the script. So you can NOT do this:

wm161 at wm161 dot net (2006-01-10 15:45:54)

When you are running a script inside of a loop that checks a socket, and it hangs on that checking (Either by flaw or design), it can't handle signals until some data is received.
A suggested workaround would be to use the stream_set_blocking function, or stream_select on the offending reads.

erik at phpcastle dot com (2005-12-27 03:46:19)

When you want to stop a CLI script by a keyboard interupt then you should use SIGINT.

Example script:
#!/usr/bin/php5
<?php

// tick use required as of PHP 4.3.0
declare(ticks 1);

// signal handler function
function sig_handler($signo)
{

         switch (
$signo) {
                 case 
SIGINT:
                         
// handle shutdown tasks
                         
echo "Keyboard pressed...\n";
                         exit;
                         break;
                 default:
                         
// handle all other signals
         
}

}

echo 
"Installing signal handler...\n";

// setup signal handlers
pcntl_signal(SIGINT"sig_handler");

// or use an object, available as of PHP 4.3.0
// pcntl_signal(SIGUSR1, array($obj, "do_something");

// Just a stupid simple loop
for ($i 1$i <= 10$i++){
    echo 
".";
    
sleep(1);
    
}

echo 
"Done\n"

?>

codeslinger at compsalot dot com (2005-02-03 21:23:11)

I ran into an interesting problem. CLI 4.3.10 Linux
The parent forked a child. The child did a bunch of stuff, and when it was done it sent SIGUSR1 to the parent and immediately exited.
Result:
The child turned into a zombie process waiting for the parent to harvest it, as it should.
But the parent was unable to harvest the child because it was receiving an endless barrage of SIGUSR1s. In fact, it logged over 200000 before I shut it down (SIGKILL). The parent was not able to do anything else (respond to other events) during that time.
No, it wasn't a bug in my child code. Apparently, after sending a signal there is some "behind the covers" work that needs to occur to acknowledge signal completion, and when you exit immediately it is not able to happen, so the system just keeps trying.
Solution: I introduced a small delay in the child, after sending the signal, and before exiting.
No more Sig loops...
----------
P.S. With respect to the note below. The whole point of the sleep function is to enable the processing of other events. So, yes, your non-renterent code, will suddenly get re-entered when you do a sleep, because you have just handed off control to the next pending event.
Ignoring the signal is only an option if the signal is unimportant to your program.... The better way to approach it, is to not do lengthy processing inside of the signal event. Instead set a global flag and get the heck out of there as fast as possible. Allow another part of your program to check the flag and do the processing outside of the signal event. Usually your program is in some kind of loop when it is receiving signals, so checking a flag regularly shouldn't be a problem.

ieure at php dot net (2005-01-19 17:15:42)

Some weird signal interactions going on here. I'm running PHP 4.3.9.
sleep() calls seem to be interrupted when any signal is received by the PHP script. But things get weird when you sleep() inside a signal handler.
Ordinarily, signal handlers are non-reentrant. That is, if the signal handler is running, sending another signal has no effect. However, sleep() seems to override PHP's signal handling. If you sleep() inside a signal handler, the signal is received and the sleep() is interrupted.
This can be worked around like this:
function handler($signal)
{
// Ignore this signal
pcntl_signal($signal, SIG_IGN);
sleep(10);
// Reinstall signal handler
pcntl_signal($signal, __FUNCTION__);
}
I don't see any mention of this behavior in the documentation.

cp at ltur dot de (2004-12-09 02:21:59)

@spam at etcpasswd dot de

Yes it is possible to use a Handler inside of a Class:

<?php
declare(ticks 1);

class 
SignalHandler
{
    function 
__construct() {
        
$this->_init();
    }
    function 
_init() {
        
pcntl_signal(SIGTERM, array(&$this,"handleSignals"));
    }
    function 
handleSignals($signal)
    {
        echo 
"$signal\n";
    }

}
$o = new SignalHandler();
posix_kill(getmypid(),SIGTERM);
// prints 15
?>

HTH
.Ape

junkmail at konvergencia dot hu (2003-05-06 04:21:28)

It seems like the scope of declare() when used in it's new global form e.g.
  declare(ticks = 1);

is restricted to the file it is used in and the one's include() -ed or require() -ed by the script. So you can NOT do this:

main.php:
<?php
 
  
include ('signals.php');
 
  
do_something_here();

?>

signals.php:
<?php

 
declare(ticks 1);

  function 
sig_handler($signo) {
     
handle_the_signal_here();
  }

  
pcntl_signal (SIGINT'sig_handler');

?>

Your signal handler function will not be called. In the above examples you have to declare() in the main.php file.

daniel[at]lorch.cc (2002-02-27 18:17:59)

There are two documents I consider reading:
Unix Signals Programming
http://users.actcom.co.il/~choo/lupg/tutorials/
Beej's Guide to Unix Interprocess Communication
http://www.ecst.csuchico.edu/~beej/guide/ipc/
Also, have a look at the manpage:
http://www.mcsr.olemiss.edu/cgi-bin/man-cgi?signal+5

易百教程