- 信号本质上就是一个软件中断,它既可以作为两个进程间的通信的方式, 更重要的是, 信号可以终止一个正常程序的执行, 通常被用于处理意外情况 ,* 信号是异步的, 也就是进程并不知道信号何时会到达
- $kill -9 3390 #向PID为3390的进程发送编号为9的信号=>一个两个进程间通信的方式之一
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
- 一共62个,不是64个, 历史原因, 没有32,33
- 1~31之间的信号叫做不可靠信号, 不支持排队, 信号可能会丢失, 也叫做非实时信号
- 34~64之间的信号叫做可靠信号, 支持排队, 信号不会丢失, 也叫做实时信号
绝大多数信号的默认处理方式都是终止进程, 另外两种默认处理方式: 忽略处理, 自定义处理
1) | SIGHUP | 连接挂断 | 终止(默认处理) |
2) | SIGINT | 终端中断,Ctrl+c产生该信号 | 终止(terminate) |
3) | SIGQUIT | 终端退出,Ctrl+ | 终止+转储 |
4) | SIGILL | *进程试图执行非法指令 | 终止+转储 |
5) | SIGTRAP | 进入断点 | 终止+转储 |
6) | SIGABRT | *进程异常终止,abort()产生 | 终止+转储 |
7) | SIGBUS | 硬件或对齐错误 | 终止+转储 |
8) | SIGFPE | *浮点运算异常 | 终止+转储 |
9) | SIGKILL | 不可以被捕获或忽略的终止信号 | 终止 |
10) | SIGUSR1 | 用户定义信号1 | 终止 |
11) | SIGSEGV | *无效的内存段访问=>Segmentation error | 终止+转储 |
12) | SIGUSR2 | 用户定义信号2 | 终止 |
13) | SIGPIPE | 向读端已关闭的管道写入 | 终止 |
14) | SIGALRM | 真实定时器到期,alarm()产生 | 终止 |
15) | SIGTERM | 可以被捕获或忽略的终止信号 | 终止 |
16) | SIGSTKFLT | 协处理器栈错误 | 终止 |
17) | SIGCHLD | 子进程已经停止, 对于管理子进程很有用 | 忽略 |
18) | SIGCONT | 继续执行暂停进程(用户一般不用) | 忽略 |
19) | SIGSTOP | 不能被捕获或忽略的停止信号 | 停止(stop) |
20) | SIGTSTP | 终端挂起,用户产生停止符(Ctrl+Z) | 停止 |
21) | SIGTTIN | 后台进程读控制终端 | 停止 |
22) | SIGTTOU | 后台进程写控制终端 | 停止 |
23) | SIGURG | 紧急I/O未处理 | 忽略 |
24) | SIGXCPU | 进程资源超限 | 终止+转储 |
25) | SIGXFSZ | 文件资源超限 | 终止+转储 |
26) | SIGVTALRM | 虚拟定时器到期 | 终止 |
27) | SIGPROF | 实用定时器到期 | 终止 |
28) | SIGWINCH | 控制终端窗口大小改变 | 忽略 |
29) | SIGIO | 异步I/O事件 | 终止 |
30) | SIGPWR | 断电 | 终止 |
31) | SIGSYS | 进程试图执行无效系统调用 | 终止+转储 |
*系统对信号响应应视具体情况而定
发送信号的主要方式:
- 键盘 //只能发送部分特殊的信号 eg:Ctrl+C可以发送SIGINT
- 程序出错 //只能发送部分特殊的信号 eg: 出现段错误, 可以发送SIGSEGV
- $kill -Signal PID #能发所有信号
- 系统函数kill()/raise()/alarm()/sigqueue()
模型
kill()
//给任何进程或进程组发任何信号,成功返回0,失败返回-1设errno
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
pid
- pid>0 //指定pid
- pid=0 //GID是调用进程PID的子进程
- pid=-1 //任何子进程
- pid<-1 //GID是PID的子进程
sig
- sig == 0 //不发任何信号,但错误检查还是会做,可以检查PID是否存在
#include<sys/types.h>
#include<signal.h>
void fa(int signo){
printf("catch signal number:%d
",signo);
}
int main(){
pid_t pid=fork();
if(0==pid){
…
while(1);
}
if(0==kill(pid,0)){ //if child exists
printf("parent starts to raise signal
");
if(-1==kill(pid,40)) //if fail to kill child
perror("kill"),exit(-1);
}
return 0;
}.
raise()
//给调用的线程或进程发信号,如果发送的信号被handler处理了,会在handler返回后再返回,成功返回0,失败返回非0
#include<signal.h>
int raise(int sig);
- 在单线程程序中等价于 kill(getpid,sig)
- 在多线程程序中等价于pthread_kill(pthread_self(), sig);
if(0!=raise(SIGINT))
perror("raise success"),exit(-1);
//raise.c
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void fa(int signo){
printf("catch %d success
",signo);
}
int main(){
//设置对SIGINT自定义处理
if(SIG_ERR==signal(SIGINT,fa))
perror("signal"),exit(-1);
//5s后使用raise发送SIGINT
unsigned int second=sleep(5);
if(0==second)
printf("sleep well");
else
printf("sleep is interrupted, there are %ds left
",second);
if(0!=raise(SIGINT))
perror("raise success"),exit(-1);
return 0;
}
alarm()
//seconds秒之后给调用进程发送SOGALRM信号,返回距离下次闹钟响起剩余的描述,没有闹钟返回0
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
seconds=0表示不设置新的SIGALRM的同时取消之前的闹钟==>专门用来取消闹钟
//alarm.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void fa(int signo){
printf("catch %d
",signo);
alarm(1);
}
int main(){
// 设置对信号SIGALRM进行自定义处理;
if(SIG_ERR==signal(SIGALRM,fa))
perror("signal"),exit(-1);
// 设置5s发送SIGALRM;
unsigned int second=alarm(5);
printf("second=%u
",second); //0
sleep(2);
//调整闹钟10s后响(发SIGALRM),second是3
second=alarm(10);
printf("second=%u
",second); //3
//取消闹钟(不会发SIGALRM),second是3
// second=alarm(0);
// printf("second=%d
",second); //10
while(1);
return 0;
}
sigqueue()
//将信号和数据排好发给指定进程,成功返回0,失败返回-1设errno
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
pid //进程的编号(给谁发信号)
sig //信号值/信号的名称(发送什么样的信号)
value //联合类型的附加数据(伴随什么样的数据)
union sigval {
int sival_int;
void *sival_ptr;
};
void fa(int signo,siginfo_t* info,void* pv){
printf("进程%d发来的信号是:%d,附加数据是%d
",info->si_pid,signo,info->si_value);
}
int main(){
//设置对信号40的自定义处理
struct sigaction action={};
action.sa_sigaction=fa;
action.sa_flags=SA_SIGINFO;
int res=sigaction(40,&action,NULL);
if(-1==res)
perror("sigaction"),exit(-1);
pid_t pid=fork();
if(-1==pid)
perror("fork"),exit(-1);
if(0==pid){ //child开始, 发送1~100之间的数据发给parent
printf("child%dstarts
",getpid());
int i=1;
for(i=1;i<=100;i++){
union sigval value;
value.sival_int=i;
sigqueue(getppid(),40,value);//这里没有错误处理
}
exit(0);
}
while(1);
return 0;
}
sleep()
//让调用的线程睡seconds秒,除非这期间收到了不能忽略的信号,成功返回0,失败返回剩余没睡的时间
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
unsigned int second=sleep(5);
if(0==second)
printf("sleep well");
else
printf("sleep is interrupted, there are %ds left
",second);
pause()
//让调用的线程一直睡直到接收到了一个终止进程或者引发捕获信号函数的信号,当接受到这样的信号时,返回-1设errno
int pause(void);
void ding(int signo){
}
int main(){
(void) signal(SIGALRM, ding); //ding是一个函数
pause();
}
Q:pause 放在signal-catching很远的地方可以吗
A:pause就相当于sleep(∞), 跟捕捉语句没什么关系,谁说一定要组合用的
Q: 写了捕获语句, 是不是就使整个进程有了捕获某个信号的能力还是只是这一句???
A:当然不是, signal就像是全局变量, 使用sigaction()对某个信号进行重定向后, 此后的程序遇到这个信号就使用新的处理方式
signal()
//重新设置对信号的处理方式,将信号signum交给handler处理,成功返回之前的signal handler,失败返回-1设errno
#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);
handler
- SIG_IGN(Signal ignore)
- SIG_DFL(Signal default)
- User-defined fhandler with argument signum
#include<signal.h>
void fa(int signo){
printf("catch signal:%d
",signo);
if(SIG_ERR==signal(signo,SIG_DFL))
perror("signal"),exit(-1);
}
int main(){
if(SIG_ERR==signal(3,fa))
perror("signal"),exit(-1);
return 0;
}
信号集
信号的集合,当前系统所支持的信号范围:1~64(共62个),就是说每个进程可以使用信号1~64,为了对信号操作方便, 我们把一些信号放在一个集合中一起处理, 很多函数(包括sigpromask,sigpending)都是对信号集操作而不是对单个信号
sigset_t
Linux中表示信号集的数据类型, 就是一个超级大的整数(128byte, 32个unsigned long int的数组), 底层采用一个二进制位来代表一个信号, 根据该二进制位为0 or 1表示该信号是否存在
typedef struct{
unsigned long int __val[(1024 / (8 * sizeof (unsigned long int)))];
}__sigset_t;
typedef __sigset_t sigset_t;
在当前系统中, 按照%d打印的话就是打印信号集类型中的低4个字节的数据其实当前只需要8byte, 设计这么大是为了未来扩展
sigemptyset() /sigfillset()/sigaddset()/sigdelset()/sigismember()
//这些都是信号集操作函数
//sigemptyset()/sigfillset()/ sigaddset()/ sigdelset() 成功返回0,失败返回-1设errno
//如果signum是指定信号集的一员,sigismember()返回1,不是返回0,失败返回-1设errno。
#include <signal.h>
int sigemptyset(sigset_t *set); //清空信号集
int sigfillset(sigset_t *set); //填满信号集
int sigaddset(sigset_t *set, int signum); //添加信号到信号集
int sigdelset(sigset_t *set, int signum); //删除信号集的信号
int sigismember(const sigset_t *set, int signum); //判断信号是否属于信号集
sigprocmask()
//sigprocmask()只适用与单线程进程,多线程的参考pthread_sigmask()
//在某些特殊程序的执行过程中, 是不能被信号打断的, 此时需要信号的屏蔽技术来解决该问题
//获取并修改调用线程的屏蔽信号集
//成功返回0,失败返回-1设errno
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how
- SIG_BLOCK //新的屏蔽信号集是原有集合(不是oldset,oldset是用来存储的)和set的合集
- SIG_UNBLOCK //set内的信号被从屏蔽集合oldset中移除
- SIG_SETMASK //屏蔽信号集就是setThe set of blocked signals is set to the argument set.
oldset
- 如果oldset 不是NULL,则之前的信号集被存储在其中
- 如果set是NULL,屏蔽信号集不会变量,但现存的屏蔽信号集还是会被存储在oldset中
//准备信号集中有:2,3
sigset_t set,oldset;
if(-1==sigemptyset(&set))
perror("sigemptyset"),exit(-1);
if(-1==sigemptyset(&oldset))
perror("sigemptyset"),exit(-1);
if(-1==sigaddset(&set,2))
perror("sigaddset"),exit(-1);
//设置屏蔽的信号集
if(-1== sigprocmask(SIG_SETMASK,&set,&oldset))
perror("sigprocmask"),exit(-1);
- 信号屏蔽并不是删除信号, 而是将信号单独保存起来, 等信号的屏蔽解除之后, 信号还是会被处理的
- 可靠信号: 支持排队, mask期间有多少信号, mask解除之后就会处理多少
- 不可靠信号:不支持排队, mask期间有多个信号时, mask解除之后只处理第一个
sigpending()
//检查mask期间捕捉到但是没有处理的信号, 通过形参带出结果,成功返回0,失败返回-1设errno
#include <signal.h>
int sigpending(sigset_t *set);
sigaction()
//是前面的信号处理的集大成者且有很多扩展,是一个非常健壮的signal处理接口,建议使用,成功返回0,失败返回-1设errno
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
signum可以代表除了SIGSTOP和SIGKILL之外的任何一个信号
如果NULL!=act,act作为针对signal的新的action。
如果NULL!=oldact, 之前的action被存储在oldact。
act/oldact:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void); //obsolete, should not be used.
};
如果 sa_flags 被设为SA_SIGINFO,则sa_sigaction用来作signum的handler, 否则,用sa_handler。
sa_handler表明针对signum的handler,和signal()第二个参数类型一致, 都是用于设置signal信号的处理方式SIG_DFL,SIG_IGN,user-defined handler
//sa_sigaction
struct siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /*Trap number that caused hardware-generated signal
pid_t si_pid; /* Sending process ID */ //发送信号的进程号
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */ //伴随信号到来的附加数据
int si_int; /* POSIX.1b signal */
void* si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void* si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address(since kernel 2.6.32) */
}
sa_mask 表明在信号handler执行期间需要屏蔽的信号,此外,出发handler的信号默认是被屏蔽的,除非flag里指定的SA_NODEFE
sa_flags(Bitwise Or)
- SA_NOCLDSTOP //If signum is SIGCHLD, do not receive notification when child processes stop (i.e., when they receive one of SIGSTOP, SIGTSTP, SIGTTIN or SIGTTOU) or resume (i.e., they receive SIGCONT) (see wait(2)). This flag is only meaningful when establishing a handler for SIGCHLD.
- SA_NOCLDWAIT //If signum is SIGCHLD, do not transform children into zombies when they terminate. See also waitpid(2). This flag is only meaningful when establishing a handler for SIGCHLD, or when setting that signal's disposition to SIG_DFL.If the SA_NOCLDWAIT flag is set when establishing a handler for SIGCHLD, POSIX.1 leaves it unspecified whether a SIGCHLD signal is generated when a child process terminates. On Linux, a SIGCHLD signal is generated in this case; on some other implementations, it is not.
- SA_NODEFER //解除对触发信号处理函数相同信号的屏蔽
- SA_ONSTACK //Call the signal handler on an alternate signal stack provided by sigaltstack(2). If an alternate stack is not available, the default stack will be used. This flag is only meaningful when establishing a signal handler.
- SA_RESETHAND //一旦信号处理函数被调用之后, 信号的处理方式就会恢复为默认处理方式
- SA_RESTART //Provide behavior compatible with BSD signal semantics by making certain system calls restartable across signals.This flag is only meaningful when establishing a signal handler. See signal(7) for a discussion of system call restarting.
- SA_SIGINFO //使用第二个函数指针设置信号的处理方式
//sigaction.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
void fa(int signo){
printf("fa1...
");
sleep(3);
printf("fa2...
");
}
int main(){
printf("current pid:%d
",getpid());
//prepare struc var
struct sigaction action={};
action.sa_handler=fa;
sigemptyset(&action.sa_mask);
sigaddset(&action.sa_mask,3);
action.sa_flags=SA_NODEFER; //解除对信号2的屏蔽,sigaction(2....)
int res=sigaction(2,&action,NULL);
if(-1==res)
perror("sigaction"),exit(-1);
printf("set signal successfully
");
while(1);
return 0;
}
sigaction()那点事:
- 使用sigaction(),需要明确两个act,一个用来存储老act的oldact,一个用来安装新act的act,
- 这两个act都是struct sigaction
- 结构体有两个函数分别指明对sig的处理方式,具体使用哪个需要看flag是否被set了SA_SIGINFO,set了,就是sa_sigaction()处理信号,没set,就是sa_handler,前者比后者的功能更多,后者其实就是signal()
- 指明了用哪个函数处理了信号,我们还可以指定在signal handler执行的过程中哪些SIG被mask,注意这个是sigset_t类型,把要mask的信号放在一个信号集里,默认条件下the signal which triggered the handler是被mask的,除非在sa_flags中set了SA_NODEFER;
- 可以看到,整个act中flags主要起到了配置的作用,它决定使用哪个handler(SA_SIGINFO),也决定要不要屏蔽触发信号(SA_NODEFER);
- 除了这两个,flags还有其他选项
void fa(int signo,siginfo_t* info,void* pv){
printf("进程%d发来了信号%d
",info->si_pid,signo);
}
int main(){
struct sigaction action={};
action.sa_sigaction=fa;
action.sa_flags=SA_SIGINFO;
if(-1== sigaction(2,&action,NULL))
perror("sigaction"),exit(-1);
return 0;
}
父子进程信号
- 对于fork()创建的child, child完全照搬parent对信号的处理方式:
- parent忽略=>child忽略;
- parent默认=>child默认;
- arent自定义=>child自定义;
- 对于vfork(),execl()启动的child, 部分照搬parent的信号处理方式:
- parent忽略=>child忽略;
- parent默认=>child默认;
- parent自定义=>child默认=>因为execl()已经跳出的子进程, 而process.out里面没有fa,所以会按默认方式处理
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void fa(int signo){
printf("%d
",signo);
}
int main(){
if(SIG_ERR==signal(2,fa))
perror("signal"),exit(-1);
if(SIG_ERR==signal(3,SIG_IGN))
perror("signal"),exit(-1);
pid_t pid=vfork();
if(-1==pid)
perror("vfork"),exit(-1);
if(0==pid){
printf("child's pid=%d
",getpid());
int res=execl("process.out","process.out",NULL);
if(-1==res)
perror("execl"),exit(-1);
while(1);
}
return 0;
}