Linux进程信号(2)--信号的保存

目录

1.阻塞信号

1.1 信号其他相关常见概念

1.实际执行信号的处理动作称为信号递达(Delivery)

2.信号从产生到递达之间的状态,称为信号未决(Pending)。

3.进程可以选择阻塞 (Block )某个信号。

1.2信号在内核中的表示

sigset_t

信号集操作函数

使用sigprocmask函数修改block表

使用sigpending函数查看pending表


1.阻塞信号

 在解释信号的保存时,我们要先了解一下信号中的专有名词及其概念。

1.1 信号其他相关常见概念

1.实际执行信号的处理动作称为信号递达(Delivery)

信号递达,通俗来说就是处理信号。

处理信号的三种方式:

1.信号的忽略

2.信号的默认

3.信号的自定义捕捉

信号的默认

这里我们先编写一个程序:

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

int main()
{
    while(true)
    {
        std::cout<<"getpid():"<<getpid()<<"    running..."<<std::endl;
        sleep(1);
    }
    return 0;
}

运行起来,然后再使用kill -信号编号 进程pid。给进程发送信号。

这里就是信号9的默认。

信号的自定义

这里我们要使用函数:

sighandler_t signal(int sugnum,sighandler_t handler)

这里signum是你想自定义信号的编号handler一个函数指针,指向的是返回值为void,参数为一个int的函数。(这里也可以用一个宏SIG_DFL恢复信号的默认处理)

作用:收到信号,不按照默认方式,自定义处理信号。

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


void handler(int signal)//自定义处理信号2的内容
{
    std::cout<<"handler:"<<signal<<std::endl;
    exit(2);
}

int main()
{
    signal(2,handler);//自定义信号2.

    while(true)
    {
        std::cout<<"getpid():"<<getpid()<<"    running..."<<std::endl;
        sleep(1);
    }
    return 0;
}

使用宏SIG_DFL恢复默认处理:

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


void handler(int signal)//自定义处理信号2的内容
{
    std::cout<<"handler:"<<signal<<std::endl;
    exit(2);
}

int main()
{
    signal(2,handler);//自定义信号2.

    signal(2,SIG_DFL);//恢复信号2,默认处理

    while(true)
    {
        std::cout<<"getpid():"<<getpid()<<"    running..."<<std::endl;
        sleep(1);
    }
    return 0;
}

信号的忽略

这里还是使用函数signal,但是要使用宏SIG_IGN才能实现函数忽略。

代码:

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


void handler(int signal)//自定义处理信号2的内容
{
    std::cout<<"handler:"<<signal<<std::endl;
    exit(2);
}

int main()
{
    signal(2,SIG_IGN);//忽略信号2.

   
    while(true)
    {
        std::cout<<"getpid():"<<getpid()<<"    running..."<<std::endl;
        sleep(1);
    }
    return 0;
}

这里我们可以看到当我们向进程发送信号2时,它并没有退出进程,所以它忽略了信号2.

这里可能大家会有这样的疑问?

signal函数的第二个参数是一个函数指针,为什么可以使用宏SIG_DFL与SIG_ING呢?

这里我们可以直接看这两个宏的定义:

这里我们可以看出,将0强转成了函数指针类型,1也强转成了函数指针类型。

如何看待信号的忽略?

这里忽略,就是处理信号。

2.信号从产生到递达之间的状态,称为信号未决(Pending)

这里我们知道,如果当进程收到信号时,正在执行更为重要的事务,进程并不会立马处理信号,而是先将信号保存下来,等待合适的时机再处理。所以在信号产生,到信号处理(信号递达)这段时间里我们叫做信号未决。

这里我们知道,保存信号是使用位图来保存的,信号未决具体点可以理解为:在信号位图中,就叫做信号未决。

3.进程可以选择阻塞 (Block )某个信号。

  信号未决之后,暂时不递达,直到接触信号的阻塞。

举例理解:

