我知道当页面缓存页面被修改时,它被标记为脏并需要写回,但是在以下情况下会发生什么:
场景: 文件 /apps/EXE 是一个可执行文件,被完全分页到页面缓存中(它的所有页面都在缓存/内存中)并被进程 P 执行
然后持续发布将 /apps/EXE 替换为全新的可执行文件。
假设 1: 我假设进程 P(以及具有引用旧可执行文件的文件描述符的任何其他人)将继续使用旧的,在内存中 /apps/EXE 没有问题,并且任何尝试执行该路径的新进程都会得到新的可执行文件。
假设2: 我假设如果不是文件的所有页面都映射到内存中,那么一切都会好起来的,直到出现页面错误需要文件中的页面已被替换,并且可能会发生段错误?
问题 1: 如果你使用 vmtouch 之类的东西来锁定文件的所有页面,这会改变场景吗?
问题 2: 如果 /apps/EXE 位于远程 NFS 上,那会有什么不同吗?(我假设不是)
请更正或验证我的 2 个假设并回答我的 2 个问题。
假设这是一个带有某种 3.10.0-957.el7 内核的 CentOS 7.6 机器
更新:进一步考虑,我想知道这个场景是否与任何其他脏页场景没有什么不同..
我想写入新二进制文件的进程将执行读取并获取所有缓存页面,因为它全部被分页,然后所有这些页面都将被标记为脏。如果它们被锁定,它们将只是在 ref 计数变为零后占用核心内存的无用页面。
我怀疑当当前执行的程序结束时,其他任何东西都会使用新的二进制文件。假设这一切都是正确的,我想只有当只有部分文件被分页时才有趣。
这是重要的部分。
发布新文件的方式是创建一个新文件(例如
/apps/EXE.tmp.20190907080000
),写入内容,设置权限和所有权,最后rename(2) 将其重命名为最终名称/apps/EXE
,替换旧文件。结果是新文件有一个新的 inode 编号(这实际上意味着它是一个不同的文件。)
旧文件有自己的 inode 编号,即使文件名不再指向它(或者不再有文件名指向该 inode) ,它实际上仍然存在。
所以,这里的关键是,当我们在 Linux 中谈论“文件”时,我们最常真正谈论的是“inode”,因为一旦打开了文件,inode 就是我们保留对文件的引用。
正确的。
不正确。旧的 inode 仍然存在,因此使用旧二进制文件的进程中的页面错误仍然能够在磁盘上找到这些页面。
通过查看运行旧二进制文件的进程的
/proc/${pid}/exe
符号链接(或等效的输出),您可以看到一些效果,这将表明名称不再存在,但 inode 仍然存在。lsof
/app/EXE (deleted)
您还可以看到二进制文件使用的磁盘空间只有在进程死亡后才会被释放(假设它是唯一打开该 inode 的进程。)检查
df
杀死进程之前和之后的输出,您会看到它的大小下降您认为不再存在的旧二进制文件。顺便说一句,这不仅适用于二进制文件,而且适用于任何打开的文件。如果您在进程中打开一个文件并删除该文件,该文件将保留在磁盘上,直到该进程关闭该文件(或死掉)。与硬链接如何保持计数器记录有多少名称指向磁盘中的 inode 类似,文件系统驱动程序(在 Linux 内核中)会记录内存中存在多少对该 inode 的引用,并且只有在来自正在运行的系统的所有引用都被释放后,才会从磁盘中释放该 inode。
这个问题基于错误的假设 2,即不锁定页面会导致段错误。它不会。
它应该以相同的方式工作,而且大部分时间都是这样,但是 NFS 有一些“陷阱”。
有时您会看到删除仍在 NFS 中打开的文件的工件(在该目录中显示为隐藏文件。)
您还可以通过某种方式将设备编号分配给 NFS 导出,以确保在 NFS 服务器重新启动时这些设备不会被“重新洗牌”。
但主要思想是一样的。NFS 客户端驱动程序仍然使用 inode,并且会在仍然引用 inode 时尝试保留文件(在服务器上)。
不,这不会发生,因为内核不会让您打开以写入替换当前正在执行的文件中的任何内容。这样的操作将因
ETXTBSY
[1]失败:当 dpkg 等更新二进制文件时,它不会覆盖它,而是使用
rename(2)
它简单地将目录条目指向一个完全不同的文件,并且任何仍然具有旧文件的映射或打开句柄的进程将继续使用它而不会出现问题.[1]保护
ETXBUSY
不扩展到其他也可以被视为“文本”(= 实时代码/可执行文件)的文件:共享库、java 类等;在被另一个进程映射时修改这样的文件将导致进程崩溃。在 linux 上,动态链接器尽职尽责地将MAP_DENYWRITE
标志传递给mmap(2)
,但不要搞错——它没有任何作用。例子:filbranden 的答案是正确的,假设连续发布过程通过
rename
. 如果不是,而是就地修改文件,情况就不同了。但是,您的心智模型仍然是错误的。不可能在磁盘上修改内容并与页面缓存不一致,因为页面缓存是规范版本并且已被修改。对文件的任何写入都是通过页面缓存进行的。如果它已经存在,则修改现有页面。如果它还不存在,尝试修改部分页面将导致整个页面被缓存,然后进行修改,就好像它已经被缓存了一样。跨越整个页面或更多页面的写入可以(并且几乎肯定会)优化分页它们的读取步骤。无论如何,只有一个文件的规范可修改版本(*)存在,即页面缓存中的那个.
(*) 我有点撒谎。对于 NFS 和其他远程文件系统,可能不止一个,而且它们通常(取决于使用哪一个以及使用什么挂载和服务器端选项)没有正确实现写入的原子性和排序语义。这就是为什么我们中的许多人认为它们从根本上被破坏并拒绝将它们用于与使用并发写入的情况。