Linux进程信号(下)

news2024/12/26 0:07:22

目录

 1:信号保存概念

2:sigset_t

3:信号集操作函数 

3.1:sigprocmask

3.2:9号进程不能被阻塞(验证)

3.3:sigpending

4:信号处理

5:sigaction

 6:可重入函数

 7:volatile

8:SIGCHLD


上一节我们谈了信号的产生,这一节课我们谈信号的保存和处理。

 1:信号保存概念

  • 信号的处理动作称作信号递达
  • 信号从产生到递达之间的状态称作信号未决
  • 进程可以选择阻塞(Block)某个信号
  • 被阻塞的信号将保持信号未决状态,直到进程解决该信号的阻塞,才执行递达
  • 阻塞和忽略是不同的,忽略是递达后的动作,而阻塞了就不会递达 

Linux中PCB有以下结构 

 右边的3个数据结构都是位图

  • block位图中下标表示哪个信号,0或者1表示信号有无阻塞
  • pending位图中下标表示哪个信号,0或者1表示信号有无被OS接收到
  • handler位图中下标表示哪个信号,数组元素对应信号处理动作的函数地址。

2:sigset_t

从上图看,信号阻塞和未决的标志都是0或者1,而不记录阻塞了多少次接收了多少,因此可以用相同的数据类型保存,sigset_t称作信号集。阻塞信号集也叫做当前进程的信号屏蔽字,而屏蔽是阻塞,不是忽略。信号集是一个类似位图结构

3:信号集操作函数 

 

#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);
  • sigemptyset表示初始化信号集。
  • sigfillset表示初始化信号集并将每一个信号置位1,表示该信号集的有效信号包括操作系统所有的信号。
  • sigaddset表示向信号集中添加某个有效信号。
  • delset表示向信号集中删除某个有效信号。
  • sigismember是判断信号signo是否在信号集set中。
  • 前4个函数成功返回0,失败返回-1。
  • 最后一个函数包含返回1,不包含返回0,失败返回-1。

3.1:sigprocmask

用途:读取或更改当前进程的信号屏蔽字 

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 
返回值:若成功则为0,若出错则为-1

set与oset情况:

  • set不为空:修改当前进程的信号屏蔽字
  • oset不为空:获取进程被修改前的信号屏蔽字
  • 都不为空:修改当前进程的信号屏蔽字并且获取之前进程的信号屏蔽字

how参数有3个:

SIG_BLOCKset表示希望添加到当前进程的信号屏蔽字,相当于mask=mask|set
SIG_UNBLOCKset表示希望希望从当前进程解除的信号屏蔽字,相当于mask=mask&~set
SIG_SETMASK设置当前屏蔽字为set值,相当于mask=set

注意:如果sigprocmask解除了若干个未决信号的阻塞,则在procmask返回之前,至少有一个信号递达。信号什么时候被处理?在下面第四大点会说。如果某进程在sigprocmask之前阻塞了2号信号,如果调用procmask解除2号信号阻塞,那么进程会立马接收到2号信号,并在合适的时候(内核态到用户态)执行处理动作。

3.2:9号进程不能被屏蔽(验证)

#include<signal.h>
#include<unistd.h>
#include<iostream>
using namespace std;
int main()
{
    sigset_t s;
    sigemptyset(&s);
    sigaddset(&s,2);
    sigaddset(&s,9);
    sigprocmask(SIG_BLOCK,&s,nullptr);
    while(1)
    {
        sleep(1);
        cout<<"我是一个进程 pid:"<<getpid()<<endl;
    }
    return 0;
}

 通过键盘发送2号信号是没用的,而通过kill -9 5553则可以。

这证明9号信号无法被阻塞。

3.3:sigpending

#include <signal.h>
int sigpending(sigset_t* set);
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。

表示将pending位图输入进set中,该参数不能用于向进程发送信号。下面来演示一下:

void myhandle(int signo)
{
    cout<<"成功解除二号信号屏蔽" <<endl;
    exit(0);
}
void printpend(sigset_t* p)
{
    for(int i = 0;i<=31;++i)
    {
        if(sigismember(p,i))
        {
            cout<<"1";
        }
        else
        {
            cout<<"0";
        }
    }
    cout<<endl;
}
int main()
{
    sigset_t s,p;
    signal(2,myhandle);
    sigemptyset(&s);
    sigaddset(&s,SIGINT);//2号信号
    sigprocmask(SIG_BLOCK,&s,nullptr);
    int cnt = 0;
    while(true)
    {
        ++cnt;
        cout<<"我是一个进程 我即将打印位图 pid:"<<getpid()<<endl;
        sigpending(&p);
        printpend(&p);
        sleep(1);
        if(cnt == 10)
        {
            cout<<"我即将解除2号信号屏蔽"<<endl;
            sigprocmask(SIG_UNBLOCK,&s,nullptr);
        }
        
    }
    return 0;
}

只有block位图中是0,pending为1,信号才有可能递达。

 

 

 可以看到发送2号信号的时候,pending位图的2号信号下标0变1,表示接收到2号信号,但是由于sigprocmask屏蔽了2号信号,所以没有递达,因此进程暂时没有执行2号信号的处理动作(成功解除二号信号屏蔽并且退出进程),当cnt等于10了解除屏蔽,信号递达,执行自定义动作。

4:信号处理

信号是什么时候被接收并且处理的呢?

直接下结论:从内核态切换成用户态的时候。

内核态:执行操作系统代码的时候,计算机所处的状态。

用户态:执行用户代码的时候,计算机所处的状态。

之所以区分状态,是因为如果只有用户态,随心所欲的访问,会造成严重问题! 

所以OS提供的所有系统调用,会在执行的时候更改执行级别。

我们都知道程序运行起来,操作系统会创建该进程的pcb,然后会通过页表页框的映射将虚拟地址空间映射至物理内存中,那么操作系统的代码和数据会不会load到内存呢?

答案是肯定的,我们在windows下开机所等待的时间就是在等待系统服务的loading。

答案知晓:用户代码和OS代码数据都会load到内存中。

 在X86环境下,0-3G为用户态空间,3-4G为内核级空间,而对于不同的进程来说,3-4G这一部分内容是相同的,因此所有进程都可以通过进程地址空间看到相同的一份OS,因此OS运行的本质就是在地址空间上运行的!所以,所谓的系统调用就是在进程的地址空间中进行函数跳转返回即可!

所以进程是如何被调度的?

OS的本质是一个死循环,其中有时钟硬件,每隔很短的时间就会发送时钟中断 ,OS就要执行对应的中断处理方法,然后检测进程的时间片,执行系统调用,这是因为中断进入内核态。

当进程被调度的时候,就是时间片到了,操作系统会保存进程的上下文并且切换,选择合适的进程,然后执行某条语句的系统调用。 

那么用户态和内核态具体切换是什么样子的呢?我们可以画一个无穷符号

 与横线的交点就是切换状态的时候。

而无穷的正中心的点表示检查pending表的时候。而且是在内核态检查pending

如果信号动作是默认的,就不会切回用户态了。

可以不进行切换状态,直接在内核态操作吗?

 答案是可以但不可行,因为用户如果执行非法操作,比如删库,那就完蛋了。

5:sigaction

除了上一章提到的signal可以捕捉信号外,sigaction也是可以。

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
成功返回0,失败返回-1

参数:

  • signo是信号编号
  • act非空,则将signo的处理动作更改为act
  • oact非空,则通过oact传出修改之前的动作

 struct sigaction是一个结构体变量,定义如下:

struct sigaction {
	void(*sa_handler)(int);
	void(*sa_sigaction)(int, siginfo_t *, void *);
	sigset_t   sa_mask;
	int        sa_flags;
	void(*sa_restorer)(void);
};

第一个变量sa_handler有3种设置方式:

  • SIG_IGN,表示动作为忽略
  • SIG_DFL,表示默认处理动作
  • 传用户自定义的动作函数指针,表示执行自定义动作

 第二个变量不用理会,置空即可。是实时信号的处理函数。

第三个变量:sa_mask

这是一个信号屏蔽字。

