『 Linux 』信号概念与信号的产生 ( 万字 )

news2024/12/27 10:35:00

文章目录

    • 信号概念
    • 前台进程与后台进程
    • 信号的本质
    • 硬件理解
    • 信号的产生
    • Core dump 标志


信号概念

请添加图片描述

"信号"一词指用来传达信息或只是的各种形式的提示或标志;

在生活中常见的信号例如红绿灯,交通标志,短信通知等

在操作系统中,"信号"是一种用于异步通知进程发生特定事件的机制;

信号允许操作系统或其他进程向目标进程发送通知以便他们能够相应某些时间或条件;

  • 异步

    异步指在处理任务或操作时发起操作与处理结果的时间节点不一致;

    意味着操作的启动和完成不是在同一时间点发生,这种机制一般用于提高程序和效率的响应性;

    • 任务启动与完成的独立性

      在异步操作中任务的发起和完成是独立的;

      发起操作后程序可以继续执行其他任务而不需要等待操作完成;

      使得程序能够在等待期间进行其他有用的工作而提高效率;

    • 回调机制

      异步操作通常使用回调函数来处理操作完成后的结果;

      回调函数会在操作完成时被调用,处理结果或执行后续操作;

      这种机制允许程序继续运行其他任务直到操作完成时才处理结果;

在默认情况下,操作系统将为每个进程预设对所有信号的接收及处理方式;

默认的信号处理行为是由操作系统定义的,确保在没有消失设置信号处理程序的情况下能够合理响应各种信号;

使得即便目前未产生信号时进程也能知道在信号产生之后该作何处理;

  • 信号的异步处理特性

    当一个信号产生时并不会立即中断进程正在执行的关键代码,而是在适当的时候处理;

    表明在信号的产生到处理的过程中必须存在一个"时间窗口"用于进程保存已经识别到的信号,并在适合时间段对信号进行处理;

    这个机制通常通过信号的阻塞(屏蔽)和信号队列来实现;

一个信号由产生到被处理一般要经过三个步骤:

  • 信号的产生
  • 信号的保存
  • 信号的处理


前台进程与后台进程

请添加图片描述

  • 前台进程

    前台进程是直接与用户进行交互的进程;

    用户可以通过中断或者命令行界面与前台进程进行实时交互;

    • 交互性

      前台进程直接接收用户的输入,并将输出显示给用户;

      如在终端运行文本编辑器,浏览器等;

    • 控制终端

      前台进程与控制终端相关联,当用户在终端输入命令时这些命令会发送给前台进程;

    • 信号响应

      前台进程通常响应特点的信号,如按下Ctrl + c发送的SIGINT信号将终止前台进程;

  • 后台进程

    后台进程是在后台执行的进程,不直接与用户交互;

    这些进程通常执行长时间运行的任务,不需要用户的实时干预;

    • 非交互性

      后台进程不直接接受用户输入也不会将输出显示给用户;

      它们通常将输出重定向到文件或是日志;

    • 脱离终端

      后台进程不与控制终端直接关联,因此不会阻塞终端,允许用户继续在终端中执行其他操作;

    • 启动方式

      后台进程通常可以通过在运行命令后加上&符号启动,如:

      ./myprocess &
      
  • 前后台进程的转换

    将前台进程转换到后台:

    $ ./myprocess 
    I am a crazy Process , PID : 15708
    I am a crazy Process , PID : 15708
    ^Z
    [1]+  Stopped                 ./myprocess
    $ bg
    [1]+ ./myprocess &
    I am a crazy Process , PID : 15708
    $ I am a crazy Process , PID : 15708
    I am a crazy Process , PID : 15708
    
    
    • 暂停前台进程

      使用Ctrl + z将前台进程挂起;

    • 将挂起的进程放到后台

      bg命令;

    将后台进程转换到前台:

    $ ./myprocess &
    [1] 15720
    $ I am a crazy Process , PID : 15720
    I am a crazy Process , PID : 15720
    fI am a crazy Process , PID : 15720
    g I am a crazy Process , PID : 15720
    %I am a crazy Process , PID : 15720
    1
    ./myprocess
    I am a crazy Process , PID : 15720
    I am a crazy Process , PID : 15720
    ^C
    
    • 使用fg命令

      $ fg %1
      

      这个命令将后台进程编号为1(非进程PID)的进程调回前台;

Linux中,一次登录中一个终端一般会配上一个bash;

每一个登录只允许一个进程是前台进程,可以允许存在多个后台进程;

当未启动其他前台进程时bash将作为一个前台进程,负责与用户交互;

当用户从键盘中输入数据时将被前台进程获取;


信号的本质

请添加图片描述

使用命令kill -l可以查看当前操作系统中可用的信号;

$ 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	

其中1 - 31号信号被称为 普通信号 ,34 - 64号信号被称为 实时信号 (不考虑实时信号);

信号列表中32号和33号信号不存在,原因为其并非标准的POSIX信号;

信号本质在内核中通过宏定义,通常可在<signal.h>,<asm/signal.h><linux/signal.h>等头文件中找到;

#define SIGHUP    1  /* Hangup (POSIX).  */
#define SIGINT    2  /* Interrupt (ANSI).  */
#define SIGQUIT   3  /* Quit (POSIX).  */
#define SIGILL    4  /* Illegal instruction (ANSI).  */
#define SIGABRT   6  /* Abort (ANSI).  */
#define SIGFPE    8  /* Floating-point exception (ANSI).  */
#define SIGKILL   9  /* Kill, unblockable (POSIX).  */
#define SIGSEGV   11 /* Segmentation violation (ANSI).  */
#define SIGPIPE   13 /* Broken pipe (POSIX).  */
#define SIGALRM   14 /* Alarm clock (POSIX).  */
#define SIGTERM   15 /* Termination (ANSI).  */
// ...

