嵌入式Linux:线程中信号处理 - 详解

news/2025/11/9 11:53:32/文章来源:https://www.cnblogs.com/yangykaifa/p/19204004

目录

1、信号与多线程结合的复杂性

2、信号在多线程环境中的映射与处理

2.1、进程级信号

2.2、线程级信号

3、信号处理函数与多线程环境

4、信号掩码与线程独立性

5、异步信号安全函数


信号最早是为了单进程的环境而设计的,用于在进程中捕捉各种事件,比如硬件异常、终止请求等。每个信号都有对应的处理动作(默认或自定义),例如:

  • SIGTERM 用于请求进程终止;
  • SIGINT 是通过键盘中断(Ctrl+C)触发的信号;
  • SIGSEGV 则用于处理段错误(非法内存访问)。

这些信号的处理方式原本是进程级别的,也就是一个信号影响整个进程。而随着多线程模型的引入,进程内部可以有多个线程同时运行,信号处理的复杂性也大大增加。

1、信号与多线程结合的复杂性

多线程应用程序不仅需要继承原有的信号处理特性,还要保证线程之间的信号处理逻辑不会冲突。

在传统的单进程模型中,信号被设计为能够中断当前的执行流(如捕捉异常或处理终止请求),但在多线程环境下,多个线程并行运行,同一进程的信号可以由任意线程接收并处理。因此,这种多线程与信号处理的结合引发了以下问题:

  • 信号由哪个线程处理:当一个信号发给进程时,内核必须决定哪个线程来处理信号,这可能会影响应用程序的行为。
  • 信号处理与线程安全问题:信号处理函数可能在任意时刻被调用,打断当前线程的执行流,如果线程正在操作共享资源,可能引发竞争条件或不一致性。
  • 信号屏蔽(masking):信号掩码决定了线程是否能够接收到特定信号,而每个线程可以有独立的信号掩码设置,这样的设计带来了更多的灵活性,但也增加了复杂性。

2、信号在多线程环境中的映射与处理

信号的映射方式取决于其触发源以及信号的类型。我们可以将信号的映射机制分为进程层面和线程层面。

2.1、进程级信号

大多数信号是针对整个进程的。例如通过 kill() 发送的信号,或者来自操作系统的控制台中断信号。这类信号发送给进程,默认情况下,内核会从进程的所有线程中随机选择一个线程来处理信号。

kill(getpid(), SIGINT); // 给当前进程发送 SIGINT 信号

当进程中的某个线程处理这个信号时,其他线程的执行不会受到影响。内核负责决定哪个线程接收到信号,通常是未屏蔽该信号的线程。

2.2、线程级信号

某些信号只能由特定线程处理。例如,当线程遇到异常情况时(如段错误 SIGSEGV,浮点异常 SIGFPE),信号只会发送给引发该错误的线程。

以下例子中,访问空指针将触发段错误,SIGSEGV 信号只会发送给导致错误的线程。

void* faulty_thread(void* arg) {int* invalid_ptr = NULL;*invalid_ptr = 42;  // 这将触发 SIGSEGVreturn NULL;
}

在使用 kill()sigqueue() 发送信号时,信号是针对整个进程的,内核会选择进程中的某个线程来处理信号。而在多线程程序中,可以使用 pthread_kill() 向同一进程中的指定线程发送信号,具体如下:

#include 
int pthread_kill(pthread_t thread, int sig);

参数说明:

  • thread:线程 ID,指定要接收信号的线程。
  • sig:信号编号,指定要发送的信号。

如果 sig 为 0,pthread_kill() 不会发送信号,但会执行错误检查。成功时返回 0,失败时返回错误编号。

除了 pthread_kill(),还可以使用 pthread_sigqueue() 发送信号。该函数与 sigqueue() 类似,但它是将信号发送给指定的线程,而不是整个进程:

#include 
#include 
int pthread_sigqueue(pthread_t thread, int sig, const union sigval value);

参数说明:

  • thread:线程 ID,指定接收信号的线程。
  • sig:要发送的信号。
  • value:伴随数据,类型为 union sigval,与 sigqueue()value 参数类似。

3、信号处理函数与多线程环境

无论是单线程还是多线程,信号处理函数在进程中是全局的。也就是说,注册的信号处理函数可能会被进程中的任何一个线程调用。

以下示例当用户按下 Ctrl+C 发送 SIGINT 信号时,signal_handler 会被调用。

#include 
#include 
#include 
void signal_handler(int sig) {printf("Caught signal %d\n", sig);
}
int main() {signal(SIGINT, signal_handler); // 注册信号处理函数while (1) {printf("Running...\n");sleep(1);}return 0;
}

在多线程环境下,多个线程可能会同时触发信号。假设我们在每个线程中都执行某种操作,信号处理函数可能会在任意线程中执行。信号处理函数必须是线程安全的,避免数据竞争或死锁等问题。

以下示例按下 Ctrl+C 时,任意线程都有可能捕获 SIGINT 信号。信号处理函数必须能在不同线程中正确处理信号事件。

#include 
#include 
#include 
#include 
void signal_handler(int sig) {printf("Thread %ld caught signal %d\n", pthread_self(), sig);
}
void* thread_function(void* arg) {while (1) {printf("Thread %ld is running...\n", pthread_self());sleep(1);}
}
int main() {pthread_t thread1, thread2;signal(SIGINT, signal_handler);  // 所有线程共享的信号处理函数pthread_create(&thread1, NULL, thread_function, NULL);pthread_create(&thread2, NULL, thread_function, NULL);pthread_join(thread1, NULL);pthread_join(thread2, NULL);return 0;
}

4、信号掩码与线程独立性

在多线程环境中,每个线程可以有自己独立的信号掩码。通过信号掩码,线程可以选择是否接收某些信号。这为线程的信号处理提供了极大的灵活性。

pthread_sigmask() 函数用于设置线程的信号掩码,控制哪些信号应该被阻止或接收。

#include 
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);

参数说明:

how:指定如何修改当前线程的信号屏蔽字。它的取值有以下几种:

  • SIG_BLOCK:将 set 中的信号添加到当前线程的信号屏蔽字中,阻塞这些信号。
  • SIG_UNBLOCK:将 set 中的信号从当前线程的信号屏蔽字中移除,解除阻塞这些信号。
  • SIG_SETMASK:将当前线程的信号屏蔽字设置为 set 中的信号集合,替换原有的阻塞信号。

set:指向 sigset_t 类型的信号集,指定要阻塞或解除阻塞的信号集合。

  • howSIG_SETMASK 时,set 中的信号会替换当前屏蔽字;当 howSIG_BLOCKSIG_UNBLOCK 时,set 中的信号将被添加到或从屏蔽字中移除。

oldset:如果不为 NULL,此参数将用来存储调用前的信号屏蔽字。这允许程序在修改信号屏蔽字后恢复原来的状态。

返回值:

  • 成功时,返回 0
  • 失败时,返回错误码,通常为 errno 中定义的错误。

以下示例中,线程会屏蔽 SIGINT 信号,即使按下 Ctrl+C,该线程也不会处理 SIGINT 信号。

#include 
#include 
#include 
#include 
void* thread_function(void* arg) {sigset_t set;sigemptyset(&set);sigaddset(&set, SIGINT);  // 屏蔽 SIGINT 信号pthread_sigmask(SIG_BLOCK, &set, NULL);while (1) {printf("Thread %ld is running...\n", pthread_self());sleep(1);}
}
int main() {pthread_t thread;pthread_create(&thread, NULL, thread_function, NULL);pthread_join(thread, NULL);return 0;
}

5、异步信号安全函数

异步信号安全函数是指那些可以在信号处理程序中调用的函数。这些函数必须是可重入的,能够在信号处理期间中断正常执行流程而不会引发不一致行为。

Linux 提供了一组异步信号安全的系统调用,例如:

上表列出的这些函数被认为是异步信号安全函数。你可以通过执行命令 man 7 signal 来查阅相关文档,获取更多信息:

man 7 signal

这些函数可以在信号处理函数中安全调用。反之,像 printf()malloc() 等函数并不安全,因为它们可能涉及内部的缓冲机制或全局状态,容易在信号处理中引发竞争条件。

通过理解信号在多线程环境中的复杂性和设计局限性,开发者可以更好地编写安全可靠的多线程程序。

  • 避免在多线程程序中使用全局信号处理函数:因为信号处理函数是全局共享的,它很容易引发线程之间的竞争。尽可能将信号处理逻辑与线程独立运行的机制分离。

  • 合理使用信号掩码:通过为不同线程设置独立的信号掩码,开发者可以避免不必要的信号干扰。尤其是在执行关键任务时,可以临时屏蔽所有不相关的信号。

  • 使用异步信号安全函数:在编写信号处理函数时,尽量只调用那些已知的异步信号安全函数,如 write()_exit() 等,避免使用 malloc()free()printf() 这样的非异步信号安全函数。

  • 信号与线程同步:避免在信号处理函数中直接操作复杂的数据结构或进行同步操作(如加锁),因为信号处理函数可能随时中断当前线程,导致死锁或数据不一致。

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

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

相关文章

2025年知名的出口蒸笼厂家最新实力排行

2025年知名出口蒸笼厂家最新实力排行:数据解读与采购指南 行业背景与市场趋势 全球竹制蒸笼市场规模在2024年达到23.6亿美元(据Global Market Insights数据),预计2025年将保持6.8%的年增长率。中国作为全球最大…

2025年口碑好的酚醛胶厂家推荐及采购指南

