Linux学习之信号

目录

1.信号的概念

2.信号的产生

3.信号的保存

4.信号的捕捉

信号的其它内容:

SIGCHLD信号


1.信号的概念

在Linux中,信号是一种用于进程之间通信的基本机制。它是一种异步事件通知,用于通知进程发生了某些事件。如下是一些常见的Linux信号类型:

SIGINT (2):中断进程,通常由终端产生,例如用户按下Ctrl+C。
SIGKILL (9):立即终止进程,无法被捕获或忽略。
SIGTERM (15):请求终止进程,可以被捕获或忽略。
SIGQUIT (3):请求进程退出并生成核心转储文件,可以被捕获或忽略。
SIGSTOP (17):暂停进程的执行,无法被捕获或忽略。
SIGCONT (19):恢复进程的执行,无法被捕获或忽略

 这些信号在进程控制、异常处理和进程间通信中扮演着重要角色。请注意,信号只是通知进程发生了什么事件,并不传递任何数据。进程对不同信号有不同的处理方式,可以指定处理函数、忽略或保留系统的默认值。信号机制在Linux编程中非常重要,帮助实现进程之间的协作和控制。

2.信号的产生

先举两个样例:

eg1:

首先我们编写一个死循环代码,编译运行后,我们的命令行就不再有用了,现在是前台程序,只运行当前的程序,当我们编译时加上&,使他成为后台程序,此时的命令行也可以继续使用,

程序在运行的时候,前台程序只能有一个,后台程序可以有多个。后台程序在运行时,我们的键盘可以输入数据,指令可以运行。

一般操作系统会自动根据情况把shell程序提到前台或者后台。下面的指令对shell无效。

前后台程序切换

./可执行 &  把程序放到后台

jobs  查看后台任务

fg number(任务编号) 把任务放到前台

ctrl+z 再加 bg number   把后台任务转到前台

ctrl+\ 默认终止

ctrl + z 暂停程序,先放到后台

而这就是信号的产生,除此之外操作系统知晓键盘的输入也是一种信号:

eg2:当键盘的某个按钮被按下的时候,就会产生高电平信号间接给cpu,cpu得知了之后某个按钮的高电平,发生中断,就产生对应的数据。

而信号的产生就是用软件来模拟中断行为。我们的指令都是发出信号,

例如接口signal

可以发出我们需要的信号。

如下一段代码:

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

void handler(int signo)
{
  std::cout<<"获得一个"<<signo<<"信号"<<std::endl;
  exit(1);
}

int main()
{
  signal(2,handler);
  while(true)
  {
    std::cout<<"pid:"<<getpid()<<",i am running......"<<std::endl;
    sleep(1);
  }
  return 0;
}

 再运行的时候,我们ctrl+z,此时退出进程就会获得一个为2的信号。

因此信号的产生可以通过键盘发出,对于我们的linux也是有许多信号的(kill -l):

其中,没有0号信号,从1-31的信号我们把它叫做普通信号,没有32,33信号,从34到64的信号,我们把它叫做实时信号。这些信号的本质就是一些函数指针数组,对应的下标就与他们的编号有关。

对于普通信号,进程是否收到了普通信号,操作系统(pcb中)会用一张位图来表示,利用位图中的第几个比特位表示编号,0表示没收到,1表示收到。

无论信号有多少种,都是只能让os来写(写)信号,因为os是进程的管理者。

了解到了信号的接收,因此我们在编写程序时就可以直接发送信号,之后自动运行对应handler方法,例如之前我们使用kill -9杀进程,现在我们发送一个为9的信号,此时自定义它的处理方法,例如只是打印一句话,那么我们kill -9的指令就不会再杀掉我们的进程,而是打印一句话。

但实际上并不可以,操作系统对于某些信号是不可以被自定义捕捉的。

除此以外,Linux提供了三种接口供我们产生信号。

方式一:通过键盘组合键发送产生信号。

方式二:通过函数接口