信号的处理方式一般分为三种:

  • 默认动作

    进程收到信号后将执行默认的动作,默认动作属于进程内置功能的一部分;

  • 忽略

    可调用系统调用接口signal()传递SIG_IGN参数来忽略信号(9号信号SIGKILL19号信号SIGSTOP无法被忽略);

    在进行忽略时不需要捕获信号,会直接进行忽略,这意味着捕获和忽略是完全不同的处理方式;

  • 自定义动作

    可调用系统调用接口signal()自定义对应的动作并设置信号的捕捉,使得进程在收到信号时作对应动作(9号信号SIGKILL19号信号SIGSTOP无法被捕捉);

处理方式只能三选一, 不能既…又… ;

本质上在使用Ctrl + C终止一个前台进程是向该前台进程发送了2号信号即SIGINT信号,可通过系统调用接口signal()使用自定义动作进行验证;

  • signal()

    NAME
           signal - ANSI C signal handling
    
    SYNOPSIS
           #include <signal.h>
    
           typedef void (*sighandler_t)(int);
    
           sighandler_t signal(int signum, sighandler_t handler);
    
    RETURN VALUE
           signal()  returns  the  previous  value  of  the signal handler, or SIG_ERR on
           error.  In the event of an error, errno is set to indicate the cause.
    

    当接口调用成功时将返回先前信号处理程序的指针,指针的情况为如下:

    • 自定义处理程序

      将返回一个有效的函数指针指向先前设置的处理程序;

    • SIG_IGN

      表示信号先前被忽略;

    • SIG_DFL

      表示信号先前有默认处理程序;

    当调用失败时返回SIG_ERR表示一个错误并且errno被设置以指示错误原因;

    调用参数如下:

    • int signum

      传入一个参数代表需要操作的信号编号;

    • sighandler_t handler

      选项操作,传入一个函数指针,这个函数指针可以是用户自定义的函数指针(自定义操作),也可以是SIG_IGNSIG_DFL;

      当用户选择自定义操作时需要自行构造一个函数用于自定义动作,自定义动作所用函数必须传入一个参数来接收所捕获的信号编号;

在原有的myprocess.cc文件中进行修改,调用signal()系统调用接口设置信号捕获与自定义动作;

#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

#include <iostream>
using namespace std;

// 自定义动作
void signal_handler(int signum) { cout << "get a signal : " << signum << endl; }

int main() {
  signal(SIGINT, signal_handler);
  while (1) {
    cout << "I am a crazy Process , PID : " << getpid() << endl;
    sleep(1);
  }
  return 0;
}

该操作为两步:

  • 设置信号处理程序

    调用signal(SIGINT, signal_handler)设置信号处理,该操作是一个同步操作;

  • 异步信号处理

    当运行程序时用户按下Ctrl + C发送SIGINT信号将立即中断当前的执行并调用signal_handler函数然后恢复执行循环;

这意味着同一个信号的一种操作的捕获只需要设置一次,如本次操作需要设置捕获到信号2的行为为执行自定义动作;

对应结果为:

 ./myprocess 
I am a crazy Process , PID : 15850
I am a crazy Process , PID : 15850
^Cget a signal : 2
I am a crazy Process , PID : 15850
I am a crazy Process , PID : 15850
^Cget a signal : 2
I am a crazy Process , PID : 15850
I am a crazy Process , PID : 15850
^Cget a signal : 2
I am a crazy Process , PID : 15850
I am a crazy Process , PID : 15850
I am a crazy Process , PID : 15850
...

