信号的概念及信号产生的条件

一,信号的基本概念

        生活中有很多信号。比如,早上的闹钟响了,你可以识别出这是通知你起床的信号,你可以采取不同的措施来处理(起床或继续睡);放学铃响了,通知大家现在到了放学时间;等等,还有很多各种各样的信号。这些信号因为某些原因产生后(比如提前设定了闹钟),首先会识别它属于什么信号(通知起床的还是其他的),然后在给出处理这些信号的动作。

        在操作系统中,也有信号通知机制。当在一个进程执行过程中因为某些原因收到信号时(如何产生下面说),会首先识别它属于哪种信号,然后将该信号保存(如何保存下面说)下来表明操作系统已经知道该信号产生了,再对该信号采取相应的处理方法。

        操作系统会识别某种信号,是因为系统中有一个信号列表,用kill -l命令查看:

[admin@localhost communication]$ 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
16) SIGSTKFLT	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
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

        可以看到,信号一共有62种:1~31为普通信号,34~64为实时信号。

        信号处理的常见方式为以下三种:

1. 忽略此信号

2. 执行默认处理动作。一般信号的默认处理动作为终止进程

3. 自定义提供一个信号处理函数,该方式称为捕捉一个信号

注意:信号和进程间通信中的信号量是没有关系的。信号量是一种通信机制,而信号是一种通知机制。

二,信号的产生

        在了解了信号的基本概念,下面介绍信号是如何产生的:

1. 通过终端按键产生

        当某个进程正在执行时,用户在终端键盘上按下某些键时,此时会产生一个硬件中断。此时终端驱动程序会将其解释为某一种信号,操作系统得知后会将该信号发送个正在前台运行的进程。并在合适的时机去处理该信号。

        如:Ctrl+C产生SIGINT信号,Ctrl+\产生SIGQUIT信号,Ctrl+Z产生SIGTSTP信号。比如执行一个死循环程序:

#include<stdio.h>                                                                                                                     
#include<sys/types.h>
#include<unistd.h>
int main()
{
    printf("%d\n",getpid());
    while(1);
    return 0;
}

        当在前台运行该进程时,按下Ctrl+\键产生一个硬件终端。此时终端驱动程序将其解释为SIGQUIT信号,并由操作系统发送给该进程。而该信号的默认处理动作是终止进程并产生Core Dump。如下所示:

[admin@localhost terminal_create]$ ./a.out 
7343
^\Quit (core dumped)

Core Dump:

        也称为核心转储。当一个进程异常终止时,可以将进程的用户空间中的所有数据保存在磁盘上,生出core文件,这就叫做Core Dump。

        之所以要核心转储,只因为进程异常终止时通常是由Bug产生,为了得知错误原因,可以通过调试器查看core文件来查明错误原因,这叫做Post-mortem-Debug(事后调试)。

        默认是不允许生成core文件的,因为core文件中可能会包含用户密码等相关信息,不安全。所以ls命令一般是看不到这个core文件的。

        但是在开发调试阶段可以通过ulimit命令改变权限,进而生成core文件。一个进程允许生成多大的core文件是由进程的Resourse Limit决定的(保存在进程的PCB中)。所以可以首先改变Shell进程的Resourse Limit,Shell是所有进程的父进程,因此也仅改变了正在运行进程的Resourse Limit。默认允许core文件最大为1024K。

        以下通过命令演示如何生成core文件:

[root@localhost terminal_create]# ulimit -a   //查看core文件的大小,此时默认为0
core file size          (blocks, -c) 0
[root@localhost terminal_create]# ulimit -c 1024   //改变core文件的大小,再次查看时已变为1024K
[root@localhost terminal_create]# ulimit -a
core file size          (blocks, -c) 1024

        再次运行可执行程序时,发现已生成core文件:

[root@localhost terminal_create]# ./a.out 
7394
^\Quit (core dumped)
[root@localhost terminal_create]# ls
a.out  core.7394  core_dump.c             //core后面的数字为进程的PID

        下面通过gdb来调试查看进程异常终止的原因(注意:用gdb调试时gcc编译文件时要加-g选项):

[root@localhost terminal_create]# gdb a.out     //进入gdb调试
(gdb) core-file core.7394 
warning: exec file is newer than core file.
[New Thread 7394]
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./a.out'.
Program terminated with signal 3, Quit.
#0  main () at core_dump.c:8
8	    while(1);

        可以看到,此时进程是被3号SIGQUIT信号异常终止。

注意:终端按键只适用于向前台进程发送信号,对后台进程是不适用的。

2. 进程引发硬件异常产生信号

        如当前进程执行了除0操作,CPU的运算单元会产生异常,内核会将该异常解释为SIGFPE信号发送给进程,进程采取相应的处理动作;再比如当前进程非法访问内存时,MMU会产生异常,内核将其解释为SIGSEGV信号将其发送给进程。

        通过下面代码来演示:

除零异常:

#include<stdio.h>                                                             
  int main()
  {
      int a = 4;
      int b = a/0;
      return 0;
  }

        运行结果:

[admin@localhost hard_create]$ ./a.out 
Floating point exception (core dumped)

        可以看到操作系统向进程发送了SIGFPE信号,该信号的默认处理动作是终止进程,并Core Dump

非法访问内存:

#include<stdio.h>                                                               
int main()
{
    int *p; 
    *p = 5;
    return 0;
}

        运行结果:

[admin@localhost hard_create]$ gcc mem.c 
[admin@localhost hard_create]$ ./a.out 
Segmentation fault (core dumped)

        可以看到操作系统向进发送了SIGSEGV信号,该信号的默认处理动作是终止进程,并Core Dump。此时,可以通过gdb调试器来查看core文件查明相应的错误信息,这里不再演示。