接口 raise 可以自己给自己发送任意信号

接口 abort  收到信号后终止运行

方式三,通过异常:

 以我们熟知的除零错误为例,首先除零错误并不是语言错误,而是进程错误,再cpu中通过各个寄存器来计算除零,此时cpu中还有表示状态的寄存器,当发生除零问题后,状态寄存器就会产生溢出标记位,从而转化为信号,就是信号8 SIGFPE  也就是flaot point exception。

当然发出信号也不仅仅可能是因为异常而导致的,也有可能是闹钟响了:

方式四:由软件条件产生信号:

alarm接口可以设置闹钟

#include<iostream>
#include<unistd.h>
#include <signal.h>
#include <stdlib.h>
int cnt=0;
void handler(int signo)
{
  std::cout<<"获得一个"<<signo<<"信号"<<"alarm is:"<<cnt<<std::endl;
 
  exit(1);
}

int main()
{
  std::cout<<"pid:"<<getpid()<<std::endl;
  //本质上就是修改函数指针数组的位置
  signal(14,handler);
  //设置1s闹钟,到点了终止进程
  alarm(1);

  while(true)
  {
    //cout<<cnt++<<endl;  可以看出外设是很慢的
    cnt++;
  }
  
}

 操作系统的时间:

当我们电脑关机了,程序结束了,再次重新启动,我们会发现,时间永远是跟着走的,实际上,即使关机了,在电脑里也会有一个纽扣电池一直给硬件供电,固定时间间隔计数,再将计数器转换为时间戳给我们的电脑。CMOS周期性的高频的发送时间中断。

3.信号的保存

. 信号其他相关常见概念
实际执行信号的处理动作称为信号递达 (Delivery)
信号从产生到递达之间的状态 , 称为信号未决 (Pending)
进程可以选择阻塞 (Block ) 某个信号。
被阻塞的信号产生时将保持在未决状态 , 直到进程解除对此信号的阻塞 , 才执行递达的动作 .
注意 , 阻塞和忽略是不同的 , 只要信号被阻塞就不会递达 , 而忽略是在递达之后可选的一种处理动作

 递达就是开始处理信号,当信号被记录再为途中时就是信号未决状态,阻塞:被阻塞的信号一直处在未决状态,只有当阻塞取消时,才进入递达状态。

阻塞与忽略是有区别的,忽略本身没有阻塞而是递达,处理了信号,效果为忽略,而阻塞是没有抵达,且没处理。

了解了以上概念,因此再管理信号的状态时,os就需要维护这三张位图表,用来表示阻塞,未决,递达这三个状态的信号。

比特位的位置:代表信号的编号

比特位的内容:对特定信号进行阻塞还是屏蔽。 

每个信号都有两个标志位分别表示block(阻塞)和pending(未决),其次还有一个函数指针表示要处理的方法。

void handler(int signo)
{
    cout<<"signo is "<<signo<<endl;
    exit(1);
}
int main()
{
    //发送2信号
    signal(2,signo);

    //把信号的粗粒设置为原来默认的
    signal(2,SIG_DFL);

    //当然还可以把信号忽略
    signal(2,SIG_IGN);

    std::cout<<"my pid id:"<<getpid()<<endl;
    while(true)
    {
        cout<<"i am running....."<<endl;
        sleep(1);
    }

}

由于有这么多信号集,操作系统还提供了许多信号及操作接口:

sigset_t 类型对于每种信号用一个 bit 表示 有效 无效 状态 , 至于这个类型内部如何存储这些 bit 则依赖于系统 实现, 从使用者的角度是不必关心的 , 使用者只能调用以下函数来操作 sigset_ t 变量 , 而不应该对它的内部数据做 任何解释, 比如用 printf 直接打印 sigset_t 变量是没有意义的。
#include <signal.h>
int sigemptyset(sigset_t *set);  //对指定的位图进行清零
int sigfillset(sigset_t *set);   //对指定的位图进行置1
int sigaddset (sigset_t *set, int signo); //对指定信号添加到指定的位图中
int sigdelset(sigset_t *set, int signo);
int sigismember const sigset_t *set, int signo); //判定一个信号是否在为位图中

