在用户空间中实现可取消的系统调用

时间:2011-04-16 03:20:14

标签: c linux pthreads posix cancellation

我正在努力在Linux上实现pthread取消,而不会在我最近的其他一些问题中讨论任何“不愉快的行为”(有些人可能会说错误)。到目前为止,取消pthread取消的Linux / glibc方法一直将它视为不需要内核支持的东西,并且可以在库级别处理,纯粹通过在进行系统调用之前启用异步取消,并恢复先前的取消状态在系统调用返回后。这至少有两个问题,其中一个非常严重:

  1. 取消可以在系统调用从内核空间返回之后但在用户空间保存返回值之前执行。如果系统调用分配了资源,则会导致资源泄漏,并且无法使用取消处理程序对其进行修补。
  2. 如果在线程被可取消的系统调用阻塞时处理信号,则整个信号处理程序在启用异步取消的情况下运行。这可能非常危险,因为信号处理程序可能会调用异步信号安全但不能同步取消安全的函数。
  3. 我解决问题的第一个想法是设置一个标志,表示线程处于取消点,而不是启用异步取消,并且当设置此标志时,让取消信号处理程序检查保存的指令指针以查看是否它指向一个系统调用指令(特定于arch)。如果是这样,这表示系统调用未完成,并且在信号处理程序返回时将重新启动,因此我们可以取消。如果没有,我认为系统调用已经返回,并推迟取消。但是,还存在竞争条件 - 线程可能根本没有到达syscall指令,在这种情况下,系统调用可能会阻塞并且永远不会响应取消。另一个小问题是,如果在输入信号处理程序时设置了取消点标志,则从信号处理程序执行的不可取消的系统调用会被错误地取消。

    我正在寻找一种新方法,并寻找有关它的反馈。必须满足的条件:

    • 在系统调用完成之前收到的任何取消请求必须在系统调用阻塞任何重要时间间隔之前执行,但不会因为信号处理程序中断而挂起重启。
    • 完成系统调用后收到的任何取消请求必须推迟到下一个取消点。

    我想到的想法需要对可取消的系统调用包装器进行专门的组装。基本想法是:

    1. 将即将发生的系统调用指令的地址压入堆栈。
    2. 将堆栈指针存储在线程本地存储中。
    3. 从线程本地存储中测试取消标志;如果已设置,则跳转到取消例程。
    4. 制作系统调用。
    5. 清除线程本地存储中保存的指针。
    6. 取消操作将涉及:

      1. 在目标线程的线程本地存储中设置取消标志。
      2. 测试目标线程的线程本地存储中的指针;如果它不为空,则向目标线程发送取消信号。
      3. 取消信号处理程序将:

        1. 检查保存的堆栈指针(在信号上下文中)是否等于线程本地存储中的已保存指针。如果没有,那么取消点被信号处理程序中断,现在无事可做。
        2. 检查程序计数器寄存器(保存在信号上下文中)是否小于或等于保存的堆栈指针中保存的地址。如果是这样,这意味着系统调用尚未完成,我们将执行取消。
        3. 我到目前为止看到的唯一问题是信号处理程序的第1步:如果它决定不动作,那么在信号处理程序返回后,线程可能在系统调用上被阻塞,忽略待处理的取消请求。为此,我看到了两个可能的解决方案:

          1. 在这种情况下,安装一个计时器将信号传递给特定的线程,基本上每毫秒重试一次,直到我们运气好。
          2. 再次提升取消信号,但是从取消信号处理器返回而不取消屏蔽取消信号。当中断的信号处理程序返回时,它将自动取消屏蔽,然后我们可以再试一次。但这可能会干扰信号处理程序中取消点的行为。
          3. 关于哪种方法最好的想法,或者是否还有其他更为根本的缺陷我不知道?

1 个答案:

答案 0 :(得分:3)

解决方案2感觉不像是黑客。我不认为这会导致你提出的问题,因为在系统调用处理程序中调用的可取消的系统调用将检查TLS中的取消标志,如果取消信号处理程序已经运行并且仍然使用信号掩码,则必须已经设置了取消标志。

(如果每个阻塞系统调用都使用sigmask参数la pselect()),实施者似乎会容易得多。