signal()的缺陷1 :当多个信号共用一个信号处理函数的时候,可能会发生重入,导致段错误。所以我们希望,在响应一个信号的时候,将其他信号阻塞。 sigaction()可以弥补
signal()的缺陷2 :不能指定接收信号来源, sigaction()可以弥补
NAME
sigaction, rt_sigaction - examine and change a signal action
SYNOPSIS
#include <signal.h>
对 signum 信号,定义新的响应act,并且保存该信号旧的响应oldact
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
The sigaction structure is defined as something like:
struct sigaction {
void (*sa_handler)(int); //响应函数 多信号共用,避免重入现象
void (*sa_sigaction)(int, siginfo_t *, void *);//三参响应函数,sa_sigaction同样可以多信号共用,siginfo_t 信号来源识别
sigset_t sa_mask; //需要阻塞的其他信号集合
int sa_flags;
void (*sa_restorer)(void);
};
两者取其一,信号的响应处理,都可以多信号共用,避免重入现象。区别在于 后者可以指定 信号来源。
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */ 信号值
.....
}
si_code指定信号来源:
SI_USER
kill(2).
SI_KERNEL
Sent by the kernel.
...
signal()的缺陷1 试验:当多个信号共用一个信号处理函数的时候,可能会发生重入
以前面的守护进程为例:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <fcntl.h>
#define FILENAME "/tmp/out"
static int craetdeamon(void)
{
pid_t pid;
int fd;
pid = fork();
if(pid < 0)
{
perror("fork()");
return -1;
}
if(pid > 0) //父进程结束
{
printf("%d\n",getpid());
exit(0);
}
fd = open("/dev/null",O_RDWR);
if(fd < 0)
{
perror("open()");
return -1;
}
//重定向 0 1 2 文件描述符
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
if(fd > 2)
{
close(fd);
}
//创建守护进程
setsid();
chdir("/");
return 0;
}
int main(int argc,char* argv[])
{
FILE* fp;
int i;
if(craetdeamon())
{
exit(1);
}
fp = fopen(FILENAME,"w");
if(fp == NULL)
{
perror("fopen()");
exit(1);
}
for(i = 0; ;i++)
{
fprintf(fp,"%d\n",i);
fflush(fp);
sleep(1);
}
exit(0);
}
该程序并不完善,因为该守护进程 已经脱离了控制终端,符合守护进程的特点,该程序只能异常终止。即 kill(),那么作如下修改:
添加信号处理,接受特定信号,终止守护进程,伪代码:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <syslog.h>
#define FILENAME "/tmp/out"
static int craetdeamon(void)
{
pid_t pid;
int fd;
pid = fork();
if(pid < 0)
{
perror("fork()");
return -1;
}
if(pid > 0)
{
printf("%d\n",getpid());
exit(0);
}
fd = open("/dev/null",O_RDWR);
if(fd < 0)
{
perror("open()");
return -1;
}
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
if(fd > 2)
{
close(fd);
}
setsid();return
chdir("/");
return 0;
}
static void exitdeamon(int s)
{
fclose(fb);
closelog();
}
int main(int argc,char* argv[])
{
FILE* fp;
int i;
signal(SIGINT,exitdeamon);
signal(SIGQUIT,exitdeamon);
signal(SIGTERM,exitdeamon);
openlog("craetdeamon",LOG_PID,LOG_DAEMON);
if(craetdeamon())
{
syslog(LOG_ERR,"craetdeamon failed!");
exit(1);
}else{
syslog(LOG_INFO,"craetdeamon successded!");
}
fp = fopen(FILENAME,"w");
if(fp == NULL)
{
syslog(LOG_ERR,"fopen %s failed!",FILENAME);
exit(1);
}
syslog(LOG_INFO,"fopen %s successede!",FILENAME);
for(i = 0; ;i++)
{
fprintf(fp,"%d\n",i);
fflush(fp);
syslog(LOG_DEBUG,"%d is printed!",i);
sleep(1);
}
exit(0);
}
程序的本意是,守护进程 接收到 SIGINT,SIGQUIT,SIGTERM 三个信号任意信号 的时候终止守护进程,多个信号共用一个信号处理函数。但是有一个问题:
接收到这三个信号中的任意信号,都会执行处理函数中内容,假如有这样的情形:
1 程序接受到了 SIGINT 信号,程序收到中断后扎内核,被调度后,从内核态切换到用户态,发现 收到了 SIGINT信号,于是开始执行处理函数,但是只执行了 fclose(fb); 这一句,就再次被打断,进程内核态,等待调度。
2 在等待调度的时候,程序又收到了 SIGQUIT 信号,等到程序被调度,从内核态切换到用户态时候,发现收到了 SIGQUIT信号,于是再次 执行 信号处理函数,再次执行到 fclose(fb);。于是fb 被两次 fclose(),会发生段错误。发生了重入!!
用 sigaction() 实现 多信号共用一个处理函数。
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <syslog.h>
#include <signal.h>
#define FILENAME "/tmp/out"
static FILE* fp;
static int craetdeamon(void)
{
pid_t pid;
int fd;
pid = fork();
if(pid < 0)
{
perror("fork()");
return -1;
}
if(pid > 0)
{
printf("%d\n",getpid());
exit(0);
}
fd = open("/dev/null",O_RDWR);
if(fd < 0)
{
perror("open()");
return -1;
}
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
if(fd > 2)
{
close(fd);
}
setsid();return
chdir("/");
return 0;
}
static void exitdeamon(int s)
{
fclose(fp);
closelog();
}
int main(int argc,char* argv[])
{
int i;
struct sigaction sa;
sa.sa_handler = exitdeamon;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask,SIGQUIT);
sigaddset(&sa.sa_mask,SIGTERM);
sigaddset(&sa.sa_mask,SIGINT);
sigaction(SIGINT,&sa,NULL);
sigaction(SIGTERM,&sa,NULL);
sigaction(SIGQUIT,&sa,NULL);
openlog("craetdeamon",LOG_PID,LOG_DAEMON);
if(craetdeamon())
{
syslog(LOG_ERR,"craetdeamon failed!");
exit(1);
}else{
syslog(LOG_INFO,"craetdeamon successded!");
}
fp = fopen(FILENAME,"w");
if(fp == NULL)
{
syslog(LOG_ERR,"fopen %s failed!",FILENAME);
exit(1);
}
syslog(LOG_INFO,"fopen %s successede!",FILENAME);
for(i = 0; ;i++)
{
fprintf(fp,"%d\n",i);
fflush(fp);
syslog(LOG_DEBUG,"%d is printed!",i);
sleep(1);
}
exit(0);
}
不会发生重入现象
signal()缺陷2:不能识别信号来源
回顾之前的漏桶实例:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#define BUFSIZE 10
static volatile int loop = 0;
static void alrm_handler(int s)
{
alarm(1);//重新定时
loop = 1;
}
int main(int argc,char *argv[])
{
int sfd,dfd=1;
char buf[BUFSIZE];
int len,ret,pos;
if(argc < 2)
{
fprintf(stderr,"Usage:%s <src_file> <dest_file>\n",argv[0]);
exit(1);
}
signal(SIGALRM,alrm_handler);
alarm(1);
do
{
sfd = open(argv[1],O_RDONLY);
if(sfd < 0)
{
if(errno != EINTR)//防止是 信号打断阻塞的系统调用
{
perror("open()");
exit(1);
}
}
}while(sfd < 0);
while(1)
{
//休眠挂起 直到收到信号,重新开始执行while(!loop)循环,实现一秒一输出
// 这里也可以 不用pause(),while()后 执行空,但是这样 CPU 占用率会很高,一秒钟会在这里执行循环上亿次,所以用pause()替换,直接休眠等待信号来唤醒
/*
while(!loop)
;
*/
while(!loop)
pause();
loop = 0;
while((len = read(sfd,buf,BUFSIZE)) < 0)
{
if(errno == EINTR)//防止是 信号打断阻塞的系统调用
continue;
perror("read()");
break;
}
if(len == 0)
break;
//确保写进去 len 个字节
pos = 0;
while(len > 0)
{
ret = write(dfd,buf+pos,len);
if(ret < 0)
{
if(errno == EINTR) //防止是 信号打断阻塞的系统调用
continue;
perror("write()");
exit(1);
}
pos += ret;
len -= ret;
}
}
close(sfd);
}
在终端1 运行
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ ./a.out /etc/services
在终端2 运行
mhr@ubuntu:~/Desktop/xitongbiancheng/parallel/signal/test$ while true ; do kill -ALRM 10405 ;done
会发现 程序会瞬间执行完成。即 从其他终端 以用户的角度 向指定进程 不停的发送 ALRM 信号,导致流控失效。问题在于 signal() 并不会检查区分 信号的来源,属性信息,只要来了该信号,就会响应动作。但是实际上程序利用 alarm来发送信号,实际上 alarm信号是从 kernel 发送过来的,而刚刚的实验信号,是从 user 发送的。所以需要指定只响应从某处来的 信号,即指定信号来源,指定从 kernel来的信号,signal() 无法完成该动作,sigaction()可以。
实例 待补充。。。。。