第一步:建立信号处理器
信号是内核传给某个进程的一个整数。当进程接收到信号,它便以以下方式之一响应:
- 忽略该信号;
- 让内核完成与该信号关联的默 认操作 ;
- 捕获该信号,即让内核将控制传给信号处理例程,等信号处理例程执行完毕,然后又从中断的地方恢复程序的执行。
所谓信号处理例程是一个函数,当某个信号发生时,内核会自动调用该函数。signal()函数为给定的信号注册一个处理例程:
typedef void (*handler)(void);
void * signal(int signum, handler);
第一个参数是信号编码。第二个参数用户定义的函数地址,当信号 signum 产生时,handler 所指向的函数被调用。
除了函数地址之外,第二个参数也可以是两个特殊的值:SIG_IGN 和 SIG_DFL。SIG_IGN 表示该信号应被忽略(注意:SIGKILL 和 SIGSTOP 在无论如何都是不能被阻塞、捕获或忽略的);SIG_DFL 指示内核该信号产生时完成默认行为。
第二步:发信号
向某个进程发信号有三种方式:
- 进程通过条用 raise() 显式地发送信号给自己;
- 信号从另一个进程发送,比方说通过 kill() 系统调用或者 Perl 脚本 ;
- 信号从内核发送。例如,当进程试图存取不属于自己的内存,或在系统关闭期间存取内存时;
第三步:产生和处理信号
下面程序注册 SIGTERM 处理器。然后产生一个 SIGTERM 信号,从而导致该处理器运行:
#include <csignal>
#include <iostream>
using namespace std;
void term(int sig)
{
//..necessary cleanup operations before terminating
cout << "handling signal no." <<sig <<endl;
}
int main()
{
signal(SIGTERM, term); // register a SIGTERM handler
raise(SIGTERM); // will cause term() to run
}
ANSI <signal.h> 的局限
当进入就绪状态的某个进程准备运行一个 SIGx 信号处理例程时又接收到另一个 SIGx 信号,这时会发生什么情况呢?一个方法是让内核中断该进程并再次运行该信号处理例程。为此,这个处理例程必须是可重入的(re-entrant )。 但是,设计可重入的处理例程决非易事。ANSI C 解决重现信号(recurring signals) 问题的方法是在执行用户定义的处理例程前,将处理例程重置为 STG_DFL。这样做是有问题的。
当两个信号快速产生时,内核运行第一个信号的处理例程,而对第二个信号则进行默认处理,这样有可能终止该进程。
在过去的三十年中出现了几个可以信号处理框架,每一种框架对重现信号的处理问题提供了不同的解决方法。POSIX 信号 API 是其中最为成熟的和可移植的一个。
POSIX 信号
POSIX 信号处理函数操作一组打包在 sigset_t 数据类型中信号:
- int sigemptyset(sigset_t * pset); 清除 pset 中的所有信号。
- int sigfillset(sigset_t * pset); 用可获得的信号填充 pset。
- int sigaddset(sigset_t * pset, int signum); 将 signum 添加到 pset。
- int sigdelset(sigset_t * pset, int signum); 从 pset 中删除 signum。
- int sigismember(const sigset_t * pset, int signum); 如果 signum 包含在 pset 中,则返回非零,否则返回 0。
Sigaction() 为特定的信号注册处理例程:
int sigaction(int signum, struct sigaction * act, struct sigaction *prev);
sigaction 结构描述内核处理 signum 的信息:
struct sigaction
{
sighanlder_t sa_hanlder;
sigset_t sa_mask; // 阻塞信号的清单
unsigned long sa_flags; // 阻塞模式
void (*sa_restorer)(void); // 未使用
};
sa_hanlder 保存函数的地址,该函数带一个整型参数,没有返回值。它还可以是两个特别值之一:SIG_DFL 和
SIG_IGN。
额外特性
POSIX API 提供多种 ANSI 库中所没有的服务。其中包括阻塞进入的信号并获取当前未决信号。
阻塞信号
sigprocmask() 阻塞和取消阻塞信号:
int sigprocmask(int mode, const sigset_t* newmask,sigset_t * oldmask);
mode 可取以下值之一:
SIG_BLOCK —— 将 newmask 中的信号添加到当前的信号挡板中。
SIG_UNBLOCK —— 从当前的信号挡板中删除 newmask 信号。
SIG_SETMASK —— 仅阻塞 newmask 中的信号。
获取未决信号
阻塞的信号处于等待状态,直到进程就绪接收它们。这样的信号被称为未决信号,可以通过调用 sigpending() 来获取。
int sigpending(sigset_t * pset);
信号的动作与信号有关的动作分为三种:SIG_DFL,SIG_IGN或指向函数的指针。在最开始,进入main( )之前,所有信号都将被置成SIG_DFL或SIG_IGN。
- SIG_DFL信号专用的默认动作:
- 如果默认动作是暂停线程,则该线程的执行被暂时挂起。当线程暂停期间,发送给线程的任何附加信号都不交付,直到该线程开始执行,但是SIGKILL除外。
- 把挂起信号的信号动作设置成SIG_DFL,且其默认动作是忽略信号 (SIGCHLD)。
- SIG_IGN忽略信号
- 该信号的交付对线程没有影响
- 系统不允许把SIGKILL或SIGTOP信号的动作设置为SIG_DFL
- 指向函数的指针--捕获信号
- 信号一经交付,接收线程就在指定地址上执行信号捕获程序。在信号捕 获函数返回后,接受线程必须在被中断点恢复执行。
- 用C语言函数调用的方法进入信号捕捉程序:
void func (signo)
int signo;
func( )是指定的信号捕捉函数,signo是正被交付信号的编码 - 如果SIGFPE,SIGILL或SIGSEGV信号不是由C标准定义的kill( )或raise( )函数所生成,则从信号SIGFPE,SIGILL,SIGSEGV的信号捕获函数正常返回后线程的行为是未定义的。
- 系统不允许线程捕获SIGKILL和SIGSTOP信号。
- 如果线程为SIGCHLD信号建立信号捕获函数,而该线程有未被等待的以终止的子线程时,没有规定是否要生成SIGCHLD信号来指明那个子线程。
每一种信号都被OSKit给予了一个符号名,对于32位的i386平台而言,一个字32位,因而信号有32种。下面的表给出了常用的符号名、描述和它们的 信号值。
符号名 | 信号值 | 描述 | 是否符合POSIX |
SIGHUP | 1 | 在控制终端上检测到挂断或控制线程死 亡 | 是 |
SIGINT | 2 | 交互注意信号 | 是 |
SIGQUIT | 3 | 交 互中止信号 | 是 |
SIGILL | 4 | 检测到非法硬件的指令 | 是 |
SIGTRAP | 5 | 从 陷阱中回朔 | 否 |
SIGABRT | 6 | 异常终止信号 | 是 |
SIGEMT | 7 | EMT 指令 | 否 |
SIGFPE | 8 | 不正确的算术操作信号 | 是 |
SIGKILL | 9 | 终 止信号 | 是 |
SIGBUS | 10 | 总线错误 | 否 |
SIGSEGV | 11 | 检 测到非法的内存调用 | 是 |
SIGSYS | 12 | 系统call的错误 参数 | 否 |
SIGPIPE | 13 | 在无读者的管道上写 | 是 |
SIGALRM | 14 | 报 时信号 | 是 |
SIGTERM | 15 | 终止信号 | 是 |
SIGURG | 16 | IO 信道紧急信号 | 否 |
SIGSTOP | 17 | 暂停信号 | 是 |
SIGTSTP | 18 | 交 互暂停信号 | 是 |
SIGCONT | 19 | 如果暂停则继续 | 是 |
SIGCHLD | 20 | 子 线程终止或暂停 | 是 |
SIGTTIN | 21 | 后台线程组一成员试图 从控制终端上读出 | 是 |
SIGTTOU | 22 | 后台线程组的成员试 图写到控制终端上 | 是 |
SIGIO | 23 | 允许I/O信号 | 否 |
SIGXCPU | 24 | 超 出CPU时限 | 否 |
SIGXFSZ | 25 | 超出文件大小限制 | 否 |
SIGVTALRM | 26 | 虚 时间警报器 | 否 |
SIGPROF | 27 | 侧面时间警报器 | 否 |
SIGWINCH | 28 | 窗 口大小的更改 | 否 |
SIGINFO | 29 | 消息请求 | 否 |
SIGUSR1 | 30 | 保 留作为用户自定义的信号1 | 是 |
SIGUSR2 | 31 | 保留作为用 户自定义的信号 | 是 |
请求按默认的规则进行信号处理
SIG_DFL (void (*)(int)) 0
请求忽略信号
SIG_IGN (void (*)(int)) 1
注意: 信号队列中最多允许有 64 个信号
SIGIO相关代码
#include<fcntl.h>;
.....
fcntl(listenfd,F_SETOWN,O_ASYNC);//make it raise SIGIO if
// have new connect-request;
.....
fcntl(connetfd,F_SETOWN,O_ASYNC);make it raise SIGIO if
// have data to read or write;
在TCP 连接中, SIGIO 信号将会在这个时候产生:
l 在一个监听某个端口的套接字上成功的建立了一个新连接。
l 一个断线的请求被成功的初始化。
l 一个断线的请求成功的结束。
l 套接字的某一个通道(发送通道或是接收通道)被关闭。
l 套接字接收到新数据。
l 套接字将数据发送出去。
l 发生了一个异步I/O 的错误。
举例来说,如果一个正在进行读写操作的TCP 套接字处于信号驱动I/O 状态下,那么
每当新数据到达本地的时候,将会产生一个SIGIO 信号,每当本地套接字发出的数据被远
程确认后,也会产生一个SIGIO 信号。对于我们的程序来讲,是无法区分这两个SIGIO 有
什么区别的。在这种情况下使用SIGIO,TCP 套接字应当被设置为无阻塞模式来阻止一个
阻塞的read 和write(recv 和send)操作。我们可以考虑在一个只进行监听网络连接操作
的套接字上使用异步I/O,这样当有一个新的连接的时候,SIGIO 信号将会产生。
SIG_DFL是个值=NULL的函数指针,由于signal函数需要一个函数指针作为第二个参数,所以直接写
signal(SIGALRM, NULL);
是不规范的,应该写成
signal(SIGALRM, (void (*)(int))NULL);
为简化起见就用宏定义SIG_DFL
但这里的宏定义也是不标准的吧?
正确的SIG_DFL是这样的:
void(*signal(int sig,void(*disp)(int)))(int);
#define SIG_IGN (void(*)()) 1
QUOTE:
在signal.h中
/* Type of a signal handler. */
typedef void (*__sighandler_t) (int)
signal(SIGCHLD, SIG_IGN); //忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧
//因为并发服务器常常fork很多子进程,子进程终结之后需要
//服务器进程去wait清理资源。如果将此信号的处理方式设为
//忽略,可让内核把僵尸子进程转交给init进程去处理,省去了
//大量僵尸进程占用系统资源。(Linux Only)
#ifdef HAVE_SIGACTION
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, NULL);
sigaction(SIGUSR1, &act, NULL);
# if defined(SA_SIGINFO)
act.sa_sigaction = sigaction_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
# else
act.sa_handler = signal_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
# endif
sigaction(SIGINT, &act, NULL);
sigaction(SIGTERM, &act, NULL);
sigaction(SIGHUP, &act, NULL);
sigaction(SIGALRM, &act, NULL);
sigaction(SIGCHLD, &act, NULL);
#elif defined(HAVE_SIGNAL)
/* ignore the SIGPIPE from sendfile() */
signal(SIGPIPE, SIG_IGN);
signal(SIGUSR1, SIG_IGN);
signal(SIGALRM, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGHUP, signal_handler);
signal(SIGCHLD, signal_handler);
signal(SIGINT, signal_handler);
#endif
#ifdef USE_ALARM
signal(SIGALRM, signal_handler);
/* setup periodic timer (1 second) */
if (setitimer(ITIMER_REAL, &interval, NULL)) {
log_error_write(srv, __FILE__, __LINE__, "s", "setting timer failed");
return -1;
}
getitimer(ITIMER_REAL, &interval);
#endif
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, NULL);
sigaction(SIGUSR1, &act, NULL);
act.sa_sigaction = sigaction_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGINT, &act, NULL);
sigaction(SIGTERM, &act, NULL);
sigaction(SIGHUP, &act, NULL);
sigaction(SIGALRM, &act, NULL);
sigaction(SIGCHLD, &act, NULL);
增加其它信号
signal(SIGPIPE,SIG_IGN); write()==-1
SIGPIPE要进行忽略,同时对errno为EPIPE,说明对方关闭连接。
if ((r = writev(fd, chunks, num_chunks)) < 0) {
switch (errno) {
case EAGAIN:
case EINTR:
r = 0;
break;
case EPIPE:
case ECONNRESET:
return -2;
default:
log_error_write(srv, __FILE__, __LINE__, "ssd",
"writev failed:", strerror(errno), fd);
return -1;
}
}
Socket的send函数在执行时报EAGAIN的错误
内容提要:
当客户通过Socket提供的send函数发送大的数据包时,就可能返回一个EGGAIN的错误。该错误产生的原因是由于send函数中的size变量大小超过了tcp_sendspace的值。tcp_sendspace定义了应用在调用send之前能够在kernel中缓存的数据量。当应用程序在socket中设置了O_NDELAY或者O_NONBLOCK属性后,如果发送缓存被占满,send就会返回EAGAIN的错误。
为了消除该错误,有三种方法可以选择:
1.调大tcp_sendspace,使之大于send中的size参数
---no -p -o tcp_sendspace=65536
2.在调用send前,在setsockopt函数中为SNDBUF设置更大的值
3.使用write替代send,因为write没有设置O_NDELAY或者O_NONBLOCK