对于block表的修改:

sigprocmask 调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)

如下代码:


int main()
{
    //例如对2号信号屏蔽
    cout<<"my pid is"<<getpid()<<endl;
    //先定义两个信号集位图
    sigset_t block,oblock;

    //先对信号集清空
    sigemptyset(&block);
    sigemptyset(&oblock);

    //其次对2号信号添加到信号集
    sigaddset(&block,2);  //当前并没有让操作系统2信号屏蔽,只是语言层面的定义
    sigaddset(&oblock,2);
    sigprocmask(SIG_BLOCK,&block,&oblock);   //真正让操作系统屏蔽、更改信号
    while(true)
    {
        sleep(1);
    }
    return 0;
}

 此时我们再发2号信号就没有作用了,ctrl+c也无法中断程序。

既然如此,那么我们是否可以将一个程序的所有信号屏蔽,这样他就有金刚不坏之身,谁也干不掉他,实际上并是不是所有的信号你都能屏蔽,就跟不是所有的信号的处理可以自定义是一样的。

比如说9号信号就无法被屏蔽。

那么pending表的修改:接口 sigpending

重要的是获取pending表.

接下来我们用一个整体的实例来认识这些接口:

void printpending(const sigset_t &pending)
{
    for(int signo=31;signo>0;signo--)
    {
        if(sigismember(&pending,signo))
        {
            cout<<"1";
        }else{
            cout<<"0";
        }
    }
    cout<<"\n";
}
//自定义捕捉
void handler(int signo)
{
    cout<<"已接受到信号"<<signo<<endl;
    //exit(1);

}

int main()
{
    //例如对2号信号屏蔽
    cout<<"my pid is"<<getpid()<<endl;

    signal(2,handler);
    //先定义两个信号集位图
    sigset_t block,oblock;

    //先对信号集清空
    sigemptyset(&block);
    sigemptyset(&oblock);

    //其次对2号信号添加到信号集
    sigaddset(&block,2);  //当前并没有让操作系统2信号屏蔽,只是语言层面的定义
    sigaddset(&oblock,2);
    sigprocmask(SIG_BLOCK,&block,&oblock);   //真正让操作系统屏蔽、更改信号


    //下打印pending表
    int cnt=0;
    sigset_t pending;
    while(true)
    {
        sigpending(&pending);
        printpending(pending);
        sleep(1);
        cnt++;
        if(cnt==5)
        {
            //直到5S,解除2信号的屏蔽
            cout<<"解除对2号信号的屏蔽,2号准备抵达"<<endl;
            sigprocmask(SIG_SETMASK,&oblock,nullptr); //设置为旧的信号 
        }

    }
    return 0;
}

 运行结果如图:

4.信号的捕捉

信号在什么时候去被捕捉处理呢,在合适的时候---从内核态返回到用户态的时候,进行信号的检测和信号的处理。

内核态:内核态是操作系统的一种状态,能够大量访问资源

用户态:用户态是一种受控的转台,能够访问的资源是有限

用户想要访问操作系统只能通过系统调用的方式访问。

首先无论进程如何调度,cpu都会找到os,我们的进程的所有代码的执行,都可以在地址空间中通过跳转的方式进行调用和返回。

 那么对于系统的信号的捕捉,首先介绍第一个接口sigaction

第三个参数表示把旧的handler表返回给我,达尔戈参数就是新的handler的设置,第一个参数为信号编号,接口的作用是检测和修改信号动作。

返回类型是sigaction的结构体类型,其中有五个字段。其中我们比较重点关注的是sa_mask字段,

如果在调用信号处理函数时,除了当前信号被屏蔽外,还希望屏蔽些别的信号,此时sa_mask就是需要被额外屏蔽的信号。

以该代码为例:

#include<signal.h>
#include<unistd.h>
#include<iostream>
using namespace std;
void print(sigset_t &pending);
void handler(int signo)
{
    cout<<"接收到信号"<<signo<<"......"<<endl;
    while(true)
    {
        //获取当前pending列表
        sigset_t pending;
        sigpending(&pending);
        print(pending);
        sleep(1);
    }
}
void print(sigset_t &pending)
{
    for(int signo=31;signo>0;signo--)
    {
        if(sigismember(&pending,signo))
        {
            cout<<"1";
        }else{
            cout<<"0";
        }
    }
    cout<<endl;
}
int main()
{

    cout<<"my pid is "<<getpid()<<endl;
    //定义新的与旧的act
    struct sigaction act,oact;
    //设置handler为当前自定义的处理方法
    act.sa_handler=handler;
    sigaction(2,&act,&oact);
    while(true) sleep(1);
    return 0;
}

用改接口接受2号信号时,和之前一样,运行程序,第一次我们ctrl+c,发出2信号时接收到2好信号,但自此之后的2好信号都被屏蔽掉了,再次crtl+c时,信号无法被接受处于未决状态。

例如:当我们要修改信号2时,这里默认会自动屏蔽信号2,如下图

信号的其它内容:

可重入函数

数被不同的控制流程调用 , 有可能在第一次调用还没返回时就再次进入该函数 , 这称 为重入,insert 函数访问一个全局链表 , 有可能因为重入而造成错乱 , 像这样的函数称为 不可重入函数 , 反之 , 如果一个函数只访问自己的局部变量或参数, 则称为可重入 (Reentrant) 函数。
如果一个函数符合以下条件之一则是不可重入的 :
调用了 malloc free, 因为 malloc 也是用全局链表来管理堆的。
调用了标准 I/O 库函数。标准 I/O 库的很多实现都以不可重入的方式使用全局数据
在这里我们就这样理解,住执行流与信号捕捉流是两种不同的流。
关键字volatile
volatile 作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量 的任何操作,都必须在真实的内存中进行操作。
那么对于信号有什么作用呢?
int flag = 0;
void handler(int sig)
{
 printf("chage flag 0 to 1\n");
 flag = 1;
}
int main()
{
 signal(2, handler);
 while(!flag);
 printf("process quit normal\n");
 return 0;
}
优化情况下,键入 CTRL - C ,2 号信号被捕捉,执行自定义动作,修改 flag 1 ,但是 while 条件依旧满足 , 进 程继续运行!但是很明显flag 肯定已经被修改了,但是为何循环依旧执行?很明显, while 循环检查的 flag , 并不是内存中最新的flag ,这就存在了数据二异性的问题。 while 检测的 flag 其实已经因为优化,被放在了 CPU寄存器当中。如何解决呢?很明显需要 volatile。
实际中在gcc中,也是有自带优化的选项。

SIGCHLD信号

我们 早已经了解到子进程在退出的时候,是要给父进程发送退出信息的,不然父进程还要维护一份没必要的资源,而子进程是给父进程发送什么样的信号呢?---SIGCHLD

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void handler(int sig)
{
 pid_t id;
 //收到退出信号  等待子进程

 while( (id = waitpid(-1, NULL, WNOHANG)) > 0)
 {
     printf("wait child success: %d\n", id);
 }
     printf("child is quit! %d\n", getpid());
}
int main()
{
  signal(SIGCHLD, handler);

  pid_t cid;

  if((cid = fork()) == 0){
  //child

  printf("child : %d\n", getpid());
  sleep(3);
  exit(1);

 }

 while(1){
 printf("father proc is running\n");
 sleep(1);

 }
 return 0;
}

可以看到子进程退出时,时回给父进程发信号的。

在Linux中支持手动忽略信号SIGCHDL,可以不用wait子进程。退出自动回收。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DC-DC转换电路简介

