5、Linux程序设计入门--信号处理


Linux下的信号事件

前言:这一章我们讨论一下Linux下的信号处理函数.

在学习之前可以学习下管道相关的知识:http://blog.csdn.net/ll2323001/article/details/7392318

Linux下的信号处理函数:
1、信号的产生
2、信号的处理
3、其它信号函数

4、一个实例

==============================================================================================================
1、信号的产生
        Linux下的信号可以类比于DOS下的INT或者是Windows下的事件.在有一个信号发生时候相信的信号就会发送给相应的进程.在Linux下的信号有以下几个. 我们使用kill -l命令可以得到以下的输出结果:
1) SIGHUP               2) SIGINT         3) SIGQUIT          4) SIGILL
5) SIGTRAP             6) SIGABRT     7) SIGBUS            8) SIGFPE
9) SIGKILL             10) SIGUSR1   11) SIGSEGV      12) SIGUSR2
13) SIGPIPE          14) SIGALRM   15) SIGTERM      17) SIGCHLD
18) SIGCONT        19) SIGSTOP   20) SIGTSTP       21) SIGTTIN
22) SIGTTOU         23) SIGURG     24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF   28) SIGWINCH   29) SIGIO
30) SIGPWR
        关于这些信号的详细解释请查看man 7 signal的输出结果.信号事件的发生有两个来源:一个是硬件的原因(比如我们按下了键盘),一个是软件的原因(比如我们使用系统函数或者是命令发出信号). 最常用的四个发出信号的系统函数是kill, raise, alarm和setitimer函数 . setitimer函数我们在计时器的使用 那一章再学习.
[cpp]  view plain copy
  1. #include <sys/types.h>  
  2. #include <signal.h>  
  3. #include <unistd.h>  
  4. int kill(pid_t pid,int sig);  
  5. int raise(int sig);  
  6. unisigned int alarm(unsigned int seconds);  

kill系统调用负责向进程发送信号sig .
如果pid是正数,那么信号sig被发送到进程pid.

如果pid等于0,那么信号sig被发送到和pid进程在同一个进程组的进程;

如果pid等于-1,那么信号发给所有的进程表中的进程,除了最大的那个进程号(即init进程)。

如果pid小于-1,,只是发送给进程组是-pid的进程.

sig:准备发送的信号代码,假如其值为零则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为零来检验某个进程是否仍在执行。

返回值说明: 成功执行时,返回0。失败返回-1,errno被设为以下的某个值

1、EINVAL:指定的信号码无效(参数 sig 不合法)

2、EPERM;权限不够无法传送信号给指定进程

3、ESRCH:参数 pid 所指定的进程或进程组不存在

      我们用最多的是第一个情况.还记得我们在守护进程那一节的例子吗?我们那个时候用这个函数杀死了父进程守护进程的创建。

       raise系统调用向自己发送一个sig信号.我们可以用上面那个函数来实现这个功能的.

       alarm函数和时间有点关系了,这个函数可以在seconds秒后向自己发送一个SIGALRM信号

      下面这个函数会有什么结果呢?

[cpp]  view plain copy
  1. #include <unistd.h>  
  2. int main(void)  
  3. {  
  4.     unsigned int i;  
  5.     alarm(1);  // 1 秒后发出警告
  6.     for(i=0;1;i++)  
  7.        printf("I=%d",i); 
  8.     return 0;
  9. }  
SIGALRM的缺省操作是结束进程,所以程序在1秒之后结束,你可以看看你的最后I值为多少,来比较一下大家的系统性能差异(我的是2232).

2、信号操作 

       有时候我们希望进程正确的执行,而不想进程受到信号的影响,比如我们希望上面那个程序在1秒钟之后不结束.这个时候我们就要进行信号的操作了.信号操作最常用的方法是信号屏蔽.信号屏蔽要用到下面的几个函数:

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t *set,int signo);
int sigismember(sigset_t *set,int signo);
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);  //
sigset_t 结构详细
  1. sigemptyset 函数初始化信号集合set,将set设置为空.
  2. sigfillset       也初始化信号集合,只是将信号集合设置为所有信号的集合.
  3. sigaddset     将信号signo加入到信号集合之中,
  4. sigdelset      将信号从信号集合中删除.
  5. sigismember查询信号是否在信号集合之中.
  6. sigprocmask是最为关键的一个函数.一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。sigprocmask()可以用来检测或改变目前的信号屏蔽字,其操作依参数how来决定,如果参数oldset不是NULL指针,那么目前的信号屏蔽字会由此指针返回。每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。

sigprocmask函数中的参数how的几种值:

SIG_BLOCK该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的并集。set包含了我们希望阻塞的附加信号.
SIG_UNBLOCK该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的交集。set包含了我们希望解除阻塞的信号.
SIG_SETMASK

该进程新的信号屏蔽是set指向的值

sigprocmask 详细学习

int   sigpending(sigset_t  *set);  获取打给你前已经传送到进程,却被阻塞的所有信号,有set信号指向信号集!

以一个实例来解释使用这几个函数.

#include <signal.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
    double y;
    sigset_t intmask;
    int i,repeat_factor;
    if(argc!=2)
    {
         fprintf(stderr,"Usage:%s repeat_factor/n/a",argv[0]);
         exit(1);
    }
    if((repeat_factor=atoi(argv[1]))<1)
         repeat_factor=10;
    sigemptyset(&intmask);      /* 将信号集合设置为空 */
    sigaddset(&intmask,SIGINT); /* 加入中断 Ctrl+C 信号*/
    while(1)
    {
         /*阻塞信号,我们不希望保存原来的集合所以参数为NULL*/
         sigprocmask(SIG_BLOCK,&intmask,NULL);
         fprintf(stderr,"SIGINT signal blocked/n");
         for(i=0;i<repeat_factor;i++)
              y=sin((double)i);
         fprintf(stderr,"Blocked calculation is finished/n");
         
         /* 取消阻塞,其实就是取消信号屏蔽,Ctrl+c将起作用*/
         sigprocmask(SIG_UNBLOCK,&intmask,NULL);
         fprintf(stderr,"SIGINT signal unblocked/n");
         for(i=0;i<repeat_factor;i++)
             y=sin((double)i);
         fprintf(stderr,"Unblocked calculation is finished/n");
    }
    exit(0);
}


程序在运行的时候我们要使用Ctrl+C来结.如果我们在第一计算的时候发出SIGINT信号,由于信号已经屏蔽了,所以程序没有反映.只有到信号被取消阻塞的时候程序才会结束.注意我们只要发出一次SIGINT信号就可以了,因为信号屏蔽只是将信号加入到信号阻塞集合之中,并没有丢弃这个信号.一旦信号屏蔽取消了,这个信号就会发生作用.有时候我们希望对信号作出及时的反映的,比如当拥护按下Ctrl+C时,我们不想什么事情也不做,我们想告诉用户你的这个操作不好,请不要重试,而不是什么反映也没有的.
 这个时候我们要用到signal、sigaction函数.
void (*signal)(int signumber , void(*func)(int));   //   参数为一个函数指针,该函数格式固定了,为void func(int sig);该函数会响应signumber信号,调用func响应 
详细:http://blog.csdn.net/muge0913/article/details/7331129

#include <signal.h>
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);
struct sigaction {
    void (*sa_handler)(int signo);
    void (*sa_sigaction)(int siginfo_t *info,void *act);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restore)(void);
}

这个函数和结构看起来是不是有点恐怖呢.不要被这个吓着了,其实这个函数的使用相当简单的.我们先解释一下各个参数的含义. signo很简单就是我们要处理的信号了,可以是任何的合法的信号.有两个信号不能够使用(SIGKILL和SIGSTOP). act包含我们要对这个信号进行如何处理的信息.oact更简单了就是以前对这个函数的处理信息了,主要用来保存信息的,一般用NULL就OK了.信号结构有点复杂.不要紧我们慢慢的学习.

    1、sa_handler   是一个函数型指针,这个指针指向一个函数,这个函数有一个参数.这个函数就是我们要进行的信号操作的函数.
    2、sa_sigaction,sa_restore和sa_handler差不多的,只是参数不同罢了.这两个元素我们很少使用,就不管了.
    3、sa_flags     用来设置信号操作的各个情况.一般设置为0好了.
    4、sa_mask      我们已经学习过了。在使用的时候,我们用sa_handler指向我们的一个信号操作函数,就可以了.
sa_handler   有两个特殊的值:SIG_DEL和SIG_IGN.SIG_DEL是使用缺省的信号操作函数,而SIG_IGN是使用忽略该信号的操作函数.
这个函数复杂,我们使用一个实例来说明.下面这个函数可以捕捉用户的CTRL+C信号.并输出一个提示语句.
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#define PROMPT "你想终止程序吗?"
char *prompt=PROMPT;

