第一步:建立信号处理器

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

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

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

int fdevent_linux_rtsig_init(fdevents *ev) { ev->type = FDEVENT_HANDLER_LINUX_RTSIG; #define SET(x) / ev->x = fdevent_linux_rtsig_##x; SET(free); SET(poll); SET(event_del); SET(event_add); SET(event_next_fdndx); SET(fcntl_set); SET(event_get_fd); SET(event_get_revent); ev->signum = SIGRTMIN + 1; sigemptyset(&(ev->sigset)); sigaddset(&(ev->sigset), ev->signum); sigaddset(&(ev->sigset), SIGIO); if (-1 == sigprocmask(SIG_BLOCK, &(ev->sigset), NULL)) { fprintf(stderr, "%s.%d: sigprocmask failed (%s), try to set server.event-handler = /"poll/" or /"select/"/n", __FILE__, __LINE__, strerror(errno)); return -1; } ev->in_sigio = 1; ev->sigbset = bitset_init(ev->maxfds); return 0; } static int fdevent_linux_rtsig_fcntl_set(fdevents *ev, int fd) { static pid_t pid = 0; if (pid == 0) pid = getpid(); if (-1 == fcntl(fd, F_SETSIG, ev->signum)) return -1; if (-1 == fcntl(fd, F_SETOWN, (int) pid)) return -1; return fcntl(fd, F_SETFL, O_ASYNC | O_NONBLOCK | O_RDWR); } if ((n = fdevent_poll(srv->ev, 1000)) > 0) { } static int fdevent_linux_rtsig_poll(fdevents *ev, int timeout_ms) { struct timespec ts; int r; ev->in_sigio = 1; ts.tv_sec = timeout_ms / 1000; ts.tv_nsec = (timeout_ms % 1000) * 1000000; r = sigtimedwait(&(ev->sigset), &(ev->siginfo), &(ts)); if (r == -1) { if (errno == EAGAIN) return 0; return r; } else if (r == SIGIO) { struct sigaction act; /* flush the signal queue */ memset(&act, 0, sizeof(act)); act.sa_handler = SIG_IGN; sigaction(ev->signum, &act, NULL); /* re-enable the signal queue */ act.sa_handler = SIG_DFL; sigaction(ev->signum, &act, NULL); ev->in_sigio = 0; r = poll(ev->pollfds, ev->used, timeout_ms); return r; } else if (r == ev->signum) { # if 0 fprintf(stderr, "event: %d %02lx/n", ev->siginfo.si_fd, ev->siginfo.si_band); # endif return bitset_test_bit(ev->sigbset, ev->siginfo.si_fd); } else { /* ? */ return -1; } }

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

Signal and SIGIO相关推荐

  1. signal(SIGPIPE, SIG_IGN)

    关于SIGPIPE导致的程序退出 当服务器close一个连接时,若client端接着发数据.根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIP ...

  2. nginx coredump 不产生core文件

    在调试Nginx功能的时候,出现如下问题: 2017/02/27 16:23:50 [notice] 13604#0: signal 17 (SIGCHLD) received 2017/02/27 ...

  3. 容器-Docker《三》容器管理

    容器-Docker<三>容器管理 下载镜像只是相当于将软件下载下来安装好,但是并不代表把它运行起来,类似于root@ubuntu2204:~# apt install nginx = do ...

  4. 十二周四次课(6月11日)

    12.13 Nginx防盗链 打开配置文件,添加以下内容 [root@localhost ~]# vi /usr/local/nginx/conf/vhost/test.com.confserver ...

  5. 2.Linux文件IO编程

    2.1Linux文件IO概述 2.1.0POSIX规范 POSIX:(Portable Operating System Interface)可移植操作系统接口规范. 由IEEE制定,是为了提高UNI ...

  6. docker部署及简单使用

    docker部署及简单使用 配置docker源 [root@localhost yum.repos.d]# curl -o docker-ce.repo https://mirrors.tuna.ts ...

  7. linux 下进程间通讯: 共享文件

    共享文件算是比较传统的进程间数据交换的一种方式,但是由于涉及到不同进程间反复文件I/O,难免显得有些效率低下.共享文件的本质,实际是就是某个进程向共享为念写入数据,一个或多个进程从文件中读取数据,有可 ...

  8. linux下使用异步通知

    阻塞式I/O是一直等待直到设备可以访问,非阻塞式I/O是定期轮询设备是否可以访问. 异步通知则是当设备可以访问时才主动通知应用程序,有点像设备的硬中断. 并不是所有的设备都支持异步通知,应用程序通常假 ...

  9. linux signal函数用法,linux信号机制之sigaction构造体浅析,signal 函数,信号捕捉.

    来自:http://hi.baidu.com/phenix_yw/blog/item/6eb4ca391d1479f23a87ce19.html 信号安装函数sigaction(int signum, ...

最新文章

  1. python 查看 nvida 驱动、 cuda、pytorch、tensorflow的版本
  2. golang函数后的 {
  3. CG CTF WEB 起名字真难
  4. Angular全套知识讲解,错过必悔!
  5. 重磅发布 | 承载亿级流量的开发框架,闲鱼Flutter技术解析与实战大公开
  6. mysql命令分类(DML、DDL、DCL)
  7. LAMP 搭建BBS论坛实战
  8. 视频图像处理仿真测试系统
  9. 我自横刀向天笑,我命由我不由天
  10. HDOJ 5071 Chat 模拟
  11. Java国际化资源绑定-----示例
  12. AForge.net简介和认识
  13. android.support.v7.app.actionbaractivity 报错
  14. application/octet-stream里的octet是什么意思
  15. 恶魔奶爸语法10-12课
  16. 【Git】remote: error: cannot lock ref
  17. 清华大学计算机系刘斌,queueing刘斌,男,工学博士 ,清华大学计算机科学与技...
  18. win10资源管理器频繁重启可能原因及解决方案——基于日志内容的查找
  19. 活动(已结束)--我们是冠军,啊呸,我们是CSDN VIP
  20. 计算机学院王春枝教授实验室,全国高等学校计算机科学与技术教学成果获奖证书.doc...

热门文章

  1. macOS Big Sur 11.7.5 (20G1225) 正式版 ISO、PKG、DMG、IPSW 下载
  2. 计算机无法对NAS硬盘操作,群晖NAS联机失败不要慌,我用经验告诉你,这样做就能完美解决...
  3. 微信小程序中wxml的标签说明
  4. 浅谈大数据专业的就业前景
  5. Y7000P电池0%解决办法
  6. mysql 如何存带有特殊符号的微信昵称
  7. VBA编程图表(二十一)
  8. OneDrive容量缩水,微软安抚用户:Office 365免费用一年
  9. 拼多多分类ID搜索商品数据分析接口(商品列表数据,商品销量数据,商品详情数据)代码对接教程
  10. AP作为WLAN用户接入认证点的PEAP用户接入流程