【Linux】信号量与信号

news2024/12/26 23:24:14

目录

先导知识

信号量

信号

信号概念及产生信号的一般方式

进程递达、阻塞和捕捉

信号集操作函数

信号的捕捉

可重入函数


先导知识

信号量与信号没有任何关系,它们是两个完全不同的概念!

操作系统的本质,就是一个死循环;操作系统的执行,时基于各种硬件中断的!

所有用户的行为,都是以进程的形式在 OS 中表现得,操作系统只要把进程调度好,就能完成所有得用户任务。

在终端中将一个可执行程序放在后台运行:./xxx & (在运行前台进程的基础后面加一个 &),前台进程只能有一个,后台进程可以有多个。

volatile 关键字作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被编译器以任何方式优化,对该变量的任何操作,都必须在真实的内存中进行操作。

操作系统的用户态和内核态
用户态受控状态,只能访问自己的 0~3G 空间
内核态可以让用户以 OS 的身份可以多访问通用的那 3~4G 空间
状态转换时间在进行系统调用的时候,从用户态切换至内核态

信号量

信号量维护一个计数器,表示可用资源的数量。当一个执行流想要去访问公共资源内的某一份资源时,需要先去申请信号量资源,并不是直接访问,而申请信号量资源,其实就是对信号量的的计数器进行 -- (减减)操作,当减减操作执行成功后,这个执行流就完成了对资源的预定工作!

那为什么访问公共资源前要在先做这么多事呢?

在进行进程间通信的时候,多个执行流会看到同一份资源,而它们可能会同时对这一份公共资源进行并发访问,这样就会导致数据不一致的问题,所以,就需要将公共数据保护起来,被保护起来的公共资源,叫临界资源,而访问该临界资源的代码,叫临界区!临界资源一次只允许一个进程访问,实现的方法有:互斥和同步

同步的方式有:匿名管道、命名管道,消息队列等

而互斥的实现方法,就是维护一个值为1的信号量,当有执行流申请信号量资源成功的时候,就将信号量的值减减,减减后信号量的值就变为了0,当信号量的值为0的时候,临界资源就不能被访问了,这样就实现了,这块共享内存(临界资源)只能同一时间只能被一个进程访问!

但信号量本质上也是公共资源(因为信号量资源会被多个进程看到),所以在内核系统层面,维护着一个专门设计的管理进程间通信的IPC模块资源,里面使用了类似多态的原理,使用 strcut kern_ipc_perm*  类型的数组来同一管理 IPC 资源,而信号量也是存储在这个 IPC 体系里的,因为在内核中,所以进程都能够访问,也就变成了公共资源。 

信号

信号概念及产生信号的一般方式

在操作西系统中,信号是进程之间事件异步通知的一种方式,属于软中断,例如,当在 shell 命令行启动一个前台进程后,在键盘按下 ctrl + C 组合键,ctrl + C 就会被OS获取,解释成一个信号,前台进程因为受到了信号,进而引起进程退出!信号是一种向目标进程发送通知进程的一种消息机制,本质就是软件,用来模拟中断的行为!

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

kill -l

进程产生信号有四种方式:

  • 通过终端按键产生信号
  • 通过系统调用函数向进程发信号
  • 由软件条件产生信号
  • 由硬件异常产生信号

1、由终端按键产生信号

常见的有:

ctrl + c —— 发出 2 号信号(中断信号)

ctrl + \ —— 发出 3 号信号(离开信号)

ctrl + z —— 发出 20 号信号(暂停信号) 

可以使用下面的程序来验证,但需要知道当前进程的 pid,否则可能会造成进程无法关闭的情况

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
void handler(int signo)
{
    cout << "  I get a sig:" << signo << " ___mypid: " << getpid() << endl;
}
int main()
{
    for(int i = 0; i <= 64; i++) signal(i, handler);
    while(1);
    return 0;
}

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

