【问题标题】:Linux: Reading file while other program might modify itLinux:读取文件而其他程序可能会修改它
【发布时间】:2016-12-16 04:18:44
【问题描述】:

程序 Foo 定期更新文件并调用我的 C 程序 Bar 来处理文件。

问题是 Foo 可能会更新文件,调用 Bar 来处理它,当 Bar 读取文件时,Foo 可能会再次更新文件。

Bar 是否可以在不一致的状态下读取文件,例如读取由第一个 Foo 写入的文件的前半部分和由第二个 Foo 写入的另一半?如果是这样,假设我只能修改 Bar 的代码,我将如何防止这种情况发生?

【问题讨论】:

  • 是的,您可以从文件的两个版本中获得一些混合数据,但发生这种情况的可能性取决于文件的大小以及 Foo 写入文件的方式以及 Bar 读取文件的方式。一种可能性是让 Bar 使用带有私有映射的 mmap() 来读取文件;这应该为您提供文件的一致快照(可能)。不过,您必须在每个周期重新映射内存。

标签: c linux


【解决方案1】:

通常,Foo 不应该简单地一次又一次地重写文件的内容,而是创建一个新的临时文件,并在完成后用临时文件替换旧文件(使用link())。在这种情况下,由于典型的 POSIX 文件系统的工作方式,只需打开文件(在任何时间点)即可为读者提供一致的内容快照。 (打开文件后,文件描述符将引用相同的 inode/contents,即使文件被删除或替换;磁盘空间只有在删除/替换文件的最后打开文件描述符关闭后才会释放。)

如果 Foo 确实一遍又一遍地重写同一个文件(没有临时文件),推荐的解决方案是 Foo 和 Bar 都使用基于 fcntl() 的咨询锁定。 (但是,使用临时文件并在完成时将其重命名/链接到实际文件会更好。)

(虽然基于flock() 的锁定似乎更容易,但实际上它是否适用于NFS 挂载有点猜谜游戏。fcntl() 有效,除非NFS 服务器配置为不支持锁定。哪个实际上,在某些商业网络主机上有点问题。)

如果您无法修改 Foo 的行为,并且它不使用咨询锁定,Linux 中仍有一些选项。

如果 Foo 关闭文件 -- 即 Bar 是唯一打开文件的 -- ,则采用独占文件租约(使用fcntl(descriptor, F_SETLEASE, F_WRLCK) 是一种可行的解决方案。如果@,您只能获得独占文件租约987654331@ 是文件上唯一打开的描述符,文件的所有者用户与进程 UID 相同(或进程具有CAP_LEASE 能力)。如果任何其他进程试图打开或截断文件,则租约所有者收到信号(默认为SIGIO),最多有/proc/sys/fs/lease-break-time秒来降级或释放租约。开启者在此期间被阻塞,这允许Bar取消处理或复制文件以供以后使用处理。

Bar 的另一个选项相当暴力。它可以每秒监视一次文件,当文件足够老时(比如说几秒钟),通过发送SIGSTOP 信号来暂停 Foo,检查/proc/FOOPID/stat 直到它停止,然后重新检查文件统计信息以验证它仍然是旧的,直到制作它的临时副本(在内存中或在磁盘上)以进行处理。文件被读取/复制后,Bar 可以通过发送SIGCONT 信号让 Foo 继续。

一些文件系统可能支持文件快照,但在我看来,上述之一比依靠非标准文件系统支持来正常运行要明智得多。如果 Foo 不能被修改以进行合作,那么是时候将它重构出来了。您不想成为您无法控制的黑匣子的人质,因此您越早将其替换为对用户/管理员更友好的东西,从长远来看,您就会越好。

【讨论】:

    【解决方案2】:

    如果没有 Foo 的合作,这很难稳健地完成。 Unix 有两种主要的文件锁定方式:

    理想情况下,您可以在合作模式下使用其中任何一种(咨询锁定),在这种模式下,所有参与者都尝试获取锁,但一次只有一个参与者会得到它。

    没有其他程序的合作,据我所知,你唯一的办法是强制锁定,如果你在文件系统上允许,你可以使用fcntl,但手册页提到 Linux 实现不可靠。

    【讨论】:

    • 此外,fcntl() 锁在 NFS 挂载上工作正常(除非服务器配置为不支持锁定),而 flock() 锁可能会或可能不会起作用,具体取决于内核(如果是 Linux)和操作系统(如 BSD)。另一方面,在 Linux 中,即使是脚本也可以通过 flock 实用程序使用 flock 锁(由 util-linux 提供,应安装在所有 Linux 发行版中);例如,如果你想在 nano/vi/vim/emacs/gedit/other 程序中编辑文件的同时聚集一个文件,即使该程序没有使用咨询锁。这就是为什么...
    • ... 数据生产者 (Foo) 必须使用临时文件(在这种情况下,即使没有建议锁定,读者也会获得一致的快照)或建议锁定。我自己的回答基本上与你的相似,除了我不可避免的冗长(许多人不喜欢)。 :)
    【解决方案3】:

    在所有 UN*X 系统中,保证以原子方式发生的是 write(2)read(2) 系统调用。内核甚至将文件 inode 锁定在内存中,所以当你 read(2)ing 或 write(2)ing 它时,它不会改变。

    要获得更多空间原子性,您必须锁定整个文件。您可以使用可用的文件锁定工具来锁定文件的不同区域。有些是建议性的(您可以强制跳过它们),有些是强制性的(您被阻止,直到对方解除对文件区域的阻止)

    请参阅fcntl(2) 和选项F_GETLKF_SETLKF_SETLKW 以获取锁定信息,分别设置读或写锁定。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 2021-08-24
      • 1970-01-01
      • 2011-04-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多