使用Ctrl + C将为前台进程发送信号2SIGINT,进程捕获到信号执行自定义动作打印对应信号编号;

  • 不可被忽略与捕获的信号

    在所有普通信号中9号信号和19号信号不可被捕获与忽略,其中9号信号SIGKILL19号信号SIGSTOP分别作用为:

    • SIGKILL

      强制终止进程,主要作用是立即停止进程的执行并将其从系统重移除;

    • SIGSTOP

      暂停进程;

      SIGSTOP信号会让进程进入停滞状态,直到接收到继续执行的信号,如SIGCONT;

    本质原因为防止不可杀死进程与无法暂停进程的恶意行为,可循环调用系统调用接口signal()进行验证;

    #include <signal.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    #include <iostream>
    using namespace std;
    
    void signal_handler(int signum) { cout << "get a signal : " << signum << endl; }
    
    int main() {
      for (int i = 1; i <= 31; ++i) {
        signal(i, signal_handler);
      }
      while (1) {
        cout << "I am a crazy Process , PID : " << getpid() << endl;
        sleep(1);
      }
      return 0;
    }
    

    循环调用将131号信号都捕获并执行自定义动作;

    同时使用shell脚本:

    while :; do 
    	for i in $(seq 1 31); do  
    		echo "Sending signal $i to process $TARGET_PID";
    		kill -$i (pid);
    		sleep 1;
    	done; 
    	sleep 50;
    done
    

    来观察对应结果;

    其结果为:

    • bash命令行

      $ while :; do for i in $(seq 1 31); do echo "Sending signal $i to process $17342" ;kill -$i 17342; sleep 1; done; sleep 50; done
      Sending signal 1 to process 17342
      Sending signal 2 to process 17342
      Sending signal 3 to process 17342
      Sending signal 4 to process 17342
      Sending signal 5 to process 17342
      Sending signal 6 to process 17342
      Sending signal 7 to process 17342
      Sending signal 8 to process 17342
      Sending signal 9 to process 17342
      Sending signal 10 to process 17342
      -bash: kill: (17342) - No such process
      ^C
      $ while :; do for i in $(seq 10 31); do echo "Sending signal $i to process $17355" ;kill -$i 17355; sleep 1; done; sleep 50; done
      Sending signal 10 to process 17355
      Sending signal 11 to process 17355
      Sending signal 12 to process 17355
      Sending signal 13 to process 17355
      Sending signal 14 to process 17355
      Sending signal 15 to process 17355
      Sending signal 16 to process 17355
      Sending signal 17 to process 17355
      Sending signal 18 to process 17355
      Sending signal 19 to process 17355
      ^C
      $ kill -9 17355
      $ while :; do for i in $(seq 20 31); do echo "Sending signal $i to process $17367" ;kill -$i 17367; sleep 1; done; sleep 50; done
      Sending signal 20 to process 17367
      Sending signal 21 to process 17367
      Sending signal 22 to process 17367
      Sending signal 23 to process 17367
      Sending signal 24 to process 17367
      Sending signal 25 to process 17367
      Sending signal 26 to process 17367
      Sending signal 27 to process 17367
      Sending signal 28 to process 17367
      Sending signal 29 to process 17367
      Sending signal 30 to process 17367
      Sending signal 31 to process 17367
      ^C
      
    • myprocess所在窗口

      $ ./myprocess
      get a signal : 1
      I am a crazy Process , PID : 17342
      get a signal : 2
      I am a crazy Process , PID : 17342
      get a signal : 3
      I am a crazy Process , PID : 17342
      get a signal : 4
      I am a crazy Process , PID : 17342
      get a signal : 5
      I am a crazy Process , PID : 17342
      get a signal : 6
      I am a crazy Process , PID : 17342
      get a signal : 7
      I am a crazy Process , PID : 17342
      get a signal : 8
      I am a crazy Process , PID : 17342
      Killed  # 被9号信号杀死
      
      $ ./myprocess 
      I am a crazy Process , PID : 17355
      get a signal : 10
      I am a crazy Process , PID : 17355
      get a signal : 11
      I am a crazy Process , PID : 17355
      get a signal : 12
      I am a crazy Process , PID : 17355
      get a signal : 13
      I am a crazy Process , PID : 17355
      get a signal : 14
      I am a crazy Process , PID : 17355
      get a signal : 15
      I am a crazy Process , PID : 17355
      get a signal : 16
      I am a crazy Process , PID : 17355
      get a signal : 17
      I am a crazy Process , PID : 17355
      get a signal : 18
      I am a crazy Process , PID : 17355
      
      [1]+  Stopped                 ./myprocess # 被19号信号暂停
      
      $ ./myprocess
      I am a crazy Process , PID : 17367
      get a signal : 20
      I am a crazy Process , PID : 17367
      get a signal : 21
      I am a crazy Process , PID : 17367
      get a signal : 22
      I am a crazy Process , PID : 17367
      get a signal : 23
      I am a crazy Process , PID : 17367
      get a signal : 24
      I am a crazy Process , PID : 17367
      get a signal : 25
      I am a crazy Process , PID : 17367
      get a signal : 26
      I am a crazy Process , PID : 17367
      get a signal : 27
      I am a crazy Process , PID : 17367
      get a signal : 28
      I am a crazy Process , PID : 17367
      get a signal : 29
      I am a crazy Process , PID : 17367
      get a signal : 30
      I am a crazy Process , PID : 17367
      get a signal : 31
      

硬件理解

请添加图片描述

当用户按下键盘Ctrl + C时将默认转化为信号2,即SIGINT信号;

通常涉及到以下步骤:

  • 键盘输入

    键盘中每个按键都将有一个唯一的键码;

    当用户按下Ctrl + C组合键时,键盘硬件将会生成一个键码,键盘控制器将负责将按键的物理动作转化为键码;

  • 键盘硬件中断

    键盘控制器将检测到按键事件并向CPU发送一个硬件中断请求;

    在发送中断请求的同时,键盘控制器还会将键码放置在其缓冲区中;

    • 普通按键

      当用户按下普通按键(字母,数字等)时,操作系统将会将扫描码转化为ASCII码或其他字符编码并放入键盘缓冲区;

      如果按下普通按键时没有按下任何控制键,操作系统将会视其为普通按键进行输入;

    • 控制按键组合

      如果用户按下了一个普通按键同时按下了一个或多个控制按键(Ctrl,Alt,Shift等),操作系统会检查当前的控制按键状态标志并将按键组合视为特殊的控制输入;

      如按下Ctrl + C会被识别为一个特定的控制字符而不是单独的CtrlC;

  • CPU中断处理

    收到键盘中断请求后CPU将会暂停当前执行的任务并通过中断向量表查找对应的中断服务例程;

    中断向量表是一个包含中断服务例程地址的表,通常存储在内存的特定位置;

  • 中断服务例程

    键盘的中断服务例程是由操作系统提供的处理函数,负责从键盘控制器中读取键码并将其转化为ASCII码或其他合适的格式;

    这些键码通常会被放入一个键盘缓冲区一遍操作系统读取和处理;

  • 操作系统处理键盘输入并生成SIGINT信号

    操作系统内核将定期检查键盘缓冲区并处理其中的键码;

    当检测到Ctrl + C组合键时将是识别出这是一个产生SIGINT信号的控制字符并将该信号发送给当前的前台进程组;

  • 信号接收和处理信号

    进程接收到SIGINT信号将根据预先定义的信号处理机制处理信号;