// kill函数可以给一个指定的进程发送指定的信号。
// raise函数可以给当前进程发送指定的信号(自己给自己发信号)
// 这两个函数都是成功返回0,错误返回-1
#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
// abort函数使当前进程接收到信号而异常终止。
#include <stdlib.h>
void abort(void);

kill 命令,也都是通过调用 kill 函数来实现的

指定发送某种信号的kill命令可以有多种写法,上面的命令还可以写成 kill -signal id 或 kill - i id , signal 是具体某个信号,i 是这个信号的编号,id 被发送信号的进程 pid

3、通过软件条件产生信号

SIGPIPE 就是管道中一种由软件产生的信号,当读端关闭,写端一直写入,OS会直接杀掉写端进程,通过向目标文件发送 SIGPIPE(13) 信号,终止目标进程,这就是一种软件条件。

还有一种软件条件,叫 “闹钟”

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号,该信号的默认处理动作是终止当前进程。当在 seconds 秒之前,进程被被终止了,而信号还没有发送,它剩余的秒数就会被 “保存” 在 alarm 函数中,当下次再调用 alarm 函数时,只需将 seconds 值设置为0,表示取消以前设定的闹钟,alarm 函数的返回值就可以得到,是以前设定的闹钟时间还余下的秒数。

4、硬件异常产生信号

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE 信号发送给进程。再比如当前进程访问了非法内存地址,MMU 会产生异常,内核会将这个异常解释 SIGSEGV 信号发送给进程。

CMOS 周期性的、高频率的在给CPU发送时钟中断,通过这种中断来使操作系统的各种调度方法运行起来了!

进程递达、阻塞和捕捉

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

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

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

被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.

注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达(没有恢复不阻塞的情况下),而忽略是在递达之后可选的一种处理动作。

每种信号在进程种都有两个标志位,block 和 pending(比特位的位置表示编号)还有一个函数指针表示处理动作。标志位在进程的 task_struct 中是通过位图来维护的,通过比特位来控制 “有没有”、“在不在”。例如,block 位图的最低一个比特位就表示是否对 1 号信号进行阻塞,pending 位图的最低一个比特位就表示是否收到 1 号信号。

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

在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

信号集操作函数

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

1、设置信号集

#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置位,表示该信号集的有效信号包括系统支持的所有信号。 

注意,在使用sigset_ t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。

这四个函数都是成功返回0,出错返回-1。

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

2、维护信号集

调用函数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返回前,至少将其中一个信号递达。

3、读取当前进程的信号集

#include <signal.h>
sigpending(&s);

sigpending 函数可以读取当前进程的未决信号集,通过 s 参数传出。调用成功则返回0,出错则返回-1。

信号的捕捉

进程在从内核态返回到用户态的时候,进行信号的检测和处理。

那么内核如何实现信号捕捉呢?

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

用户程序注册了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时发生中断或异常切换到内核态。在中断处理完毕后要返回用户态的 main 函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数。sighandler 函数和 main 函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。 sighandler函数返回后自动执行特殊的系统调用 sigreturn 再次进入内核态。如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

在信号捕捉中,一共会涉及到四次状态转换!

可重入函数

可重入函数(reentrant function)是指在多个执行流下,能够被同时调用而不会产生冲突或错误的函数。

这种函数能够保证在任意时刻,无论被同一个还是不同执行流调用,都能正确地完成预期的功能。

如果一个函数符合以下条件之一则是不可重入的:

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

下图为函数不可重入的举例:

main 函数调用 insert 函数向一个链表 head 中插入节点 node1,插入操作分为两步,刚做完第一步的时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函数,sighandler 也调用 insert 函数向同一个链表 head 中插入节点node2,插入操作的 两步都做完之后从sighandler 返回内核态,再次回到用户态就从 main 函数调用的 insert  函数中继续 往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后向链表中插入两个节点,而最后只有一个节点真正插入链表中了,与预期结果不符合,因此不是可重入函数!

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

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

相关文章

Cookie在网络爬虫中的重要作用

在互联网的海量数据中&#xff0c;网络爬虫如同一只勤劳的小蜜蜂&#xff0c;不断采集着网页上的信息。而在这过程中&#xff0c;Cookie扮演了不可或缺的角色&#xff0c;它就像是爬虫手中的“通行证”&#xff0c;帮助其顺利获取所需数据。本文将深入探讨Cookie在网络爬虫中的…

LLM2LLM: Boosting LLMs with Novel Iterative Data Enhancement

LLM2LLM: Boosting LLMs with Novel Iterative Data Enhancement 相关链接&#xff1a;arXiv GitHub 关键字&#xff1a;LLM、Data Augmentation、Fine-tuning、NLP、Low-data Regime 摘要 预训练的大型语言模型&#xff08;LLMs&#xff09;目前是解决绝大多数自然语言处理任…

axios+springboot上传图片到本地(vue)

结果&#xff1a; 前端文件&#xff1a; <template> <div> <input type"file" id"file" ref"file" v-on:change"handleFileUpload()"/> <button click"submitFile">上传</button> </div&g…

centos7 的redis的安装

文章目录 查看本机redis⾸先安装 scl 源, 再安装 redis 基本配置启动redis停止redis 查看本机redis ⾸先安装 scl 源, 再安装 redis 安装scl源 yum install centos-release-scl-rh安装redis5 yum install rh-redis5-redis安装成功 基本配置 修改etc/redis/redis.conf 文件…

javaWeb网上订餐管理系统

一、简介 在当今社会&#xff0c;随着互联网的普及&#xff0c;网上订餐已经成为了人们生活中不可或缺的一部分。为了方便用户点餐&#xff0c;同时也方便商家管理订单&#xff0c;我设计了一个基于JavaWeb的网上订餐管理系统。该系统分为前台和后台两部分&#xff0c;前台包括…

解决多线程场景下ThreadLocal的变量传递问题

问题描述&#xff1a; ThreadLocal可以用于存储线程独享的变量。可以方便的存储上下文信息&#xff0c;提升代码的简洁性。 然而&#xff0c;ThreadLocal的一个不足之处在于&#xff0c;它不支持在线程嵌套过程中自动地将数据从父线程传递到子线程。这意味着&#xff0c;即使…

面试笔记——框架篇Spring系列(Spring、SpringMVC、SpringBoot)

Spring 线程安全 singleton : bean在每个Spring IOC容器中只有一个实例。 prototype&#xff1a;一个bean的定义可以有多个实例。 问题一&#xff1a; Spring中的单例bean是否是线程安全的&#xff1f; ControllerRequestMapping("/user")public class UserContro…

Springboot+vue的旅游信息推荐系统设计与实现+数据库+论文+数据库表结构文档+免费远程调试

项目介绍: Springbootvue的旅游信息推荐系统设计与实现。Javaee项目&#xff0c;springboot vue前后端分离项目 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring SpringBoot Mybati…

为何ChatGPT日耗电超50万度?

看新闻说&#xff0c;ChatGPT每天的耗电量是50万度&#xff0c;国内每个家庭日均的耗电量不到10度&#xff0c;ChatGPT耗电相当于国内5万个家庭用量。 网上流传&#xff0c;英伟达创始人黄仁勋说&#xff1a;“AI的尽头是光伏和储能”&#xff0c;大佬的眼光就是毒辣&#xff…

【云能耗管理系统在某大型商场的应用】安科瑞Acrel-EIOT能源物联网平台方案

摘要&#xff1a;依据对上海市某大型商场现场考察的结果&#xff0c;提出通过建设云能耗管理系统的方案来改善商场能耗的管理现状。首先充分搜集建筑信息和设备运行工况&#xff0c;合理设计系统实施方案&#xff0c;解决现场数据采集和传输障碍&#xff0c;完成云能耗管理系统…

