Linux操作系统——第五章 进程信号

news2024/10/6 21:27:02

 

 

 

目录

信号概念

用kill -l命令可以察看系统定义的信号列表

信号处理常见方式概览

产生信号

1. 通过终端按键产生信号

2. 调用系统函数向进程发信号

3. 由软件条件产生信号

4. 硬件异常产生信号

阻塞信号

1. 信号其他相关常见概念

2. 在内核中的表示

3. sigset_t

4. 信号集操作函数

sigprocmask

sigpending

捕捉信号

1. 内核如何实现信号的捕捉

2. sigaction

可重入函数


 



信号概念



信号是进程之间事件异步通知的一种方式,属于软中断


用kill -l命令可以察看系统定义的信号列表

  • 每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定义 #define SIGINT 2
  • 编号34以上的是实时信号,本章只讨论编号34以下的信号,不讨论实时信号。这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明: man 7 signal 

 



信号处理常见方式概览



可选的处理动作有以下三种:
1. 忽略此信号。
2. 执行该信号的默认处理动作。
3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。 



产生信号




1. 通过终端按键产生信号



SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump

当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部 保存到磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,
事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存 在PCB中)。默认是不允许产生core文件的,
因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。 首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K: $ ulimit -c 1024


2. 调用系统函数向进程发信号


kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号(自己给自己发信号)

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
这两个函数都是成功返回0,错误返回-1。

abort函数使当前进程接收到信号而异常终止。

#include <stdlib.h>
void abort(void);
就像exit函数一样,abort函数总是会成功的,所以没有返回值。 

3. 由软件条件产生信号



SIGPIPE是一种由软件条件产生的信号。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。


4. 硬件异常产生信号



硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。

例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。

再比如当前进程访问了非法内存地址,,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。




阻塞信号




1. 信号其他相关常见概念



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



2. 在内核中的表示



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

  • 每个信号都有两个标志位分别表示阻塞(block)未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
  • 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?       
    •  POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号 

3. sigset_t



从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。
因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,

在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,

而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。

阻塞信号集也叫做 当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。



4. 信号集操作函数



sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的 

#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初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位1,表示 该信号集的有效信号包括系统支持的所有信号。
注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号

这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含
某种 信号,若包含则返回1,不包含则返回0,出错返回-1。


sigprocmask



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

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

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。

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

如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值 

 

 如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。


sigpending


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

 

 



捕捉信号



1. 内核如何实现信号的捕捉



如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:

用户程序注册了SIGQUIT信号的处理函数sighandler。

当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。

内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler
和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。

sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了


2. sigaction


 #include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);

 

 将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。


当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。

如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。 sa_flags字段包含一些选项,本章的代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数。

 



可重入函数



  • 如果一个函数符合以下条件之一则是不可重入的:
    • 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
    • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构 

只有当接收进程从内核模式返回到用户模式时,才处理信号。

 

 

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

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

相关文章

spring.aop 随笔4 如何借助jdk代理类实现aop

0. 下了有一个月的雨&#xff0c;这对鼻炎来说来吗&#xff1f;不好 其实这也算6月份的博客&#xff0c;之前一直疏于整理 本文仅关注jdk代理所实现的spring.aop下&#xff0c;两者的关系完整的aop源码走读请移步相关 spring.aop 的其他随笔 1. 反编译追踪源码 1.1 jdk代理类…

BPMN2.0规范简介

1 概述 BPMN(Business Process Model & Notation)&#xff0c;中文名为业务流程模型与符号。BPMN2.0是OMG(Object Management Group&#xff0c;对象管理组织)制定的&#xff0c;其主要目的是既给用户提供一套简单的、容易理解的机制&#xff0c;以便用户创建流程模型&…

项目性能优化-内存泄漏检测与修改

最近终于有空优化一波项目的性能了&#xff0c;第一波借助Android Studio自带的Profiler工具检测内存泄漏。 第一步、创建Profiler的SESSIONS 第二步、进入MEMORY内存监控 右侧带有绿色原点的就是此时运行的Profiler的SESSION,点击右侧MEMORY进入内存监控的详情模块 第三步…

缓存三击-缓存穿透、缓存雪崩、缓存击穿

缓存三击-缓存穿透、缓存雪崩、缓存击穿 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ Spring专栏&#x1f449;https://blog.csdn.net/weixin_53580595/category_12279…

【产品设计】掌握“4+X”模型,从0到1构建B端产品

“4X”模型是什么 4个阶段&#xff1a;规划阶段&#xff0c;设计阶段&#xff0c;实现阶段&#xff0c;迭代阶段 X:项目管理&#xff0c;数据分析&#xff0c;产品运营 1、规划阶段 这是一个产品的开始&#xff0c;它决定了产品的设计方向和基调。主要包括用户分析、市场分…

爬虫入门指南(4): 使用Selenium和API爬取动态网页的最佳方法

文章目录 动态网页爬取静态网页与动态网页的区别使用Selenium实现动态网页爬取Selenium 的语法及介绍Selenium简介安装和配置创建WebDriver对象页面交互操作 元素定位 等待机制页面切换和弹窗处理截图和页面信息获取关闭WebDriver对象 使用API获取动态数据未完待续.... 动态网页…

JVM-垃圾回收-基础知识

基础知识 什么是垃圾 简单说就是没有被任何引用指向的对象就是垃圾。后面会有详细说明。 和C的区别 java&#xff1a;GC处理垃圾&#xff0c;开发效率高&#xff0c;执行效率低 C&#xff1a;手工处理垃圾&#xff0c;如果忘记回收&#xff0c;会导致内存泄漏问题。如果回…

Linux Mint 21.2“Victoria”Beta 发布

导读近日消息&#xff0c;Beta 版 Linux Mint 21.2 “Victoria” 于今天发布&#xff0c;用户可以访问官网下载镜像。 Linux Mint 21.2 代号 “Victoria” &#xff0c;基于 Canonical 长期支持的 Ubuntu 22.04 LTS&#xff08;Jammy Jellyfish&#xff09;操作系统&#xff0…

2023年第三届工业自动化、机器人与控制工程国际会议

会议简介 Brief Introduction 2023年第三届工业自动化、机器人与控制工程国际会议&#xff08;IARCE 2023&#xff09; 会议时间&#xff1a;2023年10月27 -30日 召开地点&#xff1a;中国成都 大会官网&#xff1a;www.iarce.org 2023年第三届工业自动化、机器人与控制工程国际…

JAVA http

javahttp 请求数据格式servletservlet生命周期servletrequest获取请求数据解决乱码response相应字符&字节数据 请求数据格式 servlet servlet生命周期 servlet request获取请求数据 解决乱码 response相应字符&字节数据 response.setHeader("content-type",…

A. Portal(dp优化枚举)

Problem - 1580A - Codeforces CQXYM发现了一个大小为nm的矩形。矩形由n行m列的方块组成&#xff0c;每个方块可以是黑曜石方块或空方块。CQXYM可以通过一次操作将黑曜石方块变为空方块&#xff0c;或将空方块变为黑曜石方块。 一个大小为ab的矩形M被称为传送门&#xff0c;当…

【Linux】程序员的基本素养学习

这是目录 写在前面一、内存管理1、分段2、分页 二、线程管理三、静态库1、编译1.1、预处理1.2、编译1.3、汇编1.4、链接2、编译器3、目标文件**.text****.data****.bss****__attribute__** 3.1、符号3.2、兼容C语言 -- extern C4、链接 -- ld 写在前面 本文记录自己的学习生涯…

五.组合数据类型

目录 1、数组类型 声明数组 初始化数组 数组赋值 访问数组元素 2、切片类型 1、定义切片 2、切片初始化 3、访问 4、空(nil)切片 5、切片的增删改查操作&#xff1a; 3、指针类型 1、什么是指针 2、如何使用指针、指针使用流程&#xff1a; 3、Go 空指针 4、指…

chatgpt赋能python:如何将Python打包-一个SEO优化指南

如何将Python打包 - 一个SEO优化指南 作为一名拥有10年Python编程经验的工程师&#xff0c;我意识到很多Python开发者面临一个共同的问题&#xff1a;如何将他们的Python项目打包并发布到PyPI上&#xff1f;打包一个Python项目不仅可以让您的代码更加组织化&#xff0c;也可以…

如何拆分PDF?拆分PDF软件分享!​

那么如何拆分PDF&#xff1f;PDF是一种流行的电子文档格式&#xff0c;它可以在不同的操作系统和设备上进行查看和共享&#xff0c;而不会因为不同的软件或硬件而出现兼容性问题。同时&#xff0c;在使用的过程中&#xff0c;PDF拆分PDF文件是一个比较常见的需求&#xff0c;它…

threejs入门

个人博客地址: https://cxx001.gitee.io 前言 随着HTML5的发布&#xff0c;我们可以通过WebGL在浏览器上直接使用显卡资源来创建高性能的二维和三维图形&#xff0c;但是直接使用WebGL编程来创建三维场景十分复杂而且还容易出问题。而使用Three.js库可以简化这个过程&#xff…

机器学习——决策树1(三种算法)

要开始了…内心还是有些复杂的 因为涉及到熵…单纯的熵&#xff0c;可以单纯 复杂的熵&#xff0c;如何能通俗理解呢… 我也没有底气&#xff0c;且写且思考吧 1. 决策树分类思想 首先&#xff0c;决策树的思想&#xff0c;有点儿像KNN里的KD树。 KNN里的KD树&#xff0c;是每…

如何将非平稳的时间序列变为平稳的时间序列?

可以采用现代信号处理算法&#xff0c;比如小波分解&#xff0c;经验模态分解&#xff0c;变分模态分解等算法。 以经济金融领域的数据为例&#xff0c;经济金融领域的数据作为一种时间序列&#xff0c;和我们平常工程领域分析的信号具有相同特性。一般来说&#xff0c;信号是…

在 Maya、ZBrush 和 Arnold 中重塑来自邪恶西部的 Edgar Gravenor

今天瑞云渲染小编给大家带来Giancarlo Penton 介绍的Edgar Gravenor项目背后过程&#xff0c;展示了皮肤纹理和头发是如何制作的&#xff0c;并解释了详细的服装是如何设置的。 介绍 大家好&#xff0c;我的名字是Giancarlo Penton。我是一名3D角色艺术家&#xff0c;最近毕业…

从零开始 Spring Boot 53:JPA 属性转换器

从零开始 Spring Boot 53&#xff1a;JPA 属性转换器 图源&#xff1a;简书 (jianshu.com) 这篇文章介绍如何在 JPA&#xff08;Hibernate&#xff09;中使用属性转换器。 在前篇文章中&#xff0c;我介绍了如何使用Embedded和Embeddable将一个类型嵌入实体类&#xff0c;并映…