第十章 信号
信号是由用户,系统或者进程发送给目标程序的信息,以通知目标程序某个状态的改变或系统异常。
10.1 信号概述
一.发送信号
在Linux下,一个进程给其他进程发送信号的API是kill:
int kill(pid_t pid,int sig);
-
pid:制定目标进程
-
sig:要发送的信号
该函数成功时返回0,失败返回-1并设置error。
二.信号处理方法
目标进程在接受到信号后,需要定义一个处理的对应信号的函数,信号处理函数原型如下:
#include <signal.h>
typedef void (*sighandler_t)(int);
-
可以看出信号处理函数只带有一个整型参数,该参数用来指示信号类型。
-
信号处理函数应该是可重入的。
除了用户自定义的信号处理函数外,还有两种其他的处理方式。
#define SIG_DEL((sighandler_t) 0)
#define SIG_IGN((sighandler_t) 1)
前者忽略目标警告,后者是使用默认处理方式。
三.Linux信号
会主要讲解与网络编程关系紧密的几个信号:
-
SIGHUP
-
SIGPIPE
-
SIGURG
四.中断系统调用
如果程序在执行处于阻塞状态的系统调用时接收到了信号,并且为该信号设置了信号处理函数,则默认情况下系统调用将被中断。
10.2 信号函数
为一个信号设置处理函数:
sighandler_t signal(int signum, sighandler_t handler);
-
sig: 要捕获的信号类型
-
指定信号sig的处理函数
成功返回一个函数指针,错误返回SIG_ERR并设置error。
但更为健壮的接口是如下的系统调用:
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
- sig:指定要捕获的信号类型
- act:指定新的信号处理方式,指定了对特定信号的处理
- oact:指向的对象用来保存原来对应信号的处理
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_mask:设置进程的信号掩码。
-
sa_flags:用于设置程序收到信号时的行为。
10.3 信号集
一.信号集函数
Linux使用数据结构sigset_t来表示一组信号,sigset_t是一个长整型数组,数组的每个元素的每个位表示一个信号(和fd_set类似),故也提供了一组函数来设置,修改,删除和查询信号集。
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);
二.进程信号掩码
可以利用sigaction结构体的sa_mask设置进程的信号掩码,也可以用如下函数设置或者查看进程的信号掩码:
int sigprocmask(int _how, const sigset_t * _set, sigset_t * _oset);
-
_how:指定设置进程信号掩码的方式。
-
_set:指定新的信号掩码。
-
_oset:输出的信号掩码,若_set为NULL,则进程信号掩码不变,此时可以利用_oset参数来获得当前的信号掩码。
三.被挂起的信号
设置进程信号掩码后,被屏蔽的信号将不能被进程接受。如果给进程发送一个被屏蔽的信号,则操作系统将该信号设置为进程的一个被挂起信号。若取消对被挂起信号的屏蔽,则它能立即被进程接收到。
如下函数用来获取被挂起的信号集:
int sigpending(sigset_t *set);
- set:用于保存挂起的信号集
关于信号和信号集,Linux还提供了许多很有用的API,面面俱到肯定没有必要。
要始终清楚的知道进程在每个运行时刻的信号掩码,以及如何适当的处理捕捉到的信号,在多进程,多线程的环境中要以进程,线程为单位类处理信号和信号掩码。
10.4 统一事件源
信号是一种异步事件,信号处理函数和主循环是两条不同的执行路线,所以,信号处理函数需要尽可能的快速执行完毕,以确保信号不被屏蔽,一种典型的解决方案是:把信号的主要处理逻辑放到主循环中,而主循环根据接收到的信号值执行对应的逻辑代码,实现方式一般为信号处理函数往管道的写端写入数据,主循环则从管道的读端读出该数据。
那么问题来了,主循环怎么知道管道上何时有数据可读呢?
答:只需要使用I/O复用系统调用来监听管道的读端的文件描述符上的可读事件。
如此一来,信号事件就能和其他I/O事件一样被处理,即统一事件源。
10.5 三个与网络编程相关的信号
SIGHUP | SIGURG | SIGPIPE |
---|---|---|
当挂起进程的控制终端时将会引发此信号 | 内核通知应用程序带外数据到达将引发此信号 | (默认情况下)往一个读端关闭的管道或socket连接中写数据将引发此信号 |
关于第十章的总结
-
了解了如何在程序中发送信号和处理信号。
-
服务端程序必须处理一些常见的信号,以避免异常终止。
本章涉及到的代码有:
From
Aaron-z/linux-server-high-performance
2017/2/9 18:42:54