Linux高性能服务器编程-游双——第十章信号

  • 信号:用户、系统或进程发送给目标进程的信息,通知目标进程某个状态的改变或系统异常。

10.1 Linux信号概述

10.1.1 发送信号

一个进程给其他进程发送信号的API是kill函数

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
  • pid是目标进程的id,含义如下:
    在这里插入图片描述
  • sig是信号值
  • 成功返回0,失败返回-1并设置errno
    在这里插入图片描述

10.1.2 信号处理方式

  • 目标进程收到信号时,需要定义一个接收函数来处理
#incldue <signal.h>
typedef void(* _sighandler_t) (int);
  • 信号处理函数必须是可重入的
  • 定义了两种处理方式,SIG_IGN(忽略目标信号),SIG_DFL(使用信号默认处理方式)
#include <bits/signum.h>
#define SIG_DFL ((_sighandler_t) 0)
#define SIG_IGN ((_sighandler_t) 1)

Linux信号

  • linux可用信号都定义在bits/signum.h头文件中。
  • 主要关注的几个信号SIGHUP,SIGPIPE,SIGURG,SIGALRM,SIGCHLD
    在这里插入图片描述
    在这里插入图片描述

10.1.4 中断系统调用:errno == EINTR

  • 程序处于阻塞态的系统调用的时候,接收到信号,并且,为该信号设置了信号处理函数,那么系统调用会被中断,且errno设置为EINTR,可以使用sigaction为信号设置SA_RESTART标志,以重新启动被改信号中断的系统调用,
  • 对于默认行为是暂停进程的信号(SIGSTOP、SIGTTIN),就算没设置信号处理函数,也可以中断一些系统调用(connect,epoll_wait),这是Linux独有

10.2 信号函数

10.2.1 signal系统调用

  • 使用signal系统调用为一个信号设置处理函数
#include <signal.h>
_sighandler_t signal(int sig, _sighandler_t _handler);
  • sig参数指出捕获信号类型
  • _handler参数指定信号sig的处理函数
  • 返回值:上一次调用signal传入的函数。若是第一次,返回信号sig对应的默认处理函数。
  • 出错时返回SIG_ERR并设置errno

10.2.2 sigaction系统调用

  • 比signal更好用的是sigaction系统调用
#include <signal.h>
int sigaction(int sig, const struct sigaction* act, struct sigaction* oact);

struct sigaction{
#ifdef __USE_POSIX199309
	union{
		_sighandler_t sa_handler;
		void (*sa_sigaction) (int, siginfo_t*, void*);
	}
	_sigaction_handler;
#define sa_handler __sigaction_handler.sa_handler
#define sa_sigaction __sigaction_handler.sa_sigaction
#else
	_sighandler_t sa_handler;
#endif
	_sigset sa_mask;
	int sa_flags;
	void (*sa_restorer) (void);
}
  • sa_handler 指定信号处理函数
  • sa_mask 设置进程信号掩码,(在进程原有信号掩码上增加)以指定哪些信号不能发送给本进程。sa_mask是信号集sigset_t类型。
  • sa_flags成员用于设置程序收到信号时的行为。
  • sa_restorer已经成为历史
    在这里插入图片描述

10.3 信号集

10.3.1 信号集函数

  • sigset_t是一个长整型数组,数组的每个元素的一个位代表一个信号,和fd_set类似
#include <bits/sigset.h>
#define _SIGSET_NWORDS(1024/(8*sizeof(unsigned long int)))
typedef struct{
	unsigned long int __val[SIGSET_NWORDS];
} sigset_t
  • 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* _set, int _signo);	//将信号_signo从信号集中删除
int sigismember(sigset* _set, int _signo);	//测试_signo是否在信号集中

10.3.2 进程信号掩码

  • 设置或查看进程的信号掩码
#include <signal.h>
int sigprocmask(int how, const sigset_t* set, sigset_t* oset);
  • how指定设置进程信号掩码的方式
    在这里插入图片描述
  • set指定新的信号掩码,如果set==NULL,则进程信号掩码不变,oset就是当前进程的信号掩码
  • oset输出原来的掩码
  • 成功返回0,失败返回-1并设置errno。

