Signal and SIGIO

来源:互联网 发布:nginx epoll模型 编辑:程序博客网 时间:2024/06/06 00:24

第一步:建立信号处理器

  信号是内核传给某个进程的一个整数。当进程接收到信号,它便以以下方式之一响应:

  • 忽略该信号;
  • 让内核完成与该信号关联的默认操作;
  • 捕获该信号,即让内核将控制传给信号处理例程,等信号处理例程执行完毕,然后又从中断的地方恢复程序的执行。

  所谓信号处理例程是一个函数,当某个信号发生时,内核会自动调用该函数。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。

  1. SIG_DFL信号专用的默认动作:
    1. 如果默认动作是暂停线程,则该线程的执行被暂时挂起。当线程暂停期间,发送给线程的任何附加信号都不交付,直到该线程开始执行,但是SIGKILL除外。
    2. 把挂起信号的信号动作设置成SIG_DFL,且其默认动作是忽略信号(SIGCHLD)。
  2. SIG_IGN忽略信号
    1. 该信号的交付对线程没有影响
    2. 系统不允许把SIGKILL或SIGTOP信号的动作设置为SIG_DFL
  3. 指向函数的指针--捕获信号
    1. 信号一经交付,接收线程就在指定地址上执行信号捕获程序。在信号捕获函数返回后,接受线程必须在被中断点恢复执行。
    2. 用C语言函数调用的方法进入信号捕捉程序:
      void func (signo)















      int signo;

      func( )是指定的信号捕捉函数,signo是正被交付信号的编码
    3. 如果SIGFPE,SIGILL或SIGSEGV信号不是由C标准定义的kill( )或raise()函数所生成,则从信号SIGFPE,SIGILL,SIGSEGV的信号捕获函数正常返回后线程的行为是未定义的。
    4. 系统不允许线程捕获SIGKILL和SIGSTOP信号。
    5. 如果线程为SIGCHLD信号建立信号捕获函数,而该线程有未被等待的以终止的子线程时,没有规定是否要生成SIGCHLD信号来指明那个子线程。

每一种信号都被OSKit给予了一个符号名,对于32位的i386平台而言,一个字32位,因而信号有32种。下面的表给出了常用的符号名、描述和它们的信号值。

 

符号名 信号值 描述 是否符合POSIX SIGHUP1在控制终端上检测到挂断或控制线程死亡是SIGINT2交互注意信号是SIGQUIT3交互中止信号是SIGILL4检测到非法硬件的指令是SIGTRAP5从陷阱中回朔否SIGABRT6异常终止信号是SIGEMT7EMT 指令否SIGFPE8不正确的算术操作信号是SIGKILL9终止信号是SIGBUS10总线错误否SIGSEGV11检测到非法的内存调用是SIGSYS12系统call的错误参数否SIGPIPE13在无读者的管道上写是SIGALRM14报时信号是SIGTERM15终止信号是SIGURG16IO信道紧急信号否SIGSTOP17暂停信号是SIGTSTP18交互暂停信号是SIGCONT19如果暂停则继续是SIGCHLD20子线程终止或暂停是SIGTTIN21后台线程组一成员试图从控制终端上读出是SIGTTOU22后台线程组的成员试图写到控制终端上是SIGIO23允许I/O信号否SIGXCPU24超出CPU时限否SIGXFSZ25超出文件大小限制否SIGVTALRM26虚时间警报器否SIGPROF27侧面时间警报器否SIGWINCH28窗口大小的更改否SIGINFO29消息请求否SIGUSR130保留作为用户自定义的信号1是SIGUSR231保留作为用户自定义的信号是

请求按默认的规则进行信号处理
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

原创粉丝点击