3. 通过命令和系统调用接口产生信号

命令:

        之前我们常用的kill命令就可以对指定进发送指定信号,如在后台运行以下程序:

#include<stdio.h>                                                                                                                     
int main()
{
    while(1);
    return 0;
}

        运行结果:

[admin@localhost terminal_create]$ ./a.out &
[3] 8763
[admin@localhost terminal_create]$ kill -9 8763
[admin@localhost terminal_create]$ 
[3]+  Killed                  ./a.out

        上述表明通过kill命令PID为8763的进程发送了9号信号,该信号的默认处理动作是终止进程。此时,进程并没有产生异常,但通过向该进程发送信号使其异常退出。

系统调用接口:

(1)kill函数

int kill(pid_t pid,int signo);//头文件<signal.h>

        该函数的作用是向PID为pid的进程发送signo信号。成功返回0,失败返回-1。上述的kill命令在实现时即调用了kill函数。

代码演示如下:

进程1:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    printf("%d\n",getpid());                                                    
    while(1);
    return 0;
}

调用kill的进程2:

#include<stdio.h>                                                               
#include<stdlib.h>
#include<signal.h>

int main(int argc,char *argv[])//利用命令行参数接收进程PID和信号值
{
    if(argc != 3)
    { 
        printf("usage:%s pid,signo\n",argv[0]);//用法说明
        return 0;
    } 
    int ret = kill(atoi(argv[1]),atoi(argv[2]));//调用kill函数发送信号
    if(ret == -1)
    { 
        perror("kill");
        exit(1);
    } 
    else
    { 
        printf("send %d signo to %d\n",atoi(argv[2]),atoi(argv[1]));
    } 
    return 0;
}       

        在一个终端下运行进程1,得到以下结果:

[admin@localhost terminal_create]$ ./a.out 
8951

        在另一终端下运行进程2,得到以下结果:

[admin@localhost system_create]$ ./a.out 8951 11
send 11 signo to 8951

        最终进程1会被异常终止:

[admin@localhost terminal_create]$ ./a.out 
8951
Segmentation fault (core dumped)

(2)raise函数

int raise(int signo);//头文件<signal.h>

        该函数的功能是向调用它的进程发送signo指定的信号。演示如下:

  #include<stdio.h>                                                                                                                   
  #include<stdlib.h>
  #include<signal.h>
  #include<unistd.h>
  int main(int argc,char* argv[])
  {
      if(argc != 2)
      {   
          printf("usage:%s signo\n",argv[0]);
          return 0;
      }   
      int i = 10; 
      while(i--)
      {   
          printf("running\n");
          sleep(1);
          if(i == 5)//5s后向本进程发送指定的信号
          {   
              printf("will receive signo %d\n",atoi(argv[1]));
              sleep(1);
              int ret = raise(atoi(argv[1]));
              if(ret == -1) 
              {   
                  perror("raise error");
                  exit(1);
              }   
            else
              {
                  printf("receive signo %d\n",atoi(argv[1]));
              }
          }
      }
      return 0;
  }             

        运行结果:

[admin@localhost system_create]$ ./a.out 11
running
running
running
running
running
will receive signo 11
Segmentation fault (core dumped)

(3)abort函数

void abort(void);//头文件<stdlib.h>

        该函数通过给自己发送SIGABRT信号,该信号的默认处理动作是使进程异常终止并Core Dump。

        如果用户重新定义了该信号的处理动作,该函数会先给自己发送SIGABRT信号去执行自定义行为。然后在重新注册SIGABRT信号,将该信号的处理动作修改为默认行为,并再次发送SIGABRT信号给自己去执行默认动作:终止进程并Core Dump。

        因此,该函数像exit一样,总是会成功,所以没有返回值。演示代码如下:

#include<stdio.h>                                                                                                                     
#include<stdlib.h>
int main()
{
    printf("hello world\n");
    abort();
    printf("hello\n");
    return 0;
}

        运行结果:

[admin@localhost system_create]$ ./a.out 
hello world
Aborted (core dumped)

        因为执行到abort函数时进程会异常终止,所以不会输出“hello”语句。

4. 软件产生信号

        在“进程间通信------管道”一文中,当管道的读端关闭,写端一直向管道中写,操作系统会发送SIGPIPE信号来终止进程。这便是一种有软件产生的信号。下面介绍另一种有软件产生的信号。

unsigned int alarm(unsigned int seconds);//头文件:<unistd.h>

        该函数的作用是设定一个闹钟,在seconds秒后有操作系统向该进程发送一个SIGALRM信号,该信号的默认处理动作是终止当前进程。

        若闹钟设定成功,函数的返回值是0。如果seconds为0,表示取消以前设定的闹钟,此时可能还未到达闹钟设定的时间,所以返回值是以前设定的时间剩余的秒数。演示代码如下:

int main()
{
    unsigned int sec = alarm(2);
    printf("hello world\n");
    sleep(1);
    sec = alarm(0);
    printf("%d\n",sec);
    return 0;
}              

        运行结果:

[admin@localhost soft_create]$ ./a.out 
hello world
1

        因为alarm设定的是2s后向进程发送SIGALRM信号,但是该进程在运行1s后闹钟就被取消了,所以此时闹钟还剩余1s,故返回值为1。

阅读更多
个人分类: Linux
想对作者说点什么? 我来说一句

qam信号产生 星座图

2011年07月24日 1KB 下载

没有更多推荐了,返回首页

不良信息举报

信号的概念及信号产生的条件

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