signal函数详解

signal作用是为信号注册一个处理器。这里的“信号”是软中断信号,这种信号来源主要有三种:

  1. 程序错误:比如除0,非法内存访问。
  2. 外部信号:终端Ctrl-C产生的SIGINT信号,定时器产生的SIGALERM。
  3. 显示请求:kill函数发送的任意信号。

当kill一个进程的时候,默认会发送SIGTERM信号,此时这个信号只有默认处理操作(SIG_DFL),直接中断进程执行。如果此时该进程正在执行一个任务,直接终止该进程会导致任务没有完成。这个时候为SIGTERM信号注册一个信号处理函数就十分有必要。

介绍

typedef void (*sighandler_t) (int);
sighandler_t signal (int sig, sighandler_t handler)

参数

sig要设置信号处理函数的信号。它可以是实现定义值或下例值之一:
SIGABRT
SIGFPE
SIGILL
SIGINT
SIGSEGV
SIGTERM
定义信号类型
(宏常量)
handler信号处理函数。这必须是下列之一:SIG_DFL 宏。信号处理函数被设为默认信号处理函数。SIG_IGN 宏。忽略信号。指向函数指针。函数签名必须等价于如下:extern “C” void fun(int sig);

返回值

成功时为先前的信号处理函数,失败时为 SIG_ERR (某些实现上能禁用设置信号处理函数)。

使用

使用方法请直接看下面的例子。注册一个信号用于在按下Ctrl-C时不立刻终止进程,而是输出一段文字并sleep一段时间。

#include <signal.h>
#include <unistd.h>
#include <iostream>

bool is_stop = false;
void quit_handler(int signo) {
    is_stop = true;
    std::cout << "stopping\n";
    sleep(2);
}

int main() {
    auto prev = signal(SIGINT, quit_handler);
    if (prev == SIG_ERR) {
        abort();
    }
    while (!is_stop) {
        std::cout << "running\n";
        sleep(1);
    }
    std::cout << "stopped\n";
}

如何安装多个处理函数

signal只能为一个信号添加一个处理函数,添加多个处理函数时后面的函数会覆盖前面的函数。看如下代码

#include <signal.h>
#include <unistd.h>
#include <iostream>

bool is_stop = false;
void quit_handler1(int signo) {
    is_stop = true;
    std::cout << "handler1\n";
    sleep(2);
}

void quit_handler2(int signo) {
    is_stop = true;
    std::cout << "handler2\n";
    sleep(2);
}

int main() {
    auto prev1 = signal(SIGINT, quit_handler1);
    auto prev2 = signal(SIGINT, quit_handler2);

    if (prev1 == SIG_ERR || prev2 == SIG_ERR) {
        abort();
    }
    while (!is_stop) {
        std::cout << "running\n";
        sleep(1);
    }
    std::cout << "stopped\n";
}

运行之后按下Ctrl-C,结果发现只有handler2有效,handler1失效了。

running
running
^Chandler2
stopped

有两个quit_handler,上面的代码最终执行的是quit_handler2。后面注册的函数覆盖了前面注册的。那么如何注册两个处理器呢?

由于signal会返回上一个处理操作,可能是SIG_DFL、SIG_IGN宏,也可能是上一个处理函数。这给我们注册多个处理器带来了可能。只要我们在第二次注册的处理函数中调用前一次的处理函数就可以了!看下面代码:

#include <unistd.h>
#include <signal.h>
#include <iostream>

bool is_stop = false;
void (*prev_handler)(int) = nullptr;

void quit_handler1(int signo) {
    is_stop = true;
    std::cout << "handler1\n";
    sleep(2);
}

void quit_handler2(int signo) {
    if (prev_handler != nullptr) {
        prev_handler(signo);
    }
    is_stop = true;
    std::cout << "handler2\n";
    sleep(2);
}

