灯火互联
管理员
管理员
  • 注册日期2011-07-27
  • 发帖数41778
  • QQ
  • 火币41290枚
  • 粉丝1086
  • 关注100
  • 终身成就奖
  • 最爱沙发
  • 忠实会员
  • 灌水天才奖
  • 贴图大师奖
  • 原创先锋奖
  • 特殊贡献奖
  • 宣传大使奖
  • 优秀斑竹奖
  • 社区明星
阅读:2018回复:0

Linux信号处理

楼主#
更多 发布于:2012-08-27 15:08

Linux信号处理



这两天一直在看Linux 中信号的知识,看完了也应该总结总结了.当然以下结论可能还有不足之处,有的话尽管言明,大家共同学习学习.

     其实在现实生活中我们都知道信号是什么意思,在生活中很简单很容易理解,信号嘛就是一个信息的意思,比如说我们古代打仗的时候,兵将什么时候开始向前冲,什么时候开始奋战,当指挥部发出一个"命令"的时候,这个命令可能是吹号角,也可能是放烟火等等,再如现在,我们接到一个电话,某个朋友拜托我们去干一件事,那我们接完电话后就可能立即去办事情了,这些,"号角","烟火","电话"都是我们收到的"信号",而后面的动作就是我们收到信号后要做的动作.其实计算机也是一样的,它的内部有好多进程在执行,如果一个进程想让另一个进程办一件事情,那么它也可以通过发信号来通知另一个进程,这时候,信号在进程间的通信就起到了一定的作用,接下来我们具体看看信号的来源,种类,以及进程对信号的响应.  

      其实在计算机中信号就相当于是软中断,,它提供了一种处理异步事件的方法,也是进程键唯一的异步通信方式,我们知道异步就是说,根本没有时间规定,你(一个进程)现在可以处理你想干的事情,而当检测到有信号的时候再转去处理信号,也就是说这种信号的发生是随机的.

      信号的来源有两种方式:硬件方式和软件方式.

     信号的种类有好多中,在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>  

#include<signal.h>

/*信号处理函数*/

void handler_sigint(int signo)

{

        printf("recv SIGINTn");

}

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 SIGINTn");

}  

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 SIGINTn");

        sleep(5);

        temp+=1;

        printf("the value of temp is :%dn",temp);

        printf("in handler_sigint,after sleepn");

}

int main()

{

        struct sigaction act;

        act.sa_handler=handler_sigint;

        act.sa_flags=SA_NOMASK;//意思是在处理次信号前允许此信号再次传递,相当于中断嵌套

        sigaction(SIGINT,;act,NULL);

        while(1);

        return 0;

}  

运行结果如下:

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)

{  

    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,"-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);
  

下面来看一个例子end_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;  

    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,"-d")){

        

                value.sival_int=atoi(argv[i+1]);

                continue;

        }

        if(!strcmp(argv,"-s")){

        

                signum=atoi(argv[i+1]);

                continue;

        }

        if(!strcmp(argv,"-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 : %dn",siginfo->si_int);

}

int main()

{

        struct sigaction act;



        act.sa_sigaction=handler_sigint;

        act.sa_flags=SA_SIGINFO;//指定使用三参数的信号处理函数



        sigaction(SIGINT,;act,NULL);

        while(1);  

        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 packetn");

}

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  

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()将覆盖这种设置.

目前所掌握的就这些了,以后接触到要再做研究,有待于更深层次的研究.



喜欢0 评分0
游客

返回顶部