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


14

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

方案: 文件/ 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计数变为零后,它们将只是无用的页面而占用核心内存。

我怀疑当前执行的程序结束时,其他任何东西都将使用新的二进制文件。假设一切正确,我猜只有在仅部分文件进入页面时才有意思。


只是为了明确起见,替换文件并不是一件大事(取决于应用程序是否重新打开文件以及应用程序对修改后的内容有何反应),但是修改mmaped文件会使应用程序严重崩溃(这是一个常见问题)在Java世界中,更改了具有mmaped目录条目的zip文件)。但是,它确实取决于平台,不能保证映射区域是否会更改。
eckes

Answers:


12

然后,连续发行版将/ apps / EXE替换为全新的可执行文件。

这是重要的部分。

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

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

而且旧文件有其自己的索引节点号,即使文件名不再指向该索引节点,该序号实际上仍然存在(或者不再有指向该索引节点的文件名。)

因此,关键是当我们在Linux中谈论“文件”时,最经常谈论的是“ inodes”,因为一旦打开文件,inode就是我们对文件的引用。

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

正确。

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

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

通过查看运行旧二进制文件的进程的/proc/${pid}/exe符号链接(或等效地,lsof输出),您可以看到这种效果,这将/app/EXE (deleted)表明该名称不再存在,但索引节点仍然存在。

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

顺便说一句,这不仅与二进制文件有关,而且与任何打开的文件有关。如果您在进程中打开文件并删除该文件,则该文件将一直保留在磁盘上,直到该进程关闭文件(或死亡)为止。类似于硬链接如何保持计数器指向磁盘中的索引节点的数量,文件系统驱动程序(Linux内核)保持多少引用存在于索引节点计数器在内存中,一旦从运行系统中的所有引用已被释放,以及将只从磁盘释放i节点。

问题1:如果使用vmtouch之类的文件锁住了文件的所有页面,则会改变方案

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

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

意味着以相同的方式工作,大部分它的时间,但也有一些“陷阱”与NFS。

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

您还可以通过某种方式为NFS导出分配设备编号,以确保在NFS服务器重新引导时不会“重新排列”设备编号。

但是主要思想是相同的。NFS客户端驱动程序仍使用inode,并且将在仍引用inode的情况下尝试保留文件(在服务器上)。


1
在旧文件的引用计数变为零之前,rename(2)是否会阻塞?
Gregg Leventhal

2
不,rename(2)不会阻止。旧的inode可能会保留很长时间。
filbranden

1
请参阅@mosvy关于为什么无法写入正在执行的文件的答案(得到ETXTBSY)。取消链接和创建new具有重命名的效果:您最终得到一个新的inode。(重命名更好,因为这样一来文件名就不存在了,这是原子操作,替换了文件名以指向新的inode。)
filbranden

4
@GreggLeventhal:“您对我正在使用的连续发布过程做出什么假设,可以确定它使用临时文件?” –因为只要存在Unix,这就是并且一直是实现它的唯一明智的方法。rename几乎是唯一可以保证是原子性的文件和文件系统操作(假设我们不跨越文件系统或设备边界),因此“先创建临时文件,然后创建rename”是更新文件标准模式。例如,这也是Unix上每个文本编辑器使用的功能。
约尔格W¯¯米塔格

1
@ grahamj42:rename是POSIX的一部分。当然,它是通过参考ISO C(当前草案的7.21.4.2节)包括在内的,但是它已经存在。
约尔格W¯¯米塔格

6

假设2:我假设,如果不是文件的所有页面都映射到内存中,那么在出现页面错误要求文件中的页面已被替换之前,一切都会好起来的,并且可能会发生段错误?

不,那不会发生,因为内核不允许您打开以写入替换文件,而替换文件当前正在执行中。此类操作将失败,并显示ETXTBSY[1]:

cp /bin/sleep sleep; ./sleep 3600 & echo none > ./sleep
[9] 5332
bash: ./sleep: Text file busy

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

[1]此类保护未扩展到也可以视为“文本”(实时代码/可执行文件)的其他文件:共享库,java类等;在被另一个进程映射时修改此类文件导致其崩溃。在linux上,动态链接程序会尽职地将MAP_DENYWRITE标志传递给mmap(2),但不要出错-它没有任何作用。


1
在dpkg方案中,重命名在什么时候完成,以便/ apps / EXE的dentry将引用新二进制文件的inode?什么时候不再有旧文献的引用?这是如何运作的?
格雷格·莱文塔尔

2
rename(2)是原子的;完成后,dir条目将引用新文件。那时仍在使用旧文件的进程将只能通过现有映射或通过对其的打开句柄(可能引用一个孤立的dentry,只能通过via访问)来访问它/proc/PID/fd
mosvy

1
我最喜欢您的回答,因为您提到的ETXTBSY使我进入了utcc.utoronto.ca/~cks/space/blog/unix/WhyTextFileBusyError,它回答了我所有的问题。
格雷格·莱文塔尔

4

假设连续发布过程通过正确地原子替换了文件,filbranden的答案是正确的rename。如果没有,但是就地修改了文件,情况就不同了。但是,您的思维模式仍然是错误的。

不可能在磁盘上对事物进行修改并且与页面高速缓存不一致,因为页面高速缓存是规范版本并且已被修改。对文件的任何写操作都是通过页面缓存进行的。如果已经存在,则修改现有页面。如果尚不存在,尝试修改部分页面将导致整个页面被缓存,然后进行修改,就好像它已经被缓存一样。跨整个页面或更多页面的写入可以(并且几乎可以肯定)优化读取页面的读取步骤。在任何情况下,都只有一个标准的可修改版本的文件ever(*),页面缓存中的一个。

(*)我稍微撒谎。对于NFS和其他远程文件系统,可能不止一个,并且它们通常(取决于使用哪一个以及使用哪种安装和服务器端选项)没有正确实现写入的原子性和排序语义。这就是为什么我们很多人认为它们从根本上被破坏了,并拒绝将它们用于会与使用并发写入的情况。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.