在执行期间修改二进制


10

我经常在开发过程中遇到这种情况,我在其中运行一个二进制文件,a.out在后台说,因为它需要很长时间。在这样做的同时,我对产生a.outa.out再次编译的C代码进行了更改。到目前为止,我还没有遇到任何问题。正在运行的进程将a.out继续正常运行,不会崩溃,并且始终运行最初启动时的旧代码。

但是,a.out据说文件很大,可能相当于RAM的大小。在这种情况下会发生什么?并说它链接到共享对象文件,libblas.so如果我libblas.so在运行时进行修改怎么办?会发生什么?

我的主要问题是-操作系统是否保证当我运行时a.out,原始代码将始终按照原始二进制文件正常运行,而不管二进制.so文件或链接到的文件的大小如何,即使这些文件.o.so文件在运行期间被修改运行?

我知道有一些问题可以解决类似的问题:https : //stackoverflow.com/questions/8506865/when-a-binary-file-runs-does-it-copy-its-entire-binary-data-into-memory -at-once 如果在执行过程中编辑脚本会怎样? 程序运行时如何进行实时更新?

哪些方法帮助我进一步了解了这一点,但我认为他们并没有确切地问我想要什么,这是执行期间修改二进制文件的后果的一般规则


对我来说,您链接的问题(尤其是堆栈溢出问题)已经为理解这些后果(或没有后果)提供了重要帮助。由于内核将程序加载到内存文本区域/段中,因此它不应受到通过文件子系统进行的更改的影响。
约翰·史密斯

@JohnWHSmith在Stackoverflow上,最重要的回答是if they are read-only copies of something already on disc (like an executable, or a shared object file), they just get de-allocated and are reloaded from their source,所以我给人的印象是,如果您的二进制文件很大,那么如果您的二进制文件的一部分耗尽了RAM,但是又需要它,则它是“从源代码重新加载”的-因此,该.(s)o文件将在执行期间反映出来。但是,当然我可能会误解了-这就是为什么我要问这个更具体的问题
-texasflood 2015年

@JohnWHSmith第二个回答也说,No, it only loads the necessary pages into memory. This is demand paging.所以我实际上给人的印象是我不能保证所要求的。
texasflood 2015年

Answers:


11

尽管乍一看堆栈溢出问题就足够了,但从您的评论中我可以理解,为什么您仍然对此有疑问。对我来说,这恰恰是两个UNIX子系统(进程和文件)进行通信时所涉及的那种紧急情况

您可能知道,UNIX系统通常分为两个子系统:文件子系统和进程子系统。现在,除非通过系统调用另行指示,否则内核不应使这两个子系统相互交互。但是,有一个例外:将可执行文件加载到进程的文本区域中。当然,可以认为,这种操作也由系统调用(触发execve),但是这通常是公知的是一个其中过程子系统使得到文件子系统的隐式请求的情况。

由于流程子系统自然无法处理文件(否则,将整个对象一分为二将毫无意义),因此它必须使用文件子系统提供的任何内容来访问文件。这也意味着,将处理子系统提交给文件子系统就文件版本/删除采取的所有措施。在这一点上,我建议您阅读GillesU&L问题的回答。我剩下的答案是基于Gilles提出的更笼统的答案。

首先要注意的是,在内部,只能通过inode访问文件。如果为内核提供了路径,则其第一步将是将其转换为一个inode以用于所有其他操作。当进程将可执行文件加载到内存中时,它将通过其索引节点来执行该索引,该索引节点由路径转换后的文件子系统提供。索引节点可能与多个路径(链接)相关联,程序只能删除链接。为了删除文件及其索引节点,userland必须删除到该索引节点的所有现有链接,并确保它完全未被使用。当满足这些条件时,内核将自动从磁盘删除文件。

如果您看一下Gilles回答的“ 替换可执行文件”部分,您会看到,取决于您编辑/删除文件的方式,内核将始终通过在文件子系统内实现的机制做出不同的反应/适应。

  • 如果您尝试策略一(打开/截断为零/写入打开/写入/截断为新大小),您将看到内核不会打扰您的请求。您会收到错误26:文本文件忙ETXTBSY)。没有任何后果。
  • 如果尝试策略二,则第一步是删除可执行文件。但是,由于某个进程正在使用它,因此文件子系统将启动并阻止文件(及其索引节点)真正从磁盘上删除。从这一点来看,访问旧文件内容的唯一方法是通过其inode进行操作,这是处理子系统在需要将新数据加载到文本部分时所执行的操作(内部,使用路径没有意义,除了将它们转换为inode时)。即使您未链接文件(删除了所有路径)后,进程仍可以使用它,就好像您什么都没做一样。使用旧路径创建一个新文件不会改变任何东西:新文件将被赋予一个全新的索引节点,正在运行的进程对此一无所知。

