当 100% 分页到页面缓存中的文件被另一个进程修改时会发生什么

Gre*_*hal 14 linux cache buffer virtual-memory

我知道当页面缓存页面被修改时,它被标记为脏并需要写回,但在以下情况下会发生什么:

场景: 文件/apps/EXE是一个可执行文件,被完全分页到页面缓存中(它的所有页面都在缓存/内存中)并被进程P执行

持续发布然后用全新的可执行文件替换 /apps/EXE。

假设 1: 我假设进程 P(以及具有引用旧可执行文件的文件描述符的任何其他人)将继续使用旧的内存 /apps/EXE 没有问题,并且任何尝试执行该路径的新进程都将获得新的可执行文件。

假设 2: 我假设如果不是文件的所有页面都映射到内存中,那么事情会很好,直到出现页面错误需要已替换​​文件中的页面,并且可能会发生段错误?

问题 1: 如果您使用 vmtouch 之类的东西 mlock 文件的所有页面,这是否会改变场景?

问题 2: 如果 /apps/EXE 位于远程 NFS 上,那会有什么不同吗?(我认为不是)

请更正或验证我的 2 个假设并回答我的 2 个问题。

让我们假设这是一个带有某种 3.10.0-957.el7 内核的 CentOS 7.6 机器

更新:进一步思考,我想知道这个场景是否与任何其他脏页场景没有什么不同..

我想写入新二进制文件的进程将读取并获取所有缓存页面,因为它都被分页了,然后所有这些页面都将被标记为脏。如果它们被锁定,在引用计数变为零后,它们将只是占据核心内存的无用页面。

我怀疑当当前正在执行的程序结束时,其他任何东西都会使用新的二进制文件。假设这一切都是正确的,我想只有当只有部分文件被分页时才有趣。

fil*_*den 13

持续发布然后用全新的可执行文件替换 /apps/EXE。

这是重要的部分。

释放新文件的方式是创建一个新文件(例如/apps/EXE.tmp.20190907080000),写入内容,设置权限和所有权,最后将其重命名(2)为最终名称/apps/EXE,替换旧文件。

结果是新文件有一个新的 inode 编号(这意味着,实际上,它是一个不同的文件。)

并且旧文件有自己的 inode 编号,即使文件名不再指向它(或者不再有指向该 inode 的文件名),该编号实际上仍然存在。

所以,这里的关键是,当我们在 Linux 中谈论“文件”时,我们通常真正谈论的是“inode”,因为一旦打开文件,inode 就是我们对文件的引用。

假设 1:我假设进程 P(以及具有引用旧可执行文件的文件描述符的任何其他人)将继续使用旧的内存 /apps/EXE 没有问题,并且任何尝试执行该路径的新进程都将获得新的可执行文件。

正确的。

假设 2:我假设如果不是文件的所有页面都映射到内存中,那么事情会很好,直到出现页面错误需要已替换​​文件中的页面,并且可能会发生段错误?

不正确。旧的 inode 仍然存在,因此使用旧二进制文件的进程中的页面错误仍然能够在磁盘上找到这些页面。

您可以通过查看运行旧二进制文件的进程的/proc/${pid}/exe符号链接(或等效的lsof输出)来查看此操作的一些影响,这将表明/app/EXE (deleted)名称不再存在,但 inode 仍在。

您还可以看到二进制文件使用的磁盘空间仅在进程df终止后才会释放(假设它是唯一打开该 inode 的进程。)检查杀死进程之前和之后的输出,您会看到它减少了大小那个你认为不再存在的旧二进制文件。

顺便说一句,这不仅适用于二进制文件,还适用于任何打开的文件。如果您在一个进程中打开一个文件并删除该文件,该文件将保留在磁盘上,直到该进程关闭该文件(或死亡)。类似于硬链接如何保留指向磁盘中一个 inode 的名称的计数器,文件系统驱动程序(在 Linux 内核中)会记录内存中对该 inode 存在多少引用的计数器,并且只有在来自运行系统的所有引用也被释放后才会从磁盘中释放该 inode。

问题 1:如果您使用 vmtouch 之类的东西 mlock 文件的所有页面,是否会改变场景

这个问题基于错误的假设 2,即不锁定页面会导致段错误。不会。

问题 2:如果 /apps/EXE 位于远程 NFS 上,那会有什么不同吗?(我认为不是)

旨在以相同的方式工作,并且大部分时间都是如此,但是 NFS 存在一些“问题”。

有时您会看到删除仍在 NFS 中打开的文件(在该目录中显示为隐藏文件)的结果。

您还可以通过某种方式将设备编号分配给 NFS 导出,以确保在 NFS 服务器重新启动时这些编号不会被“重新洗牌”。

但主要思想是一样的。NFS 客户端驱动程序仍然使用 inode,并且会在 inode 仍然被引用时尝试保留文件(在服务器上)。

  • @GreggLeventhal:“您对我正在使用的持续发布流程做出了什么假设,从而确定它使用了临时文件?” – 因为只要 Unix 存在,那就是并且一直是唯一明智的方法。`rename` 几乎是唯一保证原子性的文件和文件系统操作(假设我们不跨越文件系统或设备边界),所以“创建临时文件然后 `rename`”是***标准的更新模式文件。例如,它也是 Unix 上的每个文本编辑器所使用的。 (4认同)
  • 不, rename(2) 不会阻塞。旧的 inode 可能会保留很长时间。 (2认同)

mos*_*svy 8

假设 2:我假设如果不是文件的所有页面都映射到内存中,那么事情会很好,直到出现需要替换文件中的页面的页面错误,并且可能会发生段错误?

不,这不会发生,因为内核不会让您打开当前正在执行的文件中的任何内容以进行写入替换。这样的操作将失败ETXTBSY[1]

cp /bin/sleep sleep; ./sleep 3600 & echo none > ./sleep
[9] 5332
bash: ./sleep: Text file busy
Run Code Online (Sandbox Code Playgroud)

当 dpkg 等更新二进制文件时,它不会覆盖它,而是使用rename(2)它简单地将目录条目指向一个完全不同的文件,并且任何仍然具有旧文件映射或打开句柄的进程将继续使用它而不会出现问题.

[1]所述ETXBUSY保护不延伸至其也被认为是“文本”其他文件(=现场码/可执行文件):共享库,Java类,等; 在另一个进程映射时修改这样的文件导致进程崩溃。在 linux 上,动态链接器尽职尽责地将MAP_DENYWRITE标志传递给mmap(2),但不要搞错——它没有任何影响。例子:

$ cc -xc - <<<'void lib(){}' -shared -o lib.so
$ cc -Wl,-rpath=. lib.so -include unistd.h -xc - <<<'
   extern void lib();
   int main(){ truncate("lib.so", 0); lib(); }
'
./a.out
Bus error
Run Code Online (Sandbox Code Playgroud)

  • `rename(2)` 是原子的;一旦完成,dir 条目就指向新文件。当时仍在使用旧文件的进程只能通过现有的映射或通过打开的句柄访问它(可能引用一个孤立的 dentry,除了通过 `/proc/PID/fd `)。 (2认同)