一. 前言

Linux/UNIX进程信号处理复杂易出错,而用在多线程中就更加复杂脆弱,这里不探讨相关历史渊源,只给出一种在实践中简单可靠的信号处理方式。后文讨论的线程模型是POSIX thread(pthread),Linux和主流UNIX均支持pthread。

二. 进程与线程信号模型

这里假设读者已熟悉进程信号模型,下面列出多线程环境中,信号模型的进一步细节:

1. 信号action属于进程级别。忽略信号action /  默认action(可能core dump) / 自定义action,所有线程共享该action。

2. 信号递交到进程时,内核会任选一个线程来处理,只有下面三种情况才会由特定线程处理:信号源于线程执行了特定硬件指令,如SIGBUS / SIGFPE / SIGILL / SIGSEGV。线程写一个关闭读端或出错的流式管道,触发SIGPIPE,常见于pipe / FIFO / UNIX domain socket / TCP socket。pthread_kill和pthread_sigqueue所发出信号。

3. 信号mask属于线程级别,可以通过pthread_sigmask来控制。

4. 信号pending属于线程级别,可由sigpending获取。

三. 可重入(re-entrant)函数 / 线程安全(thread-safe)函数 / 异步信号安全(async-signal-safe)函数

SUS有很长的列表规定了哪些不是线程安全函数,哪些是异步信号安全函数,但没必要记那个表格,只要对分类原理有深入理解,必要时才查标准或手册。下面列出一些关键点:

1. 可重入是一个理论概念,可以理解为给定的输入能得到确定性输出,不能是随机或undefined行为,实践中需要有上下文环境。在异步信号执行中,可重入意味着异步信号安全。在多线程执行中,可重入意味着线程安全。

2. 异步信号安全,同一个线程或不同线程,同步执行或异步执行,都能保证安全性。这在系统调用和库函数中属于少数派,所以SUS详细列出了这些函数。IO系统调用open/close/read/write属于此类;标准IO函数fopen / fclose / fread / fwrite / printf等等则不属于此类。一般修改共享数据结构意味着异步信号不安全,比如标准IO函数;不过有例外,存取sig_atomic_t,gcc支持的硬件架构中存取1/2/4/8字节的整数类型,更广义的就是最新C/C++标准支持的atomic系列函数,这些都是异步信号安全的。

3. 线程安全,可供多个线程安全调用的函数,包括异步信号安全函数。这个门槛不高,多数系统调用和库函数都满足,所以SUS只规定了哪些函数不必是线程安全的。标准IO函数属于此类,malloc / free也是,它们虽然使用了全局数据结构,但是内部有互斥锁,可以保证安全性,localtime则不是,它用了静态数据结构,内部没有锁保护,标准有localtime_r替代,’xxx_r’是一大类线程安全的函数,具体可以查文档。修改共享数据结构却没有锁保护,意味着线程不安全。

四. 运行环境

Linux: kernel 3.12 / gcc 4.8

UNIX: mac os x 10.9 / llvm 3.4

五. 一个常见的错误

很多人在信号处理函数中经常写一堆printf,几十行的demo还行,但实际项目中就是一个风险,可能出错的终将出错,往往关键时刻掉链子。下面就构造一个稍极端的例子演示会发生什么。

C源码:

点击(此处)折叠或打开

#include

#include "lgr_hdr.h"

static void handler(int sig)

{

printf("this is handler\n");

}

int main(int argc, char **argv)

{

struct sigaction sa;

sigemptyset(&sa.sa_mask);

sa.sa_flags = 0;

sa.sa_handler = handler;

if (sigaction(SIGUSR1, &sa, NULL) == -1)

err_sys("sigaction");

while (1) {

printf("this is main\n");

}

return 0;

}

构造错误场景的步骤:

1. 启动C程序。

bash-4.2 $./async_signal | grep -F handler

2. 用Python解释器每隔10毫秒,向C程序发送一个SIGUSR1,直到C程序停止输出内容。

