1, 在不停程序的情况下, 直接用 cp 命令替换程序使用的 so 文件, 导致程序崩溃:
这与 cp 命令的实现有关, cp 并不改变目标文件的 inode,cp 的目标文件会继承被覆盖文件的属性而非源文件. 实际上它是这样实现的:
- strace cp libnew.so libold.so 2>&1 |grep open.*lib.*.so
- open("libnew.so", O_RDONLY|O_LARGEFILE) = 3
- open("libold.so", O_WRONLY|O_TRUNC|O_LARGEFILE) = 4
在 cp 使用 "O_WRONLY|O_TRUNC" 打开目标文件时, 原 so 文件的镜像被意外的破坏了. 这样动态链接器 ld.so 不能访问到 so 文件中的函数入口. 从而导致 Segmentation fault, 程序崩溃.
2, 怎样在不停止程序的情况下替换 so 文件, 并且保证程序不会崩溃?
答案是采用 "rm+cp" 或 "mv+cp" 来替代直接 "cp" 的操作方法.
在用新的 so 文件 libnew.so 替换旧的 so 文件 libold.so 时, 如果采用如下方法:
- rm libold.so
- cp libnew.so libold.so
采用这种方法, 目标文件 libold.so 的 inode 其实已经改变了, 原来的 libold.so 文件虽然不能用 "ls" 查看到, 但其 inode 并没有被真正删除, 直到内核释放对它的引用. 同理, mv 只是改变了文件名, 其 inode 不变, 新文件使用了新的 inode. 这样动态链接器 ld.so 仍然使用原来文件的 inode 访问旧的 so 文件. 因而程序依然能正常运行.
到这里, 我们回想在上线操作中在替换可执行程序时, 为什么直接使用 "cp new old" 这样的命令时, 系统会禁止这样的操作, 并且给出这样的提示 "cp: cannot create regular file `old': Text file busy". 这时, 我们采用的办法仍然是用"rm+cp"或者"mv+cp"来替代直接"cp", 这跟以上提到的 so 文件的替换有同样的道理.
但是, 为什么系统会阻止 cp 覆盖可执行程序, 而不阻止覆盖 so 文件呢? 这是因为 Linux 有个 Demand Paging 机制, 所谓 "Demand Paging", 简单的说, 就是系统为了节约物理内存开销, 并不会程序运行时就将所有页 (page) 都加载到内存中, 而只有在系统有访问需求时才将其加载.
"Demand Paging" 要求正在运行中的程序镜像 (注意, 并非文件本身) 不被意外修改, 因此内核在启动程序后会锁定这个程序镜像的 inode. 对于 so 文件, 它是靠 ld.so 加载的, 而 ld.so 毕竟也是用户态程序, 没有权利去锁定 inode, 也不应与内核的文件系统底层实现耦合.
来源: http://www.bubuko.com/infodetail-3035056.html