1.基本介绍:
在使用信号时,要明白4点,信号编号,信号名,信号处理函数,信号对应默认处理行为。
LINUX系统中,信号有很明确的生命周期。首先内核产生信号,接着内核储存信号,直到可以发送信号,最后一旦空闲,就处理信号(内核发送,内核处理)。处理信号有下面三种情况:
1).忽略信号:即不采取任何操作。但有两个信号不能忽略和捕捉,即SIGKILL和SIGSTOP。因为系统管理员需要杀死或者停止进程,这两个信号会采用默认行为,甚至不能被阻塞
2).捕获并且处理信号:内核会暂停该进程正在执行的代码,并跳转到先前注册的函数中,接下来会执行这个函数。一旦进程从函数返回,其会跳回捕捉信号的地方继续执行。
经常捕捉的信号是SIGINT和SIGTERM.前者是捕捉来处理用户产生的中断符(用户CTRL-C产生),后者是在程序结束前进行必要的清理工作。SIGKILL和SIGSTOP不能被捕获。
3).执行信号的默认操作
2.常见信号介绍,下面将介绍一些常用的信号。
SIGALRM:由函数alarm()发送
SIGINT:用户产生中断符。
SIGKILL:由函数kill()发出,来给系统管理员提供一种可靠的方法无条件终止一个进程,不能被捕捉或者忽略,其结果就是杀死该进程。
SIGSTOP:由KILL()函数发出,无条件停止一个进程,不能被捕捉或者忽略。
SIGTERM:该信号由kill()函数发出,其循序用户优雅地停止进程,进程可以捕捉该函数,并在进程结束前进行清理,但是捕获该信号并不能及时地终止进程。
3.基本信号管理:
1).signal信号
#include <signal.h>
typedef void(*sighandler)(int);
sighandler signal (int signal,sighandler handler);
此函数地第一个参数是需要捕捉的信号,第二个参数有如下形式;
1.SIG_IGN,第二个参数是这个时,调用signal()函数的作用就是忽略参数一的信号
2.SIG_DFL,这个符号表示恢复对信号的系统默认处理。不写此处理函数默认也是执行系统默认操作。
3.void(*pp)(int)函数类型的指针,函数成功调用时,会将处理信号作为参数传递给函数指针,去执行函数
signal()会依参数signum 指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。
当一个信号的信号处理函数执行时,如果进程又接收到了该信号(相同类型),该信号会自动被储存而
不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。
但是如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。
即相同类型储存依次执行,不同类型直接被中断。
2).继承和执行
调用fork()函数时,子进程会继承父进程的所有信号的处理(默认,处理,忽略等等),不是产生。
子进程不会进程父进程挂起的信号(已经产生了的信号),这点十分重要,想要子进程执行信号函数,必须再次发出信号。
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <iostream>
void hander(int){
std::cout<<"hello"<<std::endl;
}
int main(){
pid_t id;
alarm(1);//----------A
signal(SIGALRM,hander);//执行一次
id=fork();
if(id==0){
alarm(1);
sleep(1);
std::cout<<"erzi"<<std::endl;
signal(SIGALRM,hander);
//子进程又直接执行一次,子进程不会继承A处的信号,但会继承信号注册和信号处理函数
}else{
sleep(2);
signal(SIGALRM,hander);//此处不执行,因为父进程只被alarm()一次
//已经被捕捉
}
std::cout<<"1"<<std::endl;
}
如上,创建的子进程不会继承父进程在A处发出的信号,但其会继承信号处理函数和信号注册函数signal()和handle().若子进程未设置alarm()函数,则父进程只会执行一次。
3).发送信号
在LINUX系统中,发送信号的函数是kill()
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int signo);
如果pid>0,则发送信号给pid的进程,信号为signo
若pid=0,则会给进程的进程组的每一个进程发送signo信号。
pid<0情况不做介绍
调用成功后,此函数返回0,失败返回-1.
给进程自己发送信号
kill(getpid(),signo);
等价于函数
int raise(int signo);
调用成功时 函数返回0,失败返回大于0的值。
直接给进程组的进程发送所有信号
调用函数killpg()
int killpg(int pgrp,int signo);第一个参数是进程组的ID 成功时返回0,失败时返回-1.
补充:当内核发送信号时,进程可能执行任何任务。当被中断时,进程可能会处于不一致的状态。信号处理程序必须注意,不要对进程中断时可能正在执行的操作做任何假设,尤其是当涉及全局变量的时候,这个十分重要,尽量避开信号处理函数修改或者操作全局变量。比如涉及变量errno时,可以在信号处理函数最开始先保存全局变量,最后再进行恢复。
4).高级信号管理函数sigaction() (用法见网络编程笔记)
#include <signal.h>
int sigaction(int signo,const struct sigaction* act,struct sigaction* oldact);
成功调用此函数时,等待捕捉signo信号,若参数二不为NULL,则信号行为被act取代
若是NULL,则进行默认行为,第三个参数若非空,则会保存上一次的act信息,但
一般都设置为空。
下面是sigaction结构体介绍:
sa_handler为指定的信号操纵(类型和signal函数相同,void(*name)(int),向行为函数传递捕捉的信号),sa_mask提供了应该在执行信号处理程序时被阻塞的信号集,一般没用,但需要用函数sigfullset()进行初始化。sa_flags是标注位,改变signo信号的处理方式。一般常用的处理方式有下面几种:
SA_NOCLDWAIT:若信号是SIGCHLD,则该标志会自动获取子进程,子进程结束是不会编程僵尸进程,父进程不需要wait/waitpid等待回收。
SA_RESTART:此标志是最常用的,其能使被信号中断的系统调用重新启动。
SA_RESETHAND:该信号表示“一次性类型”,一旦信号处理函数返回,给定信号会重新设置为默认行为。
5). 信号集
阻塞信号信号集:将某些信号加入集合,对他们设置屏蔽,当屏蔽X信号后,再收到信号,该信号的处理将被推后(解除屏蔽后再处理,即阻塞,即使对面发送也默认为丢弃)。(0,1)表示状态位。
未决信号集(发送信号但未解决的集合):
1.产生信号时,在未决信号集中描述该信号的位立刻翻转为1,表示该信号发送且未解决,当信号被解决时,回转为0,这时间很短暂。(0,1表示状态位)
2.信号产生后因为某些原因不能到达(比如被屏蔽)。这类信号成为未决信号集,在屏蔽解除之前,信号一直处于未处理的状态(未决信号集对应信号状态为一直为1)。
管理人员只能操作阻塞信号集,不能操作未决信号结,开始时,阻塞信号集都为0位,阻塞信号集和未决信号集都在进程控制块里。
信号集操作函数:
#include <signal.h>
信号集变量: sigset_t set;
int sigemptyset(sigset_t* set);//将信号集变量全部变为0(初始化),
类似于select函数的监听集合的初始化
int sigfullset(sigset_t* set);//将信号集变量全部置为1
int sigaddset(sigset_t* set,int signum);将指定信号在信号集中设为为1
int sigdelset(sigset_t* set,int signum);将指定信号在信号集中设置为0
上述四个函数就是设置自自定义信号集
设置完自定义信号集后,可以调用sigpromask()函数,将自定义信号机和内核阻塞信号集(全为0)
进行掩码计算,最后将得到设置阻塞信号的信号集
int sigprocmask(int how,const sigset_t* set,sigset_t* oldset);
第一个参数是描述函数行为的,第二个参数是要与内核阻塞信号集进行运算的信号机
参数三是保留上一次的内核阻塞信号集的结果。
int sigpending(sigset_t* newset);//此函数会保存当前进程的未决信号集,
然后将结果存在newset中,比如掩盖二号信号后发送二号信号,
但是不会处理,newset中二号位信号始终是1。
int sigismember(sigset_t* newset,int signum)
查看信号集中被遮掩的信号,如果是1,则返回1,若未被遮掩则返回零,一般和sigpending()一起用
0~31已经被宏来表示32个信号了,这个注意。
how参数如下:
SIG_SETMASK:调用进程的信号掩码设置
SIG_BLOCK:自定义信号集set(参数二)加入到进程的信号掩码中,即信号掩码变为当前进程阻塞信号集与set的并集(二进制或运算)
SIG_UNBLOCK:set中的信号被调用进程阻塞信号集中移除,即解除对应set中的屏蔽。对非阻塞信号进行解除屏蔽是非法的
但是不允许SIGKILL和SIGSTOP函数。
如下例子:补充:LINUX信号有32个,用宏进行分别表示了。
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <iostream>
int main(){
sigset_t set,olcset,newset;
sigemptyset(&set);//类似于用于初始化
sigaddset(&set,SIGALRM);//屏蔽alarm信号,未决信号集始终为1
int ret=sigprocmask(SIG_BLOCK,&set,&olcset);//将设置的信号集作用进程阻塞信号集
assert(ret>=0);
alarm(1);
while(1){
alarm(1);
sleep(1);
ret=sigpending(&newset);//内核的未决信号集结果存在newset中
assert(ret>=0);
for(int i=0;i<32;i++){
if(sigismember(&newset,i)){//查看信号集的每一位,有信号未解决 返回1,没有返回0
putchar('1');
}else{
putchar('0');
}
//LINUX信号32个 所以信号集中有32位,依次进行查看
}
std::cout<<"\n";
sleep(1);
}
}
运行结果: