知识屋:更实用的电脑技术知识网站
所在位置:首页 > 操作系统 > linux

Linux信号处理

发布时间:2014-09-05 16:02:54作者:知识屋


Linux信号处理
 
这两天一直在看Linux 中信号的知识,看完了也应该总结总结了.当然以下结论可能还有不足之处,有的话尽管言明,大家共同学习学习.
     其实在现实生活中我们都知道信号是什么意思,在生活中很简单很容易理解,信号嘛就是一个信息的意思,比如说我们古代打仗的时候,兵将什么时候开始向前冲,什么时候开始奋战,当指挥部发出一个"命令"的时候,这个命令可能是吹号角,也可能是放烟火等等,再如现在,我们接到一个电话,某个朋友拜托我们去干一件事,那我们接完电话后就可能立即去办事情了,这些,"号角","烟火","电话"都是我们收到的"信号",而后面的动作就是我们收到信号后要做的动作.其实计算机也是一样的,它的内部有好多进程在执行,如果一个进程想让另一个进程办一件事情,那么它也可以通过发信号来通知另一个进程,这时候,信号在进程间的通信就起到了一定的作用,接下来我们具体看看信号的来源,种类,以及进程对信号的响应.  www.zhishiwu.com  
      其实在计算机中信号就相当于是软中断,,它提供了一种处理异步事件的方法,也是进程键唯一的异步通信方式,我们知道异步就是说,根本没有时间规定,你(一个进程)现在可以处理你想干的事情,而当检测到有信号的时候再转去处理信号,也就是说这种信号的发生是随机的.
      信号的来源有两种方式:硬件方式和软件方式.
     信号的种类有好多中,在Linux下面,我们在终端通过使用命令 kill -l 便可以查看Linux系统支持的全部信号,至于信号的含义,这里不再赘述,有兴趣的读者可以翻阅相关的资料了解.我们也可以在头文件<signal.h>中查看这些信号.
    而进程对信号有三种处理方式:捕捉信号,忽略信号,按照系统默认方式处理.
    信号的捕捉和处理:Linux 系统对信号的处理主要由signal和sigaction函数来处理.
    1.signal函数:用来设置进程在接收到信号时的动作,我们在shell下面输入man signal可获取该函数的原型.
    #include<signal.h>
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum,sighandler_t handler);
signal会根据signum指定的信号编号来设置该信号的处理函数,当指定的信号到达时,就会转到参数handler指定的参数执行,如果参数handler不是函数指针,则必须是常数SIG_INT(忽略该信号)或者是SIG_DFL(采用默认方式处理信号).若handler是一个函数指针,它所指向的函数的类型是sighandler_t,,即它所指向函数有一个int型的参数,且返回值类型为void.函数执行成功返回以前的信号处理函数指针,当有错误发生时返回SIG_ERR(即-1).
下面通过具体的例子看一个signal函数的用法:
#include<stdio.h>  www.zhishiwu.com  
#include<signal.h>
/*信号处理函数*/
void handler_sigint(int signo)
{
        printf("recv SIGINT/n");
}
int main()
{
    /*安装信号处理函数*/
        signal(SIGINT,handler_sigint);
        while(1);
        return ;
}
当我们在终端中运行该程序后,按下Ctrl+C键,则会出现recv SIGINT,继续按键继续出现recv SIGINT,但是当我们按下Ctrl+/后则退出了,我们按下的Ctrl+/键就相当与进程收到了SIGQUIT这个信号,而这个信号的作用就是让进程终止,那么如果我们忽略这个信号呢,则程序就会永远的死在那里了,退不出来了,如改为如下所示:
#include<stdio.h>
#include<signal.h>
void handler_sigint(int signo)
{
        printf("recv SIGINT/n");
}  www.zhishiwu.com  
int main()
{
        signal(SIGINT,handler_sigint);
    signal(SIGQUIT,SIG_INT);//忽略该信号,此时Ctrl+/已不再起作用了.
        while(1);
        return ;
}
上面为什么会退出呢,就是因为进程没有对信号SIGQUIT 做处理,那么它就按照默认的方式走,即结束进程.
接下来我们看sigaction函数:同样man sigaction一下你会看到原型:
    #include<signal.h>
    int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)
