一、函数结构
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
参数:
- 参数1:要捕获的信号
- 参数2:接收到信号之后对信号进行处理的结构体
- 参数3:接收到信号之后,保存原来对此信号处理的各种方式与信号(可用来做备份)。如果不需要备份,此处可以填NULL
返回值:
- 成功时:返回0
- 出错时:返回-1,并将errno设置为指示错误
与signal的不同,有哪些新功能?
- signal只能捕获信号,对信号进行处理。但是不能获取信号的其它信息
- sigaction可以使用sigaction结构体的sa_handler函数对信号进行处理(此处等同于signal函数),也可以使用sa_sigaction函数查看信号的各种详细信息
- 并且sigaction函数还可以通过sa_mask、sa_flags对信号处理时进行很多其他操作
二、struct sigaction结构体
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
sa_handler成员:
- 对捕获的信号进行处理的函数,函数参数为sigaction函数的参数1信号(概念上等同于单独使用signal函数)
- 也可以设置为后面两个常量:常数SIG_IGN(向内核表示忽略此信号)或是常数SIG_DFL(表示接到此信号后的动作是系统默认动作)
sa_mask成员:
- 功能:sa_mask是一个信号集,当接收到某个信号,并且调用sa_handler函数对信号处理之前,把该信号集里面的信号加入到进程的信号屏蔽字当中,当sa_handler函数执行完之后,这个信号集中的信号又会从进程的信号屏蔽字中移除
- 为什么这样设计??这样保证了当正在处理一个信号时,如果此种信号再次发生,信号就会阻塞。如果阻塞期间产生了多个同种类型的信号,那么当sa_handler处理完之后。进程又只接受一个这种信号
- 即使没有信号需要屏蔽,也要初始化这个成员(sigemptyset()),不能保证sa_mask=0会做同样的事情
- sigset_t数据类型见文章:https://blog.csdn.net/qq_41453285/article/details/89228297
sa_restorer成员:
- 已经被抛弃了,不再使用
sa_flags成员:
- 指定了对信号进行哪些特殊的处理
SA_INTERRUPT 由此信号中断的系统调用不自动重启动
SA_NOCLDSTOP 若signo是SIGCHLD:
当一子进程停止(暂停时)时(作业控制), 不产生此信号
当一子进程终止时,仍旧产生此信号
若已设置此标志,则当停止的进程继续运行时,作为XSI扩展,不产生SIGCHLD信号
(参照的下面的SA_NOCLDWAIT选项)
SA_NOCLDWAIT 若signo是SIGCHLD,则当调用进程的子进程终止时, 不创建僵死进程
若调用进程在后面调用wait,则阻塞到它所有子进程都终止,此时返回-1,errno设置为ECHILD
SA_NODEFER 当捕捉到此信号时,在执行其信号捕捉函数时,系统不自动阻塞此信号(除非sa_mask成员包括了此信号)
注意,此种类型的操作对应于早期的不可靠信号
SA_NOMASK 同SA_NODEFER SA_ONSTACK 若用sigaltstack已声明了一替换栈,则此信号递送给替换栈上的进程 SA_STACK 同SA_ONSTACK SA_RESETHAND 在此信号捕捉函数的入口处,将此信号的处理方式重置为SIG_DFL,并清除SA_SIGINFO标志
注意,此种类型的信号对应于早期的不可靠信号。但是不能重置SIGILL和SIGTRAP信号的配置
设置此标志使sigaction的行为如同设置了SA_NODEFER标志
SA_ONESHOT 同SA_RESETHAND SA_RESTART 由此信号中断的系统调用自动重启动 SA_SIGINFO 此选项对信号处理程序提供了附加信息:一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针
- 关于SA_INTERRUPT、SA_RESTART说明:
- 某些早期系统(如SunOS)定义了SA_INTERRUPT标志,这些系统的默认方式是重新启动被中断的系统调用,而制定此标志则使系统调用被中断后不再重新启动。Linux定义SA_INTERRUPT标志,以便与使用该标志的应用程序兼容。但是,如若信号处理程序是用sigaction设置的,那么其默认方式是不重新启动系统调用
- Single UNIX Specifiaction的XSI扩展规定,除非说明了SA_RESTART标志,否则sigaction函数不再重启动被中断的系统调用
- 关于系统调用中断请参阅:https://blog.csdn.net/qq_41453285/article/details/89216990
sa_sigaction成员:
- 当sa_flags成员是SA_SIGINFO标志时,就调用此函数,可以来获取该信号的很多详细信息(而不是用来对信号进行处理)
- 参数1:参数为sigaction函数的参数1信号
- 参数2:一个结构体包,含了信号产生的各种详细信息
- si_signo:信号的值
- si_code:信号代码
- si_pid:信号来自于哪一个进程(哪个进程发送来的)
- si_uid:信号来自于哪个用户(发送来的进程的用户)
- si_value(重点):传递的信息。应用程序在传递信号时,可以在si_value.sival_int传递一个整型或者si_value.sival_ptr传递一块内存(下面会有一个案例)
- 若信号时SIGCHLD:则将设置si_pid、si_status、si_uid
- 若信号时SIGBUS、SIGILL、SIGFPE、SIGSEGV:则si_addr包含造成故障的根源地址(该地址可能并不正确)
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 inglibc 2.3.2 and earlier) */ int si_fd; /* File descriptor */ short si_addr_lsb; /* Least significant bit of address(since Linux 2.6.32) */ }
- 参数3:此参数为void*类型,可以自己传入一些参数进去,但是一般强转为ucontext_t结构体类型使用,该结构体标识信号传递时进程的上下文。下面是ucontext_t结构体内的一些部分成员
ucontext_t *uc_link; /* pointer to context resumed when this context returns */ sigset_t uc_sigmask; /* signals blocked when this context is active */ stack_t uc_stack; /* stack used by this context */ mcontext_t uc_mcontext; /* machine-specific representation of saved context */ //uc_stack字段描述了当前上下面使用的栈,至少包括下列成员 void *ss_sp; /* stack base or pointer */ size_t ss_size; /* stack size */ int ss_flags; /* flags */
- sa_handler和sa_sigaction函数一次只能使用其中一个
- 如果只是想处理信号,但是不获取信号信息,就使用sa_handler函数
- 如果想要处理信号,并且获取信号的各种信息,就使用sa_sigaction函数
三、演示案例
- 案例一:处理SIGQUIT信号
#include<stdio.h> #include<stdlib.h> #include<signal.h> void myHandler(int sig); int main(int argc,char *argv[]) { struct sigaction act, oact; act.sa_handler = myHandler; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGQUIT, &act, &oact); while (1) { printf("running...\n"); pause(); } } void myHandler(int sig) { printf("I got signal: %d.\n", sig); }
- 按下ctrl+\之后,就会调用myHandler函数
- 案例二:接到SIGUSR1信号时,获取其信息
#include<stdio.h> #include<stdlib.h> #include<signal.h> #include<unistd.h> void func(int signo, siginfo_t *info, void *p) { printf("signo=%d\n",signo); printf("sender sigal pid=%d\n",info->si_pid); } int main(int argc,char *argv[]) { struct sigaction act, oact; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; //设置此项 act.sa_sigaction=func; sigaction(SIGUSR1, &act, &oact); while (1) { printf("My pid=%d\n",getpid()); pause(); } }
- 我们按下ctrl+c之后,就会显示信号值和发送信号的进程
四、自定义signal函数
- 利用sigaction函数的特性,定义自己的signal函数,使用起来与系统signal函数一样,但是功能比系统的signal函数更多
重启动版本
- 系统的signal函数原型太复杂,如下所示
void (*signal(int signo, void (*func)(int)))(int);
- 为了简化起见,我们定义了自己的Sigfunc类型,因此调用函数时只需要传入一个信号值即可
typedef void Sigfunc(int); Sigfunc *signal(int signo, Sigfunc *func); Sigfunc *signal(int signo, Sigfunc *func) { struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (signo == SIGALRM){ #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; #denif } if (sigaction(signo, &act, &oact) < 0) return(SIG_ERR); return(oact.sa_handler); }
- 函数的使用:
- 参数传入一个信号和一个信号处理函数指针
- 返回值:在函数内部调用sigaction函数,并将相应信号的旧行为作为signal函数的返回值
- 几点说明:
- 要用sigemptyset函数初始化act结构的sa_mask成员,不能简单的设置act.sa_mask=0
- 如果信号是SIGALEM,我们就将sa_flags设置为SA_INTERRUPT,不希望重启动由SIGALRM信号中断的系统调用,原因是:我们希望为IO操作设置超时(可以参考)
- 如果是其他信号,sa_flags标志就加入SA_RESTART,希望重启被中断的系统调用
不重新启动版本
- 如果想要系统调用被中断后不重新启动,则可以使用这一版本
typedef void Sigfunc(int); Sigfunc *signal_intr(int signo, Sigfunc *func); Sigfunc *signal_intr(int signo, Sigfunc *func) { struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif if (sigaction(signo, &act, &oact) < 0) return(SIG_ERR); return(oact.sa_handler); }
- 如果系统定义了SA_INTERRUPT,那么为了提高可移植性。我们在sa_flags中增加该标志,这样就可以组织被中断的系统调用的重启动
五、siginfo_t枚举的si_value成员验证
- siginfo_t的应用:有时我们接收到一个信号时,希望接受一些其他数据,就可以设置这个成员
- 例如:我们有一个用链表实现一个简单的服务器,并且想要在规定的一个时间间隔发送一些数据。就可以定一个定时器,并且在接收到SIGALRM信号时,因为操作数据需要用到链表,将链表地址传递给union sigval的sival_ptr指针。这样当捕获SIGALRM信号时,就可以调用sa_action函数,然后通过参数2的sival_ptr获取这个指针
演示案例:
- 我们用一个进程给另外一个进程发送SIGINT信号,并且同时将一个整型100发送出去
//发送进程:向一个进程发送SIGINT信号,并且将整型100也传进去 #include<stdio.h> #include<signal.h> #include<unistd.h> #include<string.h> int main(int argc,char *argv[]) { if(argc!=2) { printf("arguments error!"); exit(0); } pid_t pid=atoi(argv[1]);//将进程号转化为整数 union sigval v; v.sival_int=100; //发送整型100 sigqueue(pid,SIGINT,v); return 0; }
//此进程等待接受信号的传入,并有SIGINT的信号处理函数 #include<stdio.h> #include<unistd.h> #include<signal.h> #include<string.h> void handler(int,siginfo_t *,void *); int main(void) { struct sigaction act; act.sa_sigaction=handler; sigemptyset(&act.sa_mask); act.sa_flags=SA_SIGINFO; if(sigaction(SIGINT,&act,NULL)<0) { printf("error"); exit(0); } for(;;) pause(); return 0; } void handler(int sig,siginfo_t * info,void *ctx) { //打印信息 printf("recv a sid=%d data=%d data=%d\n",sig,info->si_value.sival_int,info->si_int); }
- 演示结果: