Linux 可重入、异步信号安全和线程安全

可重入函数

当一个被捕获的信号被一个进程处理时,进程执行的普通的指令序列会被一个信号处理器暂时地中断。它首先执行该信号处理程序中的指令。如果从信号处理程序返回(例如没有调用exit或longjmp),则继续执行在捕获到信号时进程正在执行的正常指令序列(这和当一个硬件中断发生时所发生的事情相似)。但是在信号处理器里,我们并不知道当信号被捕获时进程正在执行哪里的代码。如果进程正使用malloc在它的堆上分配额外的内存,而此时由于捕捉到信号而插入执行该信号处理程序,其中又调用了malloc,这会发生什么呢?或者,如果进程正调用一个把结果存储在一个静态区域里的函数到一半,比如 getpwnam,而我们在信号处理器里调用相同的函数,又会发生什么呢?在malloc的例子里,进程可能会遭到严重破坏,因为malloc通常维护它 所有分配过的区域的链表,而插入执行信号处理程序时,进程可能正在更改此链接表。在getpwnam的例子里,返回给普通调用者的信息可能被返回给信号处理器的信息覆盖。

SUS规定了必须保证是可以再入的函数。下表列出了这些再入函数:

一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS 调度下去执行另外一段代码,而返回控制时不会出现什么错误。可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反, 不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥 (或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断, 稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时 保护自己的数据。信号安全,其实也就是异步信号安全,是说线程在信号处理函数当中,不管以任何方式调用你的这个函数如果不死锁不修改数据,那就是信号安全的。因此,我认为可重入与异步信号安全是一个概念 。

线程安全

线程安全:一个函数被称为线程安全的,当且仅当被多个并发线程反复的调用时,它会一直产生正确的结果。有一类重要的线程安全函数,叫做可重入函数,其特点在于它们具有一种属性:当它们被多个线程调用时,不会引用任何共享的数据。尽管线程安全和可重入有时会( 不正确的 )被用做同义词,但是它们之间还是有清晰的技术差别的。可重入函数是线程安全函数的一个真子集。

可重入与线程安全的区别及联系

可重入函数:重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static ),这样的函数就是purecode (纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。可重入函数是线程安全函数,但是反过来,线程安全函数未必是可重入函数。实际上,可重入函数很少,APUE 10.6 节中描述了Single UNIX Specification 说明的可重入的函数,只有115 个;APUE 12.5 节中描述了POSIX.1 中不能保证线程安全的函数,只有89 个。信号就像硬件中断一样,会打断正在执行的指令序列。信号处理函数无法判断捕获到信号的时候,进程在何处运行。如果信号处理函数中的操作与打断的函数的操作相同,而且这个操作中有静态数据结构等,当信号处理函数返回的时候(当然这里讨论的是信号处理函数可以返回),恢复原先的执行序列,可能会导致信号处理函数中的操作覆盖了之前正常操作中的数据。

不可重入的几种情况

使用静态数据结构,比如getpwnam,getpwuid:如果信号发生时正在执行getpwnam,信号处理程序中执行getpwnam可能覆盖原来getpwnam获取的旧值

  • 调用malloc或free:如果信号发生时正在malloc(修改堆上存储空间的链接表),信号处理程序又调用malloc,会破坏内核的数据结构
  • 使用标准IO函数,因为好多标准IO的实现都使用全局数据结构,比如printf(文件偏移是全局的)
  • 函数中调用longjmp或siglongjmp:信号发生时程序正在修改一个数据结构,处理程序返回到另外一处,导致数据被部分更新。

即使对于可重入函数,在信号处理函数中使用也需要注意一个问题就是errno 。一个线程中只有一个errno 变量,信号处理函数中使用的可重入函数也有可能 会修改errno 。例如,read 函数是可重入的,但是它也有可能会修改errno 。因此,正确的做法是在信号处理函数开始,先保存errno ;在信号处 理函数退出的时候,再恢复errno 。例如,程序正在调用printf 输出,但是在调用printf 时,出现了信号,对应的信号处理函数也有printf 语句,就会导致两个printf 的输出混杂在一起。如果是给printf 加锁的话,同样是上面的情况就会导致死锁。对于这种情况,采用的方法一般是在特定的区域屏蔽一定的信号。

屏蔽信号的方法:

signal(SIGPIPE, SIG_IGN); // 忽略一些信号
sigprocmask();// sigprocmask 只为单线程定义的
pthread_sigmask(); // pthread_sigmasks 可以在多线程中使用

现在看来信号异步安全和可重入的限制似乎是一样的,所以这里把它们等同看待;

线程安全:如果一个函数在同一时刻可以被多个线程安全的调用,就称该函数是线程安全的。Malloc 函数是线程安全的。不需要共享时,请为每个线程提供一个专用的数据副本。如果共享非常重要,则提供显式同步,以确保程序以确定的方式操作。通过将过程包含在语句中来锁定和解除锁定互斥,可以使不安全过程变成线程安全过程,而且可以进行串行化。很多函数并不是线程安全的,因为他们返回的数据是存放在静态的内存缓冲区中的。通过修改接口,由调用者自行提供缓冲区就可以使这些函数变为线程安全的。操作系统实现支持线程安全函数的时候,会对POSIX.1 中的一些非线程安全的函数提供一些可替换的线程安全版本。例如,gethostbyname() 是线程不安全的,在Linux 中提供了gethostbyname_r() 的线程安全实现。函数名字后面加上 _r ,以表明这个版本是可重入的(对于线程可重入,也就是说是线程安全的,但并不是说对于信号处理函数也是可重入的,或者是异步信号安全的)。多线程程序中常见的疏忽性问题:

  • 将指针作为新线程的参数传递给调用方栈。
  • 在没有同步机制保护的情况下访问全局内存的共享可更改状态。
  • 两个线程尝试轮流获取对同一对全局资源的权限时导致死锁。其中一个线程控制第一种资源,另一个线程控制第二种资源。其中一个线程放弃之前,任何一个线程都无法继续操作。
  • 尝试重新获取已持有的锁(递归死锁)。
  • 在同步保护中创建隐藏的间隔。如果受保护的代码段包含的函数释放了同步机制,而又在返回调用方之前重新获取了该同步机制,则将在保护中出现此间隔。结果具有误导性。对于调用方,表面上看全局数据已受到保护,而实际上未受到保护。
  • 将UNIX 信号与线程混合时,使用sigwait(2) 模型来处理异步信号。
  • 调用setjmp(3C) 和longjmp(3C) ,然后长时间跳跃,而不释放互斥锁。
  • 从对*_cond_wait() 或 *_cond_timedwait() 的调用中返回后无法重新评估条件。

 

  资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

总结

  • 判断一个函数是不是可重入函数,在于判断其能否可以被打断,打断后恢复运行能够得到正确的结果。(打断执行的指令序列并不改变函数的数据)
  • 判断一个函数是不是线程安全的,在于判断其能否在多个线程同时执行其指令序列的时候,保证每个线程都能够得到正确的结果。
  • 如果一个函数对多个线程来说是可重入的,则说这个函数是线程安全的,但这并不能说明对信号处理程序来说该函数也是可重入的。
  • 如果函数对异步信号处理程序的重入是安全的,那 么就可以说函数是” 异步-信号安全 ” 的。

可重入与线程安全是两个独立的概念, 都与函数处理资源的方式有关。首先,可重入和线程安全是两个并不等同的概念,一个函数可以是可重入的,也可以是线程安全的,可以两者均满足,可以两者皆不满足( 该描述严格的说存在漏洞,参见第二条) 。其次,从集合和逻辑的角度看,可重入是线程安全的子集,可重入是线程安全的充分非必要条件。可重入的函数一定是线程安全的,然过来则不成立。第三,POSIX 中对可重入和线程安全这两个概念的定义:

  • Reentrant Function :A function whose effect, when called by two or more threads,is guaranteed to be as if the threads each executed thefunction one after another in an undefined order, even ifthe actual execution is interleaved.
  • Thread-Safe Function :A function that may be safely invoked concurrently by multiple threads.
  • Async-Signal-Safe Function :A function that may be invoked, without restriction fromsignal-catching functions. No function is async-signal -safe unless explicitly described as such

以上三者的关系为:可重入函数 必然 是 线程安全函数 和 异步信号安全函数;线程安全函数不一定是可重入函数。

可重入与线程安全的区别体现在能否在signal 处理函数中被调用的问题上, 可重入函数在signal 处理函数中可以被安全调用,因此同时也是 Async-Signal-Safe Function ;而线程安全函数不保证可以在signal 处理函数中被安全调用,如果通过设置信号阻塞集合等方法保证一个非可重入函数不被信号中断,那么它也是Async-Signal-Safe Function。

值得一提的是POSIX 1003.1 的 System Interface 缺省是 Thread-Safe 的,但不是Async-Signal-Safe 的。Async-Signal-Safe 的需要明确表示,比如fork () 和signal() 。

一个非可重入函数通常( 尽管不是所有情况下) 由它的外部接口和使用方法即可进行判断。例如:strtok() 是非可重入的,因为它在内部存储了被标记分割的字符串;ctime() 函数也是非可重入的,它返回一个指向静态数据的指针,而该静态数据在每次调用中都被覆盖重写。

一个线程安全的函数通过加锁的方式来实现多线程对共享数据的安全访问。线程安全这个概念,只与函数的内部实现有关,而不影响函数的外部接口。在 C 语言中,局部变量是在栈上分配的。因此,任何未使用静态数据或其他共享资源的函数都是线程安全的。

目前的 AIX 版本中,以下函数库是线程安全的:

  • C 标准函数库
  • 与BSD 兼容的函数库

使用全局变量( 的函数) 是非线程安全的。这样的信息应该以线程为单位进行存储,这样对数据的访问就可以串行化。一个线程可能会读取由另外一个线程生成的错误代码。在AIX 中,每个线程有独立的errno 变量。

最后让我们来构想一个线程安全但不可重入的函数:

假设函数func() 在执行过程中需要访问某个共享资源,因此为了实现线程安全,在使用该资源前加锁,在不需要资源解锁。

假设该函数在某次执行过程中,在已经获得资源锁之后,有异步信号发生,程序的执行流转交给对应的信号处理函数;再假设在该信号处理函数中也需要调用函数 func() ,那么func() 在这次执行中仍会在访问共享资源前试图获得资源锁,然而我们知道前一个func() 实例已然获得该锁,因此信号处理函数阻塞——另一方面,信号处理函数结束前被信号中断的线程是无法恢复执行的,当然也没有释放资源的机会,这样就出现了线程和信号处理函数之间的死锁局面。

因此,func() 尽管通过加锁的方式能保证线程安全,但是由于函数体对共享资源的访问,因此是非可重入。

原文作者:一起学嵌入式

 

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

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

相关文章

移动隔断墙的用途和空间布局,设计合适的结构,包括固定方式

移动隔断墙的用途: 1. 划分空间:移动隔断墙可以在需要时将一个大空间划分为多个小空间,以满足不同的使用需求。 2. 提供隐私:移动隔断墙可以为需要隐私的区域提供屏障,例如办公室中的会议室或私人办公室。 3. 增加灵活…

一文速学-让神经网络不再神秘,一天速学神经网络基础-输出层(四)

前言 思索了很久到底要不要出深度学习内容,毕竟在数学建模专栏里边的机器学习内容还有一大半算法没有更新,很多坑都没有填满,而且现在深度学习的文章和学习课程都十分的多,我考虑了很久决定还是得出神经网络系列文章,不…

【SpringSecurity】七、SpringSecurity集成thymeleaf

文章目录 1、thymeleaf2、依赖部分3、定义Controller4、创建静态页面5、WebSecurityConfigurerAdapter6、权限相关7、当用户没有某权限时,页面不展示该按钮 1、thymeleaf 查了下读音,leaf/li:f/,叶子,前面的单词发音和时间time一…

RV64函数调用流程分析

RV64函数调用流程分析 1 RV64 函数调用实例2 对应代码的分析2.1 main函数及其对应的汇编程序2.1.1 main的C代码实现2.1.2 main函数对应汇编及其分析2.1.3 执行完成之后栈的存放情况 2.2 test_fun_a函数及其对应的汇编程序2.2.1 test_fun_a函数的C实现2.2.2 test_fun_a函数对应汇…

8_分类算法-k近邻算法(KNN)

文章目录 1 KNN算法1.1 KNN算法原理1.2 KNN过程1.3 KNN三要素1.4 KNN分类预测规则1.5 KNN回归预测规则1.6 KNN算法实现方式(重点)1.7 k近邻算法优缺点 2 KD-Tree2.1 KD Tree构建方式2.2 KD Tree查找最近邻2.3 KNN参数说明 1 KNN算法 定义:如…

司徒理财:8.30黄金原油今日最新行情分析及操作策略

黄金走势分析      从日线形态来看,昨晚经历了快速拉升,价格成功稳定在关键的1924压力位之上,最高甚至触及了1938的高点。这表明市场开启了新一轮走势的空间。在当天的日内交易中,我们应特别关注1925一线作为支撑,…

opencv 案例实战02-停车场车牌识别SVM模型训练及验证

1. 整个识别的流程图: 2. 车牌定位中分割流程图: 三、车牌识别中字符分割流程图: 1.准备数据集 下载车牌相关字符样本用于训练和测试,本文使用14个汉字样本和34个数字跟字母样本,每个字符样本数为40,样本尺…

基于Django的博客管理系统

1、克隆仓库https://gitee.com/lylinux/DjangoBlog.git 若失效:https://gitee.com/usutdzxy/DjangoBlog.git 2、环境安装 pip install -Ur requirements.txt3、修改djangoblog/setting.py 修改数据库配置,其他的步骤就按照官方文档。 DATABASES {def…

Git管理本地代码

一、Git配置 当安装完 Git 应该做的第一件事就是设置你的用户名称与邮件地址。 这样做很重要,因为每一个 Git 的提交都会使用这些信息,并且它会写入到你的每一次提交中,不可更改 – global全局配置 通过 --global 选项可以设置全局配置信息 …

算法笔记:球树

1 KD树的问题 算法笔记:KD树_UQI-LIUWJ的博客-CSDN博客 在kd树中,导致性能下降的最核心因素是因为kd-tree中被分割的子空间是一个个的超方体,而求最近邻时使用的是欧式距离(超球)。超方体与超球体相交的可能性是极高…

【原创】jmeter并发测试计划

bankQPS 创建线程组 设置并发参数 HTTP请求GET 添加HTTP请求 GET请求 查看结果树 HTTP请求 POST 添加HTTP请求 参数必须设置头信息格式: 添加HTTP头信息 查看结果树 可以选择,仅查看错误日志 汇总报告

【CSS】简记CSS效果:通过transition(动画过渡属性)实现侧边栏目滑入滑出

需求 在资金明细的页面中&#xff0c;点击按钮时筛选区域从左侧滑出&#xff0c;完成筛选点击确认后调用接口完成数据查询&#xff0c;筛选区域滑入左侧&#xff1b; 基于微信小程序页面实现 wxml代码 <view><!-- 操作按钮 --><button type"primary&qu…

Go测试之.golden 文件

Go测试中的.golden 文件是干什么用的&#xff1f;请举例说明 在Go语言中&#xff0c;.golden文件通常用于测试中的黄金文件&#xff08;golden files&#xff09;。黄金文件是在测试期间记录预期输出结果的文件。测试用例运行时&#xff0c;黄金文件用于比较实际输出与预期输出…

react18+antd5.x(1):Notification组件的二次封装

antdesign已经给我们提供了很好的组件使用体验&#xff0c;但是我们还需要根据自己的项目业务进行更好的封装&#xff0c;减少我们的代码量&#xff0c;提升开发体验 效果展示 开起来和官网的使用没什么区别&#xff0c;但是我们在使用的时候&#xff0c;进行了二次封装&#…

Java“牵手”1688淘口令转换API接口数据,1688API接口申请指南

1688平台商品淘口令接口是开放平台提供的一种API接口&#xff0c;通过调用API接口&#xff0c;开发者可以获取1688商品的标题、价格、库存、商品快递费用&#xff0c;宝贝ID&#xff0c;发货地&#xff0c;区域ID&#xff0c;快递费用&#xff0c;月销量、总销量、库存、详情描…

ChatGPT AIGC 一个指令总结Python所有知识点

在ChatGPT中,直接输入一个指令就可以生成Python的所有知识点大纲。 非常实用的ChatGPT功能。 AIGC ChatGPT ,BI商业智能, 可视化Tableau, PowerBI, FineReport, 数据库Mysql Oracle, Office, Python ,ETL Excel 2021 实操,函数,图表,大屏可视化 案例实战 http://t.…

【LeetCode75】第四十题 最大层内元素和

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 这道题和LeetCode75的上一题大同小异&#xff0c;都是要我们对二叉树进行层序遍历。 那具体如何层序遍历我再上一题也详细介绍过了&#…

vue2 组件库之vetur提示

当我们开发完自定义UI组件库后&#xff0c;在项目中使用时&#xff0c;想要达到以下提示效果&#xff0c;组件提示与属性提示&#xff0c;有什么解决方案呢&#xff1a; 事实上&#xff0c;这是vetur的功能&#xff0c;原文如下&#xff1a; Component Data | Vetur If a pac…

软件测试实训系统建设方案

一 、系统概述 软件测试实训系统是软件开发过程中的一项重要测试活动&#xff0c;旨在验证不同软件模块或组件之间的集成与交互是否正常。综合测试确保各个模块按照设计要求正确地协同工作&#xff0c;以实现整个软件系统的功能和性能。以下是软件测试实训系统的一般流程和步骤…

基于Jenkins自动打包并部署docker、PHP环境,ansible部署-------从小白到大神之路之学习运维第86天

第四阶段提升 时 间&#xff1a;2023年8月23日 参加人&#xff1a;全班人员 内 容&#xff1a; 基于Jenkins部署docker、PHP环境 目录 一、环境部署 &#xff08;一&#xff09;实验环境&#xff0c;服务器设置 &#xff08;二&#xff09;所有主机关闭防火墙和selinu…