Linux进程间通信三之信号与处理

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);
}


    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值