Linux futex 系统调用虚假唤醒并返回值 0?
我遇到了 Linux futex
系统调用(FUTEX_WAIT
操作)的问题,有时似乎毫无原因地提前返回。文档指定了可能导致其提前返回的某些条件(没有 FUTEX_WAKE
),但这些都涉及非零返回值:EAGAIN
(如果 futex 地址处的值确实如此)不匹配,ETIMEDOUT
表示定时等待超时,EINTR
被(非重新启动)信号中断时等。但我看到返回值为 0。什么,除了 FUTEX_WAKE
或 set_tid_address
指针指向 futex 的线程终止之外,可能会导致 FUTEX_WAIT
返回并返回值为 0?
如果它有用的话,我等待的特定 futex 是线程 tid 地址(由 clone
系统调用使用 CLONE_CHILD_CLEARTID
设置),并且线程没有< /strong> 终止。我的(显然不正确的)假设:FUTEX_WAIT
操作返回 0 只会在线程终止时发生,导致程序逻辑中出现严重错误,此后我通过循环和重试来修复该错误,即使它返回 0,但现在我很好奇为什么会发生这种情况。
这是一个最小的测试用例:
#define _GNU_SOURCE
#include <sched.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <linux/futex.h>
#include <signal.h>
static char stack[32768];
static int tid;
static int foo(void *p)
{
syscall(SYS_getpid);
syscall(SYS_getpid);
syscall(SYS_exit, 0);
}
int main()
{
int pid = getpid();
for (;;) {
int x = clone(foo, stack+sizeof stack,
CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND
|CLONE_THREAD|CLONE_SYSVSEM //|CLONE_SETTLS
|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID
|CLONE_DETACHED,
0, &tid, 0, &tid);
syscall(SYS_futex, &tid, FUTEX_WAIT, x, 0);
/* Should fail... */
syscall(SYS_tgkill, pid, tid, SIGKILL);
}
}
让它运行一段时间,最终应该以 Killed
(SIGKILL
) 终止,这只有在线程仍然存在时才可能发生。 FUTEX_WAIT
返回。
在有人假设这只是内核在完成销毁线程之前唤醒 futex 之前(这实际上可能发生在我的最小测试用例中),请注意,在我的原始代码中,我实际上观察到在线程中运行的用户空间代码FUTEX_WAIT
返回后不久。
I've run into an issue with the Linux futex
syscall (FUTEX_WAIT
operation) sometimes returning early seemingly without cause. The documentation specifies certain conditions that may cause it to return early (without a FUTEX_WAKE
) but these all involve non-zero return values: EAGAIN
if the value at the futex address does not match, ETIMEDOUT
for timed waits that timeout, EINTR
when interrupted by a (non-restarting) signal, etc. But I'm seeing a return value of 0. What, other than FUTEX_WAKE
or the termination of a thread whose set_tid_address
pointer points to the futex, could cause FUTEX_WAIT
to return with a return value of 0?
In case it's useful, the particular futex I was waiting on is the thread tid address (set by the clone
syscall with CLONE_CHILD_CLEARTID
), and the thread had not terminated. My (apparently incorrect) assumption that the FUTEX_WAIT
operation returning 0 could only happen when the thread terminated lead to serious errors in program logic, which I've since fixed by looping and retrying even if it returns 0, but now I'm curious as to why it happened.
Here is a minimal test case:
#define _GNU_SOURCE
#include <sched.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <linux/futex.h>
#include <signal.h>
static char stack[32768];
static int tid;
static int foo(void *p)
{
syscall(SYS_getpid);
syscall(SYS_getpid);
syscall(SYS_exit, 0);
}
int main()
{
int pid = getpid();
for (;;) {
int x = clone(foo, stack+sizeof stack,
CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND
|CLONE_THREAD|CLONE_SYSVSEM //|CLONE_SETTLS
|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID
|CLONE_DETACHED,
0, &tid, 0, &tid);
syscall(SYS_futex, &tid, FUTEX_WAIT, x, 0);
/* Should fail... */
syscall(SYS_tgkill, pid, tid, SIGKILL);
}
}
Let it run for a while, at it should eventually terminate with Killed
(SIGKILL
), which is only possible if the thread still exists when the FUTEX_WAIT
returns.
Before anyone goes assuming this is just the kernel waking the futex before it finishes destroying the thread (which might in fact be happening in my minimal test case here), please note that in my original code, I actually observed userspace code running in the thread well after FUTEX_WAIT
returned.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您是否可以处理父操作或子操作是否先完成之间的竞争条件?您可以通过在 foo() 的开头或在 clone() 之后立即放置小睡眠来研究这个理论,以确定事件的强制排序是否掩盖了问题。我不建议以这种方式修复任何问题,但进行调查可能会有所帮助。也许 futex 还没有准备好等待,直到子进程进一步完成初始化,但父进程的克隆有足够的空间返回给调用者?
具体来说,CLONE_VFORK 选项的存在似乎意味着这是一个危险的情况。您可能需要一种双向信号机制,以便子级向父级发出信号,表明它已经足够远,可以安全地等待子级了。
Could you be dealing with a race condition between whether the parent or child operations complete first? You can probably investigate this theory by putting small sleeps at the beginning of your foo() or immediately after the clone() to determine if a forced sequencing of events masks the issue. I don't recommend fixing anything in that manner, but it could be helpful to investigate. Maybe the futex isn't ready to be waited upon until the child gets further through its initialization, but the parent's clone has enough to return to the caller?
Specifically, the CLONE_VFORK option's presence seems to imply this is a dangerous scenario. You may need a bi-directional signaling mechanism such that the child signals the parent that it has gotten far enough that it is safe to wait for the child.