信号处理函数-malloc 线程死锁

本文探讨了在信号处理函数中使用堆操作函数导致的死锁问题。具体表现为客户端发送数据后,服务器未响应。经排查发现,一工作线程在使用OPENSSL函数时被信号处理函数打断,随后在该函数内调用malloc引发堆操作函数内部锁的嵌套,最终导致阻塞。文章强调了在信号处理函数中避免使用可能引发重入的函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近开发项目的时,会偶发一个问题:客户端与服务器发数据包后,服务器端无任何的应答,在服务器端查看进程仍然存在,最后使用调试工具,查看堆栈如下:


原来是因为:一个线程在使用OPENSSL的函数(CRYPTO_free)操作堆空间时,被信号处理函数(sig_fn)打断,然后再信号处理函数内部再次调用操作堆空间的函数malloc时,引发了堆操作函数内部锁的嵌套,导致阻塞。


以后在信号处理函数内部一定要小心,不要使用会引发重入的函数。

相关链接:

信号处理函数陷阱:调用malloc导致死锁:http://blog.youkuaiyun.com/icycode/article/details/49645725



<think>嗯,用户问的是禁止在信号处理例程中调用非异步安全函数的问题。这个问题涉及到信号处理和异步安全函数的概念,需要逐步理清楚。首先,我得回忆一下信号处理的基本机制,然后考虑为什么在信号处理函数中调用某些函数会有问题。 首先,信号处理函数是在程序运行过程中,当接收到特定信号时被调用的。这些信号可能来自外部事件,比如用户按下Ctrl+C,或者程序内部的错误,比如段错误。信号处理函数的设计需要快速响应,并且不会干扰到主程序的正常执行。这时候,问题就出现了:为什么不能调用非异步安全的函数呢? 接下来,异步安全函数的定义是关键。异步安全函数是指在信号处理函数中可以安全调用的函数,这些函数要么是不可重入的,要么在执行过程中不会被信号中断,从而不会导致竞态条件或数据不一致。常见的异步安全函数包括write、read(在某些情况下)、以及一些简单的函数如_exit等。 而非异步安全函数,比如malloc、free、printf等,可能在执行时修改全局状态或使用静态缓冲区。例如,printf内部可能使用一个静态缓冲区来格式化输出,如果在信号处理函数中调用printf,而此时主程序也在调用printf,那么可能会导致缓冲区被破坏,输出乱码,甚至程序崩溃。 然后需要解释为什么这种情况下会发生问题。信号处理函数可能在主程序执行的任何时刻被调用,包括主程序正在执行非异步安全函数的过程中。例如,如果主程序正在执行malloc分配内存,此时信号处理函数被触发,而处理函数中也调用了malloc,这可能导致堆管理结构被破坏,引发不可预知的结果。 接下来,应该给出一些例子。比如,在信号处理函数中使用printf输出信息是不安全的,应该改用write函数。或者,动态内存分配函数malloc和free同样不安全,可能导致死锁或内存泄漏。 然后,需要提供解决方案或最佳实践。比如,在信号处理函数中只使用异步安全函数,设置标志位让主程序检查并处理,或者使用自管道技巧将信号处理转换为更安全的事件循环处理。 可能还需要提到一些常见的异步安全函数列表,以及如何查阅相关文档确认函数的异步安全性。比如POSIX标准中明确哪些函数是异步安全的,或者在man手册中查找函数是否有async-signal-safe的属性。 最后,总结一下违反这一规则可能导致的后果,比如数据损坏、死锁、崩溃等,强调遵循这一规则的重要性。 现在需要把这些思路组织成一个结构清晰的回答,分步骤解释问题原因、具体例子和解决方案,确保用户能够逐步理解。</think>在信号处理中禁止调用非异步安全函数是一个关键的系统编程原则,其原理和注意事项可分解如下: --- ### 一、核心概念 1. **信号处理函数的异步性** 信号处理函数Signal Handler)是异步执行的,可能在任何时间点被触发,包括: - 主程序正在执行非原子操作时 - 线程处于临界区(如持有锁、修改全局数据时) 2. **异步安全函数(Async-Signal-Safe)** 指在信号处理函数中可安全调用的函数,满足以下条件: - 不依赖全局/静态数据 - 不会修改共享资源 - 自身可重入(Reentrant) --- ### 二、非异步安全函数的风险 #### 场景示例 假设主程序正在执行 `printf`(非异步安全): ```c printf("Count: %d\n", global_counter); // 主程序调用 ``` 此时若信号触发处理函数,且处理函数也调用 `printf`: ```c void handler(int sig) { printf("Signal received!\n"); // 非安全调用! } ``` 可能导致: 1. **数据竞争**:两个 `printf` 同时操作同一静态缓冲区 2. **死锁**:`printf` 内部可能持有锁,主程序与信号处理函数互相等待 3. **堆损坏**:如 `malloc`/`free` 在信号处理中被调用,破坏堆管理结构 --- ### 三、常见非异步安全函数 | 风险函数 | 问题原因 | |-------------|-----------------------------| | `malloc` | 修改全局堆状态,可能引发死锁 | | `free` | 同上 | | `printf` | 使用静态缓冲区 | | `strtok` | 依赖静态指针保存状态 | | `syslog` | 内部可能加锁 | --- ### 四、安全实践方案 #### 1. 仅使用异步安全函数 POSIX 定义的异步安全函数(部分): ```c write(), read(), _exit(), signal(), kill(), sigaction() ``` #### 2. 标志位 + 主循环检测(推荐) ```c volatile sig_atomic_t flag = 0; // 使用原子类型 void handler(int sig) { flag = 1; // 仅设置标志 } int main() { // 注册信号处理... while (1) { if (flag) { // 在主循环中处理实际逻辑 flag = 0; } } } ``` #### 3. 自管道技巧(Self-Pipe Trick) 通过管道将信号转换为 I/O 事件: ```c int pipefd[2]; pipe(pipefd); void handler(int sig) { write(pipefd[1], "X", 1); // 写入管道 } int main() { // 注册信号处理... while (1) { select(pipefd[0], ...); // 通过 select 检测管道可读 } } ``` --- ### 五、典型错误案例 **错误代码**: ```c void handler(int sig) { char* msg = malloc(100); // 非安全! sprintf(msg, "Signal %d\n", sig); // 非安全! printf("%s", msg); // 非安全! free(msg); // 非安全! } ``` **修正方案**: ```c void handler(int sig) { const char msg[] = "Signal received\n"; write(STDERR_FILENO, msg, sizeof(msg)); // 使用异步安全的 write } ``` --- ### 六、验证工具 1. **静态分析**:使用 `clang -fsanitize=thread` 检测数据竞争 2. **动态检测**:Valgrind 的 `helgrind` 工具检查锁冲突 --- ### 总结 禁止调用非异步安全函数的核心目标是避免**不可预测的竞态条件**。通过标志位、自管道或严格限制函数调用,可确保信号处理程序的安全性和可维护性。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值