Linux下的信号处理

最基本的信号处理函数signal

函数原型:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • signum参数:传入的信号类型
  • handler参数:处理信号的函数,函数接受一个整型参数,用于表示信号;函数的返回值是void类型。在函数内部编写处理信号的方法。

代码实例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void sig_handler(int signo) {
  if (signo == SIGINT) {
    puts("received SIGINT");
  }
}

int main() {
  if (signal(SIGINT, sig_handler) == SIG_ERR) {
    puts("can not catch SIGINT");
  }

  while (1) { 
    sleep(1);
  }

  return 0;
}

sigaction函数

signal已经不推荐使用,现在更加推荐的是该函数
函数原型:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
              struct sigaction *oldact);
  • signum:信号的类型。
  • act:安装信号,并接受到信号后,采取的行动。
  • oldact

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_sigaction也是类似的函数;在有些架构中,这两者使用了union结构,因此最好不要同时使用;一般使用sa_handler函数即可。sa_restorer可以暂时不用管它的用处。sg_mask是掩码,用于声明需要屏蔽的信号集。sa_flags是一个信号的标志集合,指明了处理信号的行为,具体参照手册。

代码实例:

#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

void sig_handler(int sig) {
  if (sig == SIGINT) {
    puts("received SIGINT");
  }
}

int main() {
  struct sigaction ac;
  memset(&ac, 0, sizeof(ac));
  ac.sa_handler = sig_handler;
  
  if (sigaction(SIGINT, &ac, NULL) < 0) {
    perror("sigaction() error\n");
  }

  while (1) {
    sleep(1);
  }

  return 0;
}

使用信号集

信号集函数类似于selectfd_set,是一组信号,本质上是一个长整型数组,元素的每一位表示一个信号,通过使用系统内置的一组函数来进行操作。

#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);  // 测试信号是否在_set中
// 设置或者查看进程的掩码,操作类型由how决定,_set是新的掩码,_oset是之前的掩码
int sigprocmask(int _how, const sigset_t* _set, int _oset);  

以下内容来自Linux高性能服务器编程
信号处理函数和程序的主循环不是一个路线,在程序设计的时候,最好是尽快处理信号,避免出现信号被忽略的情况。因为信号在处理期间,系统不会再次触发它。我们一般把信号处理的逻辑放到主循环中,处理信号函数被触发的时候,只是通知主循环接收信号,并传递相应的信号值。一般使用管道机制把信号传递给主循环,处理函数在管道的入口写信号,主循环在管道的出口读信号,之后调用I/O复用函数来监听文件描述符即可。

补充说明:Linux的每个占用一个字节,等价一个char类型长度,即使我们传入参数是整型,但是在处理的时候,还是要按照字节进行计算。

统一事件源代码实例。

#include <stdio.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <strings.h>
#include <errno.h>
#include <signal.h>

#define MAX_EVENT_NUMBER 1024
#define MAX_WAIT_NUMBER 32
#define MAX_SIGNAL_NUMBE 1024

static int pipefd[2];

// fd设置为非阻塞模式
int setnonblocking(int fd) {
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    assert(fcntl(fd, F_SETFL, new_option) != -1);
    return old_option;
}

// epoll注册事件
void addfd(int epollfd, int fd) {
    epoll_event event;
    bzero(&event, sizeof(event));
    event.data.fd = fd;
    event.events = EPOLLET | EPOLLIN;
    assert(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) != -1);
}

// 信号处理函数,把信号发给主循环
void sig_handler(int sig) {
    int save_errno = errno;
    int msg = sig;
    assert(send(pipefd[1], (char*)&msg, sizeof(char), -1) != -1);
    errno = save_errno;
}

void addsig(int sig) {
    struct sigaction sa;
    bzero(&sa, sizeof(sa));
    sa.sa_handler = sig_handler;
    sa.sa_flags = SA_RESTART;
    assert(sigaction(sig, &sa, NULL) != -1);
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        printf("Usage: %s <port of server>\n", argv[0]);
        return 1;
    }

    int port = atoi(argv[1]);
    if (port < 1024 || port > 65535) {
        perror("port error\n");
        return 1;
    }

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_addr.s_addr = htonl(INADDR_ANY);
    address.sin_port = htons(port);
    address.sin_family = SOCK_STREAM;

    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    if (listenfd < 0) {
        perror("sock() error\n");
        return 1;
    }
    setnonblocking(listenfd);  // 非阻塞模式的监听

    int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
    if (ret < 0) {
        perror("bind() error\n");
        close(listenfd);
        return 1;
    }

    ret = listen(listenfd, MAX_WAIT_NUMBER);
    if (ret < 0) {
        perror("listen() error\n");
        close(listenfd);
        return 1;
    }

    int epollfd = epoll_create1(0);
    if (epollfd < 0) {
        perror("epoll_create1() error\n");
        close(listenfd);
        return 1;
    }

    epoll_event events[MAX_EVENT_NUMBER];
    bzero(events, sizeof(events));
    addfd(epollfd, listenfd);

    // 这里添加感兴趣的信号
    addsig(SIGINT);
    addsig(SIGCHLD);

    ret = socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd);
    if (ret < 0) {
        perror("socketpair() error\n");
        close(listenfd);
        close(epollfd);
        return 1;
    }

    bool stop_server = false;
    while (!stop_server) {
        int event_num = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        if (ret < 0) {
            perror("epoll_wait() error\n");
            close(listenfd);
            close(epollfd);
            return 1;
        }

        for (int i = 0; i < event_num; ++i) {
            if (events[i].data.fd == listenfd) { // 有新的连接
                struct sockaddr_in client_address;
                bzero(&client_address, sizeof(client_address));
                socklen_t client_addrlen = sizeof(client_address);
                // 注意非阻塞accept的典型的连接方式
                int connfd = 0;
                while((connfd = accept(listenfd, (struct sockaddr*)&client_address, \
                                       &client_addrlen)) > 0) {
                    addfd(epollfd, connfd);
                }
            } else if (events[i].data.fd == pipefd[0] && events[i].events & EPOLLIN) {
                char signals[MAX_SIGNAL_NUMBE];
                bzero(signals, sizeof(signals));
                ret = recv(pipefd[0], signals, sizeof(signals), 0);
                if (ret < 0) {
                    perror("recv() error\n");
                    continue;
                } else if (ret == 0) {
                    continue;
                } else {   // 处理信号类型
                    switch (signals[i]) {
                    // 匹配并处理有关信号
                    }
                }
            } else {
                // 在这里处理其他epoll事件 
            }
        }
    }

    printf("server stop\n");
    close(epollfd);
    close(listenfd);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值