Linux信号

本文介绍了Linux系统中的信号处理,包括信号的来源、常见信号如SIGINT和SIGFPE的处理,以及如何通过信号屏蔽和sigprocmask函数自定义信号处理。还讨论了信号的保存状态,如pending和block,并展示了如何查看和控制进程的信号屏蔽位图。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在生活中的信号,有早上的起床闹钟,过马路的红绿灯,快递的运输提示等等这些都可以称为信号,而对信号的处理也大都不相同。


一、信号的准备

在技术应用上的信号,是怎么来的呢?
可以肯定的是,肯定是程序员编写来的,那程序员是怎么编写的,在我们生活中,我们为什么知道过马路,红灯亮时,要停下,绿灯亮时可以过呢,这是因为我们已经提前,给这个信号,做了准备,做了默认处理,在技术应用上也是这样的,提前给这些信号,做了预处理,并提示这是第几号信号。

在Linux上,我们可以输入kill -l 来获取这些信号提示,一共是62个信号,没有32,33,没有0号信号

[1:31]号新号被称为普通信号
[34:64]信号中带有RT的称为实时信号
分时操作系统和实时操作系统
实时操作系统有严格的时序,需要立马严格地处理完成。

在这里插入图片描述

这里我们介绍一下,几个常见的信号。

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

int main()
{
  while(1)
  {
    cout << "我正在循环打印这段话...." << endl;
    sleep(1);    
  }
  return 0;
}

在这里插入图片描述

我们在键盘上按control+c的快捷键发送的信号对应是2号信号,SIGINT,进程在收到该信号时,会进行默认处理,退出进程。


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

int main()
{
  int a  = 10;
  while(1)
  {
     cout<<"我正在除零操作"<< endl;
     a/=0;
  }
  return 0;
}

在这里插入图片描述

CPU有一个状态寄存器,CPU在做计算时,如果溢出了,状态寄存器就会被置为1,并立刻向进程发送8号信号,Floating point exception 浮点数异常 ,SIGFPE,执行收到该信号的默认处理,退出进程。

我们也可以不进行默认操作,通过信号捕捉,进行自定义处理。

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


void Custom_processing(int signal)
{
  while(true)
  {
   cout << "我捕捉到"<<signal<<"号信号,我的自定义处理方案是一直打印这句话"<<endl;
   sleep(1); 
  } 
}
int main()
{
//通过传递函数指针,我们捕捉到相对应的信号后
//进行我们自定义的处理方式
  signal(8,Custom_processing);
  int a  = 10;
  while(1)
  {
     cout<<"我正在除零操作"<< endl;
     a/=0;
  }
  return 0;
}

在这里插入图片描述

这里为什么一直在打印呢,这是因为,CPU的状态寄存器,还是为1的,这个进程没有被退出,操作系统没有修复这个异常,只能通过其他信号终止进程。


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


void Custom_processing(int signal)
{
  while(true)
  {
   cout << "我捕捉到"<<signal<<"号信号,我的自定义处理方案是一直打印这句话"<<endl;
   sleep(1); 
  } 
}

int main()
{
  signal(11,Custom_processing);
  int* a  = nullptr;
  *a = 10;
  return 0;
}

在这里插入图片描述

每一个进程都有一个独立的虚拟空间,通过页表映射到物理内存,而页表里面的MMU就是用来检查是否越界的,如果我们越界了,他会向进程的PCB发送信号11,如果我们的进程没有被退出,则信号一直存在。

操作系统还有一个更为方便我们检查出错原因的方法,这里要另外介绍一下,信号的另外一重含义,输入man 7 signal,对于一些信号,操作系统默认的处理是退出进程,而对于有一些信号,会在退出进程的同时,记录出错的上下文,并保存在当前路径下,文件名为core.pid,这个文件是可以用gdb用来调试。
在这里插入图片描述

如果你用的是云服务器,一般是不会开启这个功能的,需要输入 ulimit -a 指令在这里插入图片描述

核心转储默认为零的,可以输入ulimit -c
加大小,就可以开启核心转储,默认编译是release模式,如果要进行debug模式编译的话,需要在makefile编译后面加-g,这时编译出来的文件,才支持gdb调试。

在这里插入图片描述

我们在进入gdb调试后,再次输入 core-file +core文件,这里再进行运行调试gdb会自动定位到产生异常的位置。