signum与上面的相同,即可以是SIGKILL和SIGSTOP以外的任意信号,而act是一个结构体指针,如果act不是空指针,则为signum设置心的信号处理函数,如果oldact不是空指针,则旧的处理函数将被存储在oldact中.
接下来看一个sigaction的例子:
#include<stdio.h>
#include<signal.h>
int temp=0;
void handler_sigint(int signo)
{
        printf("recv SIGINT/n");
        sleep(5);
        temp+=1;
        printf("the value of temp is :%d/n",temp);
        printf("in handler_sigint,after sleep/n");
}
int main()
{
        struct sigaction act;
        act.sa_handler=handler_sigint;
        act.sa_flags=SA_NOMASK;//意思是在处理次信号前允许此信号再次传递,相当于中断嵌套
        sigaction(SIGINT,&act,NULL);
        while(1);
        return 0;
}  www.zhishiwu.com  
运行结果如下:
hfm@hfm-Lenovo-KL6B-KL6C-Z475:~$ gcc -o sigaction sigaction.c
hfm@hfm-Lenovo-KL6B-KL6C-Z475:~$ ./sigaction
^Crecv SIGINT
^Crecv SIGINT
^Crecv SIGINT
^Crecv SIGINT
the value of temp is :1
in handler_sigint,after sleep
the value of temp is :2
in handler_sigint,after sleep
the value of temp is :3
in handler_sigint,after sleep
the value of temp is :4
in handler_sigint,after sleep
 
注意我上面提到的中断嵌套的含义,当我们按下Ctrl+c的时候,发出SIGINT信号,打印,然后休眠5秒,但是在这5秒之内呢,你又按下了Ctrl+c,于是从sleep函数处嵌套调用信号处理函数handler_sigint,5秒之后呢,函数打印出temp的值,又返回到sleep处继续执行你刚按下的第二次Ctrl+c,一次类推执行完四次之后,temp的值也增加到了4,于是程序返回到住函数处继续执行,基本上的执行过程就是这样的啦!
信号处理函数的发送主要由函数:kill,faise,sigqueue,alarm,setitimer,abort来完成.
通过man命令可以查看kill函数的原型:
#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig);
pid顾名思义就是进程号了,而sig是发送信号的编号,我们要注意,只有root权限的进程才能向其他任一进程发送信号,而非root权限的进程只能向同一组或同一用户的进程发送信号.
看一个简单模拟kill命令的例子:kill.c
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<sys/types.h>
int main(int argc,char **argv)
{  www.zhishiwu.com  
    int i,j;
    int signum=SIGTERM;
    pid_t pid;
    if(argc!=2&&argc!=4){
        
        printf("Usage:./kill<-s signum>[pid]/n");
        exit(0);
    }
    for(i=1;i<argc;i++){
    
        if(!strcmp(argv[i],"-s")){
            signum=argv[i+1];
            break;
        }
    }
    if(argc==2){
        pid=atoi(argv[1]);
    }else{
        for(j=1;j<argc;j++){
           if(j!=i&&j!=i+1){
            pid=atoi(argv[j]);
                break;
            }
        }
    }
    if(kill(pid,signum)<0){
        perror("kill");
        exit(0);
    }
    return 0;
}
信号SIGINT 的编号在所有的Linux系统中都为2.
函数raise比较简单,用来给调用它的进程发送信号:原型:int faise(int sig);参数sig表示要发送的信号编号,成功返回0,失败返回非0值.
sigqueue函数:它是一个比较新的发送信号函数,通过man命令可以查看原型.该函数不仅可以向进程发送信号,还可以给进程发送数据,其原型为:
int sigqueue(pid_t pid,int sig,const union sigval value);
 
下面来看一个例子:send_data_signo.c:利用信号传递数据,本程序发送数据
#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<stdlib.h>
int main(int argc,char **argv)
{
    union sigval value;
    int signum=SIGTERM;
    pid_t pid;
    int i;  www.zhishiwu.com  
    value.sival_int=0;
    if(argc!=3&&argc!=5&&argc!=7){
        
            printf("./send_data_signo <-d data> <-s signum> [-p] [data]/n");
            exit(1);
    }
    /*从命令行解析出信号编号,PID 以及待传送的数据*/
    for(i=1;i<argc;i++){
        if(!strcmp(argv[i],"-d")){
        
                value.sival_int=atoi(argv[i+1]);
                continue;
        }
        if(!strcmp(argv[i],"-s")){
        
                signum=atoi(argv[i+1]);
                continue;
        }
        if(!strcmp(argv[i],"-p")){
        
                pid=atoi(argv[i+1]);
                continue;
        }
    }
    /*利用sigqueue函数来发送信号*/
    if(sigqueue(pid,signum,value)<0){
    
            perror("sigqueue");
            exit(1);
    }
    return 0;
            
}
这个例子是:利用信号传递数据,本程序接收数据
#include<stdio.h>
#include<signal.h>
void handler_sigint(int signo,siginfo_t *siginfo,void *pvoid)
{
    printf("recv SIGINT,the data value is : %d/n",siginfo->si_int);
}
int main()
{
        struct sigaction act;
 
        act.sa_sigaction=handler_sigint;
        act.sa_flags=SA_SIGINFO;//指定使用三参数的信号处理函数
 
        sigaction(SIGINT,&act,NULL);
        while(1);  www.zhishiwu.com  
        return 0;
    
}
为了容易写,我把发送数据的进程的名字名为b.c,接收进程的名字命为c.c
运行上面的程序结果如下所示:
hfm@hfm-Lenovo-KL6B-KL6C-Z475:~$ ./c &
[1] 4229
hfm@hfm-Lenovo-KL6B-KL6C-Z475:~$ ./b -s 2 -d 100 -p 4229
recv SIGINT,the data value is : 100
 
alarm函数:可以用来设置定时器,定时器超时将产生SIGALRM信号给调用进程,在shell下面可以看原型:
unsigned int alarm(unsigned int seconds);参数seconds表示设定的秒数,经过seconds后,内核将给调用该函数的进程发送一个SIGALRM 信号,如果seconds为0将不再发送信号,最新一次调用该函数将取消之前一次的设定.alarm只设定为发送一次信号,如果要多次发送,就要对alarm进行多次调用.如果之前已经调用过alarm函数,则返回之前设置的定时器剩余时间,否则如果之前没有设置过定时器,则返回0.
下面通过一个模拟网络ping的例子来看一下alarm函数的用法:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void send_ip()
{
    printf("send a icmp packet/n");
}
void recv_ip()
{
    while(1);
}
void handler_sigint(int signo)
{
    send_ip();
    alarm(2);
}
int main()
{
    signal(SIGALRM,handler_sigint);
    raise(SIGALRM);//触发一个SIGALRM信号给本进程
    recv_ip();
    return 0;
}
运行结果如下所示:
hfm@hfm-Lenovo-KL6B-KL6C-Z475:~$ gcc -o  m m.c
hfm@hfm-Lenovo-KL6B-KL6C-Z475:~$ ./m
send a icmp packet  www.zhishiwu.com  
send a icmp packet
send a icmp packet
send a icmp packet
send a icmp packet
send a icmp packet
send a icmp packet
send a icmp packet
send a icmp packet
..............
我们可以看到每当2秒过后就用raise()函数出发一个SIGALRM信号给本进程,实现ping程序的定时发包功能.同时我们也可以想象一下网络里面的路由器是怎么发送报文段的,它也是有时间规定的.我们可以通过设定alarm()函数中的值来设定.其实还有一个函数也是用来设定定时器的,它比alarm具有更多的功能,即setitimer()函数,它的原型如下:
int setitimer(int which,const struct itimerval *value,struct itimerval *ovalue);有兴趣的读者可以下去自己看一下.
void abort(void)函数用来向进程发送SIGABRT信号来终止进程,如果进程设置了SIGABRT被阻塞或忽略,abort()将覆盖这种设置.
目前所掌握的就这些了,以后接触到要再做研究,有待于更深层次的研究.
 
 
作者 hfm_honey
(免责声明:文章内容如涉及作品内容、版权和其它问题,请及时与我们联系,我们将在第一时间删除内容,文章内容仅供参考)
收藏
  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