Linux信号(signal)机制

一、什么是信号

1.信号本质

软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。
进程对信号的处理方式不同可分为三类:

第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。
第二种方法是,忽略某个信号,对该信号不做任何处理,就像未发生过一样。
第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。

2.信号分类

根据可靠性可将信号分为可靠信号和不可靠信号,与时间的关系又可将信号分为实时信号与非实时信号。

可靠信号与不可靠信号

Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,信号值小于SIGRTMIN的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是信号可能丢失。
随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。
信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数,而经过signal安装的信号不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。

实时信号和非实时信号

早期Unix系统只定义了32种信号,前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

二、Linux下的常用信号

1.查看信号

我们可以通过 kill -l 命令来查看当前系统所支持的信号,不同系统支持的信号是不相同的。
在这里插入图片描述

2.常用信号及说明

在这里插入图片描述

三、信号的使用

1.信号安装

函数原型

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);

第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。
如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR。
传递给信号处理例程的整数参数是信号值,这样可以使得一个信号处理例程处理多个信号。

2.kill 函数

通常,我们把 kill 理解为杀死一个进程,其实这样的描述是不准确的,kill 其实是作为一个信号,由其中一个进程发给另一个进程,而发送的信号就是终止信号。

3.简单程序示例

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

#define print_words "Hello World"
#define sleep_time 1

int main(int argc ,char **argv)
{
        for( ; ; )
        {
              printf("%s\n",print_words);
              sleep(sleep_time);
        }
         
        printf("I'll exit.\n");
        
        return 0;
}

编译后运行程序,将会每隔一秒打印"Hello World",当我们按下Ctrl + c 后,或者使用命令查看到进程号后使用 kill 终止程序,我们可以通过 signal() 函数来捕捉信号,让某些终止信号被忽略。程序如下:

#include <stdio.h>
#include <signal.h>
  
#define sleep_time 2
#define print_words "Hello World"

void sig_handle(int signum)
{
        printf("Catch signal [%d]\n",signum);
}

int main(int argc,char **argv)
{
        signal(SIGINT,sig_handle);
        signal(SIGTERM,sig_handle);
        
        for( ; ; )
        {
               printf("%s\n",print_words);
               sleep(sleep_time);
        }

        printf("I'll exit.\n");

        return 0;
}

编译运行程序
在这里插入图片描述
可以发现,信号被捕捉后程序将不会退出,但是,通过上面的常用信号表可以得知有一个信号是不可以被捕捉的!
观察程序,我们可以发现,程序最后的 " I’ll exit "并没有打印,这是因为程序在接收到终止信号后会立刻退出程序,而不会执行后面的内容,但很多情况下,我们希望在程序退出前做某些事情,例如关掉某些开关或者文件描述符,我们只需要对程序进行简单修改即可!

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

int found = 0;

#define sleep_time 2
#define print_words "Hello World"

void sig_handle(int signum)
{
        printf("Catch signal [%d]\n",signum);
        found = 1;
}

int main(int argc,char **argv)
{
        signal(SIGINT,sig_handle);
        signal(SIGTERM,sig_handle);
        
        while(!found)
        {
               printf("%s\n",print_words);
               sleep(sleep_time);
        }

        printf("I'll exit.\n");

        return 0;
}
发布了8 篇原创文章 · 获赞 11 · 访问量 304

猜你喜欢

转载自blog.csdn.net/weixin_45121946/article/details/104588413