我正在努力在 Linux 上实现 pthread 取消,而不会出现我最近的其他一些问题中讨论的任何“令人不快的行为”(有些人可能会说是错误)。到目前为止,Linux/glibc 取消 pthread 的方法是将其视为不需要内核支持的东西,并且可以在库级别纯粹通过在进行系统调用之前启用异步取消并恢复先前的取消状态来处理系统调用返回后。这至少有两个问题,其中一个非常严重:
- 取消可以在系统调用从内核空间返回之后,但在用户空间保存返回值之前进行。如果系统调用分配了资源,这将导致资源泄漏,并且无法使用取消处理程序修补它。
- 如果在线程被可取消的系统调用阻塞时处理信号,则整个信号处理程序在启用异步取消的情况下运行。这可能非常危险,因为信号处理程序可能会调用异步信号安全但不异步取消安全的函数。
我解决问题的第一个想法是设置线程处于取消点的标志,而不是启用异步取消,并且当设置此标志时,让取消信号处理程序检查保存的指令指针以查看它是否指向系统调用指令(特定于架构)。如果是这样,这表明系统调用尚未完成,并且会在信号处理程序返回时重新启动,因此我们可以取消。如果没有,我假设系统调用已经返回,并推迟取消。但是,还有一个竞争条件 - 线程可能根本还没有到达系统调用指令,在这种情况下,系统调用可能会阻塞并且永远不会响应取消。另一个小问题是从信号处理程序执行的不可取消的系统调用错误地变成了可取消的,
我正在寻找一种新方法,并寻求有关它的反馈。必须满足的条件:
- 在系统调用完成之前收到的任何取消请求都必须在系统调用阻塞任何重要的时间间隔之前采取行动,但在由于信号处理程序中断而等待重新启动时则不能。
- 系统调用完成后收到的任何取消请求都必须推迟到下一个取消点。
我的想法需要对可取消的系统调用包装器进行专门的组装。基本思想是:
- 将即将到来的系统调用指令的地址压入堆栈。
- 将堆栈指针存储在线程本地存储中。
- 从线程本地存储中测试取消标志;如果已设置,则跳转以取消例程。
- 进行系统调用。
- 清除保存在线程本地存储中的指针。
取消操作将涉及:
- 在目标线程的线程本地存储中设置取消标志。
- 测试目标线程的线程本地存储中的指针;如果它不为空,则向目标线程发送取消信号。
然后取消信号处理程序将:
- 检查保存的堆栈指针(在信号上下文中)是否等于线程本地存储中保存的指针。如果不是,则取消点被信号处理程序中断,现在无事可做。
- 检查程序计数器寄存器(保存在信号上下文中)是否小于或等于保存的堆栈指针处保存的地址。如果是这样,这意味着系统调用尚未完成,我们执行取消。
到目前为止,我看到的唯一问题是信号处理程序的第 1 步:如果它决定不采取行动,那么在信号处理程序返回后,线程可能会在系统调用上阻塞,忽略挂起的取消请求。为此,我看到了两个潜在的解决方案:
- 在这种情况下,安装一个计时器来将信号传递到特定线程,基本上每毫秒重试一次,直到我们走运为止。
- 再次提高取消信号,但从取消信号处理程序返回而不取消屏蔽信号。当中断的信号处理程序返回时,它会自动取消屏蔽,然后我们可以重试。不过,这可能会干扰信号处理程序中取消点的行为。
关于哪种方法最好的任何想法,或者我是否缺少其他更基本的缺陷?