策略2和策略3对可执行文件也很安全:尽管运行的可执行文件(和动态加载的库)并不是从具有文件描述符的角度打开文件,但它们的行为非常相似。只要某些程序正在运行该代码,即使没有目录条目,该文件也会保留在磁盘上。

  • 策略三非常相似,因为该mv操作是原子操作。这可能需要使用rename系统调用,并且由于在内核模式下无法中断进程,因此在此操作完成之前(成功与否),没有什么可以干扰该操作。同样,旧文件的inode不会发生变化:创建了一个新文件,即使已经与旧inode的链接之一相关联,已经运行的进程也不知道它。

使用策略3,将新文件移动到现有名称的步骤将删除导致旧内容的目录条目,并创建导致新内容的目录条目。这是通过一次原子操作完成的,因此该策略具有一个主要优势:如果进程在任何时间打开文件,它将看到旧内容或新内容-不会出现混合内容或文件未混合的风险。现有。

重新编译文件:使用时gcc(并且行为可能与许多其他编译器类似),您正在使用策略2。您可以通过运行一个strace编译器进程来看到这一点:

stat("a.out", {st_mode=S_IFREG|0750, st_size=8511, ...}) = 0
unlink("a.out") = 0
open("a.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
chmod("a.out", 0750) = 0
  • 编译器通过statlstat系统调用检测到该文件已存在。
  • 该文件未链接。在这里,虽然不再可以通过名称访问它,但是只要a.out它们的inode和内容被已经在运行的进程使用,它们就保留在磁盘上。
  • 将创建一个新文件,并使该文件名为a.out。这是一个全新的inode和全新的内容,已经在运行的进程并不在乎。

现在,对于共享库,将应用相同的行为。只要进程使用了​​库对象,无论如何更改其链接,都不会从磁盘中将其删除。每当需要将某些内容加载到内存中时,内核就会通过文件的inode进行操作,因此将忽略您对其链接所做的更改(例如将它们与新文件相关联)。


很棒的详细答案。那解释了我的困惑。因此,我是否可以正确地假设,因为该索引节点仍然可用,因此原始二进制文件中的数据仍在磁盘上,因此df用于计算磁盘上的可用字节数是错误的,因为它不占用索引节点。是否已考虑删除所有文件系统链接?所以我应该使用df -i?(这只是出于技术上的好奇,我真的不需要知道确切的磁盘使用情况!)
texasflood 2015年

1
只是为了向将来的读者澄清-我的困惑是我认为在执行时,整个二进制文件将被加载到RAM中,因此,如果RAM很小,则二进制文件的一部分将离开RAM并必须从磁盘中重新加载-这将如果您更改了文件,则会导致问题。但是答案清楚地表明,即使您rm或二进制文件也不会真正从磁盘中删除,mv因为直到所有进程都删除了到该文件节点的链接后,才会删除原始文件的inode。
texasflood 2015年

@texasflood确实如此。除去所有路径后,没有新进程(df包括在内)可以获取有关inode的信息。无论您找到什么新信息,都与新文件和新inode有关。这里的重点是流程子系统对此问题不感兴趣,因此内存管理(需求分页,流程交换,页面错误等)的概念是完全不相关的。这是一个文件子系统问题,由文件子系统解决。流程子系统不会为此而烦恼,这不是这里的目的。
约翰·史密斯

@texasflood关于的注释df -i:该工具可能从fs的超级块或其高速缓存中检索信息,这意味着它可能包括旧二进制文件的inode(已删除所有链接)。但是,这并不意味着新进程可以自由使用旧数据。
约翰·史密斯

2

我的理解是,由于正在运行的进程的内存映射,内核不允许更新映射文件的保留部分。我想如果某个进程正在运行,那么将保留其所有文件,从而对其进行更新,因为您编译源的新版本实际上会创建一组新的inode。简而言之,可通过页面错误事件在磁盘上访问可执行文件的较旧版本。因此,即使您更新了一个巨大的文件,只要进程正在运行,它也保持可访问性,并且内核仍应会看到未修改的版本。只要进程正在运行,就不应重复使用原始文件inode 。

当然必须对此进行确认。


2

替换.jar文件时,情况并非总是如此。在程序明确请求信息之前,不会从磁盘读取Jar资源和某些运行时反射类加载器。

这只是一个问题,因为jar只是存档,而不是映射到内存的单个可执行文件。这有点不合时宜,但是仍然是您提出的问题的分支,也是我为之震惊的内容。

因此对于可执行文件:是的。对于jar文件:也许(取决于实现)。

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.