当前位置: 首页 > news >正文

【Linux】系统部分——信号的概念和产生

23.信号的概念和产生

信号的概念

信号是进程之间事件异步通知的⼀种⽅式,属于软中断。 信号的处理是异步的。进程不会立即中断自己正在做的事情去处理信号

理解:

  1. 信号的识别是内置的:信号的定义和编号(如 SIGINT=2, SIGKILL=9)是操作系统内核预先定义好的

  2. 信号的处理方式必须在信号产生之前就由进程预先定义好。处理方式包括:

    • 默认:执行系统预设的操作(如终止、忽略、暂停等)。
    • 忽略:收到信号后直接丢弃,不做任何处理。
    • 自定义捕捉:让进程调用一个自己定义的函数来处理信号。
  3. 一个信号被产生(generated)后,并不会立即被处理。

补充:前台进程和后台进程

  • 前台进程运行时命令行操作无法使用,但是后台进程运行时bash可以解析命令行。前台进程可以用Ctrl C终止,但后台进程无法用这种办法终止,,可以用信号终止kill -9 [pid]

  • 创建后台进程需要在./[可执行程序]后面加上&

    user@iZ7xvdsb1wn2io90klvtwlZ:~/lesson27/Sig$ ./sig &
    
  • 如果不想让后台进程的输出结果在命令行窗口打印,在前面加上加上nohup,输出就会写入文件nohup.out

    user@iZ7xvdsb1wn2io90klvtwlZ:~/lesson27/Sig$ nohup ./sig &
    [3] 11026
    

    使用这种方法之后会在命令行后面显示作业号以及进程的pid,如果想要终止此进程,除了使用信号,还可以使用fg [作业号]的方法将这个后台进程转换为前台进程,之后使用Ctrl C,而Ctrl C这个键盘组合键在操作系统会被转换为2号信号

信号的产生

在前面我们已经了解到信号的处理方法有三种,其中对于自定义捕捉,可以使用signal系统调用对输入的信号编号的默认操作修改为自定义函数来处理信号。

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

可见,通过这个函数,我们可以吧输入的信号编号对应的默认处理方式修改为自定义函数来处理,函数第一个形参需要信号的编号或名称,第二个是函数指针

下图是常见信号的编号和名称的对应关系,其实信号的名称就是一个宏。

user@iZ7xvdsb1wn2io90klvtwlZ:~$ kill -l1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP6) 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

signal使用举例:

#include <iostream>
#include <unistd.h>
#include <signal.h>void Hander(int sig)
{std::cout << "信号:" << sig << std::endl;
}int main()
{signal(2, Hander);while(true){std::cout << "hello linux" << std::endl;sleep(1);}return 0;
}
user@iZ7xvdsb1wn2io90klvtwlZ:~/lesson27/Sig$ make
g++ -o sig Signal.cc
user@iZ7xvdsb1wn2io90klvtwlZ:~/lesson27/Sig$ ./sig 
hello linux
hello linux
^C信号:2
hello linux
hello linux
hello linux
hello linux
^\Quit (core dumped)
  • Ctrl C本来是向前台进程发送终止信号(信号2)但是我们通过signal函数将默认终止改成了Hander方法,当对应信号被触发,内核会将对应信号编号,传奇给自定义方法

  • 要注意的是,signal函数仅仅是设置了特定信号的捕捉⾏为处理⽅式,并不是直接调⽤处理动作。如果后续特定信号没有产⽣,设置的捕捉函数永远也不会被调⽤

  • 我们可以使用man 7 signal查询不同信号的缺省处理方法

    在这里插入图片描述

  • 9号信号无法被自定义捕捉

进程如何获取信号

