Linux进程间通信三之信号与处理
Linux信号是一种异步通信机制,在实现上是一种软中断。信号可以导致一正在运行的进程被打断,转而去处理一个突发事件。异步事件是不可预见的,但我们可以设定异步事件发生时完成相应的操作。
以下介绍几个常见的信号处理:
SIGCHID:当子进程退出时给父进程发送该信号。父进程可以根据该信号来完成对子进程PCB资源的回收。
SIGKILL和SIGSTOP不能被屏蔽,安装,也就是用户不能自定义这两个信号的处理。
SIGSTOP和SIGCONT是配对的。一个进程在收到SIGSTOP信号后,会暂停进程的执行,并屏蔽除SIGKILL外的所有信号。当该进程在收到SIGCONT信号后,进程会继续执行。
信号可以唤醒被中断的进程,例如,可以唤醒调用sleep()函数进入堵塞状态的进程。
信号的基本概念:
发送信号:产生信号,有多种发送信号的方式。一个进程可以向另一个进程发送一个信号,内核可以向一个进程发送一个信号,进程也可以向自己发送一个信号。
安装中断:设置信号到来时不再执行默认的操作,而是执行自定义的代码。也就是信号到来时进程执行中断服务子程序。
递送信号:一个信号被操作系统送到目标进程。
捕获信号:被递送的信号在目标进程引起某段处理程序的执行。
屏蔽信号:操作系统暂时不接收某些信号。
忽略信号:信号被送到目标进程后,目标进程不处理,直接舍弃。
未决信号:信号已经产生,但因目标进程屏蔽该信号使该信号不能被目标进程捕获的信号。
可靠信号与不可靠信号:编号小于32的为不可靠信号,大于32的为可靠信号。在进程屏蔽某个信号的时间内,其他进程多次向其发送同一个信号,不可靠信号只有一次未决记录,当进程解除对该信号的屏蔽后,该信号只会被捕获一次;可靠信号会被操作系统记录所有的发送,当进程解除对该信号的屏蔽后,操作系统会捕获对等次数。
信号的屏蔽是一个开关动作,暂时阻止该信号的处理,但不能阻止信号的产生。
发送信号:
发送信号是指一个进程向另一个进程发送某个信号,但不是直接发送的而是由操作系统转发的。
1.kill发送一个信号到进程
传递一个信号给指定的进程应使用kill()函数,传递一个信号给当前进程则使用raise()函数,唤醒一个进程和设置定时使用alarm函数。
int kill(pid_t pid,int sig)
第一个参数为要传递信号的进程号(PID),第二个参数为要发送的信号值。
pid可以取值为:
1)pid>0:将信号发送给进程的PID值为pid的进程。
2)pid=0:将信号发送给和当前进程在同一进程组的所有进程。
3)pid=-1:将信号发送给系统内的所有进程。
4)pid<0:将信号发送给进程组号PGID为pid绝对值的所有进程。
执行成功返回0,失败返回-1,并设置errno以指示错误。
向某个进程发送kill -0可以监测信号是否存在,如果该进程存在,则返回0.
2.raise自举一个信号
用来给当前进程发送一个信号,即唤醒一个进程。
int raise(int sig)
发送成功返回0,失败返回-1
3.alarm()定时
alarm()函数用来传递定时信号,即多少时间内产生SIGALRM信号。此函数每调用一次,产生一个信号,并不是循环产生SIGALRM信号。
int alarm(unsigned int seconds)
多少秒内发送SIGALRM信号给当前进程,默认情况下,当进程收到SIGALRM信号后将终止执行。
如果seconds=0,则取消所有先前发出的报警请求。
如果在调用alarm()函数前没有调用过alarm()函数,则执行成功返回0,否则返回-1,并设置errno标识错误。
如果在此前调用过alarm()函数,则将重新设置调用进程的闹钟。如果执行成功,将以当前时间为基准,返回值为上次设置的alarm()将在多少时间内产生SIGALRM信号。 否则返回-1,并设置errno标识错误。
子进程并不继承在父进程中设置的alarm信号,但在调用exec()执行新代码时,原来设置的alarm信号仍然有效。
信号处理办法:
1)忽略此信号。2)自定义捕捉信号方式.3)执行系统默认操作。
signal信号安装:
函数signal()声明 :signal(int _sig,_sighandler_t _handler);
第一个参数为接受到的信号,第二个参数为接受到此信号后的处理代码入口或下面几个宏:SIG_ERR 返回错误 SIG_DFL 执行系统默认操作 SIG_IGN 忽略该信号
当进程接受到此信号后,将执行handler函数,在某些版本的UNIX系统中,在执行完一次信号处理后有可能需要再次执行此信号的安装。建议在handler函数中再次安装信号处理函数。
signal()函数实例程序如下:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void sig_usr(int sig);
int main(int argc,char* argv[])
{
int i=0;
if(signal(SIGUSR1,sig_usr)==SIG_ERR)
printf("cannot catch SIGUSR1\n");
if(signal(SIGUSR2,sig_usr)==SIG_ERR)
printf("cannot catch SIGUSR2\n");
while(1)
{
printf("%2d\n",i);
pause();
i++;
}
return 0;
}
void sig_usr(int sig)
{
if(sig==SIGUSR1)
printf("Received SIGUSR1\n");
else if(sig==SIGUSR2)
printf("Received SIGUSR2\n");
else
printf("Undeclared signal %d\n",sig);
}
sigaction信号安装:
函数signal()只能提供简单的信号安装操作,并逐渐被淘汰,Linux提供了功能更加强大的sigaction安装信号。
函数原型如下
int sigaction(int _sig,struct sigaction *_act,struct sigaction *_oact)
此函数第一个参数为接受到的信号,第二个参数用来设置预设定的信号处理方式,第三个参数将存储执行此函数前针对此信号的安装信息。
如果参数act为空指针,则信号处理保持不变。因此,可以查询指定信号的安装方式。
结构体struct sigaction详细规定了信号处理函数和信号标识等信息。
执行成功后,返回0,否则返回-1,并设置errno以表示该错误。
sigaction()函数简单的示例程序:
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void myHandler(int sig);
int main(int argc,char* argv[])
{
struct sigaction act,oact;
act.sa_handler=myHandler;//设置信号捕获函数
/*设置掩码为空,即信号捕获函数执行期间没有屏蔽的信号*/
sigemptyset(&act.sa_mask);
act.sa_flags=0;
sigaction(SIGUSR1,&act,&oact);
while(1)
{
printf("hello world\n");
pause();
}
}
void myHandler(int sig)
{
printf("I got signal:%d.\n",sig);
}
安装信号与捕获信号:
信号是可以被屏蔽的,但这与信号忽略不同。
信号忽略:系统仍然传递该信号,只是相应进程对该信号不做处理;
信号屏蔽:即使传递信号给该进程,该进程也不捕获信号,而是使该信号处于未决状态。当进程的屏蔽信号集改变,不再屏蔽该信号时,才捕获该信号。
因为一个进程可能屏蔽多个信号,为了便于管理,Linux使用信号集的概念来管理多个信号。
等待信号:
pause()函数用来等待当前进程屏蔽集合外的任意信号,而siguspend()函数用来等待指定信号以外的任意信号。
pause()函数将进程挂起,直到接受到一个信号后才重新恢复执行,其始终返回-1,并设置errno变量为EINTR。
siguspend()函数将当前进程屏蔽的信号集替换为其参数所指定的信号集合,直到收到一个非指定集合的信号后继续执行。当收到某个信号后,进程屏蔽的信号将自动恢复到原来屏蔽的集合。不能屏蔽无法忽略的信号SIGKILL和SIGSTOP。
信号应用实例:
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
int count;//当前复制大小
int file_size;//文件大小,因在中断无法传递普通参数,故用全局变量
void sig_alarm(int arg);
void sig_usr(int sig);//处理普通信号SIGUSR1
int main(int argc,char *argv[])
{
pid_t pid;
int i;
int fd_src,fd_des;
char buf[128];
if(argc!=3)
{
printf("check the format:comn src_file des_file\n");
return -1;
}
if((fd_src=open(argv[1],O_RDONLY))==-1)
{
perror("open file src");
exit(EXIT_FAILURE);
}
file_size=lseek(fd_src,0,SEEK_END);
lseek(fd_src,0,SEEK_SET);
if((fd_des=open(argv[2],O_RDWR|O_CREAT,0644))==-1)
{
perror("open fd_des");
exit(EXIT_FAILURE);
}
if((pid=fork())==-1)
{
perror("fork");
exit(EXIT_FAILURE);
}
else if(pid>0)
{
signal(SIGUSR1,sig_usr);
do
{
memset(buf,'\0',128);
if((i=read(fd_src,buf,1))==-1)
{
perror("read");
exit(EXIT_FAILURE);
}
else if(i==0)
{
kill(pid,SIGINT);
break;
}
else
{
if(write(fd_des,buf,i)==-1)
{
perror("write");
exit(EXIT_FAILURE);
}
count+=i;//更新已经复制的大小
}
}
while(i!=0);
wait(pid,NULL,0);//等待子进程退出
exit(EXIT_SUCCESS);
}
else if(pid==0)
{
usleep(1);
signal(SIGALRM,sig_alarm);
ualarm(1,1);
while(1)
{
;
}
exit(EXIT_SUCCESS);
}
}
void sig_alarm(int arg)
{
kill(getppid(),SIGUSR1);
}
void sig_usr(int sig)
{
float i;
i=(float)count/(float)file_size;
printf("current over :0.0f%%\n",i*100);
}