signal
Introduction to signal and interrupt flow
Signal refers to software interrupt signal, referred to as soft interrupt for short
- Interrupt source (interrupt signal generation position):
- Terminal device driver
- The interrupt signals ctrl+c, ctrl+z, ctrl generated by pressing the key on the terminal equipment+\
- Hardware exception
- Software generation
- Use the kill command at the terminal to send an interrupt signal
- POSIX in php_ Kill function pcntl_alarm function
- SIGURG(TCP/IP),SIGALRM
- Interrupt response (signal processing)
- Ignore directly. But there are two signals that will never be ignored. One is SIGSTOP and the other is SIGKILL, because these two signals provide a reliable way to end the process to the kernel.
- Execute interrupt processing function (signal processing function, signal capture function)
- Execute the system default. The default response of most systems is to terminate the process.
-
Interrupt return: after the interrupt signal processing program (signal processing function, signal capture function) is completed, it will return to continue to execute the main function
-
Interrupt processing process diagram
The interrupt source sends an interrupt signal to the process, which is called an interrupt request signal,
When a process receives an interrupt signal, it can respond, which is called an interrupt response
The program for interrupt service is called interrupt handler or interrupt handler | interrupt service program
Put the interrupt back after executing the interrupt processing function
In human words, that is to say, if you are a process and you are working, suddenly the trumpet of the construction team shouted at you: "eat!", So you put down your work and went to dinner. When you were working, suddenly the trumpet of the construction team shouted to you, "pay!", So you put down your work and get paid. You were working when the trumpet of the construction team shouted at you, "someone is looking for you!", So you put down your work and see who's looking for you and what's going on. Of course, you are very capricious. You can not shout anything in the bird horn, that is, ignore the signal. You can also be more willful. When the speaker yells "eat" at you, you don't eat when you go, you go to bed, which can be up to you. In the process of working, you never stop working because you have to wait for a signal. You always wait for the signal, but the signal may come anytime and anywhere, and you only need to respond accordingly at this time. Therefore, the signal is not only a software interrupt, but also a way to deal with events in different steps.
Common interrupt signal
signal | introduce |
---|---|
SIGTSTP | Interactive stop signal, terminal hang key ctrl+z, terminal driver generates this signal [terminal stop character] to terminate + core (core file is used to record process exceptions or errors, which is convenient for debugging with gdb) |
SIGTERM | It can be captured and let the program clean up some work before terminating |
SIGSTOP | The job control signal also stops a process, just like SIGSTSP |
SIGQUIT | Exit key ctrl + \ the terminal driver generates this signal and generates the core file (terminal exit symbol) |
SIGINT | Interrupt key delete / ctrl+c (terminal interrupt character) |
SIGCHLD | Return when the child process terminates (send a signal to the parent process) |
SIGUSR1,SIGUSR2 | User defined signal |
SIGKILL,SIGSTOP | Those that cannot be caught and ignored are mainly used to make the process reliably terminate and stop |
You can write a php script and use the kill command to test the above signal
//demo1.php fprintf(STDOUT,"PID: ".posix_getpid().PHP_EOL); while(1){ ; }
-
Custom signal SIGUSR1
-
The job control signal SIGSTOP stops the process (stopping is not the end. Use ps -aux|grep php to find that the process is only stopped)
[1] + is the job number. You can use the jobs command to view the current terminal job
Interrupt signal handler
- pcntl_signal() Signal processing function
- First parameter $signo signal number | signal name
- The second parameter $handler interrupt signal handler | signal handler | signal handler
- The third parameter is $restart_syscall = true this parameter indicates whether to restart the interrupted system call
php interpreter executors call library functions or system call functions, which are provided by the system kernel
Interrupt system call concept
When a process is executing a system call and receives an interrupt signal, the system call will be interrupted. For example, if a process receives an interrupt signal while writing a file, the write operation cannot be resumed.
If the function being executed before the interrupt can be recovered, we call it reentrant function, otherwise it is non reentrant function. If it cannot be recovered after the interrupt, the system call function will return - 1, and errno will be set as EINTR interrupt error, which is commonly used when writing network programs.
# Install signal processor; pcntl_signal(SIGINT, function($signo){ fprintf(STDOUT,"I received a signal with the number:%d \n", $signo); }); // Cycle waiting signal while(1){ # distribute pcntl_signal_dispatch(); fprintf(STDOUT,posix_getpid()."Process running\n"); sleep(1); }
Execute the php script, press ctrl+c, or use kill -s SIGINT 31193 to send a signal, pcntl_ The signal function captures the signal
Pay attention to pcntl_ It is recommended not to write too much business logic in the signal callback function, which can cause errors. Generally, we use the interrupt signal for notification.
Each signal has corresponding action (signal processing program)
- User defined interrupt signal handler
- SIG_ Del system default action pcntl_signal(SIGINT, SIG_DEL);
- Ignore sig_ IGN(ignore)pcntl_signal(SIGINT, SIG_IGN);
- SIGKILL SIGSTOP signal cannot be captured, ignored
When the process starts, the signal action is the system action by default. If you are writing the corresponding signal handler, the original action will be overwritten. However, SIGKILL SIGSTOP signal cannot be covered
- We know that the fork() function creates a process almost identical to the original process through system call. Will the signal handler child process of the parent process inherit?
function sigHandler($signo){ fprintf(STDOUT,"pid: ".posix_getpid()."Received a signal numbered:%d \n", $signo); } # Install signal processor; pcntl_signal(SIGINT, 'sigHandler'); //fork process $pid = pcntl_fork(); // Cycle waiting signal while(1){ # distribute pcntl_signal_dispatch(); fprintf(STDOUT,posix_getpid()."Process running\n"); sleep(1); }
By executing the above script, pressing ctrl+c or sending a separate signal to the child process, the child process can be captured. Therefore, when the parent process creates a child process, the child process inherits the interrupt signal program of the parent process.
If you want the child process not to inherit the interrupt signal handler of the parent process, you can overwrite it. For example:
function sigHandler($signo){ fprintf(STDOUT,"pid: ".posix_getpid()."Received a signal numbered:%d \n", $signo); } # Install signal processor; pcntl_signal(SIGINT, 'sigHandler'); //fork process $pid = pcntl_fork(); if($pid == 0){ //Signal processing has been reset pcntl_signal(SIGINT, function($signo){ fprintf(STDOUT,"pid: ".posix_getpid()."I am a subprocess. I received a signal with the number:%d \n", $signo); }); } // Cycle waiting signal while(1){ # distribute pcntl_signal_dispatch(); fprintf(STDOUT,posix_getpid()."Process running\n"); sleep(1); }
Signal set
- Signal set refers to the set of signals
- The main process can select some signals. The blocked signal set is called blocking signal set, or signal shielding word Block
- When a process blocks a signal (php passes pcntl_sigprocmask To set the signal mask word)
- pcntl_sigprocmask
- The first parameter $how is to set signal blocking or remove signal blocking
- Second parameter $set blocking signal set
- The third parameter, $oldset, gets the old set of blocking signals
# Install signal processor; pcntl_signal(SIGINT, function($signo){ fprintf(STDOUT,"I received a signal with the number:%d \n", $signo); }); // Define blocking signal set $sigset = [SIGINT,SIGUSR1]; //Set blocking pcntl_sigprocmask(SIG_BLOCK, $sigset); // Cycle waiting signal while(1){ # distribute pcntl_signal_dispatch(); fprintf(STDOUT,posix_getpid()."Process running\n"); sleep(1); }
The execution script uses kill -s SIGINT 3009 to send a signal. Instead of capturing, the blocking is suspended.
- Remove signal shielding
pcntl_signal(SIGINT, function($signo){ fprintf(STDOUT,"I received a signal with the number:%d \n", $signo); }); // Define blocking signal set $sigset = [SIGINT,SIGUSR1]; //Set blocking pcntl_sigprocmask(SIG_BLOCK, $sigset); // Cycle waiting signal $i = 10; while($i--){ # distribute pcntl_signal_dispatch(); fprintf(STDOUT,posix_getpid()."Process running\n"); sleep(1); if($i == 5){ fprintf(STDOUT,"Shielding removed\n"); //Remove signal shielding //$oldset will return the previously blocked signal set | signal mask word pcntl_sigprocmask(SIG_UNBLOCK,[SIGINT,SIGUSR1], $oldset); print_r($oldset); } }
You can see the execution result. When the mask is removed, ctrl+c is not used to capture. After the mask is removed, ctrl+c can be used to capture the signal transmission, and the previously blocked signal set is obtained
Send signal
1. Signal transmission mode
- kill -s signal number | signal name process pid
- Use in program posix_kill Send a signal to a specified process or process group.
- pcntl_alarm Function to generate SIGALRM signal
- Press the special keys Ctrl + C, Ctrl + Z and Ctrl at the terminal+\
- Network end-to-end communication generates SIGURG signals, and reading and writing pipeline files will also generate SIGPIPE signals and SIGCHLD (generated when the sub process ends). These signals are sent by the kernel and operating system
posix_kill
First parameter:
When pid is greater than 0, a process is designated to send a signal
When pid is equal to 0, a signal is sent to each process in the process group
If the pid is equal to - 1, you can try it on the virtual machine yourself. Do not test in the online environment, which may cause problems in the system
pcntl_signal(SIGINT,function($signo){ fprintf(STDOUT,"PID %d Received %d signal \n",posix_getpid(),$signo); }); //$mapPid contains sibling process relationships $mapPid = []; $pid = pcntl_fork(); if($pid > 0){ $mapPid[] = $pid; $pid = pcntl_fork(); if($pid > 0){ $mapPid[] = $pid; while(1){ pcntl_signal_dispatch(); //posix_ The first parameter of kill, pid greater than 0, specifies a process to send //posix_kill pid equals 0 and sends a signal to each process in the process group posix_kill($mapPid[0],SIGINT); sleep(2); } exit(0); } } // Here is the subprocess code while(1){ pcntl_signal_dispatch(); fprintf(STDOUT, "pid=%d ppid=%d pgid=%d ...\n",posix_getpid(),posix_getppid(),posix_getpgrp()); sleep(2); }
After executing the above code, only the first sub process receives the signal, and the two sub processes are brother processes
- Set POSIX_ The kill function pid is equal to 0 and sends a signal to each process in the process group. The brother process and the parent process belong to the same process group,
pcntl_signal(SIGINT,function($signo){ fprintf(STDOUT,"PID %d Received %d signal \n",posix_getpid(),$signo); }); //$mapPid contains sibling process relationships $mapPid = []; $pid = pcntl_fork(); if($pid > 0){ $mapPid[] = $pid; $pid = pcntl_fork(); if($pid > 0){ $mapPid[] = $pid; while(1){ pcntl_signal_dispatch(); //posix_ The first parameter of kill, pid greater than 0, specifies a process to send //posix_kill pid equals 0 and sends a signal to each process in the process group posix_kill(0,SIGINT); sleep(2); } exit(0); } } // Here is the subprocess code while(1){ pcntl_signal_dispatch(); fprintf(STDOUT, "pid=%d ppid=%d pgid=%d ...\n",posix_getpid(),posix_getppid(),posix_getpgrp()); sleep(2); }
Both parent and child processes have captured the signal
SIGALRM signal
- A timing signal php is generated by pcntl_alarm Function implementation
# Install signal processor; pcntl_signal(SIGALRM, function($signo){ fprintf(STDOUT,"I received a signal with the number:%d \n", $signo); }); pcntl_alarm(2); // Cycle waiting signal while(1){ # distribute pcntl_signal_dispatch(); fprintf(STDOUT,posix_getpid()."Process running\n"); sleep(1); }
SIGALRM signal is captured two seconds after executing the script
2. Implement a function that executes every two seconds
function sigHandler($signo){ fprintf(STDOUT,"I received a signal with the number:%d \n", $signo); //Set the timing signal again pcntl_alarm(2); } # Install signal processor; pcntl_signal(SIGALRM, 'sigHandler'); //Set timing signal pcntl_alarm(2); // Cycle waiting signal while(1){ # distribute pcntl_signal_dispatch(); fprintf(STDOUT,posix_getpid()."Process running\n"); sleep(1); }
Execute the script to get regular signal transmission, once every 2 seconds
3. Pay attention to pcntl every time_ The call of the alarm() function will cancel the alarm signal set previously. That is, only the last pcntl will be executed_ The call of alarm() function is invalid
# Install signal processor; pcntl_signal(SIGALRM, function($signo){ fprintf(STDOUT,"I received a signal with the number:%d \n", $signo); }); pcntl_alarm(1); pcntl_alarm(3); pcntl_alarm(5); // Cycle waiting signal while(1){ # distribute pcntl_signal_dispatch(); fprintf(STDOUT,posix_getpid()."Process running\n"); sleep(1); }
You can see that the script executes to the fifth second and captures the signal instead of the first or third second
4. If pcntl_ If the alarm() function parameter is 0, the previously set alarm signal will be cancelled and the signal capture function will not be triggered
5. Look at the source code of workerman timer and find that it is also realized by using SIGALRM alarm signal
SIGCHLD signal
- SIGCHLD signal is ignored by default
- It can solve the problem of zombie process and recycle the sub process in time.
# Install signal processor; pcntl_signal(SIGCHLD, function($signo){ fprintf(STDOUT,"I received a signal with the number:%d \n", $signo); $pid = pcntl_waitpid(-1, $status, WNOHANG); if($pid > 0){ fprintf(STDOUT,"PID=%d The child process exited",$pid); } }); $pid = pcntl_fork(); if($pid > 0){ // Cycle waiting signal while(1){ # distribute pcntl_signal_dispatch(); fprintf(STDOUT,posix_getpid()."Process running\n"); sleep(1); } }else{ fprintf(STDOUT,"PID=%d End of subprocess \n",posix_getpid()); exit(10); }
pcntl_ The following is the original address of Han Tianfeng's article PHP official pcntl_signal performance is extremely poor)
-
PHP official pcntl_ The performance of signal is not good, because the signal processing function of PHP is implemented based on ticks, rather than registered in the signal processing function at the bottom of the real system. If you use ticks, such as delare ticks=1, the above function will be called every time you execute a PHP statement. In fact, there is no signal to be processed in most of the time, so it will cause great waste.
-
If a server program receives 1000 requests in one second, it will execute an average of 1000 lines of PHP code per request. So the pcntl of PHP_ Signal brings an additional 1000 * 1000, that is, 1 million empty function calls. This will waste a lot of CPU resources.
-
By viewing pcntl C source code to achieve discovery. pcntl_ The implementation principle of signal is to add the signal to a queue after triggering the signal. Then constantly check whether there is a signal in PHP's ticks callback function. If there is a signal, execute the callback function specified in PHP. If not, jump out of the function.
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; }
pcntl_ signal_ Implementation of dispatch function:
void pcntl_signal_dispatch() { //.... A part of the code is omitted here. Queue is the signal queue while (queue) { if ((handle = zend_hash_index_find(&PCNTL_G(php_signal_table), queue->signo)) != NULL) { ZVAL_NULL(&retval); ZVAL_LONG(¶m, 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, ¶m TSRMLS_CC); zval_ptr_dtor(¶m); zval_ptr_dtor(&retval); } next = queue->next; queue->next = PCNTL_G(spares); PCNTL_G(spares) = queue; queue = next; } }
- It's better to get rid of ticks and use pcntl instead_ signal_ Dispatch, which processes signals by itself in the code loop. Instead of using declare ticks, workerman invokes pcntl_ in the main event loop. signal_ The dispatch function is implemented so that pcntl_ signal_ The call frequency of dispatch has decreased a lot, and it is also guaranteed to achieve near real-time signal processing.
- In fact, generally, the code requiring signal processing is the back-end service program, and the general back-end service program is written according to the event processing structure, that is, there must be a main event loop in this program. Actively call pcntl in each loop of the event loop_ signal_ Dispatch can basically process the signal in real time and ensure a better performance.
- In swoole, because the bottom layer is implemented in C, signal processing is not affected by PHP. Swoole uses the most advanced signalfd in the current Linux system to process signals without any additional consumption.
- php needs to write all the signal processing functions of swoole correctly.