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。
- 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 信号将会产生。
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相关推荐
- signal(SIGPIPE, SIG_IGN)
关于SIGPIPE导致的程序退出 当服务器close一个连接时,若client端接着发数据.根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIP ...
- nginx coredump 不产生core文件
在调试Nginx功能的时候,出现如下问题: 2017/02/27 16:23:50 [notice] 13604#0: signal 17 (SIGCHLD) received 2017/02/27 ...
- 容器-Docker《三》容器管理
容器-Docker<三>容器管理 下载镜像只是相当于将软件下载下来安装好,但是并不代表把它运行起来,类似于root@ubuntu2204:~# apt install nginx = do ...
- 十二周四次课(6月11日)
12.13 Nginx防盗链 打开配置文件,添加以下内容 [root@localhost ~]# vi /usr/local/nginx/conf/vhost/test.com.confserver ...
- 2.Linux文件IO编程
2.1Linux文件IO概述 2.1.0POSIX规范 POSIX:(Portable Operating System Interface)可移植操作系统接口规范. 由IEEE制定,是为了提高UNI ...
- docker部署及简单使用
docker部署及简单使用 配置docker源 [root@localhost yum.repos.d]# curl -o docker-ce.repo https://mirrors.tuna.ts ...
- linux 下进程间通讯: 共享文件
共享文件算是比较传统的进程间数据交换的一种方式,但是由于涉及到不同进程间反复文件I/O,难免显得有些效率低下.共享文件的本质,实际是就是某个进程向共享为念写入数据,一个或多个进程从文件中读取数据,有可 ...
- linux下使用异步通知
阻塞式I/O是一直等待直到设备可以访问,非阻塞式I/O是定期轮询设备是否可以访问. 异步通知则是当设备可以访问时才主动通知应用程序,有点像设备的硬中断. 并不是所有的设备都支持异步通知,应用程序通常假 ...
- linux signal函数用法,linux信号机制之sigaction构造体浅析,signal 函数,信号捕捉.
来自:http://hi.baidu.com/phenix_yw/blog/item/6eb4ca391d1479f23a87ce19.html 信号安装函数sigaction(int signum, ...
最新文章
- python 查看 nvida 驱动、 cuda、pytorch、tensorflow的版本
- golang函数后的 {
- CG CTF WEB 起名字真难
- Angular全套知识讲解,错过必悔!
- 重磅发布 | 承载亿级流量的开发框架,闲鱼Flutter技术解析与实战大公开
- mysql命令分类(DML、DDL、DCL)
- LAMP 搭建BBS论坛实战
- 视频图像处理仿真测试系统
- 我自横刀向天笑,我命由我不由天
- HDOJ 5071 Chat 模拟
- Java国际化资源绑定-----示例
- AForge.net简介和认识
- android.support.v7.app.actionbaractivity 报错
- application/octet-stream里的octet是什么意思
- 恶魔奶爸语法10-12课
- 【Git】remote: error: cannot lock ref
- 清华大学计算机系刘斌,queueing刘斌,男,工学博士 ,清华大学计算机科学与技...
- win10资源管理器频繁重启可能原因及解决方案——基于日志内容的查找
- 活动(已结束)--我们是冠军,啊呸,我们是CSDN VIP
- 计算机学院王春枝教授实验室,全国高等学校计算机科学与技术教学成果获奖证书.doc...
热门文章
- macOS Big Sur 11.7.5 (20G1225) 正式版 ISO、PKG、DMG、IPSW 下载
- 计算机无法对NAS硬盘操作,群晖NAS联机失败不要慌,我用经验告诉你,这样做就能完美解决...
- 微信小程序中wxml的标签说明
- 浅谈大数据专业的就业前景
- Y7000P电池0%解决办法
- mysql 如何存带有特殊符号的微信昵称
- VBA编程图表(二十一)
- OneDrive容量缩水,微软安抚用户:Office 365免费用一年
- 拼多多分类ID搜索商品数据分析接口(商品列表数据,商品销量数据,商品详情数据)代码对接教程
- AP作为WLAN用户接入认证点的PEAP用户接入流程