vtun 信号处理
上一篇分析了linkfd.c的lfd_linker()函数中的while在不考虑信号中断的情况下一直在执行(执行的是虚拟网卡的读写和数据的发送接收)。
而决定是否一直循环执行的linker_term变量被信号处理函数改变了,下面分析linker_term在哪些信号处理函数中被改变了,以及信号处理函数在干嘛。
在linkfd.c中,
/* Termination flag */
static void sig_term(int sig)
{
vtun_syslog(LOG_INFO, "Closing connection");
io_cancel();
linker_term = VTUN_SIG_TERM;
}
static void sig_hup(int sig)
{
vtun_syslog(LOG_INFO, "Reestablishing connection");
io_cancel();
linker_term = VTUN_SIG_HUP;
}
注意是静态函数,只能在linkfd.c中被调用;这两个函数应该是关闭连接和重连用的,看看它们具体在哪里被调用了,
linkfd.c的linkfd函数中有一段,
memset(&sa, 0, sizeof(sa));
sa.sa_handler=sig_term;
sigaction(SIGTERM,&sa,&sa_oldterm);
sigaction(SIGINT,&sa,&sa_oldint);
sa.sa_handler=sig_hup;
sigaction(SIGHUP,&sa,&sa_oldhup);
。。。。。
这里得分析sigaction函数了,sigaction函数在另一篇文章介绍。
这段代码的意思就是,当出现信号SIGTERM SIGINT SIGHUP时执行相应函数。
SIGTERM SIGINT都是结束进程,SIGHUP是挂起进程。
那么这些信号是如何产生的呢,下面分析:
举个简单的例子,当执行kill –9 pid时就会产生一个SIGTERM信号,内核将该SIGTERM信号传给PID对应的进程,进程在接收到该SIGTERM信号,执行相应的操作,结合源码,就是,
根据代码
sa.sa_handler=sig_term;sigaction(SIGTERM,&sa,&sa_oldterm);
当进程收到SIGTERM信号时,执行sa所对应的函数sig_term操作。
我们可以理解为这些信号是系统产生的,那么系统又会在什么时候产生这些信号呢,可以是由于某种原因(故障)产生,也可能就是用户操作kill –9 pid产生。
那么sig_term sig_hup函数除了改变了linker_term又干了啥呢?下面分析,
在lib.h中,
static inline void io_cancel(void)
{
__io_canceled = 1;
}
__io_canceled是lib.h中的全局变量,让它为1,会产生什么事情呢?那就得分析它在哪里被使用了,在lib.h中read_n和write_n中使用了__io_canceled,来看read_n,write_n类同,
/* Read exactly len bytes (Signal safe)*/
static inline int read_n(int fd, char *buf, int len)
{
register int t=0, w;
while (!__io_canceled && len > 0)
{
if( (w = read(fd, buf, len)) < 0 )
{
if( errno == EINTR || errno == EAGAIN )
continue;
return -1;
}
if( !w )
return 0;
len -= w; buf += w; t += w;
}
return t;
}
即__io_canceled为1时不进行读写操作,这些读写操作在哪被调用的?
最后分析得出这些读写是使用tcp传输封装的数据时被调用的,也就是说,
__io_canceled为1时不再进行tcp发送接收。
就linkfd.c的lfd_linker()函数中的while循环体来说,当有结束进程信号产生时,
不再进行tcp读写,循环体也不再执行了。
当有结束信号产生时,不仅是只执行sigaction(SIGTERM,&sa,&sa_oldterm);对应的函数,还有其他函数也和该信号想关联。
下面分析产生某个信号所关联的所有函数,也就是产生某个信号后,源码都做了什么。
分析SIGTERM信号在源码中产生的操作:
在client.c中,看黑体字部分
sa.sa_handler=sig_term;
sigaction(SIGTERM,&sa,NULL);
static void sig_term(int sig)
{
vtun_syslog(LOG_INFO,"Terminated");
client_term = VTUN_SIG_TERM;
}
那么client_term影响谁呢?看下面代码,下面的循环条件是client_term为0或者client_term==VTUN_SIG_HUP时执行循环,也就是客户端没有收到进程结束信号或者收到进程挂起信号时,执行下面循环。
该循环体是创建socket、绑定、连接、tunnel(tunnel中还有一个循环在linkfd.c文件的lfd_linker函数中,lfd_linker中的循环条件是没有收到进程结束信号和进程挂起信号时执行读写虚拟网卡,接收发送数据包操作。)。
结合client.c和linkfd.c中的循环条件,得出,当内核发出进程结束信号时,这两个循环都停止,当内核发出进程挂起状态时,linkfd.c中的循环(读写虚拟网卡和接收发送数据包)不执行,但client.c中的循环(创建socket、bind、重连等)在执行。
while( (!client_term) || (client_term == VTUN_SIG_HUP) )
{
if( reconnect && (client_term != VTUN_SIG_HUP) )
{
if( vtun.persist || host->persist )
{
/* Persist mode. Sleep and reconnect. */
sleep(5);
}
else
{
/* Exit */
break;
}
}
else
{
reconnect = 1;
}
set_title("%s init initializing", host->host);
/* Set server address */
if( server_addr(&svr_addr, host) < 0 )
continue;
/* Set local address */
if( local_addr(&my_addr, host, 0) < 0 )
continue;
/* We have to create socket again every time
* we want to connect, since STREAM sockets
* can be successfully connected only once.
*/
if( (s = socket(AF_INET,SOCK_STREAM,0))==-1 )
{
vtun_syslog(LOG_ERR,"Can't create socket. %s(%d)", strerror(errno), errno);
continue;
}
/* Required when client is forced to bind to specific port */
opt=1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if( bind(s,(struct sockaddr *)&my_addr,sizeof(my_addr)) )
{
vtun_syslog(LOG_ERR,"Can't bind socket. %s(%d)", strerror(errno), errno);
continue;
}
/*
* Clear speed and flags which will be supplied by server.
*/
host->spd_in = host->spd_out = 0;
host->flags &= VTUN_CLNT_MASK;
io_init();
set_title("%s connecting to %s", host->host, vtun.svr_name);
if (!vtun.quiet)// 常见错误就别报啦,闭嘴吧!
vtun_syslog(LOG_INFO,"Connecting to %s", vtun.svr_name);
if( connect_t(s,(struct sockaddr *) &svr_addr, host->timeout) )
{
if (!vtun.quiet || errno != ETIMEDOUT)
vtun_syslog(LOG_INFO,"Connect to %s failed. %s(%d)", vtun.svr_name, strerror(errno), errno);
}
else
{
if( auth_client(s, host) ) //认证,认证过程也可看做隧道建立过程,注意认证过程并没有走隧道,并没有封装解封。
{
vtun_syslog(LOG_INFO,"Session %s[%s] opened",host->host,vtun.svr_name);
host->rmt_fd = s;
/* Start the tunnel */
client_term = tunnel(host);
vtun_syslog(LOG_INFO,"Session %s[%s] closed",host->host,vtun.svr_name);
}
else
{
vtun_syslog(LOG_INFO,"Connection denied by %s",vtun.svr_name);
}
}
close(s);
free_sopt(&host->sopt);
}//end while
同理分析server,挂起状态,server从accept开始循环。
SIGHUP信号所对应的操作
1、在client.c 中,
sa.sa_handler=SIG_IGN;
sa.sa_flags = SA_NOCLDWAIT;
sigaction(SIGHUP,&sa,NULL);
调用进程的子进程终止。
2、在linkfd.c中,不再执行读写虚拟网卡及发送接收数据包操作,而是,返回上层执行重建socket、重连(client端),重新accept(server端)。
3、在main.c中,重读配置文件。
sa.sa_handler=reread_config;
sigaction(SIGHUP,&sa,NULL);
SIGINT信号所对应的操作
除了完成SIGTERM信号所对应的动作外,
还在server.c的server函数中完成下列操作
sa.sa_handler=SIG_IGN;
sa.sa_flags=SA_NOCLDWAIT;;
sigaction(SIGINT,&sa,NULL);
sigaction(SIGQUIT,&sa,NULL);
sigaction(SIGCHLD,&sa,NULL);
sigaction(SIGPIPE,&sa,NULL);
sigaction(SIGUSR1,&sa,NULL);
调用进程的子进程终止,防止僵尸进程。
SIGQUIT SIGCHLD SIGPIPE信号所对应的操作一样
server和client函数中。
sa.sa_handler=SIG_IGN;
sa.sa_flags=SA_NOCLDWAIT;;
sigaction(SIGINT,&sa,NULL);
sigaction(SIGQUIT,&sa,NULL);
sigaction(SIGCHLD,&sa,NULL);
sigaction(SIGPIPE,&sa,NULL);
sigaction(SIGUSR1,&sa,NULL);
SIGUSR1信号所对应的操作
linkfd.c中,
sa.sa_handler=sig_usr1
sigaction(SIGUSR1,&sa,NULL);
static void sig_usr1(int sig)
{
/* Reset statistic counters on SIGUSR1 */
lfd_host->stat.byte_in = lfd_host->stat.byte_out = 0;
lfd_host->stat.comp_in = lfd_host->stat.comp_out = 0;
}
这个信号是怎么产生的?
SIGALRM 信号所对应的操作
linkfd.c中,
sa.sa_handler=sig_alarm;
sigaction(SIGALRM,&sa,NULL);
void sig_alarm(int sig)
{
static time_t tm_old, tm = 0;
static char stm[20];
tm_old = tm;
tm = time(NULL);
if( (lfd_host->flags & VTUN_KEEP_ALIVE) && (ka_timer -= tm-tm_old) <= 0)
{
ka_need_verify = 1;
ka_timer = lfd_host->ka_interval
+ 1; /* We have to complete select() on idle */
}
if( (lfd_host->flags & VTUN_STAT) && (stat_timer -= tm-tm_old) <= 0)
{
strftime(stm, sizeof(stm)-1, "%b %d %H:%M:%S", localtime(&tm));
fprintf(lfd_host->stat.file,"%s %lu %lu %lu %lu\n", stm,
lfd_host->stat.byte_in, lfd_host->stat.byte_out,
lfd_host->stat.comp_in, lfd_host->stat.comp_out);
stat_timer = VTUN_STAT_IVAL;
}
if ( ka_timer*stat_timer )
{
alarm( (ka_timer < stat_timer) ? ka_timer : stat_timer );
}
else
{
alarm( (ka_timer) ? ka_timer : stat_timer );
}
}//end sig_alarm
上面的lfd_host->flags ,lfd_host最终对应的是main函数中的host,是从配置文件中读取的,flags也是配置文件中设置的,但是我对配置文件的信息提取还不明白,因此不知道flags的具体值,所以暂不清楚SIGALRM 完成怎样的操作。
要想搞清楚SIGALRM完成的操作,得学习配置文件的信息提取。