bash-4.2 $ps -ef|grep async

lgr 1108 945 99 17:37 pts/0 00:00:25 ./async_signal

bash-4.2 $python

Python 2.7.5 (default, Feb 19 2014, 13:47:40)

[GCC 4.8.2 20131212 (Red Hat 4.8.2-7)] on linux2

Type "help", "copyright", "credits" or "license" for more information.

>>> import os, time

>>> for i in range(2000):

... os.kill(1108, 10)

... time.sleep(0.01)

... 3. 利用strace看进程当前系统调用,发现进程是死锁状态。上面这几个步骤在mac os x 10.9也可以重现,不过要用dtrace看系统调用。

bash-4.2 $strace -p 1108

Process 1108 attached

futex(0x43324880, FUTEX_WAIT_PRIVATE, 2, NULL

问题原因:

在Linux下标准IO内部通过pthread(NPTL)实现线程安全,而同步原语用futex实现,printf访问的stdout是一个全局数据结构,必须由互斥锁来保护,如果进程执行锁保护的代码过程中被信号中断,而信号处理函数恰好也调用printf,意味着互斥锁没有被解锁的情况下再次加锁,死锁是必然的。上面的例子是一个单线程,我测试时只发送了不到2000个信号就触发了死锁,如果是多个线程,死锁可能性将大大提高。

六. 简单可靠的信号处理方式

原则上信号处理函数尽量简单。一个解决办法就是在信号处理函数中只使用异步信号安全函数,理论上是没问题,但实现起来可能比较费劲,snprintf / write远不如printf简单明了。另一个办法就是本文推荐的同步处理方式。

原理:

每个线程都有独立的信号mask,给定一组我们感兴趣的信号,主线启动时用pthread_sigmask阻塞该组信号,意味着后续启动的所有线程都继承了主线程的mask,然后新创建一个线程调用sigwait,该信号处理线程调用sigwait是同步等待,所以信号处理函数内部只需要保证线程安全,大大降低实现复杂度。

C实现源码:

点击(此处)折叠或打开

static struct sigaction th_sa;

static void *th_signal_handler(void *arg)

{

int sig;

struct sigaction *sa = arg;

while (1) {

if (sigwait(&sa->sa_mask, &sig) != 0) {

err_ret("sigwait");

break;

}

sa->sa_handler(sig);

}

return NULL;

}

int register_signal_handler(int *sigset, int num, sig_t handler)

{

pthread_t tid;

int i;

int ret;

sigemptyset(&th_sa.sa_mask);

th_sa.sa_handler = handler;

for (i = 0; i < num; ++i) {

sigaddset(&th_sa.sa_mask, sigset[i]);

}

ret = pthread_sigmask(SIG_BLOCK, &th_sa.sa_mask, NULL);

if ( ret != 0) {

err_ret("pthread_sigmask");

return -1;

}

ret = pthread_create(&tid, NULL, th_signal_handler, &th_sa);

if (ret != 0) {

err_ret("pthread_create");

return -1;

}

return 0;

}

使用方法:

点击(此处)折叠或打开

static void handler(int sig)

{

printf("this is handler\n");

}

int main(int argc, char **argv)

{

int sigset[] = {SIGUSR1};

if (register_signal_handler(sigset, ARRAY_SIZE(sigset), handler) == -1)

err_quit("register_signal_handler");

while (1) {

printf("this is main\n");

}

return 0;

}

linux线程关闭信号,Linux/UNIX用同步方法处理异步信号相关推荐

  1. linux线程 ppt,4 linux线程与进程.ppt

    4 linux线程与进程 第4章 进程与线程 4.1进程 2.进程 什么是程序? 进程的定义:可并发执行的程序在一个数据集合上的运行过程. 进程的特性 动态性:进程具有一定的生命周期,它由创建而产生, ...

  2. linux 线程退出 signal,Linux signal 那些事儿 (3)

    这篇博客,想集中在signal 与线程的关系上,顺带介绍内核signal相关的结构.如何组织我其实并没想好,想到哪就写到哪里吧.主题一定会落在signal之内而不跑题. 提到signal与thread ...

  3. Linux线程的同步,linux线程同步

    我是linux和linux线程的新手.我花了一些时间谷歌搜索试图理解可用于线程同步的所有函数之间的差异.我还有一些问题. 我找到了所有这些不同类型的同步,每个同步都有许多锁定,解锁,测试锁等功能. & ...

  4. linux 桌面关闭防火墙,Linux关闭防火墙的方法步骤

    windows操作系统的防火墙好关闭,但是linux操作系统防火墙可能有很多新手朋友不会关闭,下面就让学习啦小编教大家Linux关闭防火墙的方法吧. Linux关闭防火墙的方法 首先我们打开linux ...

  5. linux内核关闭igmp,Linux kernel IGMP多个安全漏洞

    受影响系统: Linux kernel 2.6.9 Linux kernel 2.6.8 Linux kernel 2.6.7 Linux kernel 2.6.6 Linux kernel 2.6. ...

  6. linux线程同步 epoll,Linux网络编程--epoll 模型原理详解以及实例

    1.简介 Linux I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数.Linux 2.6内核中有提高网络I/O性能的新方法,即epoll . epoll是什么?按 ...

  7. Linux系统关闭nginx,linux下nginx启动、重启、关闭

    方式一:传统方法 一.启动 cd usr/local/nginx/sbin ./nginx 二.重启 更改配置重启nginx kill -HUP 主进程号或进程号文件路径 或者使用 cd /usr/l ...

  8. linux oracle 关闭防火墙,Linux 下关闭防火墙设置

    一.Linux下开启/关闭防火墙命令 1) 永久性生效,重启后不会复原 开启: chkconfig iptables on 关闭: chkconfig iptables off 2) 即时生效,重启后 ...

  9. linux怎么关闭iptables linux如何关闭防火墙

    Linux系统下面自带了防火墙iptables,iptables可以设置很多安全规则.但是如果配置错误很容易导致各种网络问题,那么如果要关闭禁用防火墙怎么操作呢,咗嚛本经验以centos系统为例演示如 ...

