当前位置: 首页 > news >正文

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

目录

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 <signal.h>
int pthread_kill(pthread_t thread, int sig);

参数说明:

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

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

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

#include <signal.h>
#include <pthread.h>
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 <signal.h>
#include <stdio.h>
#include <unistd.h>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 <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>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 <signal.h>
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 <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>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.dtcms.com/a/461576.html

相关文章:

  • docker启动容器慢,很慢,特别慢的坑
  • C#基础14-非泛型集合
  • 【22.1-决策树的构建1】
  • asp制作网站wordpress使用端口
  • 【机器学习】(一)实用入门指南——如何快速搭建自己的模型
  • 【数值分析】插值法实验
  • 地方门户网站的前途搜索引擎大全全搜网
  • 如何给oracle新建架构(schema)
  • 天地数码携手一半科技PLM 赋能应对全球市场,升级热转印色带研发能力
  • 构筑智能防线:大视码垛机如何重新定义工业安全新标准
  • iPhone17实体卡槽消失?eSIM 普及下的安全挑战与应对
  • 什么RPA可以生成EXE
  • 网站开发设计jw100交换链接的作用
  • 企业推广网站建设报价吉林网站建站系统平台
  • 热壁MOCVD有助于GaN-on-AlN HEMT
  • 网站app微信三合一怎么看网站后台什么语言做的
  • 【深度学习新浪潮】大模型推理实战:模型切分核心技术(上)—— 张量并行原理+国内开源案例+踩坑点
  • 高效SQLite操作:基于C++模板元编程的自动化封装
  • uniApp App内嵌H5打开内部链接,返回手势(左滑右滑页面)会直接关闭H5项目
  • 文字排版网站网站建设的宣传词
  • K8s学习笔记(十七) pod优雅终止流程
  • Redis-基础介绍
  • Redis常用数据库及单线程模式
  • Subword-Based Tokenization策略之BPE与BBPE
  • 网站关键词用热门的还是冷门青岛天河小学网站建设
  • 个人域名备案 网站名称一元购网站建设流程图
  • 企业级灰度发布架构:基于Nginx的精细化流量治理与平滑演进实践
  • 【滑动窗口专题】第一讲:长度最小的子数组
  • 软考-系统架构设计师 基于架构的软件开发方法详细讲解
  • 电子电气架构 --- 操作系统的基本概念