dhcp源码中也包含了信号的处理,现在单独拿出来看看代码的逻辑架构,分析下怎么样写一个合理的信号处理。、
先看signal.c里面的实现,如下:
#include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <signal.h> #include <string.h> #include <syslog.h> #include <unistd.h> #include "common.h" #include "signals.h" static int signal_pipe[2]; //定义pipe通信 static const int handle_sigs[] = { //信号数组的定义 SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGTERM, SIGUSR1, }; //将信号写入管道 static void signal_handler(int sig) { int serrno = errno; if (write(signal_pipe[1], &sig, sizeof(sig)) != sizeof(sig)) syslog(LOG_ERR, "failed to write signal %d: %m", sig); /* Restore errno */ errno = serrno; } //从管道读入信号 /* Read a signal from the signal pipe. Returns 0 if there is * no signal, -1 on error (and sets errno appropriately), and * your signal on success */ int signal_read(void) { int sig = -1; char buf[16]; ssize_t bytes; memset(buf, 0, sizeof(buf)); bytes = read(signal_pipe[0], buf, sizeof(buf)); if (bytes >= 0 && (size_t)bytes >= sizeof(sig)) memcpy(&sig, buf, sizeof(sig)); return sig; } /* Call this before doing anything else. Sets up the socket pair * and installs the signal handler */ int signal_init(void) { //定义pipe进程间通信方式,并设置为不阻塞以及防止任何脚本继承pipe if (pipe(signal_pipe) == -1) return -1; /* Don't block on read */ if (set_nonblock(signal_pipe[0]) == -1) return -1; /* Stop any scripts from inheriting us */ if (set_cloexec(signal_pipe[0]) == -1) return -1; if (set_cloexec(signal_pipe[1]) == -1) return -1; // 返回读pipe return signal_pipe[0]; } static int signal_handle(void (*func)(int)) { unsigned int i; struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = func; sigemptyset(&sa.sa_mask); //遍历上面信号数组,一旦由上述的信号产生,就会进入到上面的signal_handler里面,注意这里没有屏蔽信号,会中断信号的过程
if (sigaction(handle_sigs[i], &sa, NULL) == -1)return -1;return 0;}intsignal_setup(void){for (i = 0; i < sizeof(handle_sigs) / sizeof(handle_sigs[0]); i++)
//传入信号处理handler函数,见上面的定义return signal_handle(signal_handler);
}
//信号默认操作,恢复信号原始操作方式intsignal_reset(void){return signal_handle(SIG_DFL);} 接下来,继续看dhcp的主体函数在dhcpcd.c里面如下:在main函数里面,如下,
if ((signal_fd = signal_init()) == -1) exit(EXIT_FAILURE); if (signal_setup() == -1) exit(EXIT_FAILURE); add_event(signal_fd, handle_signal, NULL);
先初始化信号,然后设置信号的hanler函数,先看下面的代码流程,
* ARGSUSED */ static void handle_signal(_unused void *arg) { struct interface *ifp, *ifl; struct if_options *ifo; int sig = signal_read(); int do_release, do_rebind, i; do_rebind = do_release = 0; switch (sig) { case SIGINT: syslog(LOG_INFO, "received SIGINT, stopping"); break; case SIGTERM: syslog(LOG_INFO, "received SIGTERM, stopping"); break; case SIGALRM: #ifdef ANDROID syslog(LOG_INFO, "received SIGALRM, renewing"); for (ifp = ifaces; ifp; ifp = ifp->next) { start_renew(ifp); } #else syslog(LOG_INFO, "received SIGALRM, rebinding"); for (i = 0; i < ifac; i++) free(ifav[i]); free(ifav); ifav = NULL; ifac = 0; for (i = 0; i < ifdc; i++) free(ifdv[i]); free(ifdv); ifdc = 0; ifdv = NULL; ifo = read_config(cffile, NULL, NULL, NULL); add_options(ifo, margc, margv); /* We need to preserve these two options. */ if (options & DHCPCD_MASTER) ifo->options |= DHCPCD_MASTER; if (options & DHCPCD_DAEMONISED) ifo->options |= DHCPCD_DAEMONISED; options = ifo->options; free_options(ifo); reconf_reboot(1, ifc, ifv, 0); #endif return; case SIGHUP: syslog(LOG_INFO, "received SIGHUP, releasing"); do_release = 1; break; case SIGUSR1: syslog(LOG_INFO, "received SIGUSR, reconfiguring"); for (ifp = ifaces; ifp; ifp = ifp->next) if (ifp->state->new) configure(ifp); return; case SIGPIPE: syslog(LOG_WARNING, "received SIGPIPE"); return; default: syslog(LOG_ERR, "received signal %d, but don't know what to do with it", sig); return; } //后面与handler无关的先不用管 }
上面的函数很重要,看原型,在eloop.c里面,void
add_event(int fd, void (*callback)(void *), void *arg)
代码实现是:对fd进行相应的poll检测,如果由fd数据到达,就会进入到相应的回调函数里面
因此,当sig来的时候,步骤如下:
1)当sig发生的时候,进入到捕获函数里面,也就是:signal_handler
然后往里面写sig号,即:write(signal_pipe[1], &sig, sizeof(sig)) != sizeof(sig)
2)由于此时event机制死循环,检测到fd发生了变化,于是去读取相应的sig
然后调到了handle_signal
3)最后,通过signal_read 去读取相应的值,这样就实现了整套signal机制。
不过这里有个地方,和平时使用pipe不一样,没有fork进程,直接同一个进程通信,
恩,下次下一个demo看下!
源码第二个使用signal的地方是:configure.c里面,真正执行脚本的时候,
见下面:
static int exec_script(char *const *argv, char *const *env) { pid_t pid; sigset_t full; sigset_t old; /* OK, we need to block signals */ sigfillset(&full); sigprocmask(SIG_SETMASK, &full, &old); signal_reset();//使用默认的操作 switch (pid = vfork()) { case -1: syslog(LOG_ERR, "vfork: %m"); break; case 0: //复原了信号,然后执行脚本 sigprocmask(SIG_SETMASK, &old, NULL); execve(argv[0], argv, env); syslog(LOG_ERR, "%s: %m", argv[0]); _exit(127); /* NOTREACHED */ } /* Restore our signals */ signal_setup();//开始信号捕捉函数 sigprocmask(SIG_SETMASK, &old, NULL); return pid; }
这里就使用了signal屏蔽码的用法,需要学习下用法就行了。