DC-DC转换电路简介 1. 源由2. 工作原理3. 转换芯片4. DC-DC干扰5. DC-DC滤波5.1 PCB Layout5.2 电容滤波5.3 电感滤波 6. DC-DC电感/电容取值实验如何做&#xff1f;7. 参考资料 1. 源由 虽然说嵌入式系统涉及软件、硬件、机械、结构、网络等诸多领域内容。因此&#xff0c;在…

【MATLAB】 ICEEMDAN信号分解+FFT傅里叶频谱变换组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 展示出图效果 1 ICEEMDAN信号分解算法 ICEEMDAN 分解又叫改进的自适应噪声完备集合经验模态分解&#xff0c;英文全称为 Improved Complete Ensemble Empirical Mode Decomposition with Adaptive Noise。 ICEEMDAN (I…

诊所门诊电子处方软件操作教程及试用版下载,医务室处方笺管理系统模板教程

诊所门诊电子处方软件操作教程及试用版下载&#xff0c;医务室处方笺管理系统模板教程 一、前言 以下软件程序教程以 佳易王诊所电子处方软件V17.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 如上图&#xff0c;点击基本信息设置——处方配…

一起去吃面条子吧

微风掠进窗沿&#xff0c;拂过愣在键盘上的指尖&#xff0c;正踌躇于命题的“||”还是“&&”。      突来的惬意&#xff0c;让人猛然惊醒&#xff0c;似是季节恍惚间悄然更替&#xff0c;那么这道命题索性就先放着。      毕竟实际编译运行结果&#xff0c;与…

实现定时器的两种方法:使用windows api定时器 和使用c++11/14 定时器

前言&#xff1a; 当我有一个开发需求&#xff0c;符合下面的条件 1.需要某个任务在程序中每隔一段时间就要执行一次&#xff0c;可能把这个任务封装成了一个函数。 2.这种需要定时执行的任务&#xff0c;有2个&#xff0c;3个....越来越多。 这个时候我们就可以考虑使用定时…

web基础03-JavaScript

目录 一、JavaScript基础 1.变量 2.输出 3.变量提升 4.区块 5.JavaScript数据类型 6.查看数值类型 7.undefined和null 8.布尔值 9.和的区别 10.算数/三元/比较/逻辑/赋值运算符 11.特殊字符 12.字符串 &#xff08;1&#xff09;获取字符串长度 &#xff08;2&am…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的番茄成熟度检测系统(Python+PySide6界面+训练代码)

摘要&#xff1a;开发番茄成熟度检测系统对于提高农业产量和食品加工效率具有重大意义。本篇博客详细介绍了如何利用深度学习构建一个番茄成熟度检测系统&#xff0c;并提供了完整的实现代码。该系统基于强大的YOLOv8算法&#xff0c;并结合了YOLOv7、YOLOv6、YOLOv5的对比&…

SpringBoot约定大于配置

什么是约定大于配置 "约定大于配置"&#xff08;Convention Over Configuration&#xff09;是一种理念&#xff0c;旨在通过默认约定和规则来减少开发人员需要做的配置工作。在Spring Boot框架中&#xff0c;这一原则得到了充分应用&#xff0c;帮助开发者更快地构…

300分钟吃透分布式缓存-15讲:如何深入理解、应用及扩展 Twemproxy?

Twemproxy 架构及应用 Twemproxy 是 Twitter 的一个开源架构&#xff0c;它是一个分片资源访问的代理组件。如下图所示&#xff0c;它可以封装资源池的分布及 hash 规则&#xff0c;解决后端部分节点异常后的探测和重连问题&#xff0c;让 client 访问尽可能简单&#xff0c;同…

AI时代,我们需要什么能力?

AI 时代&#xff0c;一定会重构很多行业&#xff0c;也会重构人民的生活工作方式&#xff0c;那么 AI 时代&#xff0c;我们需要培养什么能力呢&#xff1f; 我们应该去做那些 AI 做不了的事情&#xff01;让 AI 成为我们的工具&#xff0c;助力我们更高效的解决问题&#xff…

Java - 获取汉字大写首字母输出

