Linux信号机制

Linux信号机制学习笔记

一、常见的信号有哪些

使用kill -l 命令可以查看所有的信号。
在这里插入图片描述
常用的命令及说明如下:

信号(信号值)信号产生默认动作
SIGINT(2)键盘中断Ctrl+c终止进程
SIGKILL(9)kill - 9 pid杀死进程&不能被捕获&不能被忽略
SIGSEGV(11)段错误、无效的内存引用终止进程
SIGTERM(15)kill pid 或 killall 进程名终止进程
SIGCHLD(17)子进程死的时候会给父进程发送这个信号忽略此信号

当一个进程收到信号后会有三种处理方式

  • 忽略,不对该信号做任何处理。
  • 捕获,设置处理函数,将处理函数告诉内核,当接收到信号,内核调用该处理函数。
  • 默认动作,采用系统默认的处理方式,对于不同的信号,默认的处理方式不同,有的会终止进程,有的忽略。

需要注意,虽然我们可以设置处理函数来捕获信号,但是有些信号是不可以被捕获,也不可以被忽略的,如 SIGKILL,我们通常使用 kill - 9 pid 杀死进程,它是系统提供的一种强制手段,来保证一定能杀死目标进程。
code time~~

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

void sigHandler(int signum);

int main(){
        struct sigaction sigConfig;
        sigConfig.sa_handler=sigHandler;
        //注册一个信号处理函数
        //sigaction(2,&sigConfig,NULL);
        printf("休眠30s~~~\n");
        sleep(30);
        printf("after sleep\n");
}

void sigHandler(int signum){
        printf("收到了信号%d\n",signum);
}

上面是一个简单的代码,先把注册信号处理函数的代码注释了,执行这个程序,终端必定会处于等待状态,然后我们按ctrl+c。
在这里插入图片描述
对于上面的代码,因为是直接在命令行运行的程序,并且属于前台程序,命令行会等待程序的执行完成,这里程序休眠了30s,然后没到30s我就按下了ctrl+c,前面说过 ctrl+c 就相当于发送了SIGINT信号,默认动作是终止进程,所以后面的"after sleep"也没来得及输出。
取消sigaction(2,&sigConfig,NULL) 这行代码的注释再次编译执行结果如下:
在这里插入图片描述
可以看到,当程序休眠时,我按下了ctrl+c,休眠被中断了,然后去执行了信号的处理函数,因为我给信号注册了处理函数,就不会执行它的默认动作了,也就是终止进程。处理函数执行完,又接着sleep之后的代码执行。
补充:sleep是一个系统调用,当一个信号到来时,会中断系统调用。

下面详细讲解下sigaction函数

二、sigaction函数

通过sigaction函数可以注册信号的处理函数,其原型如下:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
  • signum:要捕获的信号
  • act: sigaction 结构体,用来对该信号做一些处理配置。
  • oldact:用来获取之前对该信号做的配置。

sigaction 结构体常用参数说明

struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };
  • sa_handler:信号的处理函数,可以传SIG_DFL(默认处理方式)、SIG_DFL(忽略信号)。或者我们自己定义的函数指针,通过sa_handler的函数原型可以看出它只能接收一个参数,也就是信号的信号值作为入参。
  • sa_sigaction:也是信号的处理函数,但是它的参数比sa_handler更加丰富。使用sa_sigaction来替代sa_handler,需要我们将sa_flags 参数设置为SA_SIGINFO。可以发现sa_sigaction的函数原型中,参数有三个,第一个是信号值,第二个是信号的一些简单信息,第三个不常用。
  • sa_mask:通过它可以设置在调用信号处理程序时阻塞某些信号。注意仅仅是在信号处理程序正在执行时才能阻塞某些信号,如果信号处理程序执行完了,那么依然能接收到这些信号。
  • sa_flags:这个字段有很多取值,但是有两个是常用的,SA_SIGINFO 表示用sa_sigaction处理函数,而不是sa_handler。SA_RESTART 前面说过信号会中断系统调用,如果设置了这个值,当信号处理函数完了,被中断的系统调用会被系统重新调用。需要注意,实际上只有部分系统调用在被中断后还能重启,eg:read、wait、scanf。

三、信号的阻塞

那么什么是信号的阻塞呢?
当一个信号传递给进程时,如果该信号处于阻塞状态,那么操作系统不会将信号传递出去,被阻塞的信号也不会影响进程的行为,信号只是暂时被阻止传递,一旦解除了该信号的阻塞,系统就会把信号传递出去。阻塞和忽略是不同的,忽略是信号可以被传递,但是进程不做任何处理,将信号丢弃。
既然说到了阻塞,那么为了显的我不那么水,就再补充两个概念:

  • 信号递达:执行信号的处理动作称为信号递达。
  • 信号未决:信号从产生到抵达之间称为信号未决。

