Signal and SIGIO

第一步:建立信号处理器

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

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

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

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值