中有一个解释fs/file.c
, do_dup2() https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/tree/fs/file.c?id=refs/tags/v3.14.4#n759:
/*
* We need to detect attempts to do dup2() over allocated but still
* not finished descriptor. NB: OpenBSD avoids that at the price of
* extra work in their equivalent of fget() - they insert struct
* file immediately after grabbing descriptor, mark it larval if
* more work (e.g. actual opening) is needed and make sure that
* fget() treats larval files as absent. Potentially interesting,
* but while extra work in fget() is trivial, locking implications
* and amount of surgery on open()-related paths in VFS are not.
* FreeBSD fails with -EBADF in the same situation, NetBSD "solution"
* deadlocks in rather amusing ways, AFAICS. All of that is out of
* scope of POSIX or SUS, since neither considers shared descriptor
* tables and this condition does not arise without those.
*/
fdt = files_fdtable(files);
tofree = fdt->fd[fd];
if (!tofree && fd_is_open(fd, fdt))
goto Ebusy;
好像EBUSY
当要释放的描述符仍在打开时处于某种不完整状态时返回(fd_is_open
但不存在于fdtable
).
编辑(更多信息并且确实想要赏金)
为了了解如何!tofree && fd_is_open(fd, fdt)
可能会发生,让我们看看文件是如何打开的。这里有一个简化版本sys_open
:
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
/* ... irrelevant stuff */
/* allocate the fd, uses a lock */
fd = get_unused_fd_flags(flags);
/* HERE the race condition can arise if another thread calls dup2 on fd */
/* do the real VFS stuff for this fd, also uses a lock */
fd_install(fd, f);
/* ... irrelevant stuff again */
return fd;
}
基本上发生了两件非常重要的事情:分配文件描述符,然后 VFS 才实际打开它。这两个操作修改了fdt
的过程。它们都使用锁,所以在这两个调用中没有什么不好的。
为了记住哪个fds
已分配了一个名为的位向量open_fds
被使用的是fdt
. After get_unused_fd_flags()
, the fd
已分配并设置了相应的位open_fds
。锁在fdt
已经发布了,但是真正的VFS工作还没有完成。
就在此时,另一个线程(或者在共享的情况下是另一个进程)fdt
) 可以调用 dup2,它不会阻塞,因为锁已被释放。如果dup2
在这里走上了正常的道路fd
会被替换,但是fd_install
仍将为旧文件运行。因此检查并返回Ebusy
.
我在评论中找到了有关此竞争条件的更多信息fd_install()
这证实了我的解释:
/* The VFS is full of places where we drop the files lock between
* setting the open_fds bitmap and installing the file in the file
* array. At any such point, we are vulnerable to a dup2() race
* installing a file in the array before us. We need to detect this and
* fput() the struct file we are about to overwrite in this case.
*
* It should never happen - if we allow dup2() do it, _really_ bad things
* will follow. */