硬件产生中断,键盘的操作要先经过OS的解析,当OS将这个操作解析为发送指定信号的时候,OS会把对应信号发送给进程,这个发送的本质其实是向进程的PCB中有关信号部分位图的写入(信号发送给进程进程不会立刻进程处理,在进程中有一个位图来保存信号,bit位的位置对应信号的编号,值(0/1)表示是否接受到信号。无论以什么方式发送信号,最终都是转换到OS,让OS写入信号,因为task_struct的唯一管理者是OS

产生信号的方式
  1. 系统发出指令

    使用kill指令可以向进程发送指定信号

  2. 系统调用

    使用系统调用kill可以向进程发送指定信号,系统发出指令实际上也是调用系统调用实现的

    raise 函数可以给当前进程发送指定的信号(⾃⼰给⾃⼰发信号)

    abort 函数使当前进程接收到信号⽽异常终⽌

  3. 软件条件

    比如管道通信时如果读端已经关闭,OS会给写端发送SIGPIPE信号直接终止写端进程,像这种由软件在某一种特定情况下产生信号即为由软件条件产生信号。除此之外SIGALRM也是软件条件触发信号。

    在操作系统中,信号的软件条件指的是由软件内部状态或特定软件操作触发的信号产⽣机制。这些条件包括但不限于定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据产⽣的SIGPIPE信号)等。当这些软件条件满⾜时,操作系统会向相关进程发送相应的信号,以通知进程进⾏相应的处理。简⽽⾔之,软件条件是因操作系统内部或外部软件操作⽽触发的信号产⽣。

    #include <unistd.h>
    unsigned int alarm(unsigned int seconds);
    
    • 调⽤ alarm 函数可以设定⼀个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发SIGALRM 信号,该信号的默认处理动作是终⽌当前进程。

    • 这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。

    • 如果seconds值为0,表⽰取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数

    • 闹钟的设置是一次性的,如果想要这个闹钟重复执行,可以使用signal函数自定义捕捉,让进程调用自己定义的函数,在这个函数的结尾重新开启闹钟

      //伪代码
      void hander(int sig)
      {//........alarm(tim);
      }int main()
      {signal(SIGALRM, hander);//.....alarm(tim);//.....return 0;
      }
      
    • pause函数可以等待信号。导致调用进程(或线程)休眠,直到信号被传递,该信号要么终止进程,要么导致信号捕获函数的调用

      #include <unistd.h>
      int pause(void)
      
  4. 硬件异常产⽣信号

    硬件异常被硬件以某种⽅式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执⾏了除以0的指令, CPU的运算单元会产⽣异常, 内核将这个异常解释为SIGFPE信号发送给进程。再⽐如当前进程访问了⾮法内存地址, MMU会产⽣异常,内核将这个异常解释为SIGSEGV信号发送给进程。

    #include <stdio.h>
    #include <signal.h>
    void handler(int sig)
    {printf("catch a sig : %d\n", sig);
    }
    // v1
    int main()
    {//signal(SIGFPE, handler); // 8) SIGFPEsleep(1);int a = 10;a/=0;while(1);return 0;
    }
    
    catch a sig : 8
    catch a sig : 8
    catch a sig : 8
    catch a sig : 8
    catch a sig : 8
    catch a sig : 8
    catch a sig : 8
    ^C
    

    通过上⾯的实验,我们可能发现:发现⼀直有8号信号产⽣被我们捕获,这是为什么呢?上⾯我们只提到CPU运算异常后,如何处理后续的流程,实际上 OS 会检查应⽤程序的异常情况,其实在CPU中有⼀些控制和状态寄存器,主要⽤于控制处理器的操作,通常由操作系统代码使⽤。状态寄存器可以简单理解为⼀个位图,对应着⼀些状态标记位、溢出标记位。OS 会检测是否存在异常状态,有异常存在就会调⽤对应的异常处理⽅法。除零异常后,我们并没有清理内存,关闭进程打开的⽂件,切换进程等操作,所以CPU中还保留上下⽂数据以及寄存器内容,除零异常会⼀直存在,就有了我们看到的⼀直发出异常信号的现象。

子进程退出core dump

在我们使用man 7 signal指令查询有哪些信号的时候,我们会发现同样执行进程终止的默认操作但是有两种不同的标识TermCore

在这里插入图片描述

