Linux线程(八)线程与信号之间的关系详解

news2024/10/6 10:38:07

本小节将对线程各方面的细节做深入讨论,其主要包括线程与信号之间牵扯的问题、线程与进程控制(fork()、exec()、exit()等)之间的交互。之所以出现了这些问题,其原因在于线程技术的问世晚于信号、进程控制等,然而线程的出现必须要能够兼容现有的这些技术,不能出现冲突,这就使得线程与它们之间的结合使用将会变得比较复杂!当中所涉及到的细节问题也会比较多。

线程与信号

Linux 信号模型是基于进程模型而设计的,信号的问世远早于线程;自然而然,线程与信号之间就会存在一些冲突,其主要原因在于:信号既要能够在传统的单线程进程中保持它原有的功能、特性,与此同时,又需要设计出能够适用于多线程环境的新特性!

信号与多线程模型之间结合使用,将会变得比较复杂,需要考虑的问题将会更多,在实际应用开发当中,如果能够避免我们应尽量避免此类事情的发生;但尽管如此,事实上,信号与多线程模型确实存在于实际的应用开发项目中。本小节我们就来讨论信号与线程之间牵扯的问题。

1. 信号如何映射到线程

信号模型在一些方面是属于进程层面(由进程中的所有线程线程共享)的,而在另一些方面是属于单个线程层面的,以下对其进行汇总:

  • 信号的系统默认行为是属于进程层面。8.3 小节介绍到,每一个信号都有其对应的系统默认动作,当进程中的任一线程收到任何一个未经处理(忽略或捕获)的信号时,会执行该信号的默认操作,信号的默认操作通常是停止或终止进程。

  • 信号处理函数属于进程层面。进程中的所有线程共享程序中所注册的信号处理函数;

  • 信号的发送既可针对整个进程,也可针对某个特定的线程。在满足以下三个条件中的任意一个时,信号的发送针对的是某个线程:

    • 产生了硬件异常相关信号,譬如 SIGBUS、SIGFPE、SIGILL 和 SIGSEGV 信号;这些硬件异常信号在某个线程执行指令的过程中产生,也就是说这些硬件异常信号是由某个线程所引起;那么在这种情况下,系统会将信号发送给该线程。

    • 当线程试图对已断开的管道进行写操作时所产生的 SIGPIPE 信号;

    • 由函数 pthread_kill()或 pthread_sigqueue()所发出的信号,稍后介绍这两个函数;这些函数允许线程向同一进程下的其它线程发送一个指定的信号。

      除了以上提到的三种情况之外,其它机制产生的信号均属于进程层面,譬如其它进程调用 kill()或sigqueue()所发送的信号;用户在终端按下 Ctrl+C、Ctrl+\、Ctrl+Z 向前台进程发送的 SIGINT、SIGQUIT 以及 SIGTSTP 信号。

  • 当一个多线程进程接收到一个信号时,且该信号绑定了信号处理函数时,内核会任选一个线程来接收这个信号,意味着由该线程接收信号并调用信号处理函数对其进行处理,并不是每个线程都会接收到该信号并调用信号处理函数;这种行为与信号的原始语义是保持一致的,让进程对单个信号接收重复处理多次是没有意义的。

  • 信号掩码其实是属于线程层面的,也就是说信号掩码是针对每个线程而言。8.9 小节向大家介绍了信号掩码的概念,并介绍了 sigprocmask()函数,通过 sigprocmask()可以设置进程的信号掩码,事实上,信号掩码是并不是针对整个进程来说,而是针对线程,对于一个多线程应用程序来说,并不存在一个作用于整个进程范围内的信号掩码(管理进程中的所有线程);那么在多线程环境下,各个线程可以调用 pthread_sigmask()函数来设置它们各自的信号掩码,譬如设置线程可以接收哪些信号、不接收哪些信号,各线程可独立阻止或放行各种信号。

  • 针对整个进程所挂起的信号,以及针对每个线程所挂起的信号,内核都会分别进行维护、记录。

8.11.1 小节介绍到,调用 sigpending()会返回进程中所有被挂起的信号,事实上,sigpending()会返

回针对整个进程所挂起的信号,以及针对每个线程所挂起的信号的并集。

2. 线程的信号掩码

对于一个单线程程序来说,使用 sigprocmask()函数设置进程的信号掩码,在多线程环境下,使用pthread_sigmask()函数来设置各个线程的信号掩码,其函数原型如下所示:

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

pthread_sigmask()函数就像 sigprocmask() 一 样 ,不 同 之 处 在 于 它 在 多线 程 程 序 中 使用,所以pthread_sigmask()函数的用法与 sigprocmask()完全一样,这里就不再重述!

每个刚创建的线程,会从其创建者处继承信号掩码,这个新的线程可以调用 pthread_sigmask()函数来改变它的信号掩码。

3. 向线程发送信号

调用 kill()或 sigqueue()所发送的信号都是针对整个进程来说的,它属于进程层面,具体该目标进程中的哪一个线程会去处理信号,由内核进行选择。事实上,在多线程程序中,可以通过 pthread_kill()向同一进程中的某个指定线程发送信号,其函数原型如下所示:

#include <signal.h>
int pthread_kill(pthread_t thread, int sig);

参数 thread,也就是线程 ID,用于指定同一进程中的某个线程,调用 pthread_kill()将向参数 thread 指定的线程发送信号 sig。

如果参数 sig 为 0,则不发送信号,但仍会执行错误检查。函数调用成功返回 0,失败将返回一个错误编号,不会发送信号。

除了 pthread_kill()函数外,还可以调用 pthread_sigqueue()函数;pthread_sigqueue()函数执行与 sigqueue类似的任务,但它不是向进程发送信号,而是向同一进程中的某个指定的线程发送信号。其函数原型如下所示:

#include <signal.h>
#include <pthread.h>
int pthread_sigqueue(pthread_t thread, int sig, const union sigval value);

参数 thread 为线程 ID,指定接收信号的目标线程(目标线程与调用 pthread_sigqueue()函数的线程是属于同一个进程),参数 sig 指定要发送的信号,参数 value 指定伴随数据,与 sigqueue()函数中的 value 参数意义相同。

pthread_sigqueue()函数的参数的含义与 sigqueue()函数中对应参数相同意义相同。它俩的唯一区别在于,sigqueue()函数发送的信号针对的是整个进程,而 pthread_sigqueue()函数发送的信号针对的是某个线程。

4. 异步信号安全函数

应用程序中涉及信号处理函数时必须要非常小心,因为信号处理函数可能会在程序执行的任意时间点被调用,从而打断主程序。接下来介绍一个概念---异步信号安全函数(async-signal-safe function)。

前面介绍了线程安全函数,作为线程安全函数可以被多个线程同时调用,每次都能得到预期的结果,但是这里有前提条件,那就是没有信号处理函数参与;换句话说,线程安全函数不能在信号处理函数中被调用,否则就不能保证它一定是安全的。所以就出现了异步信号安全函数。

异步信号安全函数指的是可以在信号处理函数中可以被安全调用的线程安全函数,所以它比线程安全函数的要求更为严格!可重入函数满足这个要求,所以可重入函数一定是异步信号安全函数。而线程安全函函数的要求更为严格!可重入函数满足这个要求,所以可重入函数一定是异步信号安全函数。而线程安全函数则不一定是异步信号安全函数了。

举个例子,下面列举出来的一个函数是线程安全函数:

static pthread_mutex_t mutex;

static int glob = 0;static void func(int loops)

{

int local;

int j;

for (j = 0; j < loops; j++) {

pthread_mutex_lock(&mutex); //互斥锁上锁

local = glob;

local++;

glob = local;

pthread_mutex_unlock(&mutex);//互斥锁解锁

}

}

该函数虽然对全局变量进行读写操作,但是在访问全局变量时进行了加锁,避免了引发竞争冒险;它是一个线程安全函数,假设线程 1 正在执行函数 func,刚刚获得锁(也就是刚刚对互斥锁上锁),而这时进程收到信号,并分派给线程 1 处理,线程 1 接着跳转去执行信号处理函数,不巧的是,信号处理函数中也调用了 func()函数,同样它也去获取锁,由于此时锁处于锁住状态,所以信号处理函数中调用 func()获取锁将会陷入休眠、等待锁的释放。这时线程 1 就会陷入死锁状态,线程 1 无法执行,锁无法释放;如果其它线程也调用 func(),那它们也会陷入休眠、如此将会导致整个程序陷入死锁!

通过上面的分析,可知,涉及到信号处理函数时要非常小心。之所以涉及到信号处理函数时会出现安全问题,笔者认为主要原因在以下两个方面:

⚫ 信号是异步的,信号可能会在任何时间点中断主程序的运行,跳转到信号处理函数处执行,从而形成一个新的执行流(信号处理函数执行流)。

⚫ 信号处理函数执行流与线程执行流存在一些区别,信号处理函数所产生的执行流是由执行信号处理函数的线程所触发的,它俩是在同一个线程中,属于同一个线程执行流。

在异步信号安全函数、可重入函数以及线程安全函数三者中,可重入函数的要求是最严格的,所以通常会说可重入函数一定是线程安全函数、也一定是异步信号安全函数。通常对于上面所列举出的线程安全函数 func(),如果想将其实现为异步信号安全函数,可以在获取锁之前通过设置信号掩码,在锁期间禁止接收该信号,也就是说将函数实现为不可被信号中断。经过这样处理之后,函数 func()就是一个异步信号安全函数了。

Linux 标准 C 库和系统调用中以下函数被认为是异步信号安全函数:

_Exit()

_exit()

abort()

accept()

access()

aio_error()

aio_return()

aio_suspend()

alarm()

bind()

cfgetispeed()

cfgetospeed()

cfsetispeed()

cfsetospeed()

chdir()

chmod()

chown()

clock_gettime()

close()

connect()

creat()

dup()

dup2()

execle()

execve()

fchmod()

fchown()

fcntl()

fdatasync()

fork()

execl()

fstat()

fsync()

ftruncate()

getegid()

geteuid()

getgid()

getgroups()

getpeername()

getpgrp()getpid()

getppid()

getsockname()

getsockopt()

getuid()

kill()

link()

listen()

lseek()

lstat()

mkdir()

mkfifo()

open()

execv()

pause()

pipe()

poll()

posix_trace_event()

pselect()

raise()

read()

readlink()

recv()

recvfrom()

recvmsg()

rename()

rmdir()

select()

sem_post()

send()

sendmsg()

sendto()

setgid()

setpgid()

setsid()

setsockopt()

setuid()

shutdown()

sigaction()

sigaddset()

sigdelset()

sigemptyset()

sigfillset()

sigismember()

signal()

sigpause()

sigpending()

sigprocmask()

sigqueue()

sigset()

sigsuspend()

sleep()

sockatmark()

socket()

socketpair()

stat()

symlink()

faccessat()

tcdrain()

tcflow()

tcflush()

tcgetattr()

tcgetpgrp()

tcsendbreak()

tcsetattr()

tcsetpgrp()

time()

timer_getoverrun()

timer_gettime()

timer_settime()

times()

umask()

uname()

unlink()

utime()

wait()

waitpid()

write()

fchmodat()

fchownat()

fexecve()

fstatat()

futimens()

linkat()

mkdirat()

mkfifoat()

mknod()

mknodat()

openat()

readlinkat()

renameat()

symlinkat()

unlinkat()

utimensat()

utimes()

fchdir()

pthread_kill()

pthread_self()

pthread_sigmask()

上所列举出的这些函数被认为是异步信号安全函数,可以通过 man 手册查询,执行命令"man 7 signal",

如下所示:

异步信号安全函数大家可以通过对比 man 手册查询到的这些异步信号安全函数,来确定自己调用的库函数或系统调用是

不是异步信号安全函数,这里需要说,在本书的示例代码中,并没有完全按照安全性要求,在信号处理函数中使用异步信号安全函数,譬如在本书中的示例代码中,信号处理函数中调用了 printf()用于打印信息,事实上这个函数是一个非异步信号安全函数,当然在一个实际的项目应用程序当中不能这么用,但是本书只是为了方便输出打印信息而已。

所以对于一个安全的信号处理函数来说,需要做到以下几点:

⚫ 首先确保信号处理函数本身的代码是可重入的,且只能调用异步信号安全函数;

⚫ 当主程序执行不安全函数或是去操作信号处理函数也可能会更新的全局数据结构时,要阻塞信号的传递。

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

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

相关文章

IEC104规约的秘密之七----配置参数t1,t2,t3

104通讯前需要配置通讯参数&#xff0c;一般有如下参数&#xff1a; IP地址&#xff0c;端口号&#xff0c;k&#xff0c;w&#xff0c;t1&#xff0c;t2&#xff0c;t3&#xff0c;公共地址&#xff0c;遥控超时参数&#xff0c;104主规约还有一个t0参数。 本次只讲解t1&#…

2-113 基于matlab的图像的配准融合

基于matlab的图像的配准融合&#xff0c;采用互信息配准&#xff0c;PV差值&#xff0c;powell算法&#xff0c;小波变换的图像融合算法。在GUI界面输入两幅图像&#xff0c;完成图像的配准融合。融合图像要求像素 一样。程序代码已经有详细的注释。程序已调通&#xff0c;可直…

对操作系统中的用户态和内核态的理解

目录 引言 为什么要有用户态和内核态&#xff1f;只有一个内核态不行么&#xff1f; 一、用户态&#xff08;User Mode&#xff09; 定义 特点 应用 二、内核态&#xff08;Kernel Mode&#xff09; 定义 特点 应用 三、用户态与内核态的联系和区别 四、用户态和内…

通过dem2terrain生成MapboxGL地形服务

概述 MapboxGL在2的版本之后通过地形服务开始支持三维的展示了&#xff0c;之前也有文章“mapboxGL2中Terrain的离线化应用”对该服务进行过说明与分析。前些天在翻公众号的时候翻到了dem2terrain可以生成地形服务&#xff0c;同时做了一些优化&#xff0c;今天就给大家分享一…

2024全面升级!从零开始的大模型开发学习路线图——精通之路

第一阶段&#xff1a;基础理论入门 目标&#xff1a;了解大模型的基本概念和背景。 内容&#xff1a; 人工智能演进与大模型兴起。 大模型定义及通用人工智能定义。 GPT模型的发展历程。 第二阶段&#xff1a;核心技术解析 目标&#xff1a;深入学习大模型的关键技术和工…

多文件并发多线程MD5工具(相对快速的MD5一批文件),适配自定义MD5 Hash I/O缓存。

自己写的多文件 MD5校验工具&#xff0c;一个文件开一个线程&#xff0c;有最大I/O 缓存设置&#xff0c;兼容读写MD5后缀文件。 共计91个文件&#xff0c;合计180G左右 12分钟左右&#xff0c;UI基本卡废&#xff0c;但程序没蹦&#xff0c;属于正常。 卡的原因是基本是用 I/O…

每日OJ题_牛客_牛牛冲钻五_模拟_C++_Java

目录 牛客_牛牛冲钻五_模拟 题目解析 C代码 Java代码 牛客_牛牛冲钻五_模拟 牛牛冲钻五 (nowcoder.com) 描述&#xff1a; 牛牛最近在玩炉石传说&#xff0c;这是一款一对一对战的卡牌游戏&#xff0c;牛牛打算努力冲上钻五分段&#xff0c;获得丰厚的天梯奖励。…

力扣 中等 78.子集

文章目录 题目介绍解法解法一&#xff1a;解法二&#xff1a; 题目介绍 解法 有两种解法&#xff0c;对于计算[1,2]的子集问题&#xff1a; 解法一&#xff1a; 站在输入的角度思考&#xff1a;每个元素都可以选/不选 代码如下&#xff1a; class Solution {List<List&…

ReGCL Rethinking Message Passingin Graph Contrastive Learning

AAAI24 推荐指数&#xff1a; #paper/⭐ 总体说&#xff1a;利用梯度对对比正负样本加权的。个人觉得和与正负样本加权没有区别&#xff0c;读完之后不想做笔记了。

成都睿明智科技有限公司真实可靠吗?

在这个日新月异的电商时代&#xff0c;抖音作为短视频与直播电商的佼佼者&#xff0c;正以前所未有的速度重塑着消费者的购物习惯。而在这片充满机遇与挑战的蓝海中&#xff0c;成都睿明智科技有限公司以其独到的眼光和专业的服务&#xff0c;成为了众多商家信赖的合作伙伴。今…

RAG再总结之如何使大模型更好使用外部数据:四个不同层级及查询-文档对齐策略

我们来看看RAG进展。《Retrieval Augmented Generation (RAG) and Beyond: A Comprehensive Survey on How to Make your LLMs use External Data More Wisely》(https://arxiv.org/abs/2409.14924)&#xff0c;主要讨论了如何使大型语言模型&#xff08;LLMs&#xff09;更明智…

【Canvas与标志】白座红芯辐射标志

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>白座红芯辐射标志</title><style type"text/css"…

23.1 k8s监控中标签relabel的应用和原理

本节重点介绍 : relabel的源码在 7.7节做过详细的解读强大的relabel能力 在k8s中的应用 应用1&#xff1a; labelmap 在采集cadvisor指标时 对服务发现标签key名字截取应用2&#xff1a; 采集pod自定义指标中replace 和 keep的应用应用3&#xff1a; k8s服务组件采集时的endpo…

资产管理系统建设方案,资产盘点,rfid,出入库,消耗品管理,系统方案,系统源码(word原件)

固定资产管理系统需求要点&#xff1a; 1. 实现公司内部固定资产管理全生命周期管理&#xff0c;包括资产采购、资产入库、资产领用、资产借用、资产归还、资产报废、资产维修、资产调拨等全过程管理。 2. 可实现集团内部固定资产盘点管理&#xff0c;包括盘点计划、盘点查询等…

【深度解析】从电视广播到互联网接入:通信卫星如何改变我们的世界?

1.通信卫星的发展历程和现状 1.1 早期发展 通信卫星的发展历程可以追溯到20世纪50年代末期和60年代初期。 1957年10月4日&#xff0c;苏联成功发射了第一颗人造卫星“斯普特尼克1号”&#xff0c;标志着人类进入了太空时代&#xff0c;也推动了通信卫星的发展。 1958年12月18…

.NET 一款支持天蝎的免杀WebShell

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

【ubuntu】【VirtualBox】VirtualBox无法加载USB移动设备的解决方法(支持U盘启动盘)

TOC 提示&#xff1a;测试可用 一、安装VirtualBox VirtualBox-7.1.2-164945-Win。 下载路径。 Download_Old_Builds_7_0 – Oracle VirtualBox 二、安装Oracle_VirtualBox_Extension_Pack-7.1.2 下载路径见上文。 三、安装增强功能 四、挂载USB 4.1 设置USB协议 4.2 挂…

深度学习基础—目标检测算法

目录 1.滑动窗口算法 2.滑动窗口的卷积实现 &#xff08;1&#xff09;1*1卷积的作用 &#xff08;2&#xff09;全连接层转化为卷积层 &#xff08;3&#xff09;在卷积层上实现滑动窗口 3.Bounding Box预测&#xff08;YOLO算法&#xff09; 1.滑动窗口算法 假如要构建一…

YOLOv10改进 | 融合篇,YOLOv10改进主干网络为GhostNetV3+MLCA注意机制

摘要 GhostNetV3 引入了多分支重参数化机制,通过在卷积层中添加额外的平行分支来改善性能。这些分支在训练过程中提供更多的表征能力,最终通过将多个分支重组为一个卷积层来实现推理时的高效性。通过添加配备 BatchNorm 层的重复分支将再参数化引入紧凑型模型。因此作为YOLO…

2024年优化苹果免签封装APP H5站打包苹果APP 绿标-永不掉千(永久使用)

大家都知道苹果手机做APP签名很贵&#xff0c; 这个程序就是吧您的H5网站 一切网页可以打开的&#xff0c;封装成app 苹果手机上可以直接安装使用 永久可用&#xff01;&#xff01;很简单&#xff0c;可以看视频教程来安装使用&#xff0c; 视频教程&#xff1a; https://ww…