在上课前老师留下了放假作业(接受信号,保存),但是因为上课要听老师讲课,我们不能在课堂上写作业(被阻塞),我们只能在放学后才能写作业(接触阻塞)。

注:

1.信号未决不一定阻塞,但收到信号并阻塞,一定未决。

2.没有收到信号,可以设置信号的递达动作。

3.没有收到信号,可以设置信号阻塞,也可以解除阻塞。

4.阻塞与忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

1.2信号在内核中的表示

上面,我们介绍了信号的递达,未决,阻塞。在OS里是这样实现的。

信号在内核中的表示示意图

首先有block,pending,handler三张表,其中:

pending表:是信号未决表,表的下标代表着是几号信号,表的内容代表着是否收到信号。(使用位图的方式记录)

handler表:对应信号的处理方法,里面可以存放函数指针,或者是SIG_DFL/SIG_ING这样的宏。数组的下标代表着信号编号-1。

这里在进程启动时,就形成了这两张表。因此在进程收到信号前,就认识信号(如何处理信号)

block表:信号阻塞表,pending表一样,使用了位图。比特位的位置:表示信号编号。比特位的内容表示,是否对特定的信号进行屏蔽(阻塞)。

对于一个信号的识别,使用这三张表,要横着看

比如以1号信号为列:

当我们收到1号信号时,我们先将pending表中对应的比特位置为1,然后再看block表中对应的比特位的内容,为0,表示不阻塞,在合适的时候,会在handler表中找到对应的方法处理信号。若为1,则表示信号阻塞,不会对信号进行任何处理,等到比特位的内容变为0,会在合适的时候,在handler表中找到对应的方法处理信号。

因此在收到信号时,能不能被处理,首先取决于对因block表中内容。

上面我们知道对于信号的处理取决于PCB中的这三张表,因此如果我们要控制进程对信号的处理,那么就需要改变这三张表的内容,但是对于我们用户来说,我们并不清楚这三张是如何存储。因此操作系统为我们提供了一系列的系统调用函数,来帮助我们。

sigset_t

从上图来看 , 每个信号只有一个 bit 的未决标志 , 0 1, 不记录该信号产生了多少次 , 阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型 sigset_t 来存储 ,sigset_t 称为信号集 , 这个类型可以表示每个信号 的“ 有效 无效 状态 , 在阻塞信号集中 有效 无效 的含义是该信号是否被阻塞 , 而在未决信号集中 有效” 无效 的含义是该信号是否处于未决状态。这里将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask), 这里的 屏蔽 应该理解为阻塞而不是忽略。
在Linux中是这样定义sigset_t类型的:

信号集操作函数

#include <signal.h>

//将位图里的比特位全部清零
int sigemptyset(sigset_t *set);

//对指定的位图全部置1
int sigfillset(sigset_t *set);

//将指定信号集中的指定比特位置1
int sigaddset (sigset_t *set, int signo);

//将指定信号集中的指定比特位置0
int sigdelset(sigset_t *set, int signo);

//判定一个信号是否在信号集中
int sigismember(const sigset_t *set, int signo);

使用sigprocmask函数修改block表

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

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 

set为想要修改的信号,
oset记录调用前的block,用于恢复原来的block

返回值:若成功则为0,若出错则为-1
如果 oset 是非空指针 , 则读取进程的当前信号屏蔽字通过 oset 参数传出。如果 set 是非空指针 , 则 更改进程的信号屏蔽字, 参数 how 指示如何更改。如果 oset set 都是非空指针 , 则先将原来的信号 屏蔽字备份到 oset , 然后根据set how 参数更改信号屏蔽字。假设当前的信号屏蔽字为 mask, 下表说明了 how 参数的可选值

代码演示:屏蔽2号信号。

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


void handler(int signal)//自定义处理信号2的内容
{
    std::cout<<"handler:"<<signal<<std::endl;
    exit(2);
}

int main()
{
    signal(2,handler);
    sigset_t block,oblock;
    sigemptyset(&block);//位图置0操作
    sigemptyset(&oblock);

    sigaddset(&block,2);//这里并没有对2号信号做屏蔽

    sigprocmask(SIG_BLOCK,&block,&oblock);//调用系统接口,对2号信号进行屏蔽

    while(true)
    {
        sleep(1);
        std::cout<<"getpid: "<<getpid()<<std::endl;
    }

    return 0;
}

这里可能有人会这样问,既然我们可以控制屏蔽信号,那么如果我们把所有信号都屏蔽了,是不是一旦这样的死循环进程运行起来,是不是就不会被杀掉了?

这里我们直接上代码演示: 

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


void handler(int signal)
{
    std::cout<<"handler:"<<signal<<std::endl;
    exit(2);
}

int main()
{
    signal(2,handler);
    sigset_t block,oblock;
    sigemptyset(&block);//位图置0操作
    sigemptyset(&oblock);

    for(int i=1;i<=31;i++)
    sigaddset(&block,i);//将block位图上的所有比特位全部置为1.

    sigprocmask(SIG_BLOCK,&block,&oblock);//对所有信号进行屏蔽

    while(true)
    {
        sleep(1);
        std::cout<<"getpid: "<<getpid()<<std::cout<<"  我已经屏蔽掉了所有信号,你来打我啊!"<<std::endl;
    }

    return 0;
}

这里我们可以发现当我们对进程发送除9之外的信号,进程并不会停止(信号被屏蔽),而当收到9信号时,进程就直接执行信号9对应的默认动作。(19号信号也没有被屏蔽)

9号信号我们成为管理员信号,它并不会被屏蔽。

使用sigpending函数查看pending表

#include <signal.h>
int sigpending(sigset_t *set)

读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。 下面用刚学的几个函数做个实验。程
序如下

这里我们直接上代码:

解释程序目的,不断打印pendling表中比特位的内容。在最开始的前10秒,阻塞信号2。然后将信号2从block表中删除,我们要在10秒前向进程发送2号信号。

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


void handler(int signal)
{
    std::cout<<"handler:"<<signal<<std::endl;
    //exit(2);
}

void PrintPending(sigset_t& pending)//打印pending中每一个比特位的内容
{
    for(int signo=21;signo>=1;signo--)
    {
        if(sigismember(&pending,signo))
        {
            std::cout<<'1';
        }
        else 
        {
            std::cout<<'0';
        }
    }
    std::cout<<std::endl;
}

int main()
{

    signal(2,handler);//自定义2号信号

    //屏蔽2号信号
    sigset_t block,oblock;
    sigemptyset(&block);
    sigemptyset(&oblock);
    sigaddset(&block,2);
    sigprocmask(SIG_BLOCK,&block,&oblock);

    std::cout<<"getpid: "<<getpid()<<std::endl;
    
    int cnt=0;

    //让进程不断获取当前进程的pending表
    sigset_t pending;
    while(true)
    {
        cnt++;
        sigpending(&pending);//获取pending表中的内容
        PrintPending(pending);
        if(cnt==10)
        {
            std::cout<<"解除对2号信号的屏蔽,2号信号已递达。"<<std::endl;
            sigprocmask(SIG_SETMASK,&oblock,nullptr);
        }
        sleep(1);
    }
    return 0;
}

这里我们可以看到一旦信号2,不阻塞,就立即递达了。并且pending表中对应的比特位置0.

这里可能有人会问pending表置0这个操作是在处理信号前,还是后执行的?

这里我们可以直接用代码验证:

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

void PrintPending(sigset_t& pending);
void handler(int signal)
{
    std::cout<<"#####################"<<std::endl;
    sigset_t sig;
    sigpending(&sig);
    PrintPending(sig);
    std::cout<<"######################"<<std::endl;
    std::cout<<"handler:"<<signal<<std::endl;
    //exit(2);
}

void PrintPending(sigset_t& pending)//打印pending中每一个比特位的内容
{
    for(int signo=21;signo>=1;signo--)
    {
        if(sigismember(&pending,signo))
        {
            std::cout<<'1';
        }
        else 
        {
            std::cout<<'0';
        }
    }
    std::cout<<std::endl;
}

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

这里我们采用的是,自定义信号2,在处理时,将pending表的内容打印出来,如果全部位0,则在修改pending实在递达前实行的,否则实在递达之后修改的。

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

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

相关文章

JAVA-File五个练习

下面习题思路大多都是&#xff1a; 1.获取路径下所有列表&#xff08;listfiles&#xff09;&#xff0c;2.遍历文件或文件夹&#xff08;增强for&#xff09;&#xff0c;3.判断是否是文件&#xff08;isFile&#xff09;并直接执行逻辑&#xff0c;4.判断当前是文件夹的情况&…

【React】redux状态管理、react-redux状态管理高级封装模块化

【React】react组件传参、redux状态管理 一、redux全局状态管理1、redux概述2、redux的组成1.1 State-状态1.2 Action-事件1.3 Reducer1.4 Store 3、redux入门案例1.1 前期准备1.2 构建store1.2.1 在src下新建store文件夹1.2.2 在store文件夹下新建index.ts文件1.2.3 在index.t…

Swift Vapor 教程(查询数据、插入数据)

上一篇简单写了 怎么创建 Swift Vapor 项目以及在开发过程中使用到的软件。 这一篇写一个怎么在创建的项目中创建一个简单的查询数据和插入数据。 注&#xff1a;数据库配置比较重要 先将本地的Docker启动起来&#xff0c;用Docker管理数据库 将项目自己创建的Todo相关的都删掉…

【python】python爱心代码【附源码】

一、实现效果&#xff1a; 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 二、完整代码&#xff1a; import math import random import threading import time from math import sin, cos, pi, log from tkinter import * import re# 烟花相关设置 Fireworks [] m…

STM32F407 CAN参数配置 500Kbps

本篇CAN参数适用 芯片型号&#xff1a;STM32F407xx系统时钟&#xff1a;168MHz&#xff0c;CAN挂载总线APB1为42M波 特 率 &#xff1a;500Kpbs引脚使用&#xff1a;TX_PB9&#xff0c;RX_PB8&#xff1b;修改为PA11PA12后&#xff0c;参数不变。 步骤一、打勾开启CAN&#xf…

vue使用es的reduce方法编译报错Error: Can‘t resolve ‘core-js/modules/es.array.reduce.js‘

哈喽 大家好啊 最近在vue使用es的reduce方法编译报错Error: Cant resolve core-js/modules/es.array.reduce.js 报错如图所示&#xff1a; 解决方案&#xff1a; npm install --save core-js 然后重新编译下将正常了 参考原文: 使用import异步加载语法报错_module not foun…

CAD-autolisp(四)——编译

目录 一、编译1.1 界面操作1.2 生成的应用程序&#xff08;二选一&#xff09; 二、后续学习 一、编译 编译&#xff1a;lsp后缀名为原文件&#xff0c;后缀名为fas、vlx为编译后文件&#xff0c;其会把sld、dcl、lsp等文件都编译进一个应用程序文件中加载&#xff1a;cad命令…

ZigBee学习——在官方例程基础实现点灯