Term表示直接进程退出,不需要debug;Core表示核心转储,会在当前目录下生成一个core文件将进程在内存中的部分信息保存起来,便于日后调试。

  • 对于云服务器,这个core文件一般是默认不创建的,可以使用ulimit -a查询这个文件的大小,用ulinit -c修改这个core文件的大小

    user@iZ7xvdsb1wn2io90klvtwlZ:~/lesson27/Sig$ ulimit -a
    real-time non-blocking time  (microseconds, -R) unlimited
    core file size              (blocks, -c) 0
    data seg size               (kbytes, -d) unlimited
    scheduling priority                 (-e) 0
    file size                   (blocks, -f) unlimited
    pending signals                     (-i) 6191
    max locked memory           (kbytes, -l) 206476
    max memory size             (kbytes, -m) unlimited
    open files                          (-n) 65535
    pipe size                (512 bytes, -p) 8
    POSIX message queues         (bytes, -q) 819200
    real-time priority                  (-r) 0
    stack size                  (kbytes, -s) 8192
    cpu time                   (seconds, -t) unlimited
    max user processes                  (-u) 6191
    virtual memory              (kbytes, -v) unlimited
    file locks                          (-x) unlimited
    user@iZ7xvdsb1wn2io90klvtwlZ:~/lesson27/Sig$ ulimit -c 10240
    user@iZ7xvdsb1wn2io90klvtwlZ:~/lesson27/Sig$ ulimit -a
    real-time non-blocking time  (microseconds, -R) unlimited
    core file size              (blocks, -c) 10240
    data seg size               (kbytes, -d) unlimited
    scheduling priority                 (-e) 0
    file size                   (blocks, -f) unlimited
    pending signals                     (-i) 6191
    max locked memory           (kbytes, -l) 206476
    max memory size             (kbytes, -m) unlimited
    open files                          (-n) 65535
    pipe size                (512 bytes, -p) 8
    POSIX message queues         (bytes, -q) 819200
    real-time priority                  (-r) 0
    stack size                  (kbytes, -s) 8192
    cpu time                   (seconds, -t) unlimited
    max user processes                  (-u) 6191
    virtual memory              (kbytes, -v) unlimited
    file locks                          (-x) unlimited
    
  • ⾸先解释什么是Core Dump。当⼀个进程要异常终⽌时,可以选择把进程的⽤⼾空间内存数据全部 保存到磁盘上,⽂件名通常是core,这叫做Core Dump。

  • 进程异常终⽌通常是因为有Bug,⽐如⾮法内存访问导致段错误,事后可以⽤调试器检查core⽂件以查清错误原因,这叫做 Post-mortem Debug (事后调试)。

  • ⼀个进程允许 产⽣多⼤的 core ⽂件取决于进程的 Resource Limit (这个信息保存 在PCB中)。默认是不允许产⽣ core ⽂件的, 因为 core ⽂件中可能包含⽤⼾密码等敏感信息,不安全。

  • 在开发调试阶段可以⽤ ulimit 命令改变这个限制,允许产⽣ core ⽂件。 ⾸先⽤ ulimit 命令改变 Shell 进程的 Resource Limit ,如允许 core ⽂件最⼤为 1024K: $ ulimit -c1024

http://www.dtcms.com/a/375519.html

相关文章:

  • android定制系统完全解除应用安装限制
  • 第2节-过滤表中的行-BETWEEN
  • OpenLayers数据源集成 -- 章节三:矢量要素图层详解
  • 基于AI Agent的智能决策支持系统正在逐步取代传统规则驱动的DSS
  • License 集成 Spring Gateway:解决 WebFlux 非阻塞与 Spring MVC Servlet 阻塞兼容问题
  • spark连接mongodb
  • ubuntu新增磁盘扩展LV卷
  • PowerApps 使用Xrm.Navigation.navigateTo无法打开CustomPage的问题
  • C/C++中基本数据类型在32位/64位系统下的大小
  • TensorFlow 和 PyTorch两大深度学习框架训练数据,并协作一个电商推荐系统
  • ceph scrub 参数
  • JavaWeb--day1--HTMLCSS
  • 全国连锁贸易公司数字化管理软件-优德普SAP零售行业解决方案
  • C++面向对象之继承
  • AI原生编程:智能系统自动扩展术
  • Wireshark TS | 接收数据超出接收窗口
  • 第一代:嵌入式本地状态(Flink 1.x)
  • 4.1-中间件之Redis
  • Django ModelForm:快速构建数据库表单
  • 【迭代】:本地高性能c++对话系统e2e_voice
  • SSE与Websocket、Http的关系
  • 蓓韵安禧DHA展现温和配方的藻油与鱼油营养特色
  • 基于UNet的视网膜血管分割系统
  • python函数和面向对象
  • 嵌入式 - ARM(3)从基础调用到 C / 汇编互调
  • 07MySQL存储引擎与索引优化
  • 面向OS bug的TypeState分析
  • 【文献笔记】Task allocation for multi-AUV system: A review
  • 小红书批量作图软件推荐运营大管家小红书批量作图工具
  • ArrayList详解与实际应用