对于 Linux来说,实际信号是软中断,类似单片机中的硬中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。
信号概述
1.信号的名字和编号:
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。
具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0有特殊的应用。
2.信号的处理:
信号的处理有三种方法,分别是:忽略、捕捉和默认动作
1).忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景。
2).捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
3).系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。
具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。在此,我就不详细展开了,需要查看的,可以自行查看。也可以参考 《UNIX 环境高级编程(第三部)》的 P251——P256中间对于每个信号有详细的说明。
Linux信号忽略宏:SIG_IGN
了解了信号的概述,那么,信号是如何来使用呢?
其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 while1进程,通过 ps -aux 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束 该进程。如果查看信号编号和名称,可以发现9对应的是 9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。
对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通讯的手段,那么如何来自定义信号的处理函数呢?
信号处理函数的注册
信号处理函数的注册不只一种方法,分为入门版和高级版
1.入门版:函数signal
2.高级版:函数sigaction
信号处理发送函数
信号发送函数也不止一个,同样分为入门版和高级版
1.入门版:函数kill
2.高级版:函数sigqueue
==========================================================================
信号注册函数——入门版(注册的信号处理函数不能接收消息)
signal 的函数原型
#include <signal.h> typedef void (*sighandler_t)(int);//定义一种数据类型sighandler_t sighandler_t signal(int signum, sighandler_t handler);
参数说明:
int :信号的编号
signum:注册的信号的编号
handler:中断函数的指针,即中断函数名
信号发送函数——入门版(发送信号的同时不能携带消息)
kill 的函数原型
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);
参数说明:
pid :接收者的 pid
sig :发送的信号的类型
代码案例1
//demo23.c #include <stdio.h> #include <signal.h> void handler(int signum) { printf("signum = %d\n",signum); switch(signum) { case 2: printf("this is SIGINT\n"); break; case 9: printf("this is SIGKILL\n"); break; case 10: printf("this is SIGUSR1\n"); break; } printf("never quit\n"); } int main() { signal(SIGINT,handler);//对应终端按下Ctrl+c signal(SIGKILL,handler); signal(SIGUSR1,handler); while(1); return 0; }
通过kill命令发送信号
通过编程发送信号
//demo24.C #include <stdio.h> #include <signal.h> #include <sys/types.h> #include <stdlib.h> int main(int agrc,char** argv) { char cmd[128]={0}; int signum = atoi(argv[1]); int pid = atoi(argv[2]); //kill(pid,signum); 或者下列 sprintf(cmd,"kill -%d %d",signum,pid); system(cmd); return 0; }
代码案例2
//demo23.c #include <stdio.h> #include <signal.h> void handler(int signum) { printf("signum = %d\n",signum); switch(signum) { case 2: printf("this is SIGINT\n"); break; case 9: printf("this is SIGKILL\n"); break; case 10: printf("this is SIGUSR1\n"); break; } printf("never quit\n"); } int main() { signal(SIGINT,SIG_IGN);//对应终端按下Ctrl+c signal(SIGKILL,SIG_IGN); signal(SIGUSR1,handler); while(1); return 0; }
通过以上案例可知,SIGKILL不可以被捕捉和忽略
==========================================================================
信号注册函数——高级版(注册的信号处理函数容许接收消息)
sigaction 的函数原型
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); struct sigaction { void (*sa_handler)(int); //信号处理程序,不能接受额外数据 void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用 sigset_t sa_mask;//默认阻塞 int sa_flags;//写SA_SIGINFO表示能够接受数据 };
参数说明:
sigaction
signum:注册的信号的编号
act:可以理解为signal中的handler+消息
oldact:对之前的信号配置进行备份,以方便之后进行恢复,写NULL忽略
重点看sa_sigaction()的参数
int:同signum,信号的编号
siginfo_t *:记录接收信号携带的一些消息
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 (unused on most architectures) */ pid_t si_pid; /* 发送端的进程pid*/ 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; /* 发送端发送的消息*/ int si_int; /* 同si_value.sival_int */ 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 */ int si_band; /* Band event */ int si_fd; /* File descriptor */ }
void *:NULL代表没有接收到消息,非空代表接收到消息
信号发送函数——高级版(发送信号的同时可以携带消息)
#include <signal.h> int sigqueue(pid_t pid, int sig, const union sigval value); union sigval { int sival_int; void *sival_ptr; };
参数说明:
同kill,多出的value为传递的消息
代码案例3
//demo25.c #include <stdio.h> #include <signal.h> #include <sys/types.h> #include <unistd.h> void handler(int signum,siginfo_t *info,void *context) { printf("signum = %d\n",signum); if(context) { printf("get data = %d\n",info->si_int); printf("get data = %d\n",info->si_value.sival_int); printf("from sendpid = %d\n",info->si_pid); } } int main() { struct sigaction act = { .sa_sigaction = handler , .sa_flags = SA_SIGINFO }; printf("this is pid = %d\n",getpid()); sigaction(SIGUSR1,&act,NULL); while(1); return 0; }
//demo26.c #include <stdio.h> #include <signal.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> int main(int agrc,char** argv) { int signum = atoi(argv[1]); int pid = atoi(argv[2]); union sigval value = {.sival_int = 100}; sigqueue(pid,signum,value); printf("sendpid = %d\n",getpid()); return 0; }