Python 全栈体系【四阶】(十九)

第五章 深度学习 一、基本理论 4. 神经网络的改进 4.3 循环神经网络 4.3.1 标准 CNN 模型的不足 假设数据之间是独立的。标准 CNN 假设数据之间是独立的&#xff0c;所以在处理前后依赖、序列问题&#xff08;如语音、文本、视频&#xff09;时就显得力不从心。这一类数据…

解决 cv2.imread读取带中文路径图片问题

http://t.csdnimg.cn/i8CXn 1.问题&#xff1a; # 中草药数据集样本可视化展示 import cv2 import matplotlib.pyplot as plt %matplotlib inline plt.title("heshouwu") plt.imshow(cv2.imread(r"D:\home\aistudio\data1\archive\train\何首乌\heshouwu_0001.…

各大pdf转word软件都用的哪家的ocr引擎?

国内一般的PDF软件一般都调用某国际PDF原厂的OCR接口&#xff0c;但这家公司是主要做PDF&#xff0c;在OCR方面并不专注&#xff0c;一些不是很复杂的场景还能应付得过来&#xff0c;复杂一点的效果就强差人意了&#xff0c;推荐用金鸣表格文字识别系统&#xff0c;它主要有以下…

位段详细解释

结构体位段的使用原则 在C语言中&#xff0c;结构体&#xff08;Struct&#xff09;是一种复合数据类型&#xff0c;它允许我们将多个不同类型的数据项组合成一个单一的实体。位段&#xff08;Bit Field&#xff09;是结构体中的一个特殊成员&#xff0c;它允许我们只取结构体…

专注无线MCU:STM32WL33CCV6A、STM32WL33CCV7A、STM32WL33K8V7TR、STM32WL33KBV7TR设计用于RF无线应用

一、STM32WL33CC &#xff1a;Sub-GHz无线微控制器&#xff0c;单核Arm Cortex-M0 概述 STM32WL33xx是一款高性能超低功耗无线应用处理器&#xff0c;用于1 GHz以下频段的RF无线应用。它设计用于在免许可ISM和SRD频段&#xff08;如433、868和915 MHz&#xff09;下工作。 …

Apache Spark

一、Apache Spark 1、Spark简介 Apache Spark是用于大规模数据 (large-scala data) 处理的统一 (unified) 分析引擎。 Spark官网 Spark最早源于一篇论文Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing,该论文是由加州大学柏…

网络安全-文件包含

一、php://input 我们先来看一个简单的代码 <meta charset"utf8"> <?php error_reporting(0); $file $_GET["file"]; if(stristr($file,"php://filter") || stristr($file,"zip://") || stristr($file,"phar://&quo…

Python---常用的web框架

目录 Django创建Django项目启动Django项目引入APP视图函数例如纯文本JSON格式数据重定向渲染页面返回错误提示 FlaskPyramidTornado Django 特点&#xff1a;Django是一个全功能的Web框架&#xff0c;提供了许多内置的功能和工具&#xff0c;如ORM、表单处理、认证等。它的设计…

AWS EC2 学习之: 使用 PuTTY 从 Windows 连接到 Linux 实例

启动您的实例之后&#xff0c;您可以连接到该实例&#xff0c;然后像使用您面前的计算机一样来使用它。 注意 启动实例后&#xff0c;需要几分钟准备好实例&#xff0c;以便您能连接到实例。检查您的实例是否通过了状态检查。您可以在 Instances 页上的 Status Checks 列中查…

Python基础学习笔记(二)

Python基础语法 注释 注释有: 单行注释、多行注释、文档注释。 单行注释采用 # 符号,后面跟随的都是注释内容多行注释采用 (三个单引号) 或者 """(三个双引号) 包围文档注释采用 """ 包围&#xff0c;一般出现在模块&#xff0c;类&#xff0c;…