一、信号概述(signal)
对于Linux系统来说,实现信号的是一种软中断,就像我们单片机的中断一样,中断的触发需要信号。许多程序都需要处理信号,来做不同的事情。
1.信号的名字和编号
在Linux系统中,已经定义好了64个信号,每个信号都有一个名字和编号。信号名都以"SIG"为开头,信号定义在<signal.h>头文件中,信号的编号都定义为正整数。
具体的喜好名称及其编号可以使用"kill -l"指令来查看,也可以"kill - +信号编号"来执行信号,值得一提的是:信号编号是从1开始的,不存在0号的信号,"kill "对于信号0 是特殊的应用。
64个信号的名字及编号:
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.信号的处理
信号的处理方式一共有三种:忽略,捕捉和默认动作;
忽略一个信号时,使用SIG_IGN来传递给flag,但是"SIGKILL"和"SIGSTOP"是不能被忽略的。
例如:
signal(SIGKILL,SIG_IGN);//SIGKILL是不会被忽略掉的,所以还是会执行SIGKILL。
3.信号的相关API
信号的相关API分入门和高级两个版本:
入门版:
信号注册函数:int signal();
信号发送函数:int kill ();
高级版:
信号注册函数:int sigaction();
信号发送函数:int sigqueue ();
两个版本的区别就是,高级发送的信号能够携带消息,而入门版只是能发送信号让进程去处理简单事情,无法携带消息。简单点就是,高级版就像光纤,能带有数据,而入门版就是敲门砖,只是提醒你去开门而已。
二、信号API的详细介绍和使用
1.入门版
(1)signal();
头文件: #inlcude <signal.h>
函数原型: int signal(int signum,sighandler_t handler);
参数说明:
1. signum 信号的名称,例如SIGKILL;
2. handler 信号的处理方式或者要去执行的函数。
返回值: 成功返回0 ,失败返回 -1。
(2)kill();
头文件: #include <signal.h>、#include <sys/types.h>;
函数原型: int kill(pid_t pid , int sig);
参数说明:
1. pid 发送给哪个进程,pid就是那个进程的ID;
2. sig 需要发送的信号。
返回值: 成功返回0 ,失败返回 -1。
(3)小实验
编写两个程序,一个负责读取信号,暂且命名为:SIG_demo1.c;另一个负责发送信号,命名为SIG_demo2.c。
SIG_demo1.c代码:
#include <signal.h>
#include <stdio.h>
// typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
printf("get signum:%d\n",signum);
switch(signum){
case 2:printf("SIGINT\n");//对应Crtl+C
break;
case 9:printf("SIGKILL\n");//对应 SIGKILL 可以结束进程
break;
case 10:printf("SIGUSR1\n");
break;
}
}
int main()
{
signal(SIGINT,SIG_IGN);//忽略Crtl+C
signal(SIGKILL,handler);
signal(SIGUSR1,handler);
while(1);
return 0;
}
这个程序的功能是,运行时,会卡住,摁Crtl+C 无法退出,直到摁Crtl+Z或者另一个进程向这个进程发送SIGKILL信号才会退出。
SIG_demo2.c 代码:
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
// typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
int main(int argc,char **argv)
{
int signum;
int pid;
char cmd[128];
signum=atoi(argv[1]);//把字符转换成ASCLL码
pid =atoi(argv[2]); //把字符转换成ASCLL码
printf("num=%d,pid=%d\n",signum,pid);
// sprintf(cmd,"kill %d %d",signum,pid);
kill(pid,signum);//直接发送
// system(cmd);
printf("Send signal OK!\n");
return 0;
}
切记运行SIG_demo1.c后,要查找它的进程ID,这里我们把SIG_demo1.c编译为了pro,所以 ps -aux|grep pro 精确查找进程。
有个R+为后缀的,就是pro的进程ID:9427
两个程序配合的运行结果:
图中,我们可以看到,在 ./pro 执行之后,摁Crtl +C 只会显示 ^C ,运行 ./send之后,左边的打印Terminated 然后结束程序。这个小实验就算是成功了。
2.高级版
(1)sigaction ();
头文件: #include <signal.h>;
函数原型: int sigaction(int signum, const struct sigaction * act, struct sigaction * oldact);
参数说明:
1. signum 信号名,也就是我们要发送的信号;
2. *act 这是信号的结构体,具体的元素,实验时我们再细说;
3. *oldact 这个结构体是负责备份第二个结构体的信息,NULL 为不备份;
返回值: 成功返回0 ,失败返回 -1。
(2)sigqueue ();
头文件: #include <signal.h>;
函数原型: int sigqueue(pid_t pid, int sig, const union sigval value);
参数说明:
1. pid 发送给哪个进程,pid就是那个进程的ID;
2. sig 需要发送的信号;
3. value 为需要发送的联合体(共同体),需要发送的数据都放在这;
返回值: 成功返回0 ,失败返回 -1。
(3)小实验
编写两个程序,一个负责读取信号,暂且命名为:SIG_demo3.c;另一个负责发送信号,命名为SIG_demo4.c。
先说一下,在编写读取信号之前,我们需要了解一下sigaction 的第二个参数 *act结构体:
act 结构体里面我们所需要关注的只有 *sa_sigaction() 和 flag 标志位;sa_sigaction() 的原型为:void *sa_sigaction(int signum,sigginfo_t *info,void *netxt ); 而其中的 siginfo_t *info结构体中,有大量成员:
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; /* 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) */
}
而我们只要关注:pid、int si_int和sigval_t si_value结构体。
SIG_demo3.c代码:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
//信号处理函数
void Handler(int signum,siginfo_t *siginfo,void *context)
{
printf("get signu=%d\n",signum);
if(context!=NULL){
printf("siginfo data=%d\n",siginfo->si_int); //读取信号的数据
printf("siginfo value=%d\n",siginfo->si_value.sival_int);//读取信号的数据,和si_int是一样的
printf("signfo getpid=%d\n",siginfo->si_pid);//读取发送信号的进程的pid
}
}
int main()
{
struct sigaction *act;//定义act结构体指针
act=(struct sigaction *)malloc(sizeof(struct sigaction));//分配控制
act->sa_sigaction=Handler;//定义sa_sigaction信号处理函数
act->sa_flags=SA_SIGINFO;//定义赋值flags为SA_SIGINFO
sigaction(SIGUSR1,act,NULL);//开始获取信号
while(1);
return 0;
}
SIG_demo4.c代码:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
pid_t pid;
int signum;
union sigval val;
val.sival_int=102;
signum=atoi(argv[1]);
pid=atoi(argv[2]);
int Send_s=sigqueue(pid,signum,val);//开始发送102
if(Send_s==-1){
printf("Send failer!\n");
exit(-1);
}
printf("Send success!\n");
return 0;
}
运行结果;
把SIG_demo3.c编译为pro,SIG_demo4.c编译为send,pro的进程ID为:10697.
开始给pro进程发送信号,
send 发送的信号编号为10 进程pro 打印出了信号的编号,和信号所携带的信息102 和发送者的pid。