void ctrl_c_op(int signo){ write(STDERR_FILENO,prompt,strlen(prompt));}int main(void){ struct sigaction act; act.sa_handler=ctrl_c_op; // 指向一个函数,这个函数用于提示错误 sigemptyset(&act.sa_mask); //初始化信号集,设为空 act.sa_flags=0; if(sigaction(SIGINT,&act,NULL)<0) //设置不成功 { fprintf(stderr,"Install Signal Action Error:%s/n/a",strerror(errno)); exit(1); } while(1);     return 0; // 中断信号将不起作用}

    
在上面程序的信号操作函数之中,我们使用了write函数而没有使用fprintf函数.是因为我们要考虑到下面这种情况.如果我们在信号操作的时候又有一个信号发生,那么程序该如何运行呢? 为了处理在信号处理函数运行的时候信号的发生,我们需要设置sa_mask成员. 我们将我们要屏蔽的信号添加到sa_mask结构当中去,这样这些函数在信号处理的时候就会被屏蔽掉的.

3、其它信号函数 

由于信号的操作和处理比较复杂,我们再介绍几个信号操作函数.


#include <unistd.h>
#include <signal.h>
int pause(void);
int sigsuspend(const sigset_t *sigmask);

pause函数很简单,就是挂起进程直到一个信号发生了.

sigsuspend 也是挂起进程只是在调用的时候用sigmask取代当前的信号阻塞集合.是挂起进程,等待信号。等收到信号(除了sigmask之外的信号)后,继续执行进程

详细了解sigsuspend

#include <sigsetjmp>
int sigsetjmp(sigjmp_buf env,int val);
void siglongjmp(sigjmp_buf env,int val);
     还记得goto函数或者是setjmp和longjmp函数吗.这两个信号跳转函数也可以实现程序的跳转让我们可以从函数之中跳转到我们需要的地方.由于上面几个函数,我们很少遇到,所以只是说明了一下,详细情况请查看联机帮助.

4、一个实例 

        还记得我们在守护进程创建的哪个程序吗?守护进程在这里我们把那个程序加强一下. 下面这个程序会在也可以检查用户的邮件.不过提供了一个开关,如果用户不想程序提示有新的邮件到来,可以向程序发送SIGUSR2信号,如果想程序提供提示可以发送SIGUSR1信号.

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>

/* Linux 的默任个人的邮箱地址是 /var/spool/mail/ */
#define MAIL_DIR "/var/spool/mail/"
/* 睡眠10秒钟 */
#define SLEEP_TIME 10
#define MAX_FILENAME 255
unsigned char notifyflag=1;

/* 获得文件的大小  */
long get_file_size(const char *filename)
{
     struct stat buf;
     if(stat(filename,&buf)==-1)    //  获得文件的属性
     {
          if(errno==ENOENT)return 0;
          else return -1;
     }
     return (long)buf.st_size;
}
/*    发送邮件通知    */
void send_mail_notify(void)
{
     fprintf(stderr,"New mail has arrived/007/n");
}

void turn_on_notify(int signo)
{
     notifyflag=1;
}
void turn_off_notify(int signo)
{
     notifyflag=0;
}
/*************************    检查邮箱  ***********************/
int check_mail(const char *filename)
{
     long old_mail_size,new_mail_size;
     sigset_t blockset,emptyset ,oldset;
     /******  初始化信号集   ******/
     sigemptyset(&blockset);     
     sigemptyset(&emptyset);
     sigaddset(&blockset,SIGUSR1);
     sigaddset(&blockset,SIGUSR2);

     old_mail_size=get_file_size(filename);

     if(old_mail_size<0)
         return 1;
     if(old_mail_size>0) 
         send_mail_notify();
     sleep(SLEEP_TIME);

     while(1)
     {
         if(sigprocmask(SIG_BLOCK,&blockset,&oldset)<0)   //  设置阻塞信号,将blockset信号集阻塞,oldset信号集,保存了原来的阻塞信号集
              return 1;
         while(notifyflag==0)
              sigsuspend(&emptyset);       // 替换阻塞信号集,直到出现了,出emptyset信号集之外的信号

         if(sigprocmask(SIG_SETMASK,&oldset,NULL)<0)    //恢复原来的阻塞信号集
              return 1;
         new_mail_size=get_file_size(filename);
         if(new_mail_size>old_mail_size)
              send_mail_notify;
         old_mail_size=new_mail_size;
         sleep(SLEEP_TIME);
     }
}
/*******************  主函数main  ********************/
/*** 主要功能:实时检测邮箱,如果邮箱信息增加,将会发出通知!用户可以通过发送SIGUSR2信息来暂停检测,SIGUSR1信号:回复检测  ***/
int main(void)
{
     char mailfile[MAX_FILENAME];
     struct sigaction newact;
     struct passwd *pw;
     if((pw=getpwuid(getuid()))==NULL)
     {
          fprintf(stderr,"Get Login Name Error:%s/n/a",strerror(errno));
          exit(1);
     }
     strcpy(mailfile,MAIL_DIR);
     strcat(mailfile,pw->pw_name);  //  这里获取当前用户的信箱地址
     newact.sa_handler=turn_on_notify;
     newact.sa_flags=0;
     sigemptyset(&newact.sa_mask);
     sigaddset(&newact.sa_mask,SIGUSR1);
     sigaddset(&newact.sa_mask,SIGUSR2);
     if(sigaction(SIGUSR1,&newact,NULL)<0)  //  安装信号:SIGUSR1对应的响应函数
          fprintf(stderr,"Turn On Error:%s/n/a",strerror(errno));
     newact.sa_handler=turn_off_notify;
     if(sigaction(SIGUSR2,&newact,NULL)<0)  //  安装信号:SIGUSR2对应的响应函数
          fprintf(stderr,"Turn Off Error:%s/n/a",strerror(errno));
     check_mail(mailfile);  //检测邮箱
     return 0;
}


注:一个终端中运行源程序,另一个终端向进程发送信号:可以用kill -SIGUSR2 PID(进程号)来发送信号


       信号操作是一件非常复杂的事情,比我们想象之中的复杂程度还要复杂,如果你想彻底的弄清楚信号操作的各个问题,那么除了大量的练习以外还要多看联机手册.不过如果我们只是一般的使用的话,有了上面的几个函数也就差不多了. 我们就介绍到这里了.
























































































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值