coding~

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

void sigHandler(int signum);

int main(){
        struct sigaction sigConfig;
        sigConfig.sa_handler=sigHandler;
        sigset_t blockset;
        sigemptyset(&blockset);
        //将信号15添加到信号集中
        sigaddset(&blockset,15);
        //设置信号集中的信号为阻塞状态
        sigprocmask(SIG_BLOCK,&blockset,NULL);
        //注册一个信号处理函数
        sigaction(15,&sigConfig,NULL);
        int i=0;
        while(1){
                printf("休眠1s\n");
                sleep(1);
                i++;
                if(i==10){
                        printf("10s到了,,现在解除信号集合中信号的阻塞状态\n");
                        sigprocmask(SIG_UNBLOCK,&blockset,NULL);
                }
        }
}

void sigHandler(int signum){
        printf("收到了信号%d\n",signum);
}

如上代码,通过sigprocmask函数第一个参数SIG_BLOCK与SIG_UNBLOCK可以设置信号集中信号的阻塞与解除阻塞状态。先通过./signalText 启动程序,然后在另一个终端,连续发送3次信号值为15的信号,执行结果如下:
在这里插入图片描述
可以看到,确实满足了i==10条件后,解除了信号的阻塞,之后我们发送的信号才能被收到,但是发现没用,其实我连续发送了3个信号,为什么只收到了一个?
在这里插入图片描述
这又引入了一个概念,可靠信号与不可靠信号。
信号分为可靠信号(信号值在1-32)和不可靠信号(信号值>32)

  • 不可靠信号:相同的信号多次到来,不会做排队处理,会被合并为一个,造成信号的丢失。
  • 可靠信号:会做排队处理,不存在信号丢失问题。

ok,那我修改下代码,把信号值15换成55试试。启动程序后,在另一个终端,连续发送3次信号值为55的信号 killall -55 signalText
在这里插入图片描述
没毛病~

四、常用的信号处理函数

函数说明
sigemptyset()初始化信号集
sigaddset()增加一个信号至信号集
sigdelset()从信号集里删除一个信号
sigfillset()将所有信号添加至信号集
sigfillset()某个信号是否已添加至信号集
sigprocmask()查询或设置信号遮罩
sigaction()查询或设置信号处理方式
signal()设置信号处理方式
kill()发送信号给指定进程

具体用法参看linux的文档,不细说了。前面介绍了通过sigaction函数给进程设置处理方式,其实还可以通过signal,不过signal实在太简单了,也没sigaction的设置丰富。

五、信号处理函数的中断

当一个信号达到后,会调用信号的处理函数,如果这时候有其他的信号到来(和当前不是同一个新信号),那么当前的处理函数执行会被中断,等新信号的处理函数执行完成了才继续执行之前的信号处理函数.如果多次到来的是同一个信号,那么相同的信号会排队等待(如果是不可靠信号,需要排队执行的多个相同信号会被合并成一个)。

code time ~~

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

void sigHandler(int signum);

int main(){
        struct sigaction sigConfig;
        sigConfig.sa_handler=sigHandler;
        //注册55信号处理函数
        sigaction(55,&sigConfig,NULL);
        //注册56信号处理函数
        sigaction(56,&sigConfig,NULL);
        //注册57信号处理函数
        sigaction(57,&sigConfig,NULL);
        while(1){
                sleep(10);
        }
}

void sigHandler(int signum){
        printf("信号%d到达\n",signum);
        int i;
        for(i=0;i<5;i++){
                sleep(2);
                printf("信号%d正在执行中...\n",signum);
        }
}

编译程序后运行,然后再另一个终端向该进程发送55、56、57信号。

[root@VM-0-13-centos ~]# killall -55 signalText
[root@VM-0-13-centos ~]# killall -56 signalText
[root@VM-0-13-centos ~]# killall -57 signalText

执行结果如下:
在这里插入图片描述

这里我是用可靠信号演示的,如果是相同的信号是不会中断处理函数的执行,多个相同信号会挨个排队执行。
如果是不可靠信号,多次发送相同的不可靠信号也会排队但由于是不可靠信号,后面排队的多个相同信号在会被合并为1个。
发送信号如下:
在这里插入图片描述
代码只捕获信号值2和4,执行结果如下:
在这里插入图片描述

可以看到第一个信号2没有被第二个信号2中断,第二个以及第三个信号2会排队等待第一个信号2的处理函数执行完成,第一个信号2的处理函数没执行完又来了一个不同的信号4,信号2的处理函数的执行被中断,开始执行信号4的处理函数,接着后面又来了两个信号4,由于是相同信号,会排队,但由于是不可靠信号,后面相同的信号4会被合并成为1个,所以虽然发送了3个信号4但只打印了两个。最后又来了两个信号2,会和之前的信号2合并为一个。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值