中断信号
# 中断信号
是指软件中断信号,简称软中断。
中断源:就是产生中断信号的单元
- 终端设备驱动,即常见的终端
ctrl c
操作 - 硬件异常
- 在终端使用
kill
来发送中断信号 posix_kill
函数pcntl_alarm
函数- 软件产生的中断信号
SIGURG[TCP/IP],SIGALRM
中断响应:
对信号的处理:
- 忽略,不做处理
- 执行中断处理函数,中断信号处理程序【信号处理函数,信号捕捉函数】完以后,就会返回继续执行主程序。
- 执行系统默认,每个信号都有自己对应的动作【忽略、默认、执行用户编写好的信号处理函数】
中断返回:
就是指中断服务程序运行完之后返回的。
PHP 的pcntl
系列函数:
pcntl_alarm
pcntl_signal
pcntl_signal_dispatch
pcntl_sigprocmask
// 输出当前bash 进程号
echo posix_getpid();
while (1) {}
2
3
打印出进程号之后,我们可以使用kill
命令来发送信号中断程序。
几个比较常用的中断信号:
SIGTSTP
:交互停止信号,终端挂起键ctrl z
【会让进程丢到后台去停止(背景),前台(前景)】终端驱动产生此信号【终端停止符】终止+core
SIGTERM
:可以被捕捉,让程序先清理一些工作再终止SIGSTOP
:作业控制信号,也是停止一个进程,跟SIGTSTP
一样,但是并没有终止!,我们可以使用SIGCONT
来唤醒停止的进程,让前台继续运行,可以使用jobs
命令来查看是否在运行和停止。SIGQUIT
:退出键CTRL + \
终端去掉程序产生此信号,同时产生core
文件【终端退出符】SIGINT
:中断键delete/ctrl+c
【终端中断符】SIGCHLD
:子进程终止时返回SIGUSR1,SIGUSR2
:用户自定义信号SIGKILL SIGSTOP
:不能被捕捉以及忽略的,主要用于让进程可靠的终止和停止,就是我们经常使用的kill -9 进程号
,这里的 9 就是代表的对应的信号的序号,可以使用kill -l
查看。
[root@jb51 process]# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编写中断处理程序
pcntl
信号处理的函数:pcntl_signal
$signal 信号编号 | 信号名称
$handler 中断信号处理程序 | 信号步骤函数 | 信号处理函数 | 信号处理程序
$restart_syscall = true 这个参数表示是否需要重启被中断的系统调用
应用程序【php 解释器】 -> 库函数、系统调用函数 -> 系统内核
strace 可以跟踪应用程序调用了哪些系统接口函数
中断系统调用:
当进程正在执行系统调用的时候,接收到中断信号,那么这个系统调用的就会被中断
比如说现在进程正在写文件,突然收到中断信号,那么就停止了写的操作
如果能恢复我们称之为:“可重入函数”,否则就是非可重入函数,大概率和“断点续传”这个概念差不多的意思。
pcntl_signal(SIGINT, function ($signo) {
fprintf(STDOUT, "我接受到一个信号:%d\n", $signo);
});
while (1) {
pcntl_signal_dispatch();
fprintf(STDOUT, "pid=%d, main process is doing something\n", posix_getpid());
sleep(1);
}
2
3
4
5
6
7
8
9
这边使用的是ctrl c
来中断信号
[root@jb51 process]# php demo13.php
pid=7011, main process is doing something
pid=7011, main process is doing something
pid=7011, main process is doing something
pid=7011, main process is doing something
pid=7011, main process is doing something
pid=7011, main process is doing something
pid=7011, main process is doing something
^C我接受到一个信号:2
pid=7011, main process is doing something
pid=7011, main process is doing something
pid=7011, main process is doing something
^C我接受到一个信号:2
pid=7011, main process is doing something
pid=7011, main process is doing something
pid=7011, main process is doing something
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
也可以使用命令来终止
[root@jb51 process]# kill -s SIGUSR1 7011
pid=7011, main process is doing something
User defined signal 1
2
一般在终端信号处理函数,不要写太多的业务逻辑。
一般来说我们经常把中断信号用于通知。
每个信号都有相对于的动作【信号处理程序】
- 用户自定义的中断信号处理程序
SIG_DEF
:系统默认动作,就会让进程终止 +core
或者是停止- 忽略
SIG_IGN
,SIGKILL SIGSTOP
这两个比较特殊,不可以被捕捉以及忽略,所以这 2 个不能当做函数来编写。
警告
pcntl_singal(SIGKILL, '信号处理程序');
这样是无法被捕获的,会直接给你报错误。
Error installing singal handler for 9
SIGSTOP
同上。
注意
进程启动的时候:信号的动作默认是系统行为,如果你再编写信号对应的处理程序,就会覆盖调原来的动作。有些信号是可以覆盖调默认动作,但有些信号不可以,比如说:SIGKILL SIGSTOP
当父进程创建一个子进程的时候,子进程是继承父进程的中断信号处理程序的。
pcntl_signal(SIGINT, function ($signo) {
fprintf(STDOUT, "我接受到一个信号:%d\n", $signo);
});
$pid = pcntl_fork();
if ($pid == 0) {
// 子进程已经重设信号处理程序
pcntl_signal(SIGUSR1, function ($signo) {
fprintf(STDOUT, "子进程接受到一个信号:%d\n", $signo);
});
}
while (1) {
pcntl_signal_dispatch();
fprintf(STDOUT, "pid=%d, main process is doing something\n", posix_getpid());
sleep(1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 信号集
信号集:是指信号的集合
主程序可以选择阻塞某些信号,被阻塞的信号集称之为阻塞信号集,也叫信号屏蔽字
Block
当进程阻塞了某个信号[通过
pcntl_sigprocmask
函数来设置信号屏蔽字],如果在运行期间接收到了阻塞的信号时,这个信号的处理程序不会被执行,这个信号会被放在被挂起的信号集里,也叫信号未决集。
执行信号处理的函数称为“信号递达”
信号从生产到递达状态称为“信号未决” pending
当进程设置了信号屏蔽字时,中断源产生的信号将放在未决集里,并不会递达处理。
php
的pcntl
只提供了pcntl_sigprocmask
函数来设置进程的信号屏蔽字,不能进行获取,可以使用c/c++
的sigpending()
来进行获取。
pcntl_signal(SIGINT, function ($singal) {
fprintf(STDOUT, "pid=%d 接收到了信号%d\n", getmypid(), $singal);
});
while (1) {
pcntl_signal_dispatch();
fprintf(STDOUT, "pid=%d do something...\n", getmypid());
sleep(1);
}
2
3
4
5
6
7
8
9
现在未设置信号屏蔽字的时候,是可以使用ctrl c
进行退出的。
<?php
pcntl_signal(SIGINT, function ($singal) {
fprintf(STDOUT, "pid=%d 接收到了信号%d\n", getmypid(), $singal);
});
// 设置进程的信号屏蔽字 | 信号阻塞集
$sigset = [SIGINT, SIGUSR1];
pcntl_sigprocmask(SIG_BLOCK, $sigset);
while (1) {
pcntl_signal_dispatch();
fprintf(STDOUT, "pid=%d do something...\n", getmypid());
sleep(1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
现在加上了信号屏蔽字,我们再使用ctrl c
或者kill
函数发送信号也是没用了。
<?php
pcntl_signal(SIGINT, function ($singal) {
fprintf(STDOUT, "pid=%d 接收到了信号%d\n", getmypid(), $singal);
});
// 设置进程的信号屏蔽字 | 信号阻塞集
$sigset = [SIGINT, SIGUSR1];
pcntl_sigprocmask(SIG_BLOCK, $sigset);
$i = 10;
while ($i--) {
pcntl_signal_dispatch();
fprintf(STDOUT, "pid=%d do something...\n", getmypid());
sleep(1);
if ($i == 5) {
fprintf(STDOUT, "时间到,准备解除阻塞\n");
// 接触信号屏蔽
// $oldset 会返回之前阻塞的信号屏蔽字
pcntl_sigprocmask(SIG_UNBLOCK, [SIGINT, SIGUSR1], $oldset);
print_r($oldset);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@jb51 process]# php demo14.php
pid=18671 do something...
pid=18671 do something...
^Cpid=18671 do something...
pid=18671 do something...
^Cpid=18671 do something...
时间到,准备解除阻塞
Array
(
[0] => 2
[1] => 10
)
pid=18671 接收到了信号2
pid=18671 do something...
pid=18671 do something...
^Cpid=18671 接收到了信号2
pid=18671 do something...
pid=18671 do something...
^Cpid=18671 接收到了信号2
pid=18671 do something...
[root@jb51 process]# ^C
[root@jb51 process]# ^C
[root@jb51 process]# ^C
[root@jb51 process]#
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
现在我们加了一个定时解除阻塞的信号屏蔽字,之后我们就可以使用ctrl c
来退出。
# 发送信号
发送信号的方式:
kill -s 信号编号|信号名称 进程PID
- 在程序中使用
posix_kill
给一个指定的进程或是进程组发送信号 - 产生信号,使用
pcntl_alarm
产生一个信号,名称为对应的信号编号:SIGALARM
- 在终端按下特殊的按键:
ctrl c、ctrl z、ctrl \
- 有时候还跟网络
SIGURG
紧急信号产生,或者读写的时候产生SIGPIPE
,SIGCHLD
:当子进程结束的时候,内核会把SIGCHILD
发给父进程。
<?php
pcntl_signal(SIGINT, function ($singal) {
fprintf(STDOUT, "pid %d 接收到 %d 的信号\n", posix_getpid(), $singal);
});
$mapPid = [];
$pid = pcntl_fork();
if ($pid > 0) {
$mapPid[] = $pid;
$pid = pcntl_fork();
if ($pid > 0) {
$mapPid[] = $pid;
while (1) {
pcntl_signal_dispatch();
posix_kill($mapPid[0], SIGINT);
// 父进程
sleep(2);
}
// 父进程结束之后就直接退出了
exit(0);
}
}
// 这里是子进程的运行代码
while (1) {
pcntl_signal_dispatch();
fprintf(STDOUT, "pid %d ppid=%d pgid=%d doing ...\n", posix_getpid(), posix_getppid(), posix_getpgid());
sleep(1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
│ └─bash,26552
│ └─php,22568 demo15.php
│ ├─php,22569 demo15.php
│ └─php,22570 demo15.php
2
3
4
创建的子进程都是兄弟进程,父进程 ID,组 ID 都是一样的。
// 这个就会给这个进程组所有的发送信号
posix_kill(0, SIGINT);
2
[root@jb51 process]# php demo15.php
pid 23655 ppid=23654 pgid=23654 doing ...
pid 23655 接收到 2 的信号
pid 23655 ppid=23654 pgid=23654 doing ...
pid 23656 接收到 2 的信号
pid 23656 ppid=23654 pgid=23654 doing ...
pid 23655 ppid=23654 pgid=23654 doing ...
pid 23656 ppid=23654 pgid=23654 doing ...
pid 23654 接收到 2 的信号
pid 23656 接收到 2 的信号
pid 23656 ppid=23654 pgid=23654 doing ...
pid 23655 接收到 2 的信号
pid 23655 ppid=23654 pgid=23654 doing ...
pid 23656 ppid=23654 pgid=23654 doing ...
pid 23655 ppid=23654 pgid=23654 doing ...
pid 23654 接收到 2 的信号
pid 23656 接收到 2 的信号
pid 23656 ppid=23654 pgid=23654 doing ...
pid 23655 接收到 2 的信号
pid 23655 ppid=23654 pgid=23654 doing ...
pid 23656 ppid=23654 pgid=23654 doing ...
pid 23655 ppid=23654 pgid=23654 doing ...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
└─php,23654 demo15.php
│ ├─php,23655 demo15.php
│ └─php,23656 demo15.php
2
3
pid == -1
的时候,最好在自己的机器上测试,可能会发生意外的问题。
使用pcntl_alarm
函数
<?php
pcntl_signal(SIGALRM, function ($singal) {
fprintf(STDOUT, "pid %d 接收到 %d 的信号\n", posix_getpid(), $singal);
});
// 2秒时间到之后,这个定时就会被清理掉
pcntl_alarm(2);
while (1) {
pcntl_signal_dispatch();
fprintf(STDOUT, "pid %d ppid=%d pgid=%d doing ...\n", posix_getpid(), posix_getppid(), posix_getpgid($pid));
sleep(1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
2 秒就会接收到一个 14 的信号。
<?php
pcntl_signal(SIGALRM, function ($singal) {
fprintf(STDOUT, "pid %d 接收到 %d 的信号\n", posix_getpid(), $singal);
// 每2秒钟执行一次这个函数
pcntl_alarm(2);
});
pcntl_alarm(2);
// 在给个0,就会给你清掉,会把前面的覆盖掉,会取消前面设置的信号
pcntl_alarm(0);
while (1) {
pcntl_signal_dispatch();
fprintf(STDOUT, "pid %d ppid=%d pgid=%d doing ...\n", posix_getpid(), posix_getppid(), posix_getpgid($pid));
sleep(1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
设置SIGCHILD
<?php
pcntl_signal(SIGCHLD, function ($singal) {
fprintf(STDOUT, "pid %d 接收到 %d 的信号\n", posix_getpid(), $singal);
// 每2秒钟执行一次这个函数
// pcntl_alarm(2);
// 回收子进程占用的内存资源
$pid = pcntl_waitpid(-1, $status, WNOHANG);
if ($pid > 0) {
fprintf(STDOUT, "pid %d 退出了\n", $pid);
}
});
$pid = pcntl_fork();
if ($pid > 0) {
while (1) {
pcntl_signal_dispatch();
fprintf(STDOUT, "pid %d ppid=%d pgid=%d doing ...\n", posix_getpid(), posix_getppid(), posix_getpgid($pid));
sleep(1);
}
} else {
// 子进程
fprintf(STDOUT, "pid %d child\n", posix_getpid());
exit(10);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@jb51 process]# php demo16.php
pid 27972 ppid=26552 pgid=27972 doing ...
pid 27973 child
pid 27972 接收到 17 的信号
pid 27973 退出了
pid 27972 ppid=26552 pgid=0 doing ...
pid 27972 ppid=26552 pgid=0 doing ...
pid 27972 ppid=26552 pgid=0 doing ...
pid 27972 ppid=26552 pgid=0 doing ...
pid 27972 ppid=26552 pgid=0 doing ...
2
3
4
5
6
7
8
9
10
17) SIGCHLD
就是我们的这个信号,让子进程主动告诉父进程退出了,然后去回收,就不会变成僵尸进程。