当某个信号的处理函数被调用的时候,内核自动将该信号加入该进程的信号屏蔽字中,当信号处理函数返回时自动恢复原来的信号屏蔽字(不包含该信号或者自定义了其他一起被屏蔽的信号时候的屏蔽字),这就保证了在处理一个信号的时候,当该信号再次产生,那么会一直阻塞直到处理动作结束。如果想要当前信号被屏蔽以外,还需要其他信号也被屏蔽,可以修改sa_mask字段。

第四个变量:sa_flags包含一些选项,给0即可。

第五个变量:sa_restorer不使用他

下面展示一下:第三点是什么意思

void printpend(sigset_t* p)
{
    for(int i = 1;i<=31;++i)
    {
        if(sigismember(p,i))
        {
            cout<<"1";
        }
        else
        {
            cout<<"0";
        }
    }
    cout<<endl;
}
void myhandler(int signo)
{
    cout<<"我执行"<<signo<<"号信号的动作"<<endl;
    int cnt = 30;
    while(cnt--)
    {
        sigset_t pending;
        sigemptyset(&pending);
        sigpending(&pending);
        printpend(&pending);
        sleep(1);
    }
}
int main()
{
    struct sigaction act,oact;
    memset(&act,0,sizeof(act));
    memset(&oact,0,sizeof(oact));
    act.sa_handler = myhandler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask,3);
    sigaddset(&act.sa_mask,4);
    sigaddset(&act.sa_mask,5);
    sigaction(2,&act,&oact);
    while(true)
    {
        cout<<"我是一个进程 pid:"<<getpid()<<endl;
        sleep(1);
    }
}

我们将345信号屏蔽字都添加进sa_mask中,自定义2号信号动作,并且同时发送345信号,根据3的说法,在执行某个动作的时候,会阻塞sa_mask和目前执行动作对应的信号,如图所示,果真如此:

 6:可重入函数

这样一个函数,当调用insert(&node1)的时候,p->next=head;执行完发送信号,执行信号动作。这个时候可以具象化一下:

 在执行完毕自定义动作后是这样:

 这个时候切换回内核态,再执行上次中断处代码,也就是head=p,这个时候是这样:

 

 如此以来就造成了内存泄漏,node2不知道怎么访问了。

在这种第一次调用还没返回时候再一次进入该函数称为重入。

如果一个函数满足以下条件则可以成为不可重入:

  • 调用了 malloc free, 因为 malloc 也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

 7:volatile

作用是保持内存可见性

 

int flag = 0;
void myhandle(int signo)
{
    cout<<"捕捉到信号"<<signo<endl;
    flag = 1;
}
int main()
{
    signal(2.myhandle);
    while(!flag);
    cout<<"进程退出正常"<<endl;
    return 0;
}

理想情况是ctrl+c后,进程正常退出。

 确实是这样,但是如果加上编译器优化呢?-o 表示优化级别

 这个时候就算捕捉到信号,flag变了1,while也仍在死循环?为什么?

 不优化前,cpu可以在寄存器中读取,而i =1则保存在寄存器中,而这样的一个死循环,也没有干什么事情,flag却在被高频访问,就需要不停的将寄存器中的内容load到cpu里,效率差。优化后就是告诉cpu只去内存中访问,不去访问寄存器,因此就变成图上所示,flag永远是0。

解决办法:

volatile int flag = 0;

8:SIGCHLD

 进程讲过对于一个僵尸子进程,父进程既可以阻塞的等,也可以非阻塞的轮询(WNOHANG),采用第一种方式父进程就不能做自己的事情了,采用第二种方式,父进程就要在做自己工作的时候不断的询问子进程,程序实现复杂。

有没有办法让子进程退出成为僵尸进程的时候告诉一下父进程,父进程来进行处理呢?答案是有的,子进程退出的时候会向父进程发送SIGCHLD信号,因此基于这样一个原理我们可以写一个例子:

pid_t id;
void myhandle(int signo)
{
    printf("捕捉到信号 %d,who:%d \n",signo,getpid());
    sleep(2);
    while(1)
    {
        pid_t res = waitpid(-1,nullptr,WNOHANG);
        if(res>0)
        {
            printf("wait success,res: %d id: %d\n",res,id);
        }
        else break;
    }
    cout<<"wait done"<<endl;

}
int main()
{
    signal(SIGCHLD,myhandle);
    id = fork();
    if(id == 0)
    {
        int cnt = 3;
        //子进程
        while(cnt--)
        {
          cout<<"我是子进程 pid是"<<getpid()<<" ppid是"<<getppid()<<endl;
          sleep(1);
        }
        exit(-1);
    }
    //父进程
    while(true)
    {
        
        cout<<"我是父进程 我在做自己的事情"<<endl;
        sleep(1);
    }
}

