我有一个共享对象gateway.so(在Linux / C)。 而a.out应用程序正在使用它。
我猜:当进程a.out启动时,加载器加载gateway.so(我没有使用dl函数,如dlopen
)。 所以所有的运行时符号parsing到gateway.so都会在内存中发生。 它不需要再从磁盘访问gateway.so。
我对吗?
所以我不能用一个更新的版本replacegateway.so,而a.out正在运行,对不对?
另一个相关的问题:一旦我代替了gateway.so文件的过期版本,我收到了这个消息
“a.out:无法parsing符号”Test_OpenGateway“”
哪个程序组件(loader / linker …)发送这个输出? 这个组件是作为同一个进程上下文的一部分执行的吗?
不,如果运行时链接程序( ld.so
)将其映射到进程的地址空间中,则仍然需要从磁盘读取该文件。 这种映射发生的方式是通过mmap(2)
系统调用和标志PROT_EXEC
来允许执行。
映射不会将整个文件放到内存中,但实际上会创建一个内存区域,如果所请求的内存未被复制,将会按需按需调用页面错误,内核空间通过读取文件中的适当偏移量。
关于第二个问题,运行时链接程序( ld.so
)抱怨这个问题。 加载ld.so
代码被编译时链接器( ld
)命名为程序启动代码,因此在调用main
之前在用户空间执行。
问题A
如果以正确的方式进行操作,可以在应用程序正在使用时替换库。
在我们到达那里之前,让我们看看主要的程序二进制文件。 这是一个示例程序:
#include <unistd.h> void justsit(void) { for (;;) { sleep(1); } } int main(int argc, char **argv) { printf("My PID is %d\n", getpid()); justsit(); return 0; }
编译并启动它:
$ gcc -Wall -o example example.c $ ./example My PID is 4339
现在它会坐在那里,所以打开一个新的终端来做到这一点:
$ gcc -Wall -o example-updated example.c $ cp example-updated example cp: cannot create regular file `example': Text file busy
现在发生什么事? 内核拒绝更改文件示例,因为它有一个运行该文件的进程。
现在让我们尝试删除它:
$ rm example
什么? 那有效? 为什么文件可以被删除,但不能被替换? 是的,或者说,文件并没有被真正删除,只是“名称”,内核告诉文件系统保留文件的内容。 当没有文件打开时,内容也被删除。 ( dentry被立即删除,但inode被释放,当没有用户的文件系统的人会说)
这可以在/ proc中看到(这就是程序打印PID的原因,所以你可以很容易地检查这个)
$ readlink /proc/4339/exe /tmp/t/example (deleted)
无论如何。 事实上,它是这样工作的,意味着可以安全地升级程序,方法是删除旧的二进制文件并将新的二进制文件放在同一个地方。 有一个程序来处理这个:安装(1)。
好的,回到你的问题 – 共享对象。
让我们把这个例子分成两个部分,main.c和shared.c:
/* main.c */ #include <stdio.h> #include <sys/types.h> #include <unistd.h> void justsit(void); int main(int argc, char **argv) { printf("My PID is %d\n", getpid()); justsit(); return 0; }
和
/* shared.c */ #include <stdio.h> #include <unistd.h> void justsit(void) { for (;;) { sleep(1); } }
像这样编译它们:
$ gcc -Wall --shared -o libshared.so shared.c $ gcc -Wall -L. -o main main.c -lshared
现在希望如果我们尝试替换libshared.so,我们会得到一个类似的“文本文件忙”错误? 让我们来看看。 首先启动主程序 – 当前目录不在lib搜索路径中,所以告诉动态链接程序在那里搜索:
$ LD_LIBRARY_PATH=. ./main My PID is 5697
去一个不同的终端,并用明显破碎的东西来替换库:
$ echo "junk" > libshared.so $
首先 – 它不会像更换程序二进制文件一样被拒绝。 而在另一个终端发生了一些有趣的事情,程序停止运行,出现以下错误消息:
Segmentation fault $
所以不能用一个程序替换正在使用的库! 但是从上面的例子可以看出,它会带来灾难性的后果。
幸运的是,用于替换正在运行的二进制文件的相同“技巧”可以用来替换正在使用的库。 重新启动主程序(不要忘记重新编译libshared.so,因为它被垃圾取代),看看如何在库上执行rm是安全的。 可以检查/ proc / PID / maps来查看进程正在使用的共享对象:
$ cat /proc/5733/maps | grep libshared.so 008a8000-008a9000 r-xp 00000000 08:01 2097292 /tmp/t/libshared.so 008a9000-008aa000 r--p 00000000 08:01 2097292 /tmp/t/libshared.so 008aa000-008ab000 rw-p 00001000 08:01 2097292 /tmp/t/libshared.so $ rm libshared.so $ cat /proc/5733/maps | grep libshared.so 008a8000-008a9000 r-xp 00000000 08:01 2097292 /tmp/t/libshared.so (deleted) 008a9000-008aa000 r--p 00000000 08:01 2097292 /tmp/t/libshared.so (deleted) 008aa000-008ab000 rw-p 00001000 08:01 2097292 /tmp/t/libshared.so (deleted)
主程序继续运行良好。 再一次,这是因为只有名称(dentry)从磁盘中删除,而不是实际的内容(inode)。 删除后,可以安全地创建名为libshared.so的新文件,而不影响正在运行的程序。
所以,总结一下 – 只需使用install命令来安装程序和二进制文件即可。
问题B
是的,这是由用户空间中的动态链接器打印的。
#include <stdio.h> #include <unistd.h> int main(int argc, char **argv) { execl("./main", "main", NULL); printf("exec failed?\n"); return 0; }
使用gcc -Wall -o execit execit.c
编译它。 请记住, execl
用指定的命令替换当前进程。
$ ./execit main: error while loading shared libraries: libshared.so: cannot open shared object file: No such file or directory $ rm main $ ./execit exec failed?
发生了什么,它告诉我们什么? 首先, error while loading shared libraries
没有exec failed?
情况下error while loading shared libraries
exec failed?
。 没有“执行失败”表明该过程已被成功替换。 这意味着内核将控制权移交给失败的动态链接器。 “主”被删除后,它提前失败,并不会被取代。
答:是的,一旦共享库映射到内存,你不能再取代它。 甚至可能系统已经为某个其他进程加载了以前版本的lib,并检测到已经映射到内存,并将其重新映射为启动进程的一部分。 这就是为什么你必须重新启动(甚至是* nixes)关键更新后;)
到B:可执行文件使用的符号记录在二进制文件的符号表中。 系统加载程序扫描此表并尝试解析所需函数的地址。 如果找不到,就会出现这个错误。 所以答案是,这个消息是由动态链接加载器产生的。
一个。 对。 在这种情况下,您必须使用dl_*()
函数尽快关闭文件。
湾 如果您替换所述文件,并且它不包含所需的符号,加载失败,您会收到上述错误。