2025年口碑好的酚醛胶厂家推荐及采购指南行业背景与市场趋势酚醛树脂胶粘剂作为重要的工业材料,在木材加工、建筑装饰、汽车制造等领域应用广泛。根据中国胶粘剂工业协会最新统计数据显示,2024年我国酚醛胶市场规模已…

逆向基础--C++数据类型 (02)

逆向基础--C++数据类型 (02)一.介绍使用编程语言进行编程时,需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着,当您创建一个变量时,就会在内存中保留一些空间。变量的名称可以由字母…

2025年口碑好的pe实壁缠绕管设备厂家最新热销排行

2025年口碑好的PE实壁缠绕管设备厂家最新热销排行行业背景与市场趋势随着我国城镇化进程加速推进和基础设施建设持续投入,PE实壁缠绕管作为市政排水、工业排污等领域的重要材料,市场需求呈现稳定增长态势。根据中国塑…

2025年知名的甜酒酿用户好评厂家排行

2025年知名的甜酒酿用户好评厂家排行行业背景与市场趋势甜酒酿作为中国传统发酵食品的代表之一,近年来随着消费者对健康食品需求的增长,市场规模持续扩大。据中国食品工业协会最新数据显示,2024年我国甜酒酿市场规模…

ubuntu: apt安装redis

一,安装 # apt install redis 查看状态: # systemctl status redis.service ● redis-server.service - Advanced key-value storeLoaded: loaded (/usr/lib/systemd/system/redis-server.service; enabled; preset:…

2025年口碑好的PPR给水管设备厂家最新热销排行

2025年口碑好的PPR给水管设备厂家最新热销排行行业背景与市场趋势PPR给水管作为建筑给排水系统中的核心材料,近年来随着我国城镇化进程加速和建筑品质提升需求增长,市场规模持续扩大。据中国塑料加工工业协会最新数据…

高性能场景推荐使用PostgreSQL - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年知名的家具三折轨最新TOP厂家排名

2025年知名的家具三折轨最新TOP厂家排名行业背景与市场趋势随着中国家居制造业的持续升级和消费者对家具品质要求的不断提高,家具五金配件行业迎来了快速发展期。据中国五金制品协会最新数据显示,2024年中国家具五金…

“最小删除步数”错题复盘

“最小删除步数”错题复盘在刷“最小删除步数使两个字符串相等”这道题时,我从“思路跑偏”到“实现全错”,再到“逐步修正”,踩了很多典型坑。这道题看似是简单的字符串操作,实则考察对动态规划(LCS)的理解和题…

两个数组的dp问题 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

k8s级联删除策略

k8s级联删除策略前台级联删除:pod等从属对象删除之后,再删除所有者对象(比较慢)删除顺序:删除pod a -> 删除rs a-> 删除deploy a命令:kubectl delete deploy a --cascade=foreground 后台级联删除:删除所…

2025年口碑好的异形工业铝型材厂家最新推荐排行榜

2025年口碑好的异形工业铝型材厂家最新推荐排行榜行业背景与市场趋势工业铝型材作为现代工业制造的重要基础材料,在建筑、交通、电子、机械等领域应用广泛。根据中国有色金属工业协会最新数据显示,2024年中国铝型材市…

qemu+linux kernel+busybox搭建linux内核学习环境

前言:里面的知识很多理解的都不到位,不保证正确性,等后期学习好,再来修改。 前提:架构x86_64 一、准备工作sudo apt update sudo apt install build-essential qemu-system-x86 gdb git flex bison libncurses5-d…

2025年正规的电加热导热油炉厂家选购指南与推荐

2025年正规的电加热导热油炉厂家选购指南与推荐行业背景与市场趋势电加热导热油炉作为工业加热领域的关键设备,近年来随着环保政策趋严和能效标准提升,市场需求持续增长。根据中国锅炉行业协会2024年发布的《工业加热…

数据库原理与设计 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年口碑好的湘潭水泥支撑厂家推荐及选择参考

2025年口碑好的湘潭水泥支撑厂家推荐及选择参考行业背景与市场趋势水泥支撑行业作为建筑基础材料领域的重要组成部分,近年来随着我国基础设施建设的持续投入而保持稳定增长。据中国建筑材料联合会最新数据显示,2024年…

2025年正规的仪器计量校准厂家最新用户好评榜

2025年正规的仪器计量校准厂家最新用户好评榜行业背景与市场趋势仪器计量校准行业作为现代工业质量保障体系的重要支撑,近年来随着"中国制造2025"战略的深入推进和制造业转型升级的需求,市场规模持续扩大。…

AIGC|AI优化企业新榜单与选择指南 - 二当家

AI优化企业:2025年数字营销生态的变革者 解码AI优化企业如何重构流量生态与商业价值 AI优化企业——从技术穿透到全域增长的智能引擎 在2025年的数字营销战场,AI优化已从技术工具升维为商业生态重构的核心引擎。当传…

实用指南:# 深入理解Linux内核与用户态通信:Netlink机制实战

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …