Linux线程(八)线程与信号之间的关系详解
本小节将对线程各方面的细节做深入讨论,其主要包括线程与信号之间牵扯的问题、线程与进程控制(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()用于打印信息,事实上这个函数是一个非异步信号安全函数,当然在一个实际的项目应用程序当中不能这么用,但是本书只是为了方便输出打印信息而已。
所以对于一个安全的信号处理函数来说,需要做到以下几点:
⚫ 首先确保信号处理函数本身的代码是可重入的,且只能调用异步信号安全函数;
⚫ 当主程序执行不安全函数或是去操作信号处理函数也可能会更新的全局数据结构时,要阻塞信号的传递。
相关文章:
Linux线程(八)线程与信号之间的关系详解
本小节将对线程各方面的细节做深入讨论,其主要包括线程与信号之间牵扯的问题、线程与进程控制(fork()、exec()、exit()等)之间的交互。之所以出现了这些问题,其原因在于线程技术的问世晚于信号、进程控制等,然而线程的…...
红帽操作系统Linux基本命令2( Linux 网络操作系统 06)
本文接着上篇Linux常用命令-1继续往后学习其他常用命令。 2.3 目录操作类命令 1.mkdir命令 mkdir命令用于创建一个目录。该命令的语法为: 上述目录名可以为相对路径,也可以为绝对路径。 mkdir命令的常用参数选项如下。 -p:在创…...
降重秘籍:如何利用ChatGPT将重复率从45%降至10%以下?
AIPaperGPT,论文写作神器~ https://www.aipapergpt.com/ 重复率高达45%?很多人一查论文的重复率,瞬间想“完了,这次真的要重写了”。但其实不用这么绝望!有了ChatGPT,降重真的没那么难。今天就教你几招&a…...
sql-labs靶场第九关测试报告
目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、寻找注入点 2、注入数据库 ①寻找注入方法 ②爆库,查看数据库名称 ③爆表,查看security库的所有表 ④爆列,查看users表的所有列 ⑤成功获取用户名…...
AI大模型的转折点,关注哪些机遇?
近期,人工智能领域取得又一突破性进展,OpenAI官方隆重推出了其最新力作——模型o1。这款模型的最大亮点在于,它融合了强化学习(RL)的训练方法,并在模型推理过程中采用了更为深入的内部思维链(ch…...
Stable Diffusion 常用大模型及其特点
SD(Stable Diffusion)的常用大模型及其特点可以归纳如下: 一、基础大模型 SD 1.x 特点:Stable Diffusion的早期版本,主要用于图像生成任务。这里的1.x表示1系列的主要版本,x是一个变量,表示具体…...
macos安装mongodb
文章目录 说明安装和配置安装mongodb配置PATH变量 验证日志及数据存放目录 mac启动和关闭mongodb后台启动失败问题mongodb-compass(GUI) 说明 Homebrew core 列表目前已经将 MongoDB 移除,不再为其提供支持。但是使用国内镜像的brew还是可以安装的!这里直接从官网下…...
IDEA基础开发配置以及和git的联动
1.1方向一:工具介绍 我今天要介绍的就是学习Java大部分情况下都会选择的一款工具-----IDEA,这个和我们熟悉的这个pycharm一样,都是属于这个Jetbrains公司的,虽然我对于这个并不是很了解,但是确实知道一点,…...
【前端】前端数据转化为后端数据
【前端】前端数据转化为后端数据 写在最前面格式化数组代码解释hasOwnProperty是什么? 🌈你好呀!我是 是Yu欸 🌌 2024每日百字篆刻时光,感谢你的陪伴与支持 ~ 🚀 欢迎一起踏上探险之旅,挖掘无限…...
LabVIEW回转支承间隙自动化检测系统
开发了一种基于LabVIEW软件的回转支承间隙检测系统,通过高精度传感器和数据采集卡,自动化、高效地测量回转支承的轴向间隙和径向间隙,提高了检测精度和生产质量。以下是对系统的详细描述与应用案例分析,希望能为有类似需求的开发者…...
数据结构-4.3.串的存储结构
一.串的顺序存储: 1.静态数组会由系统自动回收;动态数组需要手动回收; 2.优点:随机存取,可以立即找到所需的字符;缺点:插入和删除较麻烦; 3.串的顺序存储方案: 对于方…...
LeetCode讲解篇之34. 在排序数组中查找元素的第一个和最后一个位置
文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 这题让我们求目标值的左边界和右边界,我们可以采用二分查找搜索有序数组内大于等于目标值的最左边的下标 然后我们只需要在有序数组查找一下大于等于target的最左边下标 如果该下标越界或者下标对应…...
Linux自动化构建工具Make/Makefile
make是一个命令 makefile是一个文件 touch 创建并用vim打开makefile 写入依赖对象和依赖方法 mycode是目标文件 第二行数依赖方法 以tab键开头 make makefile原理 makefile中写的是依赖关系和依赖方法 clean英语清理文件 后不用加源文件。.PHONY定义clean是伪目标。 make只…...
自动驾驶系列—智能驾驶中的“换挡革命”:线控换挡技术详解
🌟🌟 欢迎来到我的技术小筑,一个专为技术探索者打造的交流空间。在这里,我们不仅分享代码的智慧,还探讨技术的深度与广度。无论您是资深开发者还是技术新手,这里都有一片属于您的天空。让我们在知识的海洋中…...
.mallox勒索病毒肆虐:了解它的危害与防范措施
引言 在数字化时代,随着信息技术的飞速发展,网络空间的安全问题日益凸显。其中,勒索病毒作为一种新型的恶意软件,以其独特的加密方式和勒索行为,成为了网络安全领域的一大威胁。近年来,.mallox勒索病毒以其…...
如何使用ssm实现基于Java的民宿预订管理系统的设计与实现
TOC ssm773基于Java的民宿预订管理系统的设计与实现jsp 绪论 1.1课题研究背景意义 随着科技的发展,计算机的应用,人们的生活方方面面都和互联网密不可分。计算机的普及使得人们的生活更加方便快捷,网络也遍及到我们生活的每个角落&#x…...
光伏组件模型模板在SketchUp中如何完成成模数化设计?
选中模板组件,点击左侧工具栏中移动工具,按住Ctrl再依次点击组件起始点和终点,完成组件复制,输入需要复制的组件数量(*n)后回车,即可完成模数化设计。 选中模组的多块模型右键进行创建组件或群…...
常用组件详解(十):保存与加载模型、检查点机制的使用
文章目录 1.保存、加载模型2.torch.nn.Module.state_dict()2.1基本使用2.2保存和加载状态字典 3.创建Checkpoint3.1基本使用3.2完整案例 1.保存、加载模型 torch.save()用于保存一个序列化对象到磁盘上,该序列化对象可以是任何类型的对象,包括模型、张量…...
基于SpringBoot+Vue+MySQL的在线学习交流平台
系统展示 用户前台界面 管理员后台界面 系统背景 随着互联网技术的飞速发展,在线学习已成为现代教育的重要组成部分。传统的面对面教学方式已无法满足广大学习者的需求,特别是在时间、地点上受限的学习者。因此,构建一个基于SpringBoot、Vue.…...
前端开发在AI时代如何保持核心竞争力
随着人工智能(AI)技术的迅猛发展,前端开发领域正经历着前所未有的变革。AI辅助开发工具、自动化测试框架、智能代码补全等技术的出现,极大地提高了开发效率,同时也对前端开发人员的技能和角色提出了新的要求。在这个背…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
