信号处理
理解概念
可以用来处理进程间的异步事件——即进程间可以通过系统调用来发送信号,只是告知某进程发生了什么事,使得被告知的进程去做对应的事件(信号处理),要注意的是,发送信号的过程并不会传送任何数据。通过kill -l
可以看到信号的名字和序号。
可以通过这个案例来说明:
在终端运行top
来查看系统运行的一些相关信息,可以看到终端的数据一直是变化的,同事通过ps -aux|grep top
来查看现在系统是否正在运行该指令,可以得到运行该指令的进程号,然后用kill -9 进程号
将该进程杀掉,我们此时通过ps -aux|grep top
来发现此时top
的运行相关信息已经没有了。这个过程就是一个进程给另一个进程发送了 SIGKILL
的信号。(注意:kill
,就是送出一个特定的信号给某个进程,而该进程根据信号做出相应的动作(sigqueue
也是),-9
可以通过kill -l
看出是SIGKILL
)
gqx@gqx-Lenovo-Product:~$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
......
信号处理方式
一般信号的处理可以分为三种:
-
忽略信号
大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是
SIGKILL
和SIGSTOP
)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景 -
捕捉信号
需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
-
系统的默认动作
对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
信号处理的注册函数
1. signal函数
/* Type of a signal handler. */
typedef void (*__sighandler_t) (int);
/* Set the handler for the signal SIG to HANDLER, returning the old
handler, or SIG_ERR on error.
By default `signal' has the BSD semantic. */
extern __sighandler_t signal (int __sig, __sighandler_t __handler);
第一个参数表示信号量类型(对应的kill -l
里的数据),第二个参数则表示该进程被告知该信号后的处理函数。参考案例如下:
#include <iostream>
#include <list>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
void timeout(int sig){
if(sig == SIGALRM){
puts("Time out!");
}
alarm(2);
}
void keycontrol(int sig){
if(sig == SIGINT){
puts("CTRL + C pressed");
}
}
int main(){
int i;
signal(SIGALRM, timeout); //到达通过了alarm函数设置的时间,调用函数timeout
signal(SIGINT, keycontrol); //键盘键入Ctrl+后,调用keycontrol函数
alarm(2);
for(i = 0; i < 6; i++){
puts("wait...");
sleep(100);
}
return 0;
}
这段代码要注意的是,在signal
中注册信号函数后,调用信号函数的则是操作系统,但进程处于睡眠状态的时间为100s,而alarm函数等待的时间是2秒,即2秒后会产生SIGALRM
信号,此时将唤醒处于休眠状态的进程,而且进程一旦被唤醒,则不会再进入休眠状态,所以上述程序运行时间比较短。
2. sigaction
该函数已经完全取代了上述signal
函数。
struct sigaction {
void (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
};
/* Get and/or set the action for signal SIG. */
extern int sigaction (int __sig, const struct sigaction *__restrict __act,
struct sigaction *__restrict __oact) __THROW;
第一个参数表示信号量类型;第二个参数信号处理函数;第三个参数:通过此参数获取之前注册的信号处理函数指针,若不需要,则传递0;
程序示例如下,改程序用来消除由父进程产生的两个子进程会导致僵尸进程的产生的情况,当子进程的生命周期结束后,回收子进程的内存信息,而不用等到父进程结束才去回收销毁子进程:
#include <iostream>
#include <list>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
void read_childproc(int sig){
int status;
pid_t id = waitpid(-1, &status, WNOHANG); //消灭子进程结束后产生的僵尸进程
if(WIFEXITED(status)){
printf("Remove proc id: %d \n", id);
printf("Child send: %d \n", WEXITSTATUS(status));
}
}
int main(){
pid_t pid;
struct sigaction act;
act.sa_handler = read_childproc;
sigemptyset(&act.sa_mask); //将sa_mask所有位初始化为0(初始化sa_mask中传入的信号集,清空其中所有信号)
act.sa_flags = 0;
sigaction(SIGCHLD, &act, 0); //SIGCHLD 子进程结束信号
pid = fork();
if(pid == 0){
puts("Hi, I am a child process!");
sleep(6);
return 12;
}else{
printf("Child proc id: %d \n", pid);
pid = fork();
if(pid == 0){
puts("Hi, I am a child process!");
sleep(13);
exit(24);
}else{
int i;
printf("Child proc id: %d \n", pid);
for(i = 0; i < 4; i++){
puts("wait...");
sleep(5);
}
}
}
return 0;
}