一般来说(不是专门针对 Linux)...
当可执行文件启动时,操作系统(内核)创建虚拟地址空间和(最初为空)进程,并检查可执行文件的标头。可执行文件的标头描述了“部分”(例如.text
, .rodata
, .data
, .bss
等)其中每个节都有不同的属性 - 节的内容是否应该放入虚拟地址空间(例如是符号表或运行时不使用的东西),如果内容是部分是否包含文件(例如.bss
),以及该区域是否可执行、只读或读/写。
通常,可执行文件(的已使用部分)由虚拟文件系统缓存;并且已经存在于 VFS 缓存中的文件片段可以被映射(作为“写时复制”)到新进程的虚拟地址空间。对于尚未在 VFS 缓存中的部分,可以将文件的这些部分映射为“需要获取”到新进程的虚拟地址空间。
然后进程启动(给定 CPU 时间)。
如果进程从尚未加载的页面读取数据;操作系统(内核)暂停进程,从磁盘上的文件中提取页面到 VFS 缓存中,然后将该页面作为“写时复制”映射到进程中;然后允许进程继续(允许进程重试从未加载的页面读取,这将在页面加载后起作用)。
如果进程写入的页面仍然是“写时复制”;操作系统(内核)暂停进程,分配一个新页面并将原始页面的数据复制到其中,然后用进程自己的副本替换原始页面;然后允许进程继续(允许进程重试写入,现在该进程拥有自己的副本,该写入将起作用)。
如果进程从尚未加载的页面写入数据;操作系统(内核)结合了前面的两件事(从磁盘获取原始页面到VFS缓存,创建一个副本,将进程的副本映射到进程的虚拟地址空间)。
如果操作系统开始耗尽可用 RAM;然后:
位于 VFS 缓存中但未与任何进程作为“写入时复制”共享的文件数据页可以在 VFS 中释放,无需执行任何其他操作。下次使用该文件时,这些页面将从磁盘上的文件中提取到 VFS 缓存中。
VFS 缓存中的文件数据页以及与任何进程共享为“写入时复制”的文件数据页可以在 VFS 中释放,并且任何/所有进程中的副本标记为“尚未获取”。下次使用该文件时(包括进程访问“尚未获取”页面时),这些页面将从磁盘上的文件获取到 VFS 缓存,然后在进程中映射为“写时复制” )。
已修改的数据页(或者因为它们最初是“写时复制”但被复制,或者因为它们根本不是可执行文件的一部分 - 例如.bss
节、可执行文件的堆空间等)可以保存到交换空间,然后释放。当进程再次访问页面时,将从交换空间中获取它们。
注意:如果可执行文件存储在不可靠的介质上(例如可能有划痕的 CD),“比平均水平更智能”的操作系统可能会首先将整个可执行文件加载到 VFS 缓存和/或交换空间中;因为当进程使用该文件时,除了使进程崩溃之外,没有明智的方法来处理“从内存映射文件读取错误”(例如SIGSEGV
)并使其看起来像是可执行文件有错误,而实际上并没有,因为这提高了可靠性(因为您依赖于更可靠的交换,而不是依赖于不太可靠的有划痕的 CD)。还;如果操作系统防止文件损坏或恶意软件(例如,可执行文件中内置了 CRC 或数字签名),则操作系统可能(应该)将所有内容加载到内存(VFS 缓存)中,以在允许可执行文件被执行之前检查 CRC 或数字签名。执行,并且(对于安全系统,如果在可执行文件运行时修改了磁盘上的文件)释放 RAM 时可能会将未修改的页面存储在“更可信”的交换空间中(与修改页面时的情况相同)以避免从原始的“不太受信任”的文件中获取数据(部分原因是您不想每次从文件加载页面时都进行整个数字签名检查)。
我的问题是:假设程序在运行时将全局变量的值从 2018 年更改为 2019 年,并且似乎包含全局变量的虚拟页最终将分页到磁盘,这意味着 .data 部分具有全局变量现在变量为2019,所以我们更改了哪些不应该更改的可执行目标文件?
该页面包含2018
将以“未获取”开始,然后(当访问时)加载到 VFS 缓存中并作为“写时复制”映射到进程。在任一时刻,操作系统都可以释放内存并从磁盘上的可执行文件中获取数据(未更改)(如果再次需要)。
当进程修改全局变量(将其更改为包含2019
)操作系统为进程创建它的副本。此后,如果操作系统想要释放内存,则操作系统需要将页面的数据保存在交换空间中,并在再次访问时从交换空间加载页面的数据。可执行文件不会被修改,并且(对于该页面,对于该进程)不会再次使用可执行文件。