信号概念
每个信号都有一个名字,以 SIG 开头,例如 SIGABRT,SIGALRM 等。
信号产生:
- 当用户按某些终端键时,引发终端产生信号。如 Ctrl+C 通常产生中断信号(SIGINT)。
- 硬件异常产生信号:除数为 0、无效的内存引用等。
- 进程调用 kill(2) 函数可将任意信号发送给另一个进程或进程组。
- 用户调用 kill(1) 命令将信号发送给其他进程。
- 当检测到某种软件条件已经发生,并应将其通知有关进程时也产生信号。
信号处理:
- 忽略此信号。SIGKILL 和 SIGSTOP 不能被忽略,它们向内核和超级用户提供了使进程终止或停止的方法。另外,如果忽略某种由硬件异常产生的信号(如非法内存引用或除以0),则进程的运行行为是未定义的。
- 捕捉信号。通知内核在某种信号发生时,调用一个用户函数,在用户函数中可以执行用户系统对这种事件进行的处理。注意不能捕捉 SIGKILL 和 SIGSTOP 信息。
- 执行系统默认动作。注意,对大多数信号的系统默认动作是终止该进程。
函数 signal
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
- signo:信号名
- func:常量 SIG_IGN、常量 SIG_DFL 或接到此信号后要调用的函数的地址。
- SIG_IGN 忽略此信号
- SIG_DFL 系统默认动作
- 指定函数地址,则在信号发生时,调用该函数,此函数为信号处理程序(signal handler)或信号捕捉函数(signal-catching function)
signal 函数原型说此函数要求两个参数,返回一个函数指针。
实例1
#include <signal.h>
#include <printf.h>
#include <unistd.h>
static void sig_usr(int);
int main(void) {
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
printf("can't catch SIGUSR1");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
printf("can't catch SIGUSR2");
for (;;)
pause();
}
static void sig_usr(int signo) {
switch (signo) {
case SIGUSR1:
printf("received SIGUSR1\n");
break;
case SIGUSR2:
printf("received SIGUSR2\n");
break;
default:
printf("received signal %d\n", signo);
}
}
可重入函数
所谓可重入是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。
一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。
保证函数的可重入性的方法:
- 在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量);
- 对于要使用的全局变量要加以保护(如采取关中断、信号量等互斥方法),这样构成的函数就一定是一个可重入的函数。
满足下列条件的函数多数是不可重入(不安全)的:
- 函数体内使用了静态的数据结构;
- 函数体内调用了malloc() 或者 free() 函数;
- 函数体内调用了标准 I/O 函数。
信号集
表示多个信号——信号集(signal set)的数据类型。
一个进程有一个信号集,这个信号集表示当前屏蔽(阻塞)了哪些信号。
如果把信号集中的某个位置置1,表示屏蔽了该信号,如果再来此信号,该进程是收不到的。
函数 sigprocmask
sigprocmask主要是设置该进程对应的信号集中的内容:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数how:
| how | 说明 |
|---|---|
| SIG_BLOCK | SIG_BLOCK表明设置当前进程新的信号屏蔽字为 “当前信号屏蔽字” 和 第二个参数指向的信号集的并集,set包含了我们希望阻塞的附加信号 |
| SIG_UNBLOCK | 该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集补集的交集,set包含了我希望解除阻塞的信号 |
| SIG_SETMASK | 该进程新的信号屏蔽字将被set指向的信号集的值代替 |
实例
#include <stdio.h>
#include <stdlib.h> //malloc
#include <unistd.h>
#include <signal.h>
#include <errno.h>
//信号处理函数
void sig_quit(int signo) {
printf("收到了SIGQUIT信号!\n");
if (signal(SIGQUIT, SIG_DFL) == SIG_ERR) {
printf("无法为SIGQUIT信号设置缺省处理(终止进程)!\n");
exit(1);
}
}
int main(int argc, char *const *argv) {
sigset_t newmask, oldmask;
if (signal(SIGQUIT, sig_quit) == SIG_ERR) { //注册信号对应的信号处理函数,"ctrl+"
printf("无法捕捉SIGQUIT信号!\n");
exit(1); //退出程序,参数是错误代码,0表示正常退出,非0表示错误,但具体什么错误,没有特别规定
}
sigemptyset(&newmask); //newmask信号集中所有信号都清0(表示这些信号都没有来);
sigaddset(&newmask, SIGQUIT); //设置newmask信号集中的SIGQUIT信号位为1,也就是说,再来SIGQUIT信号时,进程就收不到,设置为1就是该信号被阻塞掉
//sigprocmask():设置该进程所对应的信号集
//第一个参数用了SIG_BLOCK表明设置进程新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
//一个 ”进程“ 的当前信号屏蔽字,刚开始全部都是0的;所以相当于把当前 "进程"的信号屏蔽字设置成 newmask(屏蔽了SIGQUIT); //第三个参数不为空,则进程老的(调用本sigprocmask()之前的)信号集会保存到第三个参数里,用于后续,这样后续可以恢复老的信号集给线程 printf("sigprocmask(SIG_BLOCK)失败!\n");
exit(1);
}
printf("我要开始休息10秒了--------begin--,此时我无法接收SIGQUIT信号!\n");
sleep(10); //这个期间无法收到SIGQUIT信号的;
printf("我已经休息了10秒了--------end----!\n");
if (sigismember(&newmask, SIGQUIT)) { //测试一个指定的信号位是否被置位(为1),测试的是newmask
printf("SIGQUIT信号被屏蔽了!\n");
} else {
printf("SIGQUIT信号没有被屏蔽!!!!!!\n");
}
if (sigismember(&newmask, SIGHUP)) { //测试另外一个指定的信号位是否被置位,测试的是newmask
printf("SIGHUP信号被屏蔽了!\n");
} else {
printf("SIGHUP信号没有被屏蔽!!!!!!\n");
}
//把信号集还原回去
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) { //第一个参数用了SIGSETMASK表明设置进程新的信号屏蔽字为第二个参数指向的信号集,第三个参数没用
printf("sigprocmask(SIG_SETMASK)失败!\n");
exit(1);
}
printf("sigprocmask(SIG_SETMASK)成功!\n");
if (sigismember(&oldmask, SIGQUIT)) { //测试一个指定的信号位是否被置位,这里测试的当然是oldmask
printf("SIGQUIT信号被屏蔽了!\n");
} else {
printf("SIGQUIT信号没有被屏蔽,您可以发送SIGQUIT信号了,我要sleep(10)秒钟!!!!!!\n");
int mysl = sleep(10);
if (mysl > 0) {
printf("sleep还没睡够,剩余%d秒\n", mysl);
}
}
printf("end main!\n");
return 0;
}
函数 sigaction
sigaction函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作),函数原型:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
- signum参数指出要捕获的信号类型
- act参数指定新的信号处理方式
- oldact参数输出先前信号的处理方式(如果不为NULL的话)
struct sigaction {
union {
sighandler_t sa_handler;
void (*sa_sigaction)(int, struct siginfo*, void*);
};
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
- sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
- sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
- sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。
sa_sigaction 字段是一个替代的信号处理程序,如果设置了 SA_SIGINFO 标志,使用该信号处理程序。sa_sigaction 字段和 sa_handler 字段可能使用同一存储区,所以只能使用这两个字段中的一个。
void (*sa_sigaction)(int signo, struct siginfo *info, void *context);
siginfo 结构包含了信息产生原因的有关信息。
应用程序在递送信号时,在 si_value.sival_int 中传递一个整型数或者在 si_value.sival_ptr 中传递一个指针值。
若信号是 SIGCHLD, 则将设置 si_pid、si_status 和 si_uid 字段。
若信号时 SIGBUS、SIGILL、SIGFPE 或 SIGSEGV, 则 si_addr 包含造成故障的根源地址,该地址可能并不准确。
信号处理程序的 context 参数是无类型指针,它可被强制类型转换为 ucontext_t 结构类型,该结构标识信号传递时进程的上下文。
int register_signal(void (*handler)(int, siginfo_t *, void *)) {
stack_t ss;
if (NULL == (ss.ss_sp = calloc(1, SIGNAL_CRASH_STACK_SIZE))) return XCC_ERRNO_NOMEM;
ss.ss_size = SIGNAL_CRASH_STACK_SIZE;
ss.ss_flags = 0;
if (0 != sigaltstack(&ss, NULL)) return XCC_ERRNO_SYS;
struct sigaction act;
memset(&act, 0, sizeof(act));
sigfillset(&act.sa_mask);
act.sa_sigaction = handler;
act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
size_t i;
for (i = 0; i < sizeof(signal_crash_infos) / sizeof(signal_crash_infos[0]); i++) {
if (0 !=
sigaction(signal_crash_infos[i].signum, &act, &(signal_crash_infos[i].oldact)))
return XCC_ERRNO_SYS;
}
return 0;
}
函数 sigsetjmp 和 siglongjmp
c 语言中 setjmp 和 longjmp
Exceptions in C with Longjmp and Setjmp
当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动地加到进程的信号屏蔽字中。这阻止了后来产生的这种信号中断该信号处理程序。
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask);
返回值:若直接调用则返回0,若从siglongjmp调用返回则返回非0值
void siglongjmp(sigjmp_buf env, int val);
这两个函数与setjmp和longjmp之间的唯一区别是sigsetjmp增加了一个参数。如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。调用siglongjmp时,如果带 非0 savemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。