int main() {
    auto prev1 = signal(SIGINT, quit_handler1);
    auto prev2 = signal(SIGINT, quit_handler2);
    if (prev1 == SIG_ERR || prev2 == SIG_ERR) {
        abort();
    }
    if (prev2 != SIG_DFL && prev2 != SIG_IGN) {
        prev_handler = prev2;
    }
    while (!is_stop) {
        std::cout << "running\n";
        sleep(1);
    }
    std::cout << "stopped\n";
}

使用一个变量prev_handler保存前一次注册的handler,在quit_handler2中,调用prev_handler。运行之后会发现两个handler都会被调用,结果如下:

running
running
running
^Chandler1
handler2
stopped

信号列表

SignalDescription
SIGABRT由调用abort函数产生,进程非正常退出
SIGALRM用alarm函数设置的timer超时或setitimer函数设置的interval timer超时
SIGBUS某种特定的硬件异常,通常由内存访问引起
SIGCANCEL由Solaris Thread Library内部使用,通常不会使用
SIGCHLD进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略
SIGCONT当被stop的进程恢复运行的时候,自动发送
SIGEMT和实现相关的硬件异常
SIGFPE数学相关的异常,如被0除,浮点溢出,等等
SIGFREEZESolaris专用,Hiberate或者Suspended时候发送
SIGHUP发送给具有Terminal的Controlling Process,当terminal 被disconnect时候发送
SIGILL非法指令异常
SIGINFOBSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程
SIGINT由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程
SIGIO异步IO事件
SIGIOT实现相关的硬件异常,一般对应SIGABRT
SIGKILL无法处理和忽略。中止某个进程
SIGLWP由Solaris Thread Libray内部使用
SIGPIPE在reader中止之后写Pipe的时候发送
SIGPOLL当某个事件发送给Pollable Device的时候发送
SIGPROFSetitimer指定的Profiling Interval Timer所产生
SIGPWR和系统相关。和UPS相关。
SIGQUIT输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程
SIGSEGV非法内存访问
SIGSTKFLTLinux专用,数学协处理器的栈异常
SIGSTOP中止进程。无法处理和忽略。
SIGSYS非法系统调用
SIGTERM请求中止进程,kill命令缺省发送
SIGTHAWSolaris专用,从Suspend恢复时候发送
SIGTRAP实现相关的硬件异常。一般是调试异常
SIGTSTPSuspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程
SIGTTIN当Background Group的进程尝试读取Terminal的时候发送
SIGTTOU当Background Group的进程尝试写Terminal的时候发送
SIGURG当out-of-band data接收的时候可能发送
SIGUSR1用户自定义signal 1
SIGUSR2用户自定义signal 2
SIGVTALRMsetitimer函数设置的Virtual Interval Timer超时的时候
SIGWAITINGSolaris Thread Library内部实现专用
SIGWINCH当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程
SIGXCPU当CPU时间限制超时的时候
SIGXFSZ进程超过文件大小限制
SIGXRESSolaris专用,进程超过资源限制的时候发

关于typedef void (*sighandler_t) (int);

这里有个typdef比较奇怪,没见过这种写法。其实抛开typedef的自定义类型,只看函数,其实就和 int fun(int a,cha b)类似,这就很通俗易懂。

要想看懂上面的函数,就必须理解typedef的用法。

首先,我们看这个定义:typedef char *p ,这里,我们首先把typedef盖上,那么就是char *p。这句话想必大家都能看懂,也就是声明了一个p指针。p指针指向char类型的数据,加上typedef之后就解释成声明了一种指向char变量指针的类型p,也就是说,p=char *,p a=char *a。

然后我们来看 typedef void (*sighandler_t)(int),首先不看typedef,也就是void (*sighandler_t)(int),这就话的意思是声明了一个指向一个函数并且这个函数能接受一个整型参数并返回一个无类型指针变量sighandler_t,加上typedef之后,sighandler_t就变成了一个类型,sighandler_t p,p就是一个指向一个函数并且这个函数能接受一个整型参数并返回一个无类型指针的变量。

所以一句话就是,typedef在语句中所起的作用就是把语句原先定义变量的功能变成了定义类型的功能,仅此而已。

赞赏

微信赞赏支付宝赞赏

其他

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注