首页 > 系统相关 >【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD

【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD

时间:2023-05-21 23:00:41浏览次数:36  
标签:SIGCHLD int signo handler volalite 信号 进程 include


信号

  • 1. 信号的产生 - 信号发送前
  • 1.1 键盘产生
  • 1.2 异常
  • 1.3 系统调用
  • 1.4 软件条件
  • 1.5 理解发送信号
  • 2. 信号的保存 - 信号发送中
  • 2.1 相关概念 & 内核结构
  • 2.2 sigset_t 及一系列系统调用函数
  • 3. 信号的处理 - 信号发送后
  • 3.1 内核如何实现信号捕捉
  • 3.2 sigaction
  • 4. 可重入函数
  • 5. volalite关键字
  • 6. SIGCHLD信号


people change

前排声明:以后更新文章会慢一些吧,如上文所言,people change… (哈哈哈距离我刚开始写下这句话时候已过去一个月了,果然,人生总是在不经意的想法中出现转折,曾经认为理所当然的事儿… 所以顺其自然…

“而且我们都是学计算机的,我们知道如果把宇宙所有的原子结构保存,配上超强的算法,一切都是必然的,我们决定不了的”

“你一上升到这个高度我就讨论不了了”

“我只是想说顺其自然”

a zing never lies @小边小边别发愁

生活中有许多关于信号的场景:比如红绿灯,你daddy的脸色。。。

这些场景触发时,我立马就知道我接下来该做什么,这来自于天生or后天习得,并不是它们真的发生时我知道该怎么做。同理,进程具有识别并处理信号的能力是远远早于信号产生的,这是由那些编写操作系统的工程师写好的。

在生活中,我们收到信号时,不一定会立即处理,因为当前我可能做着更重要的事儿。同理,信号随时都可能产生(异步),进程收到某种信号时,不一定是立即处理的,而是在合适的时候处理

比如有一小伙儿跟你表白了,你虽然没有立即回复(此时你正向闺蜜群组求助哈哈哈),但你也记着要给个答复。既然有时信号不能被立即处理,进程就需要先将信号保存起来,以供合适时机处理。那应该保存在哪里呢?struct task_struct,信号的发送即向进程控制块写入信号数据。task_struct是一个内核数据结构,内核不相信任何人,只相信自己,无论以何种方式发送信号,本质都是通过OS发送的,那一定是操作系统向task_struct写入信号数据。

kill -l 查看系统支持的信号列表

前31个(1 ~ 31)是普通信号,后31个(34 ~ 64)是实时信号 ——


【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_#include

一般进程收到信号的处理方案有三种情况 ——

  1. 默认动作:终止自己、暂停等
  2. 忽略动作:是一种信号处理的方式,只不过就是什么也不干
  3. 自定义动作(信号的捕捉):比如如下的signal方法,修改处理信号的动作,由默认动作变为自定义动作

1. 信号的产生 - 信号发送前

信号产生的方式有哪些呢?

1.1 键盘产生

写一段死循环函数,可以ctrl+c终止程序。我们键盘ctrl+C时,本质是向指定进程发送2号信号SIGINT。那你怎么证明?可以通过signal函数为信号指定处理函数:

signal - ANSI C signal handling

#include <signal.h>

typedef void (*sighandler_t)(int); //函数指针 - 返回值为void,参数为int

sighandler_t signal(int signum, sighandler_t handler);

修改进程对信号的默认处理动作 ——

  • signal:实际上这些大写的信号都是#define 整数定义出来的
  • handler:注册函数。注册时不是调用这个函数,只有信号到来时,函数才会被调用。

代码如下 ——

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

void handler(int signo)    
{    
  printf("get a signal: %d, pid: %d\n", signo, getpid());    
  exit(1);
}    
    
int main()    
{ 
  int sig;
  for(sig = 1; sig<=31; sig++)
  {
    //通过signal注册对1~31号信号的处理动作,改成我们自定义的动作    
    signal(sig, handler);                                                           
  }
  
  while(1)    
  {    
    printf("people change...pid: %d\n", getpid());    
    sleep(1);    
  }    
  return 0;    
}

发送信号时,执行自定义方法 ——


【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_位图_02

信号的产生方式的其中一种是通过键盘产生,键盘产生的信号只能终止前台进程,杀掉后台进程&得用kill命令 ——

kill -9 [pid]

我们还可以设置不同信号的处理方式 ——

#include<stdio.h>    
#include<unistd.h>    
#include<signal.h>    
#include<stdlib.h>    
    
void handler(int signo)    
{    
  switch(signo)    
  {    
    case 2:                                                                     
      printf("Ayayaya~\n");    
      break;    
    case 3:    
      printf("people change a lot\n");    
      break;    
    case 9:    
      printf("I am tired\n");    
      break;    
    default:
      printf("signo: %d\n", signo);
      break;    
  }    
  //exit(1);
}    
    
int main()    
{    
  int sig;    
  for(sig = 1; sig<=31; sig++)    
  { 
    //通过signal注册对1~31号信号的处理动作,改成我们自定义的动作    
    signal(sig, handler);    
  }    
  while(1)    
  {    
    printf("people change...pid: %d\n", getpid());
    sleep(1);
  }
  return 0;
}

发现9号信号不可被捕捉(自定义) ——

【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_linux_03


为什么呢?we will talk about it later…

1.2 异常

信号产生,也可能由于程序中存在异常问题,使进程收到信号并退出。

写一段野指针解引用代码 ——

int* p = NULL;    
  *p = 100;

进程为什么会崩溃呢?本质是因为进程收到了11号信号SIGSEGV。而且如果不exit会刷屏,说明不断收到11号信号。


【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_#include_04

除0 ——

int a = 10;
  a /= 0;

除0收到的是8号信号SIGFPE,浮点数异常 ——


【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_c语言_05

综上,在win或Linux环境下,进程崩溃的本质是进程收到了对应的信号,并执行默认处理动作(杀死进程)。那为什么会收到信号呢?

软件上的错误通常会体现在硬件或其它软件上,而操作系统是硬件管理者,就应该对硬件的健康负责,找到有问题的进程并发送信号反馈,终止进程。我们在C++中的捕捉异常,实际上就是在处理信号。

进程崩溃时,你最关心的是崩溃的原因,这是通过waitpid()的status参数的低7位来获取退出信号。(忘了的宝子去复习进程控制吧~)


【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_linux_06

从前从前我们就知道,在Linux中,进程正常退出时,它的退出码和退出信号都会被设置;进程异常退出时,它的退出信号会被设置,表明进程退出的原因。另外我还想知道是在哪一行崩溃的喂?

从前从前我们挖了这样一个坑:如有必要,OS会设置退出信息中的core dump标志位,并将进程在内存中的数据转储到磁盘中,方便我们后期调试。

在云服务器上,core dump被关掉了,我们把它打开~

ulimit -a	查看系统资源
ulimit -c 10240


【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_#include_07

这样就允许你core dump了,(但也不是所有的信号都会core dumped),此时再运行,还生成了core文件 ——

【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_linux_08

【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_#include_09

我们打开生成的core.10225,发现是人类看不懂的乱码,因为是把磁盘内容直接拷下来的,我们也不关心啦~

我们需要小小修改一下Makefile文件,编译时带上-g选项,允许用gdb调试。用core-file命令,得知错误原因及错误行数 ——

【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_linux_08

【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_#include_11

这种方案叫做事后调试。曾经我们程序崩溃,我们只能注释掉定位或者一行一行的单步调试。

若进程出现异常时被core dump,core dump会被设置为1,我们来通过位操作获取退出信息 ——

#include<stdio.h>    
#include<unistd.h>                                                            
#include<sys/types.h>    
#include<sys/wait.h>    

int main()    
{    
    if(fork() == 0)    
    {    
      while(1)    
      {    
        printf("I am child...\n");    
        int a = 10;    
        a /= 0;    
      }    
    }    

    int status = 0;    
    waitpid(-1, &status, 0);    
    printf("exit code: %d, exit sig:%d, core dump flag: %d\n", (status>>8)&0xFF, status&0x7F, (status>>7)&1); return 0;    
}


【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_#include_12

1.3 系统调用

通过系统调用产生信号 ——

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

int kill(pid_t pid, int sig); //给任意进程发送任意信号
int raise(int sig); //给自己发任意信号
#include <stdlib.h>

void abort(void); //给自己发送SIGABRT信号

kill命令就是调用kill函数实现的 ——

#include<stdio.h>    
#include<sys/types.h>    
#include<signal.h>    
#include<stdlib.h>    
    
static void Usage(const char* proc)    
{    
  printf("Usage:\n\t%s signo who", proc);    
}    
 
// ./mytest signo who
int main(int argc,char* argv[])    
{    
  if(argc != 3)    
  {    
    Usage(argv[0]);    
    return 1;    
  }    
      
  int signo = atoi(argv[1]);    
  int who = atoi(argv[2]);    
      
  kill(who, signo);    
      
  return 0;                                                                                     
}

【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_#include_13

1.4 软件条件

通过某种软件(OS),来触发信号的发送,在系统层面设置定时器,或某种操作导致条件不就绪等这样的场景下,触发的信号。

在进程间通信匿名管道的4种场景中,写端疯狂写,若此时读端关闭了读fd,则写端会**收到13号信号SIGPIPE**退出,这就是一种软件条件触发的信号发送。今天我们再介绍 ——

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

设置一个计时器/闹钟,延迟n秒后发送14号信号SIGALRM ——

  • 返回值:可以认为alarm被正常触发时,返回值就是0;或者是距离设定时间余下的秒数。
#include<stdio.h>    
#include<sys/types.h>    
#include<signal.h>    
#include<stdlib.h>    
#include<unistd.h>    
    
void handler(int signo)    
{    
  printf("well, I recieved %d signal\n", signo);    
}    
    
int main()    
{    
  int sig;    
  for(sig = 1; sig <= 31;sig++)    
  {    
    signal(sig, handler);    
  }    
    
  int ret = alarm(20);    
    
  sleep(5);    
  ret = alarm(0); //取消闹钟                                                                                                                                        
  printf("autually, it still left %d seconds\n", ret);    
    
  return 0;    
}

【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_c语言_14

【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_linux_15

我们不断打印count,统计1s中server对count可以递增到三万多次;然后屏蔽掉printf,而只是在捕捉信号处打印count,发现count达到了5亿次 ——

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

int count = 0;    

//没有设置alarm信号(14)的捕捉/自定义动作, 执行默认动作:终止进程
//void handler(int signo)    
//{    
//	printf("after 1s... count: %d\n", count);                                                               //  exit(1);    
//}    

int main()    
{    
    signal(SIGALRM, handler);    
    alarm(1); /    
    while(1)    
    {    
      //printf("people change...%d\n", count);    
      count++;    
    }    
    return 0;    
}


【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_#include_16

因为IO是非常慢的。。

1.5 理解发送信号

信号产生方式的种类非常多,但就算信号产生的方式千差万别,最终一定是通过OS向目标进程发送信号。

如何理解OS给进程发送信号?朴素的理解就是OS发送信号数据给task_struct,我们要进一步来谈 ——

我们发现信号的编号是有规律的**[1, 31],进程内部一定要有对应的数据变量,来保存记录是否收到了对应的信号。那用什么数据类型,来标识进程是否收到信号**呢?唔唔唔!位图

struct task_struct{
    //进程的各种属性
    uint32_t sigs; 
};

比特位的位置(第几个),代表的是哪一个信号;比特位的内容(0/1),代表是否收到信号 ——

//比如这个,表示收到3号信号
00000000 00000000 00000000 00000100

综上,“信号的发送”更准确的说是信号的写入,即OS向指定进程的task_struct的信号位图(pending位图)比特位 置为1。

2. 信号的保存 - 信号发送中

信号随时都可能产生(异步),进程收到某种信号时不一定是立即处理的,因为当前我可能做着更重要的事儿,而是把它暂存起来在合适的时候处理。怎么保存信号?合适又是什么时候?

2.1 相关概念 & 内核结构

信号有三张表:pendingblockhandler,我们姑且不关心它们底层的数据结构,其中pending表就是用来写入接收到的信号的位图 ——


【Linux】信号 —— 信号的产生 | 信号的保存 | 信号的处理 | volalite关键字 | SIGCHLD_linux_17

  • 实际执行信号的处理动作称为信号递达(Delivery) —— 自定义捕捉、默认、忽略
  • 信号从产生到递达之间的状态,称为信号未决(Pending) —— 本质是这个信号被暂存在task_struct的信号位图pending中。
  • 进程可以选择阻塞信号(Block) —— 本质是OS允许进程暂时屏蔽指定信号,表示该信号仍然是未决的,该信号不会被递达,直到解除阻塞方可执行递达的动作

注意:阻塞和递达中的忽略有区别吗?yes,忽略是递达的一种方式,而阻塞是递达前的独立状态。

grep -ER 'SIG_DFL | SIG_IGN' /usr/include	递归查找一下这两个宏
    
#define SIG_DFL	((__sighandler_t)0)	/* Default action */
#define SIG_IGN	((__sighandler_t)1)	/* ignore signal */
  • block表:信号屏蔽字,阻塞位图,表示是否阻塞,不影响接收信号但影响递达信号。是一种状态位图,同样的也是uint32_t block;无符号整数类型:比特位的位置,代表信号的编号;比特位的内容,代表信号是否被阻塞(屏蔽)。这些信号不会被递达直到解除阻塞。
  • pending表:未决位图,保存的是已经收到但没有被递达的信号。比特位的位置(第几个),代表的是哪一个信号;比特位的内容(0/1),代表是否收到信号。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。所以你看,发送信号时都要给pid和信号数。
  • handler表:函数指针数组,每个信号的编号就是该数组的下标。实际上,我们执行signal方法,对特定的信号注册自定义方法的本质就是,把handler函数地址填入到信号编号对应下标的 handler表中,这样执行的就是自定义方法。

内核中对信号会做类似如下检测 ——

int ishandled(int signo)
{
    if(block & signo) { //该信号被block
        //根本就不看是否收到该信号
	}
    else if(pending & signo) { //该信号没被block,且已经收到了
        handler_array[signo](signo); /*回调函数*/
        return 0;
	}
    return 1;
}

综上,这张表应该横着看,进程内置了识别信号的方式,[三元组] 是否被屏蔽→是否被递达(→handler),即你是谁?你现在能否处理?怎么处理?

2.2 sigset_t 及一系列系统调用函数

从上图来看,每个信号只有一个1bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,这种类型无法直接对其进行位操作,因为这是极不安全的,就必须通过系统调用接口。因为不同OS对于sigset_t位图结构的实现是不一样的,因此不能让用户直接修改。它定义的变量和我们之前的int、double没有任何区别,都是在用户栈上。

标签:SIGCHLD,int,signo,handler,volalite,信号,进程,include
From: https://blog.51cto.com/u_15091587/6320556

相关文章

  • 全国产化 PCIe3.0/千兆/万兆 信号处理卡
    UDPCIe-404全国产化信号处理模块为标准PCIe全高的结构,对外支持PCIe3.0×8通信,也可以采用千兆以太网(RJ45连接器)、万兆以太网(或RapidIO、Aurora,QSFP+连接器)接口进行通信,支持多板级联,模块为100%国产化设计(同时也兼容进口器件)。FPGA芯片可选上海复旦微或深圳国微公司的V7-690T,两组DDR3......
  • 锁机制和信号量机制实现水果问题,同步机制
    使用Semaphore类实现packagecom.huo.HelperClass.demo;importsun.security.krb5.internal.TGSRep;importjava.util.concurrent.Semaphore;/***@version1.0*@Author作者名*@Date2022/9/1311:26*///使用Semaphore实现水果同步问题publicclassFruitDem......
  • 锯齿波调制的FMCW雷达差拍信号的推导与分析
    1、背景又是同事的问题,同事当时问了一下雷达的中频信号跟信号的起始的频率是否有关,我当时没有回答出来。于是我痛并思痛,找了一些相关的资料,来记录和总结一下,算是自己的一个学习,也方便后面自己的查阅,如果能够帮到大家,那便是极好的。话不多说,咱们进入正题。2、推导与分析......
  • Java Semaphore 信号量详解
    Semaphore基本使用场景Semaphore的基本使用场景是限制一定数量的线程能够去执行.举个简单的例子:一个单向隧道能同时容纳10个小汽车或5个卡车通过(1个卡车等效与2个小汽车),而隧道入口记录着当前已经在隧道内的汽车等效比重.比如1个小汽车和1个卡车,则隧道入口显示3.若隧道......
  • 使用bandpass Butterworth filter对信号数据进行滤波去噪
    在信号处理中,有些信号会包含大量的噪声,需要用一些滤波器去噪,本文介绍使用bandpassButterworthfilter进行去噪。直接使用sciPypython库可以实现噪声滤波步骤1:导入所需python模块fromscipy.signalimportfiltfiltfromscipyimportstatsimportCSVimportpandasaspdim......
  • Linux的信号处理机制
    信号是Linux操作系统中进程间通讯的方式,是一种异步的通知机制。信号的产生1.硬件方式用户输入:比如在终端上按下组合键ctrl+C,产生SIGINT信号;硬件异常:CPU检测到内存非法访问等异常,通知内核生成相应信号,并发送给发生事件的进程;2.软件方式通过系统调用,发送signal信号:kill(),rai......
  • FMC子卡设计资料原理图450-基于ADRV9009的双收双发射频FMC子卡 数字信号处理卡 射频收
    FMCJ450-基于ADRV9009的双收双发射频FMC子卡   一、板卡概述       ADRV9009是一款高集成度射频(RF)、捷变收发器,提供双通道发射器和接收器、集成式频率合成器以及数字信号处理功能。这款IC具备多样化的高性能和低功耗组合,FMC子卡为2路输入,2路输出的......
  • 基于FPGA的FIR低通滤波器verilog开发,包含testbench测试程序,输入噪声信号使用MATLAB
    1.算法仿真效果VIVADO2019.2/matlab2022a仿真结果如下:运行matlab:将matlab得到的数据文件保存到FPGA的project_13.sim\sim_1\behav\xsim路径,测试仿真时,可以自动调用matlab任意产生的测试数据。rtl:2.算法涉及理论知识概要FIR(FiniteImpulseResponse)滤波器:有限长单位冲激......
  • 基于FPGA的FIR低通滤波器verilog开发,包含testbench测试程序,输入噪声信号使用MATLAB
    1.算法仿真效果VIVADO2019.2/matlab2022a仿真结果如下: 运行matlab:    将matlab得到的数据文件保存到FPGA的project_13.sim\sim_1\behav\xsim路径,测试仿真时,可以自动调用matlab任意产生的测试数据。   rtl:   2.算法涉及理论知识概要       ......
  • 电流信号输入输出板,风电控制器控制板,光伏PID抑制器,逆变器总控板,公司DSP项目,如下:
    电流信号输入输出板,风电控制器控制板,光伏PID抑制器,逆变器总控板,公司DSP项目,如下:1)4通道-10mA~10mA电流信号采集,4路-40mA~40mA电流信号采集,1路-40mA~40mA电流信号输出。采用TMS320F28035控制,CAN通讯,提供原理图,PCB,元器件明细表。2)风力发电控制板,主要涉及电压电流信号采集,PWM波输出,风......