在这里插入图片描述
核心转储文件往往比较大,一般服务器是关闭了这个功能的。

二、信号的保存

信号的概念

  1. 实际执行信号的处理动作称为信号递达(Delivery)。
  2. 信号从产生到递达之间的状态,称为信号未决(Pending)。
  3. 进程可以选择阻塞 (Block )某个信号(即屏蔽某个信号)。
  4. 被阻塞(屏蔽)的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞(屏蔽),才执行递达的动作。
  5. 注意阻塞(屏蔽)和默认执行的是不同的,只要信号被阻塞(屏蔽)就不会递达,而默认执行是递达之后可选的一种处理方法。

在这里插入图片描述
在这里插入图片描述

操作系统在收到信号时的默认处理方法,保存在handler表里

在这里插入图片描述

三、信号的处理

操作系统提供了sigset_t 类型来帮助我们更好的,控制pending和block两个位图表。

这些是操作系统提供给我们的函数接口

#include <signal.h>//头文件
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
  1. 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,初始化该位图。
  2. sigfillset用来将参数set信号集初始化,然后把所有的信号加入到此信号集里即将所有的信号标志位置为1,屏蔽所有的信号。
  3. sigismember是用来判断,某个信号是否被置1,如果为1则返回1,为零则返回零
  4. sigaddset是用来增加某个信号,可以用来增加屏蔽信号
  5. sigdelset是用来删除某个信号,可以用来删除屏蔽信号

sigprocmask

通过我们设置好的block位图表去更改操作系统里面默认的block位图表,也可以用来读取。

int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
//若操作成功返回0 ,否则返回-1 
//how和set都是输入型参数,oset为输出型参数
  1. 如果oset是非空指针,则读取进程的当前信号屏蔽位图通过oset参数传出。

  2. 如果set是非空指针,则更改进程的信号屏蔽位图,参数how指示如何更改。

  3. 如果oset和set都是非空指针,则先将原来的信号屏蔽位图备份到oset里,然后根据set和how参数更改信号屏蔽位图。

假设当前的信号屏蔽字为mask,下表说明了how参数的可选值


SIG_BLOCK set包含了我们希望添加到当前屏蔽位图的信号,相当于mask=mask|set


SIG_UNBLOCK set包含了我们希望从当前屏蔽位图解除的信号,相当于mask=mask&(~set)


SIG_SETMASK 设置当前信号屏蔽字为set的值,即mask=set


sigpending

读取当前进程的信号集。

下面我们编写一段代码模拟一下

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

using namespace std;

void show_block(sigset_t* set)
{
  cout << "我是屏蔽位图表:";
  for(int signal = 1;signal<32;++signal)
  {
  //判断0-31号信号是否有被屏蔽的情况
    if(sigismember(set,signal))
    {
      cout <<"1"<<" ";
    }
    else
    {
      cout << "0"<<" ";
    }
  }
  cout << endl;
}

void show_pending(sigset_t* set)
{
  cout << "我是信号位图表:";
  for(int signal = 1;signal<32;++signal)
  {
  //判断是否有收到0-31号信号
    if(sigismember(set,signal))
    {
      cout <<"1"<<" ";
    }
    else
    {
      cout << "0"<<" ";
    }
  }
  cout << endl;
}

  int main()
{
  sigset_t set;
  sigset_t oset;
  
  sigset_t pending;
  //先初始化两个表
  sigemptyset(&set);
  sigemptyset(&oset);
  //把2号信号添加到屏蔽位图里
  sigaddset(&set,2);
  //把我们的信号屏蔽位图表,添加到进程中的屏蔽位图表
  sigprocmask(SIG_SETMASK,&set,&oset);

  show_block(&set);
  while(1)
  {
  //获取当前进程的信号集,即当前收到的信号
    sigpending(&pending);
    show_pending(&pending);
    sleep(1);
  }
  return 0;
}

在这里插入图片描述

pending位图表示是否收到信号
运行可执行程序后,刚开始因为没有信号,所以pending表都是0,在使用2号信号想要干掉进程时,由于2号信号被阻塞, 无法终止进程 并且pending表中对应的2号信号的比特位出现1
只能通过其他信号终止该进程了,需要注意的是,即使我们把32个信号都屏蔽了,也还是有一个信号我们是无法屏蔽的,就是9号信号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值