linux C语言使用IO复用处理信号
一、为什么要使用IO复用的方式管理信号?
-
考虑这样一个场景,事实上经常会遇到这种场景。
-
设备中存在一个守护进程A。
-
设备中存在一个用户进程,B。B在某些情况下需要重启日志服务。
-
如果要B自己来处理这个需求,必须使用系统调用。system();函数。
-
使用system函数重启了日志服务之后,日志服务进程的父进程就是B。此时,B因为其它的原因,需要重新启动,重新加载参数。
-
此时,B死亡前,可能没法处理日志服务。这种做法是十分不合理的。一个服务的父进程,不应该是一个随时可能产生变化的用户进程。
-
因此,希望使用守护进程A来处理这个问题。也就是B发送信号或者是网络消息给A,让A来做这件事。
-
重启某个服务的例子是个例,可能有很多其它的情况。
-
-
守护进程可能会需要处理各种各样来自其它进程的消息。
-
可能是信号。
-
可能是网络信息。
-
可能是自己需要一个超时信息。
-
-
这种时候,就需要使用IO复用的方法来管理。
- 如果多个其它进程同时发送多个信号给守护进程A,将不好管理。不好处理并发。
二、如何管理
-
使用管道处理信号。
#include <stdlib.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <strings.h> #include <unistd.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <getopt.h> #include <errno.h> #include <sys/types.h> #include <syslog.h> #include <signal.h> #include <stdint.h> #include<netinet/in.h> #include <time.h> #include <netdb.h> #include <sys/socket.h> #include <arpa/inet.h> #include<regex.h> #define RENYJ_TRACE( format, args... )do {fprintf(stderr, "RENYJ_TRACE >>> %s->%s()->line.%d : " format "\n", __FILE__, __FUNCTION__, __LINE__, ##args);}while(0) static struct fd_pair signal_pipe; struct fd_pair { int rd; int wr; }; #define piped_pair(pair) pipe(&((pair).rd)) #define xpiped_pair(pair) xpipe(&((pair).rd)) static void signal_handler(int sig) { unsigned char ch = sig; /* use char, avoid dealing with partial writes */ if (write(signal_pipe.wr, &ch, 1) != 1) { RENYJ_TRACE("cannot send signal"); } RENYJ_TRACE("Write Sig %d",ch); } int sig_setup() { xpiped_pair(signal_pipe); close_on_exec_on(signal_pipe.rd); close_on_exec_on(signal_pipe.wr); ndelay_on(signal_pipe.wr); signal(SIGUSR1, signal_handler); signal(SIGALRM,signal_handler); signal(SIGUSR2,signal_handler); return 0; } int safe_read(int fd, void *buf, size_t count) { int n; do { n = read(fd, buf, count); } while (n < 0 && errno == EINTR); return n; } int sig_pip_read(const fd_set *rfds) { unsigned char sig; if (!FD_ISSET(signal_pipe.rd, rfds)) return 0; if (safe_read(signal_pipe.rd, &sig, 1) != 1) return -1; return sig; } int sig_pip_set(fd_set *rfds, int extra_fd) { FD_ZERO(rfds); FD_SET(signal_pipe.rd, rfds); if (extra_fd >= 0) { close_on_exec_on(extra_fd); FD_SET(extra_fd, rfds); } return signal_pipe.rd > extra_fd ? signal_pipe.rd : extra_fd; } void xpipe(int filedes[2]) { if (pipe(filedes)) { RENYJ_TRACE("Can't create pipe"); exit(-1); } } int close_on_exec_on(int fd) { return fcntl(fd, F_SETFD, FD_CLOEXEC); } int ndelay_on(int fd) { return fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK); } int ndelay_off(int fd) { return fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) & ~O_NONBLOCK); } int do_wait_ns(int s) { while(s--) { RENYJ_TRACE("waiting s=%d",s); sleep(1); } return 0; } int main(int argc, char *argv[]) { int ret = -1; struct timeval tv; int max_fd = 0; int cur_sig = -1; fd_set rfds; sig_setup(); while(1) { max_fd = sig_pip_set(&rfds,-1); ret = select(max_fd + 1, &rfds, NULL, NULL, NULL); if (ret < 0) { if (errno == EINTR) continue; } cur_sig = sig_pip_read(&rfds); switch(cur_sig) { case SIGUSR1: { RENYJ_TRACE("SIG_IO Recieve SIGUSR1"); do_wait_ns(5); break; } case SIGUSR2: { RENYJ_TRACE("SIG_IO Recieve SIGUSR2"); do_wait_ns(5); break; } case SIGALRM : { RENYJ_TRACE("SIG_IO Recieve SIGALRM"); do_wait_ns(5); break; } default: break; } } }
-
解释一波
- 主循环使用select,可以用来监听网络socket,超时,还有信号。
- 接收到信号之后,优先调用 signal_handler 函数写入到管道中。
- select 会依次监听管道中的数据是否可读来一一处理短时间之内收到的全部信号。
-
效果
- 看到select 可以依次处理管道中的信号。
-
这里有个问题。
-
信号发送的顺序并不一定是处理的顺序。
-
比如我发送的顺序可能是这样的
但是实际的顺序可能是这样的
所以,如果希望做两件事情,都要使用信号,并且对两件事情的顺序有要求,就不要使用这种方法。
-
三、参考文献
busybox 中 dhcp 相关源码。