>>转载请注明来源:
飘零的代码 piao2010 ’s blog
,谢谢!^_^
>>本文链接地址:
Linux共享库(so)动态加载和升级
学习Linux共享库动态加载缘于一个生产环境升级apache so文件常见错误操作:apache在运行中直接cp覆盖目标so文件,一段时间后错误日志里面出现关键词:
Segmentation fault (段错误)
,一个个worker进程就这样渐渐退出,最后无法处理HTTP请求。
首先了解一下共享库的创建,
源文件test.c
#include<stdio.h>
#include<unistd.h>
void
test1
(
void
)
{
printf
(
"This is do test1
\n
"
)
;
sleep
(
10
)
;
printf
(
"End of test1
\n
"
)
;
}
void
test2
(
void
)
{
printf
(
"This is do test2
\n
"
)
;
sleep
(
10
)
;
printf
(
"End of test2
\n
"
)
;
}
|
执行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
#include <stdio.h>
int
main
(
)
{
test1
(
)
;
test2
(
)
;
return
0
;
}
|
动态库链接:gcc -o main2 -L . -ltest main2.c 生成二进制程序main2
参数含义:
-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
#include <stdio.h>
#include <dlfcn.h> /* 必须加这个头文件 */
int
main
(
)
{
void
*
lib_handle
;
void
(
*
fn1
)
(
void
)
;
void
(
*
fn2
)
(
void
)
;
char
*
error
;
lib_handle
=
dlopen
(
"libtest.so"
,
RTLD_LAZY
)
;
if
(
!
lib_handle
)
{
fprintf
(
stderr
,
"%s
\n
"
,
dlerror
(
)
)
;
return
1
;
}
fn1
=
dlsym
(
lib_handle
,
"test1"
)
;
if
(
(
error
=
dlerror
(
)
)
!=
NULL
)
{
fprintf
(
stderr
,
"%s
\n
"
,
error
)
;
return
1
;
}
fn1
(
)
;
fn2
=
dlsym
(
lib_handle
,
"test2"
)
;
if
(
(
error
=
dlerror
(
)
)
!=
NULL
)
{
fprintf
(
stderr
,
"%s
\n
"
,
error
)
;
return
1
;
}
fn2
(
)
;
dlclose
(
lib_handle
)
;
return
0
;
}
|
接口函数介绍:
(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操作:
省略前面
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
+++
|
SIGSEGV信号估计和mmap只读映射之后写入(覆盖)文件有关?
详见续篇
《为何cp覆盖进程的动态库(so)会导致coredump》
。
参考资料:
http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
http://hi.baidu.com/luoxsbupt/item/a9d346b7653a2771254b09bc

