程序运行时如何进行实时更新?


15

我不知道如何在仍在运行时通过系统的程序包管理器更新杀手级应用程序,例如Thunderbird或Firefox。旧代码在更新时会怎样?当我要编写程序a.out并在运行时进行自我更新时,该怎么办?



@derobert不完全是:该线程不会进入可执行文件的特殊性。它提供了相关的背景,但不是重复的。
吉尔斯(Gillles)“所以-别再邪恶了”

@Gilles好吧,如果您把其余的内容留在里面,那就太宽泛了。这里有两个(至少)问题。另一个问题非常接近,它问为什么替换文件以进行更新在Unix上有效。
derobert 2014年

如果您确实想使用自己的代码执行此操作,则可能需要查看编程语言Erlang,其中“热”代码更新是关键功能。learnyousomeerlang.com/relups
mattdm 2014年

1
@Gilles BTW:看到您在回应中添加的文章后,我撤回了我的近距离投票。您已经把这个问题变成一个合适的地方,可以指出任何想知道更新如何工作的人。
derobert 2014年

Answers:


21

一般替换文件

首先,有几种替换文件的策略:

  1. 打开现有文件进行写入,将其截短为0,然后写入新内容。(一种不太常见的变体是打开现有文件,用新内容覆盖旧内容,如果文件长度较短,则将其截断为新长度。)用shell术语来说:

    echo 'new content' >somefile
    
  2. 删除旧文件,然后使用相同的名称创建一个新文件。用外壳术语来说:

    rm somefile
    echo 'new content' >somefile
    
  3. 以临时名称写入新文件,然后新文件移至现有名称。此举将删除旧文件。用外壳术语来说:

    echo 'new content' >somefile.new
    mv somefile.new somefile
    

我不会列出这些策略之间的所有差异,我只在这里提及一些重要的差异。对于状态1,如果当前有任何进程正在使用该文件,则该进程会在更新时看到新内容。如果该进程希望文件内容保持不变,则可能会造成混乱。请注意,这仅涉及打开文件的进程(在文件中lsof或文件中可见;打开了文档的交互式应用程序(例如,在编辑器中打开文件))通常不会保持文件打开,它们会在打开文件过程中加载文件内容。 “打开文档”操作,它们在“保存文档”操作期间替换文件(使用上述策略之一)。/proc/PID/fd/

使用策略2和3,如果某个进程somefile打开了文件,则旧文件在内容升级期间保持打开状态。使用策略2时,删除文件的步骤实际上只会删除目录中文件的条目。仅当文件本身没有目录条目时才将其删除(在典型的Unix文件系统上,同一文件可以有多个目录条目并且没有进程打开它。这是观察这种情况的一种方法-仅在sleep进程被杀死rm时才删除文件(仅删除其目录条目)。

echo 'old content' >somefile
sleep 9999999 <somefile &
df .
rm somefile
df .
cat /proc/$!/fd/0
kill $!
df .

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

替换可执行文件

如果您在Linux上使用正在运行的可执行文件尝试策略1,则会收到错误消息。

cp /bin/sleep .
./sleep 999999 &
echo oops >|sleep
bash: sleep: Text file busy

“文本文件”是指出于晦涩的历史原因而包含可执行代码的文件。与许多其他unix变体一样,Linux拒绝覆盖正在运行的程序的代码。某些unix变体允许这样做,除非新代码是对旧代码的很好修改,否则会导致崩溃。

在Linux上,您可以覆盖动态加载的库的代码。这很可能导致正在使用它的程序崩溃。(您可能无法观察到这一点,sleep因为它在启动时会加载它需要的所有库代码。请尝试一个更复杂的程序,该程序在休眠后会执行一些有用的操作,例如perl -e 'sleep 9; print lc $ARGV[0]'。)

如果解释器正在运行脚本,则解释器将以常规方式打开脚本文件,因此无法防止覆盖脚本。一些解释器在开始执行第一行之前先阅读并解析整个脚本,其他解释器则根据需要阅读该脚本。请参见如果在执行过程中编辑脚本会发生什么?以及Linux如何处理Shell脚本?更多细节。

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

升级应用程序

由于上面提到的主要优点,大多数软件包管理器都使用策略3替换文件-在任何时间点,打开文件都会产生文件的有效版本。

应用程序升级可能会中断的地方是,虽然一个文件的升级是原子性的,但如果应用程序包含多个文件(程序,库,数据等),则整个应用程序升级不是必需的。请考虑以下事件序列:

  1. 应用程序的实例已启动。
  2. 该应用程序已升级。
  3. 正在运行的实例应用程序将打开其数据文件之一。

在步骤3中,应用程序旧版本的运行实例正在从新版本中打开数据文件。是否有效取决于应用程序,它是哪个文件以及文件被修改了多少。

升级后,您会注意到旧程序仍在运行。如果要运行新版本,则必须退出旧程序并运行新版本。程序包管理器通常会在升级时终止并重新启动守护程序,但不理会最终用户应用程序。

一些守护程序具有特殊的过程来处理升级,而不必终止该守护程序并等待新实例重新启动(这会导致服务中断)。对于init来说这是必需的,它不能被杀死;初始化系统提供了一种请求正在运行的实例调用execve以将其自身替换为新版本的方法。


“然后将新文件移动到现有名称。移动将删除旧文件。” 这有点令人困惑,因为它实际上只是一个unlink,稍后将介绍。也许“替换了现有名称”,但这仍然有些令人困惑。
derobert 2014年

@derobert我不想沉迷于“ unlink”术语。我在引用目录条目时使用“删除”,这是一个细微的差别,稍后将进行解释。在那个阶段是否令人困惑?
吉尔(Gilles)'所以

可能不会造成混淆,不足以保证多出一两个段落来解释取消链接。我希望有些措辞不会造成混淆,但在技术上是正确的。也许只是再次使用“删除”,您已经放置了一个链接来解释?
derobert 2014年

3

可以在程序运行时运行升级,但是您看到的正在运行的程序实际上是其旧版本。在关闭程序之前,旧的二进制文件将保留在磁盘上。

说明:在Linux系统上,文件只是一个索引节点,它可以具有多个链接。例如。在/bin/bash你看到的只是一个链接inode 3932163我的系统上。您可以通过ls --inode /path在链接上发出来找到链接到哪个inode 的链接。仅当指向文件(索引节点)的链接为零且未被任何程序使用时,才会删除该文件(索引节点)。包管理器升级时,例如。/usr/bin/firefox,它首先取消链接(删除硬链接/usr/bin/firefox),然后创建一个名为的新文件/usr/bin/firefox,该文件是到另一个inode(包含新firefox版本的inode )的硬链接。现在将旧的inode标记为空闲,并且可以重复使用以存储新数据,但仍保留在磁盘上(仅在构建文件系统时创建inode,并且永不删除它们)。在下一个开始firefox,将使用新的。

如果要编写一个在运行时对其自身进行“升级”的程序,我想到的唯一可能的解决方案是定期检查其自己的二进制文件的时间戳,如果它比程序的开始时间新,请重新加载自身。


1
实际上,这是由于在Unix上删除(取消链接)文件的方式所致。请参见unix.stackexchange.com/questions/49299/…而且,至少在Linux上,您实际上无法写入正在运行的二进制文件,您将收到“文本文件繁忙”错误。
derobert 2014年

奇怪...那怎么办 Debian的apt升级工作?我可以升级任何正在运行的程序,而不会出现任何问题,包括IceweaselFirefox)。
psimon 2014年

