>>本文链接地址: Linux共享库(so)动态加载和升级

学习Linux共享库动态加载缘于一个生产环境升级apache so文件常见错误操作:apache在运行中直接cp覆盖目标so文件,一段时间后错误日志里面出现关键词: Segmentation fault (段错误) ,一个个worker进程就这样渐渐退出,最后无法处理HTTP请求。
首先了解一下共享库的创建, 源文件test.c

? View Code C

执行gcc -fPIC -shared -o libtest.so test.c 会生成共享库文件 libtest.so
参数含义:
-fPIC/-fpic: Compiler directive to output position independent code, a characteristic required by shared libraries. 创建共享库必须的参数
-shared: Produce a shared object which can then be linked with other objects to form an executable.

然后使用共享库:源文件main2.c

? View Code C -L 指定动态库目录为当前目录
-l 指定动态库名test,不要写libtest.so

执行main2程序发现报错: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
原因是共享库不在系统默认的路径里面,可以在shell执行 export LD_LIBRARY_PATH=./ 添加当前路径或者在 /etc/ld.so.conf 增加路径并ldconfig生效
执行main2成功输出:
This is do test1
End of test1
This is do test2
End of test2

接下来是主角:动态加载,源文件main.c

? View Code C (1) dlopen
函数原型:void *dlopen(const char *libname,int flag);
功能描述:dlopen必须在dlerror,dlsym和dlclose之前调用,表示要将库装载到内存,准备使用。
如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。
参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库名称,在dlopen会按照下面的机制去搜寻:
a.根据环境变量LD_LIBRARY_PATH查找
b.根据/etc/ld.so.cache查找
c.查找依次在/lib和/usr/lib目录查找。
flag参数表示处理未定义函数的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内 存,等用到没定义的函数再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。

(2) dlerror
函数原型:char *dlerror(void);
功能描述:dlerror可以获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。

(3) dlsym
函数原型:void *dlsym(void *handle,const char *symbol);
功能描述:在dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。
如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数,

(4) dlclose
函数原型:int dlclose(void *);
功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。

编译gcc -o main main.c -ldl 生成二进制程序main,执行输出
This is do test1
End of test1
This is do test2
End of test2

到这里共享库动态加载就介绍完了:)
最后模拟一下升级so故障:
在执行main的时候,趁sleep期间cp 另外的so文件覆盖libtest.so,一会就出现Segmentation fault。
但是如果是mv 另外的so文件覆盖libtest.so,则无此问题,或者先rm libtest.so 再cp/mv 也不会有问题,因此升级方法就是这两种,当然最好是先停应用再升级。 至于原因,可以参考我前一篇博客 《Linux cp mv rm ln 命令对于 inode 和 dentry 的影响》
12.5更新:
今天咨询了维扬同学,可以用strace观察程序运行期间的系统调用,发现有不少mmap操作:

? View Code C open ( "/root/so/libtest.so" , O_RDONLY ) = 3 read ( 3 , " \177 ELF \1 \1 \1 \3 \0 \0 \0 \0 \0 \0 \0 \0 \3 \0 \3 \0 \1 \0 \0 \0 \240 \3 \0 \000 4 \0 \0 \0 " ... , 512 ) = 512 brk ( 0 ) = 0x8227000 brk ( 0x8248000 ) = 0x8248000 fstat64 ( 3 , { st_dev = makedev ( 253 , 0 ) , st_ino = 17559 , st_mode = S_IFREG | 0755 , st_nlink = 1 , st_uid = 0 , st_gid = 0 , st_blksize = 4096 , st_blocks = 16 , st_size = 4348 , st_atime = 2012 / 05 / 13 - 14 : 13 : 18 , st_mtime = 2012 / 05 / 13 - 14 : 13 : 01 , st_ctime = 2012 / 05 / 13 - 14 : 13 : 01 } ) = 0 mmap2 ( NULL , 5772 , PROT_READ | PROT_EXEC , MAP_PRIVATE | MAP_DENYWRITE , 3 , 0 ) = 0x6e6000 mmap2 ( 0x6e7000 , 4096 , PROT_READ | PROT_WRITE , MAP_PRIVATE | MAP_FIXED | MAP_DENYWRITE , 3 , 0 ) = 0x6e7000 close ( 3 ) = 0 munmap ( 0xb7753000 , 15020 ) = 0 fstat64 ( 1 , { st_dev = makedev ( 0 , 11 ) , st_ino = 3 , st_mode = S_IFCHR | 0620 , st_nlink = 1 , st_uid = 0 , st_gid = 5 , st_blksize = 1024 , st_blocks = 0 , st_rdev = makedev ( 136 , 0 ) , st_ atime = 2012 / 05 / 13 - 14 : 56 : 03 , st_mtime = 2012 / 05 / 13 - 14 : 56 : 03 , st_ctime = 2012 / 05 / 13 - 14 : 53 : 31 } ) = 0 mmap2 ( NULL , 4096 , PROT_READ | PROT_WRITE , MAP_PRIVATE | MAP_ANONYMOUS , - 1 , 0 ) = 0xb7756000 write ( 1 , "This is do test1 \n " , 17 ) = 17 rt_sigprocmask ( SIG_BLOCK , [ CHLD ] , [ ] , 8 ) = 0 rt_sigaction ( SIGCHLD , NULL , { SIG_DFL , [ ] , 0 } , 8 ) = 0 rt_sigprocmask ( SIG_SETMASK , [ ] , NULL , 8 ) = 0 nanosleep ( { 10 , 0 } , 0xbfd63fe4 ) = 0 write ( 1 , "End of test1 \n " , 13 ) = 13 write ( 1 , "This is do test2 \n " , 17 ) = 17 rt_sigprocmask ( SIG_BLOCK , [ CHLD ] , [ ] , 8 ) = 0 rt_sigaction ( SIGCHLD , NULL , { SIG_DFL , [ ] , 0 } , 8 ) = 0 rt_sigprocmask ( SIG_SETMASK , [ ] , NULL , 8 ) = 0 nanosleep ( { 10 , 0 } , 0xbfd63fe4 ) = 0 --- SIGSEGV ( Segmentation fault ) @ 0 ( 0 ) --- +++ killed by SIGSEGV +++ 详见续篇 《为何cp覆盖进程的动态库(so)会导致coredump》

参考资料:
http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
http://hi.baidu.com/luoxsbupt/item/a9d346b7653a2771254b09bc