问题
如何编写信号安全的应用程序?
Linux 应用程序安全性讨论
场景一:不需要处理信号
- 应用程序实现单一功能,不需要关注信号
- 如:数据处理程序,文件加密程序,科学计算程序
场景二:需要处理信号
- 应用程序长时间运行,需要关注信号,并即使处理
- 如:服务端程序,上位机程序
场景一:不需要信号处理 (单一功能应用程序)
场景二:需要处理信号 (长时间运行的应用)
同步方案
- 通过标记同步处理信号,整个应用中只有一个执行流
异步方案
- 专用任务处理,应用中存在多个执行流 (多线程应用)
- 设置专用信号处理任务,其它任务忽略信号,专注功能实现
同步解决方案 (单任务)
信号处理逻辑与程序逻辑位于同一个上下文
- 即:信号处理函数和主函数不存在资源竞争关系
方案设计一
- 将任务分解为子任务(每个任务可对应一个函数)
- 信号递达时,信号处理函数中仅标记递达状态
- 子任务处理结束后,真正执行信号处理
同步方案示例一
存在的问题
由于给每个信号唯一的标记位置,因此,所有信号转变为不可靠信号;并且仅保留最近递达的信号信息
方案设计二
将任务分解为子任务 (每个任务可对应一个函数)
- 创建信号文件描述符,并阻塞所有信号 (可靠信号递达前位于内核队列中)
- 子任务处理结束后,通过 select 机制判断是否有信号需要处理
- true => 处理信号 false => 等待超时
关键系统函数
#include <sys/select.h>
#include <sys/signalfd.h>
int signalfd(int fd, const sigset_t* mask, int flag);
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
使用 signalfd() 处理信号
先屏蔽所有信号 (无法递达进程),之后为屏蔽信号创建文件描述符;当时机成熟,通过 read() 系统调用读取未决信号 (主动接收信号)
使用 select() 监听文件描述符
使用 select() 处理信号
存在的问题
由于使用了 select 机制,即便没有信号需要处理,也需要等待 select 超时,任务实时性受到影响
异步解决方案 (多任务)
使用独立任务处理信号,程序逻辑在其他任务中执行
即:通过多线程分离信号处理与程序逻辑
- 主线程:专用于信号处理
- 其他线程:完成程序功能
多线程信号处理
信号的发送目标是进程,而不是某个特定的线程
发送给进程的信号仅递送给一个进程
内核从不会阻塞目标信号的线程中随机选择
每个线程拥有独立的信号屏蔽掩码
异步解决方案 (多任务)
主线程:对目标信号设置信号处理的方式
- 当信号递达进程时,只可能时主线程进行信号处理
其他线程:首先屏蔽所有可能的信号,之后执行任务代码
- 无法接收到信号,不具备信号处理能力
进程与线程
进程:应用程序的一次加载执行 (系统执行资源分配的基本单位)
线程:进程中的程序执行流
- 一个进程中可以存在多个线程 (至少存在一个线程)
- 每个线程执行不同的任务 (多个线程可并行执行)
- 同一个进程中的多个线程共享进程的系统资源
Linux 多线程 API 函数
头文件:#include<pthread.h>
线程创建函数:int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg);
- thread:pthread_t 变量的地址,用于返回线程标识
- attr:线程的属性,可设置为 NULL,即:使用默认属性
- start_routine:线程入口函数
- arg:线程入口函数参数
线程标识:
- pthread_t pthread_self(void);
- 获取当前线程的 ID 标识
线程等待:
- int pthread_join(pthread_t thread, void** retval);
- 等待目标线程执行结束
多线程编程示例
异步方案示例 -- 主线程
异步方案示例 -- 任务线程
信号设计模式小结
多数模式不需要处理信号,因此可直接屏蔽信号
需要处理信号的程序,重点考虑信号安全性问题
- 同步处理方案,通过设计让任务代码和信号处理代码交替执行
- 问题:信号处理是否及时?任务执行是否实时?
- 异步处理方案,任务代码与信号处理代码位于不同执行流
- 问题:将信号安全性问题转换为线程安全性问题
- 因此,程序本身是否能做到线程安全?