这里它说你可以重写一个可执行文件,并且该进程将运行得很好 - 当进程重新启动时它将被重新读取。
但是,当我尝试在进程运行时替换二进制文件(使用 scp,从开发服务器到测试服务器)时,它会显示“文件忙”。如果我替换共享库文件 (*.so),则链接它的所有进程都会崩溃。
为什么这样?我错过了什么吗?如何在不停止/崩溃进程的情况下替换二进制文件?
答案1
正如中提到的为什么软件包升级后仍能正常运行?,锁被放置在 inode 上而不是文件名上。当您加载并执行二进制文件时,该文件被标记为繁忙 - 这就是为什么当您尝试写入该文件时会收到 ETXTBSY(文件繁忙)错误。
现在,对于共享库,情况略有不同:库将内存映射到进程的地址空间mmap()
。尽管MAP_DENYWRITE
可能已指定,但至少 Linux 上的 Glibc 会默默地忽略它(根据手册页,请随意检查源代码) - 检查此线。因此,您实际上可以写入该文件,并且由于它是内存映射的,因此任何更改几乎立即可见 - 这意味着如果您足够努力,您可以设法 砖通过覆盖库来您的机器。
因此正确的更新方法是:
删除文件,这会从文件系统中删除对数据的引用,以便任何可能想要使用它的新生成的应用程序都无法访问该文件,同时保持数据可供任何已打开(或映射)的人访问;
创建一个包含更新内容的新文件。
新创建的进程将使用更新的内容,正在运行的应用程序将访问旧版本。这就是任何健全的包管理实用程序所做的事情。请注意,这并不是完全没有任何危险 - 例如,dlsym()
如果库的 API 默默更改,则动态加载代码(使用和朋友)的应用程序将遇到麻烦。
如果你想成为真正的人,真的为了安全起见,关闭系统,从另一个操作系统实例挂载文件系统,更新并再次启动更新的系统。
答案2
rpm 升级的作用是相同的 - 运行二进制文件和库而不会崩溃。
那么区别是什么呢:
- 取消链接文件
- 写入同名的新文件
这不会就地替换文件:引用正在使用的二进制文件的 inode 仍然“忙”,直到最后一个保持其打开的对象完成为止。新文件将使用新的索引节点号创建。
现在scp
或cp
将尝试就地替换文件 - 这将更改 inode 引用的内容。正如您所描述的,这不起作用。