2
APT(或更确切地说是dpkg)不会覆盖文件。而是取消链接它们,并以相同的名称放置一个新的。请参阅我链接的问题与解答以获取解释。
derobert 2014年

2
这不仅是因为它仍在RAM中,而且仍在磁盘上。在程序的最后一个实例退出之前,实际上不会删除该文件。
derobert 2014年

2
该网站不会让我建议编辑(一旦您的代表足够高,您就可以进行编辑,就不能再建议它们了)。因此,作为一个注释:Unix系统上的文件(索引节点)通常具有一个名称。但是,如果您添加带有ln(硬链接)的名称,它可能会有更多。您可以使用rm(取消链接)删除名称。您实际上无法直接删除文件,只能删除其名称。当文件没有名称并且没有打开时,内核将删除它。一个正在运行的程序具有从打开处运行的文件,因此即使删除所有名称,该文件仍然存在。
derobert 2014年

0

我不知道如何在仍在运行时通过系统的程序包管理器更新杀手级应用程序,例如Thunderbird或Firefox? 好吧,我可以告诉你,这确实不能很好地工作...如果在运行软件包更新时将其保持打开状态,Firefox会令人发指。有时我不得不强行杀死它并重新启动它,因为它是如此损坏以至于无法正确关闭它。

旧代码在更新时会怎样? 通常在Linux上,程序会加载到内存中,因此在程序运行时不需要或不使用磁盘上的可执行文件。实际上,您甚至可以删除可执行文件,而该程序并不在乎...但是,某些程序可能需要可执行文件,某些操作系统(例如Windows)会锁定可执行文件,从而防止删除甚至重命名/移动。程序正在运行。Firefox崩溃了,因为它实际上非常复杂,并使用一堆数据文件来告诉它如何构建GUI(用户界面)。在软件包更新期间,这些文件将被覆盖(更新),因此,当较旧的Firefox可执行文件(在内存中)尝试使用新的GUI文件时,可能会发生奇怪的事情...

当我要编写程序a.out并在运行时进行自我更新时,该怎么办? 您的问题已经有很多答案。检查一下:https : //stackoverflow.com/questions/232347/how-should-i-implement-an-auto-updater 顺便说一句,关于编程的问题最好在StackOverflow上解决。


2
可执行文件实际上是按需分页(交换)的。它们没有完全加载到内存中,并且每当系统需要RAM进行其他操作时,它们都可能会从内存中删除。实际上,旧版本仍保留在磁盘上。参见unix.stackexchange.com/questions/49299/…。至少在Linux上,您实际上无法写入正在运行的可执行文件,您将收到错误“文本文件忙”。连root也做不到。(不过,您对Firefox非常正确)。
derobert 2014年
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.