信号是传递给进程的一种事件通知,也称作软中断。比如在终端按下ctrl+c可以结束进程,实际上就是给进程发送了一个SIGINT信号;程序发生错误的时候,比如除数为0,非法访问内存的时候,内核也会向发生异常的那个进程传递错误信号。
有关信号的术语:
- 当发生了一个需要引起进程注意的时间而导致信号出现,我们称对此进程发送信号
- 当被发送的那个进程识别了信号并且采取了适当动作时,我们称信号交付给了进程,或者成进程接受了信号。
- 信号交付给进程后,进程可以通过一个函数捕获该信号并进行下一步操作。这个捕获函数就称作信号句柄
- 当一个信号已经生成,但还未交付,我们称这个信号是悬挂的。
- 每个进程都有一个信号屏蔽字标识当前被阻塞 交付的信号集合。如果某个信号对应的位被设置,则该型号是被阻塞的
如何发送信号?
raise函数:
#include <signal.h>
int raise(int sig);
给本进程发送名为sig的信号。如果安装了sig句柄函数,则raise()只会在句柄函数返回时才会返回
kill函数:
#include <signal.h>
int kill(pid_t pid, int sig);
给pid号进程发送名为sig的信号,如果进程不存在,则返回-1。因此kill函数经常用来检测进程是否存在
如何接受信号?
signal函数
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t handler);
sig指明是哪一种信号;handler可以是函数指针,也可以是如下的宏:
- SIG_DFL:采取默认动作
- SIG_IGN:忽略该信号
sigaction函数
signal函数缺乏可靠性,且功能局限。一般采用sigaction函数。
#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
act和oact都是指向sigaction结构体的指针。act规定发送信号的动作;oact可以为NULL,如果不为NULL,则可用用来接收之前的动作对象。
sigaction是描述信号动作的一个结构,定义为:
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t*, void*);
sigset_t sa_mask;
int sa_flags;
};
- sa_handler与signal()的第二个参数相同,指定一个信号句柄,其值也可以是SIG_DFL、SIG_IGN
- sa_sigaction也是一个信号句柄,是一个更高级的信号句柄,只用当sa_flags 设为SA_SIGINFO时起作用。
示例代码
//信号的中断测试
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void Handler(int sig, siginfo_t *info, void *context)
{
if (sig == SIGALRM)
{
for (int i = 0; i < 10; i++)
{
printf("CallBack is running...\n");
sleep(1);
}
}
}
int main()
{
struct sigaction sa;
sa.sa_sigaction = Handler;
sa.sa_flags = SA_RESTART | SA_SIGINFO;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) == -1) //设定闹钟信号
{
printf("error\n");
return -1;
}
alarm(2); //两秒后将发送闹钟信号
for (int i = 0; i < 10; i++)
{
printf("main is running...\n");
sleep(1);
}
return 0;
}
运行结果:
可以看出,进程接收信号后会立即中断当前操作,转而执行信号句柄。
设置信号屏蔽
每一个进程都有一个信号屏蔽,进程在创建是继承父进程的信号屏蔽。
sigset_t类型及其操作
sigset_t称为信号集类型,是多个型号的集合。它只能用一下几个函数操纵:
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()
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
how的值指明如何改变信号屏蔽,它必须是下列的值之一:
- SIG_BLOCK:阻塞set集中的信号
- SIG_UNBLOCK:放开set集中的信号
- SIG_SETMASK:用set所指的信号集进程的新信号集屏蔽
oset作为输出参数,可以为NULL,输出之前该进程屏蔽的信号集。
示例代码
//信号屏蔽测试
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void SignalHandler(int sig)
{
if (sig == SIGUSR1)
{
printf("signal1 is clicked\n");
}
else if (sig == SIGUSR2)
{
printf("signal2 is clicked\n");
}
}
int main()
{
struct sigaction sa;
sa.sa_handler = SignalHandler;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGUSR1, &sa, NULL) == -1){
printf("error\n");
return -1;
}
if (sigaction(SIGUSR2, &sa, NULL) == -1){
printf("error\n");
return -1;
}
/* 这几行代码回导致进程屏蔽SIGINT和SIGQUIT从而无法退出
sigset_t mask_quit;
sigemptyset(&mask_quit);
sigaddset(&mask_quit, SIGINT);
sigaddset(&mask_quit, SIGQUIT);
if (sigprocmask(SIG_BLOCK, &mask_quit, NULL) < 0)
{
printf("mask quit error\n");
return -1;
}
*/
sigset_t st;
sigemptyset(&st);
sigaddset(&st, SIGUSR2);
if (sigprocmask(SIG_BLOCK, &st, NULL) < 0)
{
printf("mask sig2 error\n");
return -1;
}
printf("raise SIGUSR1\n");
raise(SIGUSR1);
printf("raise SIGUSR2\n");
raise(SIGUSR2);
while (1);
return 0;
}