10.3.3 被挂起的信号

  • 对于被掩码屏蔽的信号,进程接收到之后会设置为进程的被挂起的信号。当我没取消对挂起信号的屏蔽,那么他立刻就能被进程接收。
  • 获取当前进程被挂起的信号集
#include <signal.h>
int sigpending(sigset_t* set);
  • 注意,进程多次接收到同一个被挂起的信号,set也只能反映一个,也就是set只能反映有没有,不能反映挂起的数量。
  • 成功返回0,失败返回-1并设置errno。

10.4 统一事件源

  • 信号处理函数和程序主循环是不同的执行路线。
  • 信号在处理期间,系统不会再次触发它。所以应该尽快把信号处理函数执行完毕。
  • 典型处理方法:把信号的主要处理逻辑放在程序的主循环中,当信号处理函数触发时,信号处理函数简单的通知主循环程序接收信号,并把信号值传递给主循环,主循环根据接收到的信号值,执行目标信号对应的逻辑代码。
  • 信号处理函数通常使用管道来传递信号,使用IO复用系统监听管道读端的可读时间,这样信号时间就能和其他IO事件一起被处理。——这就是统一事件源

下边的程序有点问题

//统一事件源
#include "../create_sockfd.h"
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>

#define MAX_EVENT_NUMBER 1024
static int pipefd[2];

//信号处理函数
void sig_handler(int sig){
    //保留原来的errno,在函数最后恢复,保证函数的可重入性
    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);
}
void addfd(int epollfd, int fd){
    epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = fd;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);
}
int main(int argc, char* argv[]){
    if(argc <= 2){
        printf("Usage : %s ip port\n", basename(argv[0]));
        return 1;
    }
    createSockfd sockfd(argv[1], atoi(argv[2]));
    assert(sockfd.bindSockfd() != -1);
    assert(sockfd.listenfd(5) != -1);

    epoll_event events[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);
    assert(epollfd != -1);
    addfd(epollfd, sockfd.sockfd);

    //创建管道,并注册管道事件
    assert(socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd) != -1);
    setnonblocking(pipefd[1]);
    addfd(epollfd, pipefd[0]);

    //设置信号处理函数
    addsig(SIGHUP);
    addsig(SIGCHLD);
    addsig(SIGTERM);
    addsig(SIGINT);

    bool stop_server = false;
    while(!stop_server){
        int n = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        if(n < 0 && errno != EINTR){
            printf("epoll failure\n");
            return 1;
        }
        for(int i = 0; i != n; ++i){
            int sfd = events[i].data.fd;
            if(sfd == sockfd.sockfd){
                sockfd.connectSockfd();
                addfd(epollfd, sockfd.connfd);
            } else if((sfd == pipefd[0]) && (events[i].events & EPOLLIN)){
                int sig;
                char signals[1024];
                int 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){
                        switch (signals[i]){
                            case SIGCHLD:
                            {
                                printf("SIGCHLD\n");
                                continue;
                            }
                            case SIGHUP:{ //终端挂起
                                printf("SIGHUP\n");
                                continue;
                            }
                            case SIGTERM:{ //kill命令进程
                                printf("SIGTERM\n");
                                stop_server = true;
                                break;
                            }
                            case SIGINT:{ //ctrl+c中断进程
                                printf("SIGINT\n");
                                stop_server = true;
                            }
                        }
                        

                    }
                }
            }
        }
    }
    return 0;
}

10.5 网络编程相关信号

10.5.1 SIGHUP

  • 挂起进程的控制终端时,SIGHUP信号会被触发。
  • 没有控制终端的网络后台程序常常使用SIGHUP信号强制服务器重读配置文件。

10.5.2 SIGPIPE

  • 往一个读端关闭的管道或socket连接中写数据将引发SIGPIPE信号
  • 我们应该在代码中捕获并处理该信号,因为程序接收到SIGPIPE信号的默认行为是结束进程。

10.5.3 SIGURG

  • Linux环境下,内核通知应用程序带外数据到达主要有两种方法,一种是IO复用技术,select系统调用在接收到带外数据时候返回。如9-1所示。另一种是使用SIGURG信号。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值