信号的产生

请添加图片描述

信号的产生可以源于多种情况:

  • 用户输入

    • SIGINT

      用户通过键盘按键组合Ctrl + C产生,作用为中断当前前台进程;

    • SIGQUIT

      用户通过键盘按键组合Ctrl + \产生,作用为强制终止进程并生成核心转储文件;

    • SIGTSTP

      用户通过键盘按键组合Ctrl + Z产生,作用为暂停(停止)当前前台进程,将其放入后台;

  • kill -signo pid

    通过kill命令带信号编号及进程PID产生信号;

  • 系统调用接口

    • kill()

      可通过kill()函数调用产生信号;

      NAME
             kill - send signal to a process
      
      SYNOPSIS
             #include <sys/types.h>
             #include <signal.h>
      
             int kill(pid_t pid, int sig);
      
         Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
      
             kill(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE
      
      RETURN VALUE
             On  success (at least one signal was sent), zero is returned.  On error, -1 is
             returned, and errno is set appropriately.
      
      

      当函数成功调用(至少发送一个信号)返回0,失败则返回-1并设置errno;

      其中参数pid_t pidint sig分别为进程的PID和信号的信号编号;

      可用该接口模拟实现一个kill命令;

      #include <signal.h>
      #include <sys/types.h>
      #include <unistd.h>
      
      #include <iostream>
      #include <string>
      using namespace std;
      
      // 运行用法为
      /*
          ./mykill signum pid
             0       1      2
              一共3个 argc = 3
      */
      
      void Usage(string proc) {// 使用手册
        cout << "usage:\n\t" << proc << " signum pid" << endl << endl;
      }
      
      int main(int argc, char* argv[]) {
        if (argc != 3) {
          Usage(argv[0]);
          exit(3);
        }
        int signum = stoi(argv[1]);
        int pid = stoi(argv[2]);
        kill(pid, signum);
        return 0;
      }
      
    • raise()

      通过调用raise()接口向调用者发送一个信号;

      NAME
             raise - send a signal to the caller
      
      SYNOPSIS
             #include <signal.h>
      
             int raise(int sig);
      
      DESCRIPTION
             The  raise()  function  sends a signal to the calling process or thread.  In a
             single-threaded program it is equivalent to
      
                 kill(getpid(), sig);
      
      RETURN VALUE
             raise() returns 0 on success, and nonzero for failure.
      
      

      函数调用成功时返回0,调用失败时返回!0;

      参数int sig表示需要发送的信号编号;

      验证:

      #include <signal.h>
      #include <sys/types.h>
      #include <unistd.h>
      
      #include <iostream>
      using namespace std;
      
      void signal_handler(int signum) { cout << "get a signal : " << signum << endl; }
      
      int main() {
        signal(SIGINT, signal_handler);
        int n = 4;
        while (n--) {
          cout << "I am a crazy Process , PID : " << getpid() << endl;
          sleep(1);
        }
        raise(2);
        return 0;
      }
      

      当进程执行4s后运行raise(2)向自己发送一个信号,并调用signal()设置捕获;

      $ ./myprocess 
      I am a crazy Process , PID : 18003
      I am a crazy Process , PID : 18003
      I am a crazy Process , PID : 18003
      I am a crazy Process , PID : 18003
      get a signal : 2
      

      结果为发送了信号2并被捕获;

      本质上raise()函数是kill(getpid(), sig)的封装;

    • abort()

      NAME
             abort - cause abnormal process termination
      
      SYNOPSIS
             #include <stdlib.h>
      
             void abort(void);
      
      DESCRIPTION
             The abort() first unblocks the SIGABRT signal, and then raises that signal for
             the calling process.  This results in the abnormal termination of the  process
             unless  the  SIGABRT  signal  is caught and the signal handler does not return
             (see longjmp(3)).
      
             If the abort() function causes  process  termination,  all  open  streams  are
             closed and flushed.
      
             If  the  SIGABRT  signal  is ignored, or caught by a handler that returns, the
             abort() function will still terminate the process.  It does this by  restoring
             the  default  disposition for SIGABRT and then raising the signal for a second
             time.
      
      RETURN VALUE
             The abort() function never returns.
      

      通过调用abort()函数向进程发送一个6号信号SIGABRT信号,并强行终止进程;

      #include <signal.h>
      #include <sys/types.h>
      #include <unistd.h>
      #include <cstdlib>
      #include <iostream>
      using namespace std;
      
      void signal_handler(int signum) { cout << "get a signal : " << signum << endl; }
      
      int main() {
        signal(SIGABRT, signal_handler);// 6号信号可被捕获与忽略
        int n = 0;
        while (++n) {
          cout << "I am a crazy Process , PID : " << getpid() << endl;
          sleep(1);
          if (n % 5 == 0) {
            abort();
          }
        }
        return 0;
      }
      

      自定义动作中并未涉及进程退出;

      5s过后将调用abort()向进程发送一个SIGABRT信号,并使得signal()捕获到该信号并执行自定义动作;

      $ ./myprocess 
      I am a crazy Process , PID : 18218
      I am a crazy Process , PID : 18218
      I am a crazy Process , PID : 18218
      I am a crazy Process , PID : 18218
      I am a crazy Process , PID : 18218
      get a signal : 6
      Aborted
      $ 
      

      运行结果表示信号被捕获但进程仍被终止;

      实际上abort()的封装过程中封装了_exit();

      /* 可能的abort()实现 (简化版本) */
      void abort(void) {
          // 发送 SIGABRT 信号
          raise(SIGABRT);
      
          // 如果 SIGABRT 信号被捕获,并且处理函数返回,则直接退出
          _exit(1); // 使用 _exit 直接退出,避免调用清理函数
      }
      
  • 硬件异常

    • 除零错误

      int main() {
        cout << "div before" << endl;
        sleep(1);
        int a = 10;
        a /= 0;	// 将引发除零错误
        cout << "div after" << endl;
        sleep(1);
      
        return 0;
      }
      

      运行结果为:

      $ ./myprocess 
      div before
      Floating point exception
      $
      

      进程直接崩溃退出;

      当进程执行到除零错误的代码时将会引发程序崩溃,本质上是进程接收到了8号信号SIGFPE即浮点异常,该信号默认动作将进程终止;

      可调用系统调用接口signal()捕获信号进行验证;

      void signal_handler(int signum) {
        cout << "get a signal : " << signum << endl;
        sleep(1);
      }
      
      int main() {
        signal(SIGFPE, signal_handler);
        cout << "div before" << endl;
        sleep(1);
        int a = 10;
        a /= 0;
        cout << "div after" << endl;
        sleep(1);
      
        return 0;
      }
      

      利用signal()系统调用接口捕获8号信号并使用自定义动作,运行结果为:

      $ ./myprocess 
      div before
      get a signal : 8
      get a signal : 8
      get a signal : 8
      get a signal : 8
      get a signal : 8
      get a signal : 8
      get a signal : 8
      ^C
      

      signal()捕获到了8号信号并执行自定义动作,同时进程不退出且不停进行信号捕获执行自定义动作;

      在操作系统组成中存在一个寄存器为 状态寄存器 ,状态寄存器是中央处理器CPU中的一个重要组件,用于存储处理器的状态信息包括各种标志位,标志位以比特位的形式存在,一般情况下当标志位为有效时将标定为1,无效则为0;

      在标志位中存在一个标志位为 溢出标志位(Overflow Flag) ,该标志位用于指示算术运算中是否发生了溢出;

      在进行除零操作时CPU会将溢出标志位设置为有效1;

      操作系统作为硬件的管理者将知道在处理该进程的代码时其溢出标志位将标定为有效即1;

      会将该错误根据错误类型写至该进程task_struct内核结构体中关于 信号处理 的位图中,根据对应的信号错误编号在对应位置进行标志为1;

      当进程task_struct内核结构体中关于信号处理的位图中某个或多个位置被标定为1时表示出现该编号的信号,即需要对信号进行处理;

      此处代码循环调用自定义动作方法的原因在于进程在设置捕获信号后将长时间有效;

      自定义动作并未设置退出,进程将在调度队列中不停被调度;

      调度时EIP寄存器读取对应代码交由CPU处理依旧出现硬件错误并向标志位写入1;

      操作系统依旧读取到为1的标志位并写入至进程属性(位图),进程对信号进行处理,处理方式依旧为自定义动作且不退出,从而造成无限调用;

    • 野指针(段错误)

      int main() {
        signal(SIGSEGV, signal_handler);
        cout << "point error before" << endl;
        sleep(1);
        int *n = nullptr;
        *n = 100;
        cout << "point error after" << endl;
        sleep(1);
        return 0;
      }
      

      运行结果为:

      $ ./myprocess 
      point error before
      Segmentation fault
      

      进程直接崩溃退出;

      当进程执行到野指针的代码时将会引发程序崩溃,本质上是进程接收到了11号信号SIGSEGV即非法内存访问,该信号默认动作将进程终止;

      可调用系统调用接口signal()捕获信号进行验证;

      void signal_handler(int signum) {
        cout << "get a signal : " << signum << endl;
        sleep(1);
      }
      
      int main() {
        signal(SIGSEGV, signal_handler);
        cout << "point error before" << endl;
        sleep(1);
        int *n = nullptr;
        *n = 100;
        cout << "point error after" << endl;
        sleep(1);
        return 0;
      }
      

      利用signal()系统调用接口捕获11号信号并使用自定义动作,运行结果为:

      $ ./myprocess 
      point error before
      get a signal : 11
      get a signal : 11
      get a signal : 11
      get a signal : 11
      get a signal : 11
      get a signal : 11
      get a signal : 11
      get a signal : 11
      ^C
      

      signal()捕获到了11号信号并执行自定义动作,同时进程不退出且不停进行信号捕获执行自定义动作;

      该处持续调用自定义动作原因与除零错误框架大致相同;

      EIP读取代码数据交由CPU处理,读取至内存访问时使MMU内存管理单元进行访问地址并通过页表对地址进行访问;

      MMU访问到空指针nullptr地址转换失败将结果返回给CPU;

      CPU接收到地址转换失败将状态寄存器对应的标志位标为1;

      操作系统读取到标志位为有效将进程信号处理位图对应位置标位1,进程对信号进行处理;

      其往复调用自定义动作原因与除零错误相同,即捕获了信号但未对信号进行处理,硬件异常仍然存在;

    硬件异常只能通过信号的方式告诉上层所出异常的位置以及大致原因,无论是操作系统还是CPU都没有能力对硬件异常进行处理;

  • 软件条件

    • 管道

      管道为进程间通信方式的一种,数据流为单向即一个读端一个写端;

      当写端关闭时读端将读取到文件结束符号EOF,当读端关闭时写端向管道文件写入时将接收到SIGPIPE信号即13号信号;

      void signal_handler(int signum) {
        cout << "get a signal : " << signum << endl;
        sleep(1);
      }
      
      int main() {
        signal(SIGPIPE, signal_handler);
        int pipefd[2];
        int n = pipe(pipefd);
        if (n < 0) {
          cerr << "pipe" << endl;
          exit(1);
        }
      
        pid_t id = fork();
        if (id == 0) {
          // child r
          close(pipefd[1]);
          int cnt = 3;
          char str[1024] = {0};
          while (--cnt) {
            read(pipefd[0], str, sizeof(str));
            sleep(1);
          }
        }
        // parent w
        close(pipefd[0]);
        while (1) {
          write(pipefd[1], "c", 1);
          sleep(1);
        }
        return 0;
      }
      

      执行结果为:

      $ ./myprocess 
      get a signal : 13
      get a signal : 13
      get a signal : 13
      get a signal : 13
      get a signal : 13
      ^C
      

      捕获到了13号信号,但异常并未处理持续进行自定义动作;

    • alarm()闹钟

      该系统调用接口为为用户定一个n秒的闹钟,当闹钟事件发生时发送14号信号SIGALRM;

      NAME
             alarm - set an alarm clock for delivery of a signal
      
      SYNOPSIS
             #include <unistd.h>
      
             unsigned int alarm(unsigned int seconds);
      
      DESCRIPTION
             alarm() arranges for a SIGALRM signal to be delivered to the calling process in seconds seconds.
      
             If seconds is zero, any pending alarm is canceled.
      
             In any event any previously set alarm() is canceled.
      
      RETURN VALUE
             alarm()  returns  the  number of seconds remaining until any previously scheduled alarm was due to be delivered, or
             zero if there was no previously scheduled alarm.
      

      返回值为该进程所定的上一个闹钟的剩余秒数;

      参数unsigned int seconds为传入需要定时的时间,单位为s;

      int main() {
        alarm(5);
        while (1) {
          cout << "run ..." << endl;
          sleep(1);
        }
        return 0;
      }
      

      调用alarm()定一个5s的闹钟并进行无限循环打印字符串;

      运行结果为:

      $ ./myprocess 
      run ...
      run ...
      run ...
      run ...
      run ...
      Alarm clock
      

      进程崩溃退出,可调用signal()捕获14号信号SIGALRM验证;

      void signal_handler(int signum) {
        cout << "get a signal : " << signum << endl;
        sleep(1);
      }
      
      int main() {
        signal(SIGALRM, signal_handler);
        alarm(5);
        while (1) {
          cout << "run ..." << endl;
          sleep(1);
        }
        return 0;
      }
      

      运行结果为:

      $ ./myprocess 
      run ...
      run ...
      run ...
      run ...
      run ...
      get a signal : 14
      run ...
      run ...
      run ...
      ^C 
      

      闹钟事件只响一次,除非在自定义动作中再次设定闹钟,如:

      void signal_handler(int signum) {
        cout << "get a signal : " << signum << endl;
        alarm(2); // 自定义动作中设置闹钟
        sleep(1);
      }
      
      int main() {
        signal(SIGALRM, signal_handler);
        alarm(2);
        while (1) {
          cout << "run ..." << endl;
          sleep(1);
        }
        return 0;
      }
      

Core dump 标志

请添加图片描述

core dump标志为核心转储,指示子进程在因信号终止时是否生成了核心转储文件的重要标志;

通过该标志开发者可以了解程序崩溃时的状态;

在服务器环境中通常Core dump功能是被关闭的,原因为核心转储文件较大,若是服务器频繁的掉线重启将生成多个核心转储文件从而对磁盘进行冲击;

可用ulimit -c unlimited将核心转储大小设置为无限制,或是ulimit -c n(n为自定义的大小)设置核心转储大小;

NAME
       wait, waitpid, waitid - wait for process to change state

SYNOPSIS
       #include <sys/types.h>
       #include <sys/wait.h>

       pid_t wait(int *status);

       pid_t waitpid(pid_t pid, int *status, int options);

       int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       waitid():
           _SVID_SOURCE || _XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED
           || /* Since glibc 2.12: */ _POSIX_C_SOURCE >= 200809L
RETURN VALUE
       wait(): on success, returns the process ID of the terminated child; on error, -1 is returned.

       waitpid():  on  success,  returns the process ID of the child whose state has changed; if WNOHANG was specified and
       one or more child(ren) specified by pid exist, but have not yet changed state, then 0 is returned.  On error, -1 is
       returned.

       waitid(): returns 0 on success or if WNOHANG was specified and no child(ren) specified by id has yet changed state;
       on error, -1 is returned.  Each of these calls sets errno to an appropriate value in the case of an error.

进程等待wait()/waitpid()参数中存在一个int *status输出型参数将会返回一个状态信息;

状态信息为16位,一般分为进程 正常终止被信号所杀 ;

  • 正常终止

    正常终止时低八位代表进程退出的退出码;

    次第八位表示进程的退出状态;

    int main() {
      pid_t id = fork();
      if (id == 0) {
        // child
        int cnt = 3;
        while (--cnt) {
          sleep(1);
          cout << "child process : " << getpid() << endl;
        }
      }
      // parent
      int status = 0;
      pid_t rid = waitpid(id, &status, 0);
      if (id == rid) {
        printf(
            "child quit sucess ,\nrid : %d\nexit code : %d\nexit signal: %d\n",
            rid, ((status >> 8) & 0xFF), (status & 0x8F));
      }
      return 0;
    }
    

    利用位运算进行验证,运行结果为:

    $ ./myprocess 
    child process : 23065
    child process : 23065
    child quit sucess ,
    rid : 23065
    exit code : 0
    exit signal: 0
    
  • 被信号杀死

    被信号所杀时低七位表示进程终止信号编号;

    第八位代表Core dump标志;

    高八位不用;

    int main() {
      pid_t id = fork();
      if (id == 0) {
        // child
        int cnt = 3;
        while (cnt) {
          sleep(1);
          cout << "child process : " << getpid() << endl;
        }
      }
      // parent
      int status = 0;
      pid_t rid = waitpid(id, &status, 0);
      if (id == rid) {
        printf(
            "child quit sucess ,\nrid : %d\nexit code : %d\nexit signal : %d\ncore "
            "dump : %d\n",
            rid, ((status >> 8) & 0xFF), (status & 0x8F), ((status >> 7) & 1));
      }
      return 0;
    }
    

    运行进程并在另外一个会话窗口利用8号信号终止该进程的子进程;

    # 命令
    $ kill -8 23172
    
    # 进程结果
    $ ./myprocess 
    child process : 23172
    child process : 23172
    child quit sucess ,
    rid : 23172
    exit code : 0
    exit signal : 136
    core dump : 1
    

    结果core dump标志为1,说明生成了一个核心转储文件表示运行时错误;

    $ ls
    core.23172  makefile  mykill  myprocess  myprocess.cc 
    # 其中core.23172文件即为核心转储文件
    
  • 核心转储文件的使用

    在使用核心转储文件进行 事后调试 时由于需要用到GDB调试所以需要带上-g选项;

    存在一段具有除零错误的代码:

    int main(){
      int a = 10;
      a/=0;
      return 0;
    }
    

    运行时因除零错误崩溃:

    $ ./myprocess 
    Floating point exception (core dumped)
    

    不同为崩溃后出现了一个core dumped标志表示生成了一个核心转储文件;

    可使用gdb调试对该可执行文件进行调试并通过core-file COREFILENAME的形式定位到具体错误位置:

    (gdb) core-file core.23199 
    [New LWP 23199]
    Core was generated by `./myprocess'.
    Program terminated with signal 8, Arithmetic exception.
    #0  0x00000000004008a1 in main () at myprocess.cc:19
    19	  a/=0;
    Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.3.x86_64 libgcc-4.8.5-44.el7.x86_64
    (gdb) 
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1953311.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flink大状态作业调优——DataStream篇

一、Flink 状态&#xff08;State&#xff09;简介 在流式计算中有些操作一次处理一个独立的事件(比如解析一个事件), 有些操作却需要记住多个事件的信息(比如窗口操作)。那些需要记住多个事件信息的操作就是有状态的。流式计算分为无状态计算和有状态计算两种情况。状态可以理…

jionlp根据词典进行行政区划补全

背景 需要对地址数据进行行政区划补全的,可以用下面的方法,当然是有条件限制的,只限于提供本省的词典和补全本身的地址数据,否则容易错乱 效果测试 lp = LocationParser() loc = 侨英街道乐海南里170号 res = lp(loc) print(res)1、安装或者更新 python安装 pip insta…

【秋招笔试题】小明的美食

解析&#xff1a;思维题。由于需要互不相同&#xff0c;每次操作取重复的值与最大值相加即可&#xff0c;这样即可保证相加后不会新增重复的值。因此统计重复值即可。 #include <iostream> #include <algorithm>using namespace std; const int maxn 1e5 5; int…

【高中数学/对数函数,指数函数】设方程10^x=|lg(-x)|的两根分别为x1,x1,则以下四选项正确的是? (PS:牛顿中值法失效的案例)

【问题】 设方程10^x|lg(-x)|的两根分别为x1,x1,则以下四选项正确的是&#xff1f; A.x1*x2<0 B.x1*x20 C.x1*x2>1 D.0<x1*x2<1 【解答】 10^x|lg(-x)|的两根&#xff0c;即函数y10^x与y|lg(-x)|的两个交点。 函数y10^x的曲线无须赘述&#xff0c;y|lg(-x)…

C++初阶学习第四弹——类与对象(中)

目录 一. 类的默认成员函数 二.六种默认成员函数 1、构造函数 1.1 构造函数的作用 1.2 特性 1.3 默认构造函数 2、析构函数 2.1 析构函数的作用 2.2 析构函数的用法 3、拷贝构造函数 3.1 拷贝构造函数的作用 3.2 特征 3.3 默认拷贝构造函数 三.总结 类与对象&…

LwIP入门实战 — 1 计算机网络简述

目录 1 计算机网络类别 2 常用网络协议与协议栈 2.1 常用网络协议 2.2 常用TCP/IP协议栈 3 网络协议的分层模型 4 协议层报文间的封装与拆封 5 WAN接口和LAN接口 1 计算机网络类别 广域网WAN(Wide Area Network)&#xff1a;广域网的作用范围通常为几十到几千公里&…

浅析JWT原理及牛客出现过的相关面试题

原文链接&#xff1a;https://kixuan.github.io/posts/f568/ 对jwt总是一知半解&#xff0c;而且项目打算写个关于JWT登录的点&#xff0c;所以总结关于JWT的知识及网上面试考察过的点 参考资料&#xff1a; Cookie、Session、Token、JWT_通俗地讲就是验证当前用户的身份,证明-…

记录|使用HslCommunication库进行写入Real数据的坑

项目场景&#xff1a; 现在已经通过HslCommunication连接上了PLC&#xff0c;需要对DB1.DBD10的位置处进行数据写入。 问题描述 但是进行将12.2写入指定位置DB1.DBD10时&#xff0c;发现无法从博图中实时检测到数据的写入。 下面是我当时错误的数据写入方法&#xff1a;【主…

Maven实战(一)- Maven安装与配置

Maven实战&#xff08;一&#xff09;- Maven安装与配置 文章目录 Maven实战&#xff08;一&#xff09;- Maven安装与配置1.下载安装包2.配置环境变量。3.安装目录分析4.设置HTTP代理5.镜像 前言&#xff1a; ​ 最近博主看完了《Maven实战》&#xff08;许晓斌著&#xff09;…

iOS collectionView 滑动出现空白

iOS collectionView 滑动出现空白 一个很常见的 banner 轮播&#xff0c;滑动的时候&#xff0c;有时候会出现空白&#xff0c;检查了下&#xff0c;发现代码没什么问题&#xff0c;上网查了也没啥结果&#xff0c;最后的解决方法是自定义layout解决 interface TMLoopViewLayo…

从0到1,AI我来了- (3)AI图片识别的理论知识-I

从上篇文章&#xff0c;我们分析通过Pytorch 封装的各种方法&#xff0c;解读了一遍图片识别训练到测试的流程。 这篇我们从理论上&#xff0c;理解下&#xff0c;图片是如何被识别的&#xff1f; 核心需要理解的内容有&#xff1f; 一张图片&#xff0c;如何被计算机读懂&a…

探索 Python 异步编程的利器:gevent 库

探索 Python 异步编程的利器&#xff1a;gevent 库 第一部分&#xff1a;背景介绍 在现代的软件开发中&#xff0c;异步编程模式因其在处理 I/O 密集型任务时的高效率而越来越受到重视。Python&#xff0c;作为一种动态、解释型的高级编程语言&#xff0c;其原生的异步编程支持…

如何判断IP是否属于网段10.134.208.0/20

首先想要判断IP是否属于网段&#xff0c;我们首先需要了解IP地址的组成结构&#xff1a; 网络IP地址的划分主要包括网络部分&#xff08;网络地址&#xff09;和主机部分&#xff0c;以及一个特殊的地址——广播地址。以下是详细的划分说明&#xff1a; 一、IP地址的组成 每…

中国式浪漫的源头之一:《楚辞》

文章目录 引言亦余心之所善兮,虽九死其犹未悔。惟草木之零落兮,恐美人之迟暮。沧浪之水清兮,可以濯吾缨。悲莫悲兮生别离,乐莫乐兮新相知。苟余心之端直兮,虽僻远其何伤。孰无施而有报兮,孰不实而有获?尺有所短,寸有所长。引言 楚辞中表里俱佳的文字,很有启发性。楚辞…

【学一点儿前端】本地或jenkins打包报错:getaddrinfo ENOTFOUND registry.nlark.com.

问题 今天jenkins打包一个项目&#xff0c;发现报错了 error An unexpected error occurred: "https://registry.nlark.com/xxxxxxxxxx.tgz: getaddrinfo ENOTFOUND registry.nlark.com". 先写解决方案 把yarn.lock文件里面的registry.nlark.com替换为registry.npmmi…

linux驱动--中断

中断号和中断的申请 中断号的添加-----定义设备节点&#xff0c;描述当前设备 通过设备树文件获取 /dts/xxxx.dts文件中进行设备的设置 在dts设备树文件中进行设备的定义&#xff0c;包括继承的设备&#xff0c;中断号的设置 需要对我们的dts设备树文件进行编译&#xff0…

Flink Doirs Connector 常见问题:Doris目前不支持流读

常见问题 Doris Source 在数据读取完成后&#xff0c;流为什么就结束了&#xff1f; 目前 Doris Source 是有界流&#xff0c;不支持 CDC 方式读取。 问题&#xff1a;对于 Flink Doris DataStream&#xff0c;Flink 想要在 流式读取 Doirs / 实时读 Doris&#xff0c;目前读…

网页的相关概念

什么是网页 网站是指在因特网上根据一定的规则&#xff0c;使用 HTML 等制作的用于展示特定内容相关的网页集合。 网页是网站中的一页&#xff0c;通常是 HTML 格式的文件&#xff0c;需通过浏览器来阅读。 网页是构成网站的基本元素&#xff0c;通常由图片、链接、文字、声…

MySQL表的增删查改(3)——复杂情况

1. 表的设计 三大范式&#xff1a; 一对一&#xff1a;如一个学生可以有一个学号&#xff0c;一个学号只能被一个学生拥有 一对多&#xff1a;如一个学生只能属于一个班级&#xff0c;但一个班级可以有多个学生&#xff08;这种方案在MySQL中是不可行的&#xff0c;但在有些…

深入浅出mediasoup—关键帧请求

当丢包或者解码错误导致无法正确解码视频流&#xff0c;或者当一个新的接收者加入到视频通话时&#xff0c;需要一个关键帧来恢复和开始正常解码。关键帧请求机制是确保视频流在不可靠网络环境下能够恢复和维持高质量播放的关键技术之一&#xff0c;mediasoup 支持关键帧请求。…