背景 有个项目需要将一批字符串的拼音首字母输出并大写&#xff0c;写了个工具类。 实现 需要引入外部jar。 <dependency><groupId>com.belerweb</groupId><artifactId>pinyin4j</artifactId><version>2.5.1</version> </dep…

C++之set、multiset

1、set简介 set是一种关联式容器&#xff0c;包含的key值唯一&#xff0c;所有元素都会在插入时自动被排序&#xff0c;其存储结构为红黑树。set只能通过迭代器(iterator)访问。 set和multiset的区别&#xff1a; &#xff08;1&#xff09;set不允许容器中有重复的元素&…

VUE3中的组件传值

一、父传子(props) 在子组件中可以使用defineProps接收父组件向子组件的传值 父组件fatherPage.vue&#xff1a; <template><div class"father"><button click"a a 1">按钮</button><childPage :a"a" /><…

在Windows系统上安装Docker和SteamCMD容器的详细指南有哪些?

在Windows系统上安装Docker和SteamCMD容器的详细指南有哪些&#xff1f; 安装Docker&#xff1a; 首先&#xff0c;需要在Windows操作系统上激活WSL2功能。这是因为Docker作为一个容器工具&#xff0c;依赖于已存在并运行的Linux内核环境。可以通过使用winget来安装Docker。具体…

禁止u盘拷贝的方法,U盘文件防止拷贝的方法

某大型制造企业在研发一款新产品时&#xff0c;涉及到了大量的机密数据和设计图纸。为了方便工作&#xff0c;研发部门的员工经常使用U盘在不同电脑之间传输数据。 然而&#xff0c;由于缺乏对U盘的有效管理&#xff0c;导致了一起严重的数据泄露事件。 事件经过&#xff1a;…

IEEE 802.11a协议

IEEE 802.11 系列协议主要使用了 OFDM 调制技术&#xff0c;是现今局域无线网的通用标准&#xff0c;被广泛应用于 WIFI 通信中&#xff0c;WIFI 版本及其对应的 802.11 协议版本如下&#xff1a; Wi-Fi 1 是 1999 年发布的 802.11b 标准。Wi-Fi 2 是 802.11a 标准&#xff0c…

Vue3_2024_2天【Vue3组合式setup用法及响应式ref和reactive】

第一&#xff1a;浅谈 | 不可不知 1.vue3目录介绍&#xff08;区别Vue2没有的&#xff09; vue3&#xff0c;默认使用ts语言&#xff0c;但是ts一开始无法识别某些文件&#xff0c;这里是系统默认配置&#xff1b; 2.vue2中的入口文件是main.js&#xff0c;而vue3这里的入口文…

【CSS】CSS简介,CSS基础选择器详解

目录 css简介 css语法规范 css代码风格&#xff1a; css选择器的作用 css基础选择器 标签选择器 类选择器 类选择器---多类名 id选择器 id选择器和类选择器的区别&#xff1a; 通配符选择器 总结 ⭐css简介 CSS 是层叠样式表 ( Cascading Style Sheets ) 的简称,也…

二级医院云HIS系统,云HIS源码,支持分院HIS,集团HIS

云HIS具有可扩展、易共享、易协同、低成本、体验号、更便捷、易维护的优势&#xff0c;重新定义了数字化医院信息系统&#xff0c;实现数字化医院信息系统的转型升级。云 HIS 系统功能完善&#xff0c;涵盖临床各业务部门&#xff0c;采集、抽提、汇总、存贮、展现所有的临床诊…

【HTML】HTML基础系列文章小小总结,运用标签写出网页!

宇宙级声明&#xff01;这次只运用了一些基础标签&#xff0c;希望不要丑到大家~ 目录 效果预览&#xff1a;​编辑​编辑 点击百度百科 点击图片 点击下载 标签说明 源代码 效果预览&#xff1a; 点击百度百科 点击图片 点击下载 标签说明 标题 加粗文字 下划线 换行 分…