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