最新文章

  1. sqlmap 常用操作
  2. 模式识别之数字识别---扑克牌识别
  3. 好物推荐 | 轻薄神器,妈妈再也不用担心我的颈椎了
  4. C#socket之TCP开发详解(一)
  5. ltrim函数_常用基础函数
  6. checkpoint_通过Main的Checkpoint Restore加快Java启动速度
  7. 我的第一程序语言python
  8. 定制CentOS 5.6精简的自动安装版及心得
  9. yii2实现WebService 使用 SoapDiscovery
  10. 论文页眉奇偶页不同怎么设置_什么!论文排版这么简单的吗?!
  11. D3/Echarts/G2的对比分析
  12. HBase region的split过程浅析
  13. nodejs后台系列--第六篇-把nodejs后台部署到云服务器上面
  14. 区块链专利申请全球过半 厉害了我的国
  15. linux pwm控制蜂鸣器 滴滴_linux pwm实现蜂鸣器
  16. python中and、or、not、in和not in五种运算用法
  17. H5视频自动播放和循环播放
  18. Android百度地图API使用教程
  19. Web Confidential for mac(密码信息管理软件)
  20. “希希敬敬对”Alpha版本发布说明

热门文章

  1. html tfoot标签,HTML tfoot 标签
  2. JWT 应该保存在哪里?
  3. java 多线程间通讯_JAVA多线程间通讯常用实现方法解析
  4. prism项目搭建 wpf_WPF Step By Step 系列-Prism框架在项目中使用
  5. 软工文档”注意注意“事项
  6. python logsumexp示例
  7. 人脸识别 性能评价指标
  8. It is a distutils installed project and thus we cannot accurately
  9. pytorch 初始化权重
  10. KNN、MOG2和GMG