IAR版本 :10.10.1 Z-stack版本 :3.0.2 文章目录 一、买的板子原理图二、实现过程2.1 重定义LED的物理映射(HAL层)2.2 创建LED事件(应用层)2.2.1 定义用户事件2.2.2 修改zclGenericApp_event_loop() 2.3 触发事件 一、买的板子原理图 二、实现过程 2.1 重定义LED的物理映射(HAL…

windows10忘记密码的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

处理SERVLET中的错误

处理SERVLET中的错误 问题陈述 一位用户在使用在线计算机应用程序时输入一个非数字字符做数字加法。servlet试图将用户输入的值转换成整数型时,引发了NumberFormException类型的异常。要创建一个Web应用程序来使用自定义错误页面处理该异常。该自定义错误页面需要向用户提供关…

uniapp android和微信小程序实现PDF在线预览

在使用uniapp开发移动端时&#xff0c;微信开发者工具里webview能正常打开后端接口返回的pdf文件流。正式发布后&#xff0c;在配置了业务域名和服务器域名的前提下&#xff0c;预览pdf文件却只能看到白屏&#xff0c;因此我猜测微信小程序不能通过webview读取文件流。这个想法…

笔记本电脑的WIFI模块,突然不显示了,网络也连接不上

问题复现&#xff1a; 早上&#xff0c;在更新完笔记本电脑的系统之后&#xff0c;连网之后&#xff0c;网络突然直接断开&#xff0c;一查看&#xff0c;WiFi模块居然不见了&#xff0c;开机重启也是如此&#xff0c;这种情况常常出现在更新系统之后&#xff0c;WiFi模块驱动就…

Unix五种I/O模型(阻塞、非阻塞、多路复用、信号驱动、异步)

文章目录 概要一、I/O基础二、阻塞式I/O三、非阻塞式I/O三、I/O多路复用四、信号驱动I/O五、异步I/O六、小结 概要 在工作中&#xff0c;经常使用Nginx、Redis等开源组件&#xff0c;常提到其高性能的原因是网络I/O的实现是基于epoll&#xff08;多路复用&#xff09;。这次呢…

复制命令COPY

copy在英文中是复制的意思&#xff0c;所谓复制就是原来的文件并没有任何改变&#xff0c;重新产生了一个内容和原来文件没有任何差别的文件。复制&#xff08;copy&#xff09;命令主要用于复制一个或更多个文件到指定的位置&#xff0c;该命令可以被用于合并文件&#xff0c;…

ES6-数组的解构赋值

一、数组的解构赋值的规律 - 只要等号两边的模式相同&#xff0c;左边的变量就会被赋予对应的值二、数组的解构赋值的例子讲解 1&#xff09;简单的示例&#xff08;完整的解构赋值&#xff09; 示例 //基本的模式匹配 // a&#xff0c;b,c依次和1&#xff0c;2&#xff0c…

Element UI+Spring Boot进行CRUD的实例

ElementUI安装与使用指南 前端代码&#xff1a;点击查看learnelementuispringboot项目源码 后端代码&#xff1a;点击查看 LearnElementUiAndSpringBoot 一、前端配置 安装axios Gitee的axios介绍与使用 GitHub的axios介绍与使用 方式一&#xff1a;使用npm安装 $ npm in…

深度学习(生成式模型)—— Consistency Models

文章目录 前言预备知识&#xff1a;SDE与ODEMethod实验结果 前言 Diffusion model需要多次推断才能生成最终的图像&#xff0c;这将耗费大量的计算资源。前几篇博客我们已经介绍了加速Diffusion model生成图像速率的DDIM和Stable Diffusion&#xff0c;本节将介绍最近大火的Co…

力扣热门100题刷题笔记 - 5.最长回文子串

力扣热门100题 - 5.最长回文子串 题目链接&#xff1a;5. 最长回文子串 题目描述&#xff1a; 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同&#xff0c;则该字符串称为回文字符串。示例&#xff1a; 输入&#xff1a;s "…

【DDD】学习笔记-数据设计模型

通过分析活动获得的数据项模型&#xff0c;可以认为是数据分析模型&#xff0c;它确定了系统的主要数据表、关系及表的主要属性。到了建模的设计活动&#xff0c;就可以继续细化数据项模型这个分析模型&#xff0c;例如丰富每个表的列属性&#xff0c;或者确定数据表的主键与外…

从零开始手写mmo游戏从框架到爆炸(二)— 核心组件抽离与工厂模式创建

上一章我们已经完成了一个基本netty的通信&#xff0c;但是netty的启动很多代码都是重复的&#xff0c;所以我们使用工厂模式来生成不同的ServerBootstrap。 首先创建一个新的组件core组件&#xff0c;和common组件&#xff0c;主要用于netty通信和工具类&#xff0c;从server…