c-文件写入期间的争用情况



假设两个不同的进程独立地打开同一个文件,因此在打开文件表(系统范围)中有不同的条目。但它们指的是同一个i-node条目。

由于文件描述符引用打开文件表(系统范围)中的不同条目,因此它们可能具有不同的文件偏移量。在write期间,由于文件偏移量不同,是否有可能出现竞争条件?内核如何避免这种情况?

书籍:Linux编程接口;第95页;第5章(文件I/O:更多详细信息);第5.4节

(我假设,因为您使用了write(),所以这个问题指的是POSIX系统。)

假设POSIX系统(根据write()的使用推测),每个write()操作都被认为是全原子的。

根据POSIX 7的2.9.7线程与常规文件操作的交互:

以下所有函数都应是原子函数POSIX.1-2017中规定的其他影响常规文件或符号链接:

chmod()
chown()
close()
creat()
dup2()
fchmod()
fchmodat()
fchown()
fchownat()
fcntl()
fstat()
fstatat()
ftruncate()
lchown()
link()
linkat()
lseek()
lstat()
open()
openat()
pread()
read()
readlink()
readlinkat()
readv()
pwrite()
rename()
renameat()
stat()
symlink()
symlinkat()
truncate()
unlink()
unlinkat()
utime()
utimensat()
utimes()
write()
writev()

如果两个线程各自调用其中一个函数,则每个调用应要么查看其他调用的所有指定效果,要么不查看他们close()函数的要求也应适用无论何时成功关闭文件描述符,无论其原因是什么(对于例如,由于调用close()、dup2()或过程终止)。

但要特别注意write()(粗体矿)的规范:

write()函数应尝试写入nbyte字节。。。

POSIX表示,对文件的write()调用应为原子。POSIX没有说write()调用将完成。这是一个Linux错误报告,其中一个信号中断了部分完成的write()。注意说明:

现在,就规范(POSIX、SUS…)而言,这是完全有效的行为(如果我遗漏了什么,请纠正我)。所以我认为这个程序是不正确的。但OTOH我同意这在a50527b1之前是不可能的,我们不想打破用户空间。我不想恢复提交,因为它允许我们中断进行大型写入的进程(尤其是当出现问题时),但如果你向我们解释为什么这种行为对你来说是个问题,那么我想我必须恢复它。

这几乎承认了POSIX要求write()调用是原子的,如果不是完整的,并提供恢复到以前的行为,在这种情况下,write()调用显然也都是完整的。

不过,请注意,有很多文件系统不符合POSIX标准。

由于文件描述符引用Open文件表(系统范围)中的不同条目,因此它们可能具有不同的文件偏移量。在写入过程中,由于文件偏移量不同,是否有可能出现竞争条件?

Linux中的任何write()都可能返回短计数,例如,由于信号被传递到用户空间处理程序。为了简单起见,让我们忽略这一点,只考虑成功写入的数据会发生什么。

有两种情况:

  1. 写入的区域不重叠。

    (例如,一个进程从偏移量23开始写入100个字节,另一个进程在偏移量200开始写入50个字节。)

    在这种情况下不存在竞赛条件。

  2. 写入的区域确实重叠。

    (例如,一个进程从偏移量50开始写入100个字节,而另一个进程则从偏移量70开始写入10个字节。)

    这是比赛条件。不可能预测(如果没有咨询锁定等)数据更新的顺序。

    根据目标文件系统的不同,如果写入足够大(从而可以观察到分页效果),则两次写入甚至可能是"0";混合的";(以页面大小的块为单位),即使POSIX说这不应该发生。

通常情况下,写入通过Linux页面缓存。其中一个进程可能已使用O_DIRECT | O_SYNC打开文件,从而绕过页面缓存。在这种情况下,可能会出现许多额外的拐角情况。具体而言,即使您使用共享时钟源,并且可以显示在进行直接写入调用之前完成了正常/页面缓存写入,页面缓存写入仍可能覆盖直接写入内容。

内核如何避免它?

没有。为什么要这样做?POSIX表示,每次写入都是原子性的,但没有切实可行的方法来避免仅依赖于此的竞争条件(并获得一致和预期的结果)。

用户空间程序至少有四种不同的方法来避免这种竞争:

  1. Advisory文件使用flock()接口锁定整个打开的文件。

  2. 咨询文件使用lockf()接口锁定整个打开的文件。在Linux中,这些只是在整个文件上放置/删除fcntl()咨询锁的简写。

  3. 咨询记录使用fcntl()接口锁定文件。只要将文件服务器配置为支持文件锁定,即使在共享卷之间也可以这样做。

  4. 使用fcntl()接口获取打开文件的独占租约。

咨询文件锁就像路灯:它们用于合作过程,以轻松确定谁何时可以离开。然而,它们并没有阻止任何其他过程实际上忽略";锁定";以及访问该文件。

文件租约是一种机制,其中一个或多个进程可以同时对同一文件获得读取租约,但只有一个进程可以获得写入租约,并且只有当该进程是唯一打开文件的进程时。当被授予时,写租约(或独占租约)意味着,如果任何其他进程试图打开同一文件,租约所有者进程会收到一个信号(您可以使用fcntl()接口进行控制),并有一个配置的时间(通常为45秒;请参阅man 5 proc和/proc/sys/fs/lease-break-time,以秒为单位)来重新命名租约。开启器在内核中被阻止,直到租约降级或租约中断时间过去,在这种情况下内核中断租约。这使得租赁持有人可以将开业时间推迟一段时间。然而,租约持有人不能阻止打开,也不能例如用诱饵文件替换文件;opener已经控制了inode,租约中断时间只是清理工作的宽限期。

从技术上讲,第五种方法是强制文件锁定,但除了内核使用wrt.executed二进制文件外,它们没有被使用,而且在Linux中实际上是有缺陷的。在Linux中,只有当内核将索引节点作为二进制文件执行时,索引节点才会被锁定以防修改。(您仍然可以重命名或删除原始文件,并创建一个新文件,这样任何后续的exec都将执行修改后的/新的数据。尝试将正在执行的文件修改为二进制文件将失败,并出现错误EBUSY。)

相关内容

  • 没有找到相关文章

最新更新