首页 > 代码库 > Linux高性能服务器编程——信号及应用
Linux高性能服务器编程——信号及应用
信号
信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。Linux信号可由如下条件产生:
对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入Ctrl+C通常会给进程发送一个终端信号。
2.系统异常
系统状态变化
运行kill命令或调用kill函数
Linux信号概述
发送信号
Linux下,一个进程给其他进程发送信号的API是kill函数。其定义如下:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
该函数把信号sig发送给目标进程;目标进程可由pid参数指定,其可能取值及含义如下:
pid参数 | 含义 |
pid > 0 | 信号发送给PID为pid的进程 |
pid = 0 | 信号发送给本进程组内的其他进程 |
pid = -1 | 信号发送给出了init进程外的所有进程,但需要对目标进程有发送权限 |
pid < -1 | 发送给组IP为-pid的进程组中的所有成员 |
该函数成功时返回0,失败是返回-1并设置errno。
信号的处理方式
目标进程在收到信号时,需要定义一个接收函数来处理之。信号处理函数的原型如下:
#include <signal.h
typedef void (* _sighandler_t)( int );
信号处理函数只带一个整形参数,该参数用来指示信号类型。信号处理函数应该是可重入的,否则很容易引发一些竞态条件,所以在信号处理函数中严禁调用一些不安全的函数。
除了用户自定义信号处理函数外,bits/signum.h文件中还定义了两种其他处理方式——SIG_IGN和SIG_DFL:
#include <bits/signum.h>
#define SIG_DFL ((_sighandler_t)0)
#define SIG_IGN((_sighandler_t) 1)
SIG_IGN表示忽略目标信号,SIG_DFL表示使用信号的默认处理方式。信号的默认处理方式有如下几种:结束进程(Term)、忽略信号(Ign)、结束进程并生成核心转储文件(Core)、暂停进程(Stop),以及继续进程(Cont)。
Linux的可用信号都定义在bits/signum.h头文件中,其中包括标准信号和POSIX实时信号。
终端系统调用
如果程序在执行处于阻塞的系统调用时接收到信号,并且我们为该信号设置了信号处理函数,则默认情况下系统调用将中断,并且errno被设置为EINTR。我们可以使用sigaction函数为信号设置SA_RESTART标志以自动重启被信号中断的系统调用。
对于默认行为是暂停进程的信号(比如SIGSTOP、SIGTTIN),如果我们没有为它们设置信号处理函数,则它们也可以中断某些系统调用。
信号函数
signal系统调用
可以是使用signal系统调用为一个信号设置处理函数。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
sig参数指出要捕捉的信号类型。_handler参数是_sighandler_t类型的函数指针,用于指定信号sig的处理函数。
signal函数成功时返回一个函数指针,该函数指针的类型也是_sighandler_t。这个返回值是前一次调用signal函数时传入的函数指针,或者是信号sig对应的默认处理函数指针SIG_DEF(如果是第一次调用signal的话)。
signal系统调用出错时返回SIG_ERR,并设置errno。
sigaction系统调用
设置信号处理函数的更健壮的接口是如下的系统调用:
#include <signal.h>
int sigaction(int signum, const structsigaction *act, struct sigaction *oldact);
signum参数之处要捕捉的信号类型,act参数指定新的信号处理方式,oldact参数则输出信号先前的处理方式(如果不为NULL的话)。act和oact都是sigaction结构体类型的指针,sigaction结构体描述了信号处理的细节,其定义如下:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
该结构体中的sa_handler成员指定信号处理函数。sa_mask成员设置进程的信号掩码(确切地说是在进程原有信号掩码的基础上增加信号掩码),以指定哪些信号不能发送给本进程。sa_mask是信号机sigset_t类型,该类型指定一组信号。sa_flag成员用于设置程序收到信号时的行为。sa_restorer成员已经过时,不在使用。
信号集
信号集函数
Linux使用数据结构sigset_t来表示一组信号,其定义如下:
#include <bits/sigset.h>
#define _SIGSET_NWORDS(1024 / (8 * sizeof(unsigned long int)))
typedef struct
{
unsignedlong 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 signum); //将信号signum添加到set信号集中
int sigdelset(sigset_t *set, int signum); //删除信号
int sigismember(const sigset_t *set, intsignum);//测试信号是否在信号集中
进程信号掩码
子进程将继承父进程的信号掩码。我们可以用sigaction结构体中的sa_mask成员来设置进程的信号掩码。此外,一下函数也可以用于设置或查看进程的信号掩码:
#include<signal.h>
intsigprocmask(int how, const sigset_t *set, sigset_t *oldset);
_set参数指定新的信号掩码,_oset参数则输出原来的信号掩码(如果不为NULL的话)。如果_set参数不为NULL,则_how参数指定设置进程信号掩码的方式,其可选值如下表所示:
how参数 | 含义 |
SIG_BLOCK | 新的进程掩码是当前值和set指定信号集的并集 |
SIG_UNBLOCK | 当前值和~set信号集的交际,因此set指定的信号将不被屏蔽 |
SIG_SETMASK | 直接设置为set |
如果set为NULL,则进程信号掩码不变,此时我们仍然可以利用oldset参数获得进程当前的信号掩码。
sigprocmask成功返回0,失败返回-1并设置errno。
被挂起的信号
设置进程掩码后,被屏蔽的信号将不能被进程接收。如果给进程发送一个被屏蔽的信号,则操作系统将该信号设置为进程的一个被挂起的信号。如果我们取消对被挂起信号的屏蔽,则它能立即被进程接收到。如下函数可以获得当前被挂起的信号集:
#include<signal.h>
intsigpending(sigset_t *set);
set参数用于保存被挂起的信号集。显然,进程即使多次接收到同一个被挂起的信号,sigpending函数也只能反映一次。并且,当我们使用sigprocmask使被挂起的信号不被屏蔽时,该信号的处理函数也只能被触发一次。
统一事件源
信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线。很显然,信号处理函数需要尽可能地执行完毕,以确保该信号不被屏蔽(为了一些竞态条件,信号在处理期间,系统不会再触发它)太久。一种典型的解决方案是:把信号的主要逻辑放到程序的主循环中,当信号处理函数被触发时,它只是简单的通知主循环程序接收信号,并把信号值传递给主循环,主循环再根据被接收到的信号值执行目标信号的逻辑代码。信号处理函数通常用使用管道来将信号“传递“给主循环:信号处理函数往管道的写端写入信号值,主循环则从管道的独断读出该信号值,这里主循环可以用I/O复用系统监听管道的读端文件描述符上的刻度事件。如此一来,信号事件就能和其他I/O事件一样被处理,即统一事件源。
程序清单1是统一事件源的一个简单实现。
网络编程相关信号
SIGHUP
当挂起进程的控制终端时,SIGHUP信号将被触发。对于没有控制终端的网络后台程序而言,他们通常利用SIGHUP信号来强制服务器重读配置文件。
SIGPIPE
默认情况下,往一个读端关闭的管道或socket连接中写数据将引发SIGPIPE信号。我们需要在代码中捕获并处理该信号,至少不能忽略它。因为程序接收到SIGPIPE信号的默认行为是结束进程,这是我们不希望的。
SIGURG
SIGURG
Linux下内核通知应用程序带外数据到达的主要有两种方法:一种是I/O复用技术,select等系统调用接收到带外数据时将返回,并向应用程序报告socket上的一场事件;另一种方法就有事使用SIGURG信号。程序清单2使用了SIGURG检测带外数据是否到达。
客户端发送带外数据部分参见http://write.blog.csdn.net/postedit/36003039
则服务器上先输出如下:
got 1 bytes ofoob data ‘c‘
got 5 bytes ofnormal data ‘123ab‘
got 3 bytes ofnormal data ‘123‘
程序清单1: #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <signal.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <stdlib.h> #include <sys/epoll.h> #include <pthread.h> #define MAX_EVENT_NUMBER 1024 static int pipefd[2]; int setnonblocking( int fd ) { int old_option = fcntl( fd, F_GETFL ); int new_option = old_option | O_NONBLOCK; fcntl( fd, F_SETFL, new_option ); return old_option; } void addfd( int epollfd, int fd ) { epoll_event event; event.data.fd = fd; event.events = EPOLLIN | EPOLLET; epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event ); setnonblocking( fd ); } void sig_handler( int sig ) { int save_errno = errno; int msg = sig; send( pipefd[1], ( char* )&msg, 1, 0 ); errno = save_errno; } void addsig( int sig ) { struct sigaction sa; memset( &sa, '\0', sizeof( sa ) ); sa.sa_handler = sig_handler; sa.sa_flags |= SA_RESTART; sigfillset( &sa.sa_mask ); assert( sigaction( sig, &sa, NULL ) != -1 ); } int main( int argc, char* argv[] ) { if( argc <= 2 ) { printf( "usage: %s ip_address port_number\n", basename( argv[0] ) ); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] ); int ret = 0; struct sockaddr_in address; bzero( &address, sizeof( address ) ); address.sin_family = AF_INET; inet_pton( AF_INET, ip, &address.sin_addr ); address.sin_port = htons( port ); int listenfd = socket( PF_INET, SOCK_STREAM, 0 ); assert( listenfd >= 0 ); //int nReuseAddr = 1; //setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &nReuseAddr, sizeof( nReuseAddr ) ); ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) ); if( ret == -1 ) { printf( "errno is %d\n", errno ); return 1; } //assert( ret != -1 ); ret = listen( listenfd, 5 ); assert( ret != -1 ); epoll_event events[ MAX_EVENT_NUMBER ]; int epollfd = epoll_create( 5 ); assert( epollfd != -1 ); addfd( epollfd, listenfd ); ret = socketpair( PF_UNIX, SOCK_STREAM, 0, pipefd ); assert( ret != -1 ); setnonblocking( pipefd[1] ); addfd( epollfd, pipefd[0] ); // add all the interesting signals here addsig( SIGHUP ); addsig( SIGCHLD ); addsig( SIGTERM ); addsig( SIGINT ); bool stop_server = false; while( !stop_server ) { int number = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 ); if ( ( number < 0 ) && ( errno != EINTR ) ) { printf( "epoll failure\n" ); break; } for ( int i = 0; i < number; i++ ) { int sockfd = events[i].data.fd; if( sockfd == listenfd ) { struct sockaddr_in client_address; socklen_t client_addrlength = sizeof( client_address ); int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength ); addfd( epollfd, connfd ); } else if( ( sockfd == pipefd[0] ) && ( events[i].events & EPOLLIN ) ) { int sig; char signals[1024]; ret = recv( pipefd[0], signals, sizeof( signals ), 0 ); if( ret == -1 ) { continue; } else if( ret == 0 ) { continue; } else { for( int i = 0; i < ret; ++i ) { //printf( "I caugh the signal %d\n", signals[i] ); switch( signals[i] ) { case SIGCHLD: case SIGHUP: { continue; } case SIGTERM: case SIGINT: { stop_server = true; } } } } } else { } } } printf( "close fds\n" ); close( listenfd ); close( pipefd[1] ); close( pipefd[0] ); return 0; }
程序清单2 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <signal.h> #include <fcntl.h> #define BUF_SIZE 1024 static int connfd; void sig_urg( int sig ) { int save_errno = errno; char buffer[ BUF_SIZE ]; memset( buffer, '\0', BUF_SIZE ); int ret = recv( connfd, buffer, BUF_SIZE-1, MSG_OOB ); printf( "got %d bytes of oob data '%s'\n", ret, buffer ); errno = save_errno; } void addsig( int sig, void ( *sig_handler )( int ) ) { struct sigaction sa; memset( &sa, '\0', sizeof( sa ) ); sa.sa_handler = sig_handler; sa.sa_flags |= SA_RESTART; sigfillset( &sa.sa_mask ); assert( sigaction( sig, &sa, NULL ) != -1 ); } int main( int argc, char* argv[] ) { if( argc <= 2 ) { printf( "usage: %s ip_address port_number\n", basename( argv[0] ) ); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] ); struct sockaddr_in address; bzero( &address, sizeof( address ) ); address.sin_family = AF_INET; inet_pton( AF_INET, ip, &address.sin_addr ); address.sin_port = htons( port ); int sock = socket( PF_INET, SOCK_STREAM, 0 ); assert( sock >= 0 ); int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) ); assert( ret != -1 ); ret = listen( sock, 5 ); assert( ret != -1 ); struct sockaddr_in client; socklen_t client_addrlength = sizeof( client ); connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength ); if ( connfd < 0 ) { printf( "errno is: %d\n", errno ); } else { addsig( SIGURG, sig_urg ); fcntl( connfd, F_SETOWN, getpid() ); char buffer[ BUF_SIZE ]; while( 1 ) { memset( buffer, '\0', BUF_SIZE ); ret = recv( connfd, buffer, BUF_SIZE-1, 0 ); if( ret <= 0 ) { break; } printf( "got %d bytes of normal data '%s'\n", ret, buffer ); } close( connfd ); } close( sock ); return 0; }