进程编程#001#IPC-信号机制

信号是一种软件中断。常驻程序尤其需要注意处理这些信号,如果没有处理,同时也没有了解信号的默认动作,进程可能会莫名其妙的退出或者core。信号早已有之,但在老的操作系统中,可能会出现信号丢失。4.3BSD和SVR3之后增加了可靠信号机制,我们可以放心使用信号机制。本文根据阅读Nginx代码,参考其信号处理机制,总结了信号使用方法和注意事项。

信号列表

我们使用如下命令查看系统信号列表:

1
$ kill -l

命令执行结果如下图所示:

下表列出了各信号在Linux环境中的含义,以及默认动作:

名字说明ISO CSUS默认动作
1SIGHUP连接断开终止
2SIGINT终端中断符终止
3SIGQUIT终端退出符终止+core
4SIGILL非法硬件指令终止+core
5SIGTRAP硬件故障XSI终止+core
6SIGABRT异常终止(abort)终止+core
7SIGBUS硬件故障终止+core
8SIGFPE算术异常终止+core
9SIGKILL终止终止
10SIGUSR1用户自定义的信号终止
11SIGSEGV无效内存引用终止+core
12SIGUSR2用户自定义的信号终止
13SIGPIPE写至无读进程的管道终止
14SIGALRM超时(abort)终止
15SIGTERM终止终止+core
16SIGSTKFLT协处理器故障终止
17SIGCHLD子进程状态改变忽略
18SIGCONT使暂停进程继续继续/忽略
19SIGSTOP停止暂停进程
20SIGTSTP终端停止符暂停进程
21SIGTTIN后台读控制tty暂停进程
22SIGTTOU后台写至控制tty暂停进程
23SIGURG紧急情况(套接字,带外数据)忽略
24SIGXCPU超过CPU限制(setrlimit)XSI终止+core/忽略
25SIGXFSZ超过CPU限制(setrlimit)XSI终止+core/忽略
26SIGVTALRM虚拟时间闹钟(setitimer)XSI终止
27SIGPROF梗概事件超时(setitimer)XSI终止
28SIGWINCH终端窗口大小改变忽略
29SIGIO异步I/O终止/忽略
30SIGPWR电源失效/重启动终止/忽略
31SIGSYS无效系统调用XSI终止+core

注释

  • POSIX.1包含了ISO C标准函数库,同时结构分类两部分:必须部分和可选部分(X/Open系统接口    X/Open System Interface,XSI)。
  • SUS 即 单一UNIX规范(Single UNIX Specification,缩写为SUS),它是UNIX系统的统一规格书。SUS是POSIX的扩展,扩充了POSIX标准,定义了标准UNIX操作系统。只有遵循XSI的实现才能称为UNIX系统。
  • SUS、POSIX、ISO C的关系,可以总结为 SUS > POSIX > ISO C。

信号编程

下表列出C头文件 /usr/include/x86_64-linux-gnu/asm/signal.h 中关于信号的定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#define SIGHUP		 1
#define SIGINT		 2
#define SIGQUIT		 3
#define SIGILL		 4
#define SIGTRAP		 5
#define SIGABRT		 6
#define SIGIOT		 6
#define SIGBUS		 7
#define SIGFPE		 8
#define SIGKILL		 9
#define SIGUSR1		10
#define SIGSEGV		11
#define SIGUSR2		12
#define SIGPIPE		13
#define SIGALRM		14
#define SIGTERM		15
#define SIGSTKFLT	16
#define SIGCHLD		17
#define SIGCONT		18
#define SIGSTOP		19
#define SIGTSTP		20
#define SIGTTIN		21
#define SIGTTOU		22
#define SIGURG		23
#define SIGXCPU		24
#define SIGXFSZ		25
#define SIGVTALRM	26
#define SIGPROF		27
#define SIGWINCH	28
#define SIGIO		29
#define SIGPOLL		SIGIO
#define SIGPWR		30
#define SIGSYS		31

信号函数 signal

1
2
3
4
5
#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

在实际开发环境中,通常使用上述简单信号函数,来注册信号的对应处理函数。我们使用如下的 测试代码文件 ,来演示该函数的使用方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<unistd.h>
#include<stdio.h>
#include<signal.h>

void sig_handle(int sig)
{
    switch (sig)
    {
        case SIGUSR1 : printf("sig_handle::get SIGUSR1:%d\n", sig); break;
        case SIGUSR2 : printf("sig_handle::get SIGUSR2:%d\n", sig); break;
        default      : printf("sig_handle::get signal :%d\n", sig); break;
    }
}

int main(int argc, char ** argv)
{
    printf("sgtest::getpid()::%d\n", (int)getpid());

    if (signal(SIGUSR1, sig_handle) == SIG_ERR) 
    { 
        perror("signal SIGUSR1 register error"); 
        return 1; 
    }

    if (signal(SIGUSR2, sig_handle) == SIG_ERR) 
    { 
        perror("signal SIGUSR2 register error"); 
        return 1; 
    }

    while(1)
    {
        // 我们将sleep放置到while循环中,
        // 是因为当信号来临时,sleep被信号中断,然后sleep会马上返回
        // 如果sleep不在while循环中,那么该程序执行完信号响应函数之后,会马上执行到底,从而整体退出
        int ileft = sleep(10); // ileft表示被中断后,sleep执行中的剩余时间
                               // man 3 sleep : Zero if the requested time has elapsed, 
                               // or the number of seconds left to sleep, if the call was interrupted by a signal handler.
		
        printf("ileft:%d\n", ileft);
    }

    return 0;
}

使用命令编译该测试代码:

1
$ cc -o sigtest sigtest.c

我们先启动一个终端,开始运行sigtest程序,然后在另外一个终端,向sigtest进程发送信号,执行过程如下图所示:

参考资料