退出后发送信号,这个时候父进程再去等待。

如果创建了多个子进程,怎么办?

创建多个子进程,因为位图不会记录多少个信号,只会记录有无,所以只会释放一个子进程。所以可以像我上面这样设置一个while循环,不断的去等待,只要有子进程就等待,这样就可以释放僵尸进程了。

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

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

相关文章

C++知识点 -- C++的IO流

C知识点 – C的IO流 文章目录 C知识点 -- C的IO流一、C标准IO流1.多个输入数据2.多行数据读入 二、C文件IO流1.ifstream读文件2.文件读写类 三、stringstream 一、C标准IO流 C系统实现了一个庞大的类库&#xff0c;其中以ios为基类&#xff0c;其他类都是直接或间接派生自ios类…

springboot 阿里云oss图片上传和异常处理

自己去申请开通阿里云oss。 对象存储 OSS_云存储服务_企业数据管理_存储-阿里云 1.在pom.xml添加依赖 <dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.14.0</version></dep…

港科夜闻|香港科大与香港资管通有限公司签署校企合作备忘录,成立校企合作基金促科研成果落地...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大与香港资管通有限公司签署校企合作备忘录&#xff0c;成立校企合作基金促科研成果落地。“港科资管通领航基金”28日在香港成立&#xff0c;将致力于推动高校科研成果转化&#xff0c;助力香港国际创科中心建设。…

Fabric.js 拖拽顶点修改多边形形状

theme: smartblue 我正在参加「掘金启航计划」 本文简介 戴尬猴&#xff0c;我是德育处主任 这次要介绍的一个demo是"拖拽多边形定点修改多边形形状"。 其实 Fabric.js 官网也有这个demo&#xff1a;Fabric.js demos Custom controls, polygon 。但这个demo可能对于…

ASEMI代理ADV7611BSWZ原装ADI车规级ADV7611BSWZ

编辑&#xff1a;ll ASEMI代理ADV7611BSWZ原装ADI车规级ADV7611BSWZ 型号&#xff1a;ADV7611BSWZ 品牌&#xff1a;ADI/亚德诺 封装&#xff1a;LQFP-64 批号&#xff1a;2023 引脚数量&#xff1a;64 工作温度&#xff1a;-40C~85C 安装类型&#xff1a;表面贴装型 …

怎么轻松地搞定Win11系统备份任务?

“我是一个电脑小白&#xff0c;不是很懂电脑的一些操作。我刚买了一台新电脑&#xff0c;它装的是Win11系统&#xff0c;我害怕它出现什么问题&#xff0c;听朋友说可以通过备份的方法保护系统&#xff0c;这是真的吗&#xff1f;有谁知道该怎么进行Win11系统备份吗&#xff1…

OJ练习第101题——柱状图中最大的矩形

柱状图中最大的矩形 力扣链接&#xff1a;84. 柱状图中最大的矩形 题目描述 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 思路 我们先嵌…

第2章 Nginx环境搭建

第2章 Nginx环境搭建 2.1 下载 免费开源版的官方网站&#xff1a;http://nginx.org Nginx 有 Windows 版本和 Linux 版本&#xff0c;但更推荐在 Linux 下使用 Nginx&#xff1b; 下载nginx-1.14.2.tar.gz的源代码文件&#xff1a;wget http://nginx.org/download/nginx-1.…

登录功能实现及文件上传下载功能补充

登录功能实现简单介绍&#xff1a; 1&#xff1a;登录时密码输入错误刷新登陆页面并提示登录信息错误&#xff1b; 2&#xff1a;输入正确用户名及密码点击sign in 登录成功。&#xff08;如果勾选remember me 只要服务器未停止&#xff0c;可以直接免登录进入欢迎页面&#x…

《光电容积法在评估高血压中的应用》阅读笔记

目录 一、论文摘要 二、论文十问 Q1&#xff1a;论文试图解决什么问题&#xff1f; Q2&#xff1a;这是否是一个新的问题&#xff1f; Q3&#xff1a;这篇文章要验证一个什么科学假设&#xff1f; Q4&#xff1a;有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课…

ubuntu系统配置软件脚本自启动

背景 项目因为某些原因需要服务器自启动来执行脚本, 因此需要在ubuntu服务器上面实现自启动功能. 步骤 ubuntu作为服务器使用时&#xff0c;常常需要在机器重启时能自动启动我们开发的服务。 Ubuntu 18.04不再使用initd管理系统&#xff0c;改用systemd&#xff0c;包括用sys…

Redis:哨兵集群

目录 基于pub/sub 机制的哨兵集群组成基于pub/sub 机制的客户端事件通知由哪个哨兵执行主从切换哨兵实例是不是越多越好&#xff0c;如果同时调大 down-after-milliseconds 值&#xff0c;对减少误判是不是也有好处 部署多个哨兵实例就形成了一个哨兵集群。哨兵集群中的多个实例…

【Redis】Redis面试题

Redis的事务 什么是Redis的事务 Redis的事务是一个单独的隔离操作&#xff0c;事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中&#xff0c;不会被其他客户端发送来的命令请求所打断&#xff0c;所以Redis事务是在一个队列中&#xff0c;一次性、顺序性、排他…

人工智能基础:从机器学习到深度学习的发展和应用

随着信息技术的快速发展&#xff0c;人工智能技术已经成为当今最热门的技术之一。在人工智能技术中&#xff0c;机器学习和深度学习是最为关键的两个技术分支。本文将从机器学习的基础概念、机器学习的发展史、深度学习的基本概念、深度学习的应用以及深度学习的未来趋势等方面…

Chunjun数据同步工具初体验

chunjun (纯钧) 官方文档纯钧 chunjun 有四种运行方式&#xff1a;local、standalone、yarn session、yarn pre-job 。 运行方式/环境依赖flink环境hadoop环境localstandalone√yarn session√√yarn pre-job√√ 1.下载 官网已经提供了编译好的插件压缩包&#xff0c;可以…

【译】Java 内存泄露的构造和检测

1. 概述 在 Java 应用程序中&#xff0c;内存泄漏会导致严重的性能下降和系统故障。开发人员必须了解内存泄漏的发生原因以及如何识别和解决它们。 在本教程中&#xff0c;我们将提供一个使用失效的监听器问题作为示例来创建 Java 内存泄漏的指南。我们还将讨论各种检测内存泄…

GitHub Copilot 使用介绍

什么是 Github Copilot https://github.com/features/copilot Github Copilot 是一种人工智能辅助开发工具&#xff0c;由 GitHub 和 OpenAI 合作开发&#xff0c;旨在通过机器学习算法为开发人员提供自动化的代码提示和智能建议&#xff0c;从而提高开发效率&#xff0c;该系…

【Vue】 CLI WebStorage

CLI 本地存储 自定义事件 WebStorage(js本地存储)localStoragesessionStorage WebStorage(js本地存储) 存储内容大小一般支持5MB左右(不同浏览器可能还不一样) 浏览器端通过Window.sessionStorage 和Winodw.localStorage 属性来实现本地存储机制相关API xxxStorage.setItem(‘…

TCP协议介绍

文章目录 一、TCP协议二、TCP协议段格式4位首部长度可靠性理解32位序号和32位确认序号16位窗口大小TCP协议中的6个标记位16位紧急指针 三、TCP三次握手和四次挥手TCP的三次握手TCP的四次挥手状态变化 四、超时重传机制五、滑动窗口高速重发机制(快重传) 六、流量控制七、拥塞控…

Java分布式事务(十八)

文章目录 🔥最终一致性分布式事务解决方案_什么是最大努力通知型分布式事务🔥最大努力通知型分布式事务_最大努力通知与可靠消息最终一致性的区别🔥最大努力通知型分布式事务解决方案🔥最大努力通知型分布式事务_案例业务说明🔥最大努力通知型分布式事务实战_实现充值…