当前位置:   article > 正文

linux 信号

linux 信号

信号

信号是Linux系统提供的让用户(进程)给其他进程发送异步信息的一种方式
异步:信号的产生,是由别的进程产生的,进程收到之前,一直在做自己的事情,两个进程并发在跑
那为什么要有信号呢?
系统要求进程有随时响应外部信号的能力,随后做出反应

  1. 在没有发生时,进程已经知道发生时怎么处理了
  2. 进程能够认识信号,是设置了设别特定信号的方式
  3. 信号到来时,进程在处理更重要的事情,暂时不能处理到来的信号,所以进程必须暂时将到来的信号保存起来
  4. 信号到来,可以不立即处理,在合适的时候处理
  5. 信号的产生是随时产生的,无法准确预料,所以信号是异步发送的

信号概念

kill -l :查看所有的信号
在这里插入图片描述
其中没有0,32,33号信号,总共62个信号,其中34-62是实时信号,我们不做学习,1-31是非实时信号,总共31个 。
其中每个信号都有一个编号和一个宏定义,通过编号或宏定义都可以表示一个信号。
之前说过,信号到来时进程可能有更重要的事情,所以需要对信号进行保存,在Linux中,信号保存在进程的PCB中,同时Linux使用位图来存储信号,所以我们经常说的发送信号本质是操作系统往进程PCB中写信号,位图中的比特位的位置表示是哪一个信号,比特位的内容为1表示收到该信号,为0表示没有收到该信号

之前说过,ctrl + c可以终止当前进程,其实ctrl + c本质是向进程发送了2号信号

信号的处理方式:
1. 默认动作, 进程收到信号的默认动作大多都是终止进程
使用 man 7 signal 可以查看每个信号的默认动作
在这里插入图片描述
2. 忽略信号
3. 自定义处理信号(信号捕捉)
在这里插入图片描述
signal函数可以自定义信号处理方式,当收到指定信号时,执行用户所定义的信号处理函数

在这里插入图片描述

硬件中断
  1. 我们按下了ctrl + c,被解释为信号,进程收到了2号信号,默认终止进程。
  2. OS怎么知道键盘输入数据呢? 硬件中断

OS在开始时会注册一张表,表中有各种软硬件操作(函数指针),表的名称为中断向量表(函数指针数组)
同时CPU有很多针脚,和外设物理连接,每个针脚有自己的编号,键盘按下按键会给特定的针脚发送高电平,触发硬件中断,CPU会识别到针脚的编号(中端号),并保存在指定寄存器中,当中断发生时,CPU会暂停当前正在执行的程序,并保存当前进程的上下文数据,然后查找中断向量表,找到与中断号对应的处理函数的地址,回调方法。
通过上述操作,就可以读取键盘数据了,如果判定为字符,就写到键盘的文件缓冲区,如果判定为控制命令,比如ctrl + c,就解释为信号,发送给进程,

信号的产生

  1. kill命令

  2. 键盘产生
    ctrl + c -> OS解释为SIGINT信号 ->发送给进程
    ctrl + \ -> OS解释为SIGQUIT信号 -> 发送给进程

    在这里插入图片描述
    在这里插入图片描述
    SIGINT的默认动作是终止进程,SIGQUIT的默认动作也是终止进程,那么Core和Trem有什么区别吗?
    首先,云服务器默认将进程的Core退出进行了特殊处理,默认Core是关闭的

    如何打开?使用ulimit命令允许Core文件最大为1024k ulimit -c 1024
    在这里插入图片描述
    core dump:将进程再内存中的核心数据(与调试相关)转储到磁盘中形成core文件
    为什么要有呢?一个进程异常退出,想通过core定位到进程为什么退出以及执行到哪里推出的
    core文件可以帮助我们事后用调试器检查core文件以查清错误原因事后调试

  3. 系统调用
    在这里插入图片描述
    kill:给指定进程发送指定信号
    在这里插入图片描述
    raise:自己给自己发送信号
    在这里插入图片描述
    abort:给自己发送6号信号,引起进程终止

  4. 软件条件
    在匿名管道中,如果读端不读并且关闭了读端,这时写端再往管道里写,就会收到13 GIGPIPE信号,终止进程。
    在这里插入图片描述
    alarm函数可以设置一个闹钟,也就是告诉OSseconds之后向当前进程发送14 SIGARRM信号,该信号默认终止当前进程。函数返回值为0或以前设定闹钟剩下的秒数。
    在这里插入图片描述
    设定闹钟是再OS内部,OS内可能有很多进程使用闹钟,所以OS要管理所有的闹钟,先描述,再组织,所以内核中一定要有描述闹钟属性的结构体,并用特定的数据结构组织起来,这里可以使用小根堆来对闹钟进行管理。这里的结构体,数据结构都属于软件条件哦

  5. 异常
    段错误在这里插入图片描述
    除0错误在这里插入图片描述
    硬件异常被检测到并通知OS,然后OS向当前进程发送合适的信号
    除0错误:当前进程执行了除0的指令,CPU的运算单元会发生异常,内核将这个异常解释为SIGFPE信号发送给进程
    段错误:当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程

综上,无论信号产生的方式有多少种,最终都是OS向进程写入信号

信号的保存

实际执行信号的处理动作称为信号的抵达
信号产生到抵达之间的状态称为信号未决(pending)
进程可以选择阻塞(block)某个信号。

阻塞和屏蔽:
阻塞:一个信号阻塞,则该信号永远无法抵达处理,除非解除阻塞
忽略:忽略是信号抵达的一种方式

在这里插入图片描述

block表:位图结构,比特位位置表示信号编号,比特位内容表示该信号是否被阻塞
pending表:位图结构,比特位的位置表示信号编号,比特位内容表示该信号是否未决
handler表:函数指针数组,每个函数指针的类型为void(*)(int),数组下标表示信号编号,数组内容表示该信号的抵达处理动作

因为pending表是位图结构,所以信号在抵达之前最多只计一次
pending表和block表可以使用相同的数据类型sigset_t存储,sigset_t称为信号集,阻塞信号集也叫做信号屏蔽字

信号集操作函数
在这里插入图片描述
sigprocmask:读取或更改进程的信号屏蔽字在这里插入图片描述
how参数选项:在这里插入图片描述
sigpending:读取当前进程的信号集
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
信号抵达时,一定会把对应的位图清0,先清0,再抵达

9,19号信号无法被阻塞,18号信号会被做特殊处理

信号的处理

之前说过:信号到来,可以不立即处理,在合适的时候处理,什么是合适的时候呢?
合适的时候:进程从内核态,切换会用户态时,信号会被检测并处理
在这里插入图片描述
内核态:OS的运行状态,权限级别高
用户态:普通程序的运行状态
在这里插入图片描述

32位平台下,进程地址空间中[0,3]GB属于用户空间,[3,4]GB属于内核空间
电脑开机时,第一个加载的软件是OS
OS只有一份,所以所有进程虚拟地址空间中的内核空间相同,用户空间不同,进程无论怎么切换,总能找到OS,本质就是通过进程的地址空间来访问即可
我们使用系统调用或者访问系统数据,其实还是在进程的地址空间中进行跳转的
用户可以随意访问用户空间,但用户不能直接访问内核空间,这时候,就必须能区分当前用户的运行模式:内核态和用户态设置CPU内寄存器指定标志位,更改CPU执行权限

在这里插入图片描述
在这里插入图片描述
sigaction结构体:
sa_handler:自定义动作
sa_sigaction:和实时信号相关,不做处理
sa_mask:执行signum的handler方法时,可再屏蔽其他信号
sa_flags:默认传0即可
sa_restorer:和实时信号相关,不做处理

当某个信号处理函数被调用时,内核会自动将当前信号加入进程的信号屏蔽字中,当信号处理函数返回时自动恢复原来的信号屏蔽字。如果设置了sa_mask,处理函数被调用时,内核会自动将sa_mask信号集中的信号加入进程的信号屏蔽字中,当信号处理函数返回时自动恢复原来的信号屏蔽字。

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
using namespace std;

 void handler(int signo)
 {
     cout << "get a signal, signo : " << signo << endl;
    sigset_t s;
    sigemptyset(&s);
    while (true)
    {
        cout << "pending set : ";
        sigpending(&s);
        for (int i = 31; i >= 1; i--)
        {
            if(sigismember(&s, i))
                cout << '1';
            else
                cout << '0';
        }
        cout << endl;
        sleep(1);
    }
}

int main()
{
    cout << "pid : " << getpid() << endl;
    struct sigaction act, oact;
    act.sa_handler = handler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    sigaddset(&act.sa_mask, 5);

    //信号抵达处理时,阻塞该信号
    int n = sigaction(2, &act, &oact);
    assert(n == 0);

    while (true)
        sleep(1);
    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/503175
推荐阅读
相关标签
  

闽ICP备14008679号