总结《Linux高性能服务器编程》10-11章
第10章 信号
- 信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常;
- Linux信号可由如下条件产生:
- 对于前台进程,可以通过输入特殊的终端字符来给它发送信号;
- 系统异常、系统状态变化;
- 运行kill命令或调用kill函数;
- Linux定义的信号值都大于0;
Linux信号概述
-
发送及接收信号
-
Linux下,一个进程给其他进程发送信号的API是kill函数:
#include<sys/types.h> #include<signal.h> int kill(pid_t pid,int sig);//该函数把信号sig发送给目标进程pid
- 成功时返回0,失败则返回-1并设置errno;
-
Linux下,目标进程收到信号时使用的信号处理函数原型
#include<signal.h> typedef void(*__sighandler_t)(int); #include<bits/signum.h> #define SIG_DFL((__sighandler_t)0) //SIG_DFL表示使用信号的默认处理方式 #define SIG_IGN((__sighandler_t)1) //SIG_IGN表示忽略目标信号
-
-
Linux信号
- Linux的可用信号都定义在bits/signum.h头文件中,其中包括标准信号和POSIX实时信号;
- 重点关注与网络编程关系紧密的几个信号:SIGHUP、SIGPIPE和SIGURG;
-
中断系统调用
- 如果程序在执行处于阻塞状态的系统调用时接收到信号,并且设置了信号处理函数,则默认情况下系统调用将被中断,并且errno被设置为EINTR,可以为信号设置SA_RESTART标志以自动重启被该信号中断的系统调用;
- 对于默认行为是暂停进程的信号(比如SIGSTOP、SIGTTIN),如果我们没有为它们设置信号处理函数,则它们也可以中断某些系统调用(比如connect、epoll_wait);
信号函数
-
要为一个信号设置处理函数,可以使用signal系统调用
#include<signal.h> _sighandler_t signal(int sig,_sighandler_t_handler)
- sig参数指出要捕获的信号类型;
- _handler参数指定信号sig的处理函数;
-
设置信号处理函数的更健壮的接口sigaction系统调用
#include<signal.h> int sigaction(int sig, const struct sigaction* act, struct sigaction* oact);
- sig参数指出要捕获的信号类型,act参数指定新的信号处理方式,oact参数则=输出信号先前的处理方式(如果不为NULL的话);
- sigaction结构体描述了信号处理的细节,如信号处理函数,进程掩码,信号集,收到信号后的行为等;
信号集
-
信号集函数
#include<bits/sigset.h> #define_SIGSET_NWORDS(1024/(8*sizeof(unsigned long int))) typedef struct { unsigned long int__val[_SIGSET_NWORDS]; }__sigset_t;
- sigset_t实际上是一个长整型数组,数组的每个元素的每个位表示一个信号,这种定义方式和文件描述符集fd_set类似;
- Linux提供了如下一组函数来设置、修改、删除和查询信号集:
#include<signal.h> int sigemptyset(sigset_t*_set)/*清空信号集*/ int sigfillset(sigset_t*_set)/*在信号集中设置所有信号*/ int sigaddset(sigset_t*_set, int_signo)/*将信号_signo添加至信号集中*/ int sigdelset(sigset_t*_set, int_signo)/*将信号_signo从信号集中删除*/ int sigismember(_const sigset_t*_set, int_signo)/*测试_signo是否在信号集中*/
-
进程信号掩码
-
以指定哪些信号不能发送给本进程;
-
可以利用sigaction结构体的sa_mask成员来设置进程的信号掩码;
-
可以用sigprocmask函数设置或查看进程的信号掩码;
#include<signal.h> int sigprocmask(int how, const sigset_t * set, sigset_t * oset;
- set参数指定新的信号掩码,oset参数则输出原来的信号掩码,how参数指定设置进程信号掩码的方式(如交集、并集等);
-
-
被挂起的信号
-
如果给进程发送一个被屏蔽的信号,则操作系统将该信号设置为进程的一个被挂起的信号;
-
取消对被挂起信号的屏蔽,则它能立即被进程接收到;
-
如下函数可以获得进程当前被挂起的信号集
#include<signal.h> int sigpending(sigset_t*set);
-
统一事件源
- 信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线;
- 信号处理函数通常使用管道来将信号“传递”给主循环:信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出该信号值;
- 统一事件源:使用I/O复用系统调用来监听管道的读端文件描述符上的可读事件,信号事件就能和其他I/O事件一样被处理;
代码清单10-1 统一事件源
网络编程相关信号
-
SIGHUP
- 当挂起进程的控制终端时,SIGHUP信号将被触发;
- 对于没有控制终端的网络后台程序而言,通常利用SIGHUP信号来强制服务器重读配置文件;
-
SIGPIPE
- 默认情况下,往一个读端关闭的管道或socket连接中写数据将引发SIGPIPE信号,程序接收到SIGPIPE信号的默认行为是结束进程;
- 需要在代码中捕获并处理该信号,或者至少忽略它,否则将因错误的写操作而导致程序退出;
- 引起SIGPIPE信号的写操作将设置errno为EPIPE;
- 使用send函数的MSG_NOSIGNAL标志来禁止写操作触发SIGPIPE信号;
- 利用I/O复用系统调用来检测管道和socket连接的读端是否已经关闭;
-
SIGURG
- 在Linux环境下,内核通知应用程序带外数据到达主要有两种方法:
- I/O复用技术:select等系统调用在接收到带外数据时将返回并报告socket上的异常事件;
- 使用SIGURG信号;
- 在Linux环境下,内核通知应用程序带外数据到达主要有两种方法:
第11章 定时器
- 定时是指在一段时间之后触发某段代码的机制,可以在这段代码中依次处理所有到期的定时器;
- 要将每个定时事件分别封装成定时器,然后放在定时器容器中并统一管理;
socket选项SO_RCVTIMEO和SO_SNDTIMEO
-
用来设置socket接收数据超时时间和发送数据超时时间,仅对与数据接收和发送相关的socket专用系统调用有效;
代码清单11-1 设置connect超时时间
SIGALRM信号
- 由alarm和setitimer函数设置的实时闹钟一旦超时,将触发SIGALRM信号,可以利用该信号的信号处理函数来处理定时任务;
- 一般SIGALRM信号按照固定的频率生成,即由alarm或setitimer函数设置的定时周期T保持不变,因此T反映了定时的精度;
- 服务器程序通常要定期处理非活动连接:给客户端发一个重连请求,或者关闭该连接,或者其他;
- 可以通过socket选项KEEPALIVE来激活;
- 或利用定时器在应用层实现类似的机制,以管理所有长时间处于非活动状态的连接;
I/O复用系统调用的超时参数
- Linux下的3组I/O复用系统调用都带有超时参数,因此它们不仅能统一处理信号和I/O事件,也能统一处理定时事件;
代码清单11-4 利用I/O复用系统调用定时