共享库的动态加载/卸载

这份文档来自<<Advanced Linux
Programming>>,解释如何动态加载共享库,使用这

种技术你可以在程序中精确控制加载某个共享库。本文同时讨论了共享库中符号解析

问题。



dlopen( "libtest.so", RTLD_LAZY );



这个调用将打开共享库libtest.so,第二形参通常都是RTLD_LAZY。为了使用dlopen

函数,需要包含<dlfcn.h>头文件,指定链接开关-ldl。



假设libtest.so中定义了函数my_function



--------------------------------------------------------------------------

void *handle;

void ( *test ) ( void );



handle = dlopen( "libtest.so", RTLD_LAZY );

test = dlsym( handle, "my_function" );

test();

dlclose( handle );

--------------------------------------------------------------------------



dlsym()系统调用还可用于获取指向共享库中某个静态变量的指针。dlopen与dlsym调

用失败时均返回NULL,此时可以调用dlerror(没有形参)获取相应的可读错误信息。



dlclose函数用于卸载共享库。技术上,dlopen只在一个共享库尚未被加载的情况下

真正加载它。如果一个共享库已经被加载了,dlopen简单地递增该共享库引用计数。

类似的,dlclose递减共享库引用计数,当引用计数达到零时卸载共享库。



如果你是用C++编写共享库,而又想用dlsym访问其中的函数、静态变量,可能需要这

样定义:



--------------------------------------------------------------------------

#ifdef __cplusplus

extern "C"

{

#endif



static char my_char;

static void my_funtion ( void );



#ifdef __cplusplus

}

#endif

--------------------------------------------------------------------------



这使得C++编译器保持my_char、my_funtion的原始名字。对于C编译器不存在该问题。



一个共享库可能会引用外部定义的函数和变量。假设你用dlopen打开这样一个共享库。

如果指定了RTLD_LAZY,Linux不会在立即解析未定义的符号名,直到共享库中代码第

一次试图引用未定义的符号名,Linux才开始解析它。如果解析成功,程序继续执行,

反之显示错误信息并终止程序。如果指定了RTLD_NOW,Linux会在调用dlopen时立即

解析未定义的符号名。解析失败时dlopen返回NULL。



那么Linux根据什么解析未定义的符号名呢,有这么几种情况:



如果主程序引出(exports)任意动态符号(共享库正是这样做的),则这些符号自然可

用。然而缺省情况下,常规可执行程序不会以动态符号形式引出它们的函数和变量名。

为了达到这个效果,可以在编译时指定"-Wl,-export-dynamic",这实际是链接选项。



如果主程序编译时选择了动态链接,则dlopen打开的库可以引用编译时共享库中的符

号。



如果主程序使用dlopen打开共享库A、B,缺省情况下A与B彼此不能引用对方定义的动

态符号。但是可以在dlopen第二形参上逻辑或一个RTLD_GLOBAL标志,使得相应共享

库中动态符号全局可见。



--------------------------------------------------------------------------

#include <stdio.h>

#include <stdlib.h>

#include <assert.h>

#include <dlfcn.h>



int main ( int argc, char * argv[] )

{

void *handle;

void ( *foo ) ( void );



handle = dlopen( "libfoo.so", RTLD_LAZY );

foo = dlsym( handle, "foo" );

foo();

dlclose( handle );

return( EXIT_SUCCESS );

} /* end of main */

--------------------------------------------------------------------------



假设foo()调用了另一个函数bar(),而bar()不是libfoo.so中定义的,怎么办。



一种办法是在你的主程序里包含bar()函数体,然后指定"-Wl,-export-dynamic"编译

主程序:



$ cc -Wl,-export-dynamic -o main main.c bar.c -ldl



第二种办法是将bar()放到单独一个共享库里,编译时动态链接进主程序



$ cc -fPIC -shared -o libbar.so bar.c

$ cc -o main main.c libbar.so -ldl



第三种办法是将bar()放到单独一个共享库里,运行时由main函数调用dlopen动态加

载。注意,dlopen的第二形参中必须逻辑或RTLD_GLOBAL标志,否则libfoo.so看不到

libbar.so引出的动态符号。



--------------------------------------------------------------------------

void *bar_handle;



bar_handle = dlopen( "libbar.so", RTLD_LAZY | RTLD_GLOBAL );

--------------------------------------------------------------------------



Q: 我试图用dlopen()打开一个共享库,该共享库使用了一个extern型变量,后者位

于主调二进制文件中。但是这个dlopen()调用失败了,报告存在无法解析的外部

符号。我用的是gcc(Cygnus version 2.9),怎么解决这个问题。



--------------------------------------------------------------------------

/*

* Library testlib.c

* gcc -Wall -pipe -O3 -o testlib.o -c testlib.c

* /usr/ccs/bin/ld -G testlib.o -o testlib.so

*/

extern int abc;



void testing ( void )

{

abc = 0;



return;

}



/*

* Main testcall.c

* gcc -Wall -pipe -O3 -o testcall testcall.c -ldl

*/

#include <stdio.h>

#include <stdlib.h>

#include <ctype.h>

#include <unistd.h>

#include <dlfcn.h>



int abc = -1;



int main ( void )

{

void *testlib;

void ( *testing_call ) ();



fprintf( stderr, "abc = %d/n", abc );

if ( ( testlib = dlopen( "./testlib.so", RTLD_NOW | RTLD_GLOBAL ) ) != NULL )

{

testing_call = dlsym( testlib, "testing" );

( *testing_call )();

fprintf( stderr, "abc = %d/n", abc );

}

return( EXIT_SUCCESS );

}

--------------------------------------------------------------------------



A: flyriver 2001-12-16 22:48



Linux系统中编译主调程序时加上"-rdynamic"参数



Q: 在libfoo.so中实现了func1()、func2(),其中func2()调用了func1()。主调程序

prog.c中包含一个同名函数func1(),但与libfoo.so中func1()有不同形参类型。

prog.c使用dlopen()打开libfoo.so,并调用其中的func2()。我期望此时func2()

仍去调用libfoo.so中的func1(),但现在事实是调用了prog.c中的func1(),怎么

办。



A: Paul Pluzhnikov <ppluzhnikov@earthlink.net> 2003年5月31日 13:05



对于Win32、AIX来说,你所期待的效果是缺省行为,但其它操作系统未必如此。对于

Solaris、FreeBSD、Linux,使用gnu ld生成libfoo.so时应指定-Bsymbolic。缺省情

况下没有指定-Bsymbolic,此时prog.c中的func1()被func2()调用。



A: Mars Rullgard <mru@users.sourceforge.net> 2003年5月30日 17:09



如果libfoo.so没有使用prog.c中符号(变量、函数),使用gnu ld链接prog.c时不要

指定-rdynamic或者-export-dynamic,这样prog.c中func1符号不被引出,func2()就

不会调用func1()。如果还是不能解决问题,设法使func1这个符号成为weak symbol,

假设你正在使用gcc,可以这样做:



int __attribute__((weak)) func1 ( ... )

{

... ...

}
 
Oracle 数据泵(Data Pump)错误 `ORA-31694` 表示在执行导出(expdp)或导入(impdp)操作时,无法加载卸载主表(Master Table),这通常意味着与主表相关的元数据操作失败。以下是可能的原因及解决方案: ### 可能原因及解决方法 #### 1. **权限不足** - 主表的创建和删除需要相应的权限。如果执行用户没有足够的权限,例如对系统表空间的配额限制、缺少 `CREATE TABLE` 或 `UNLIMITED TABLESPACE` 权限,则可能导致此错误。 - 解决方案:确保执行 Data Pump 操作的用户拥有足够的权限。可以授予以下权限: ```sql GRANT CREATE TABLE TO <username>; ALTER USER <username> QUOTA UNLIMITED ON SYSTEM; ALTER USER <username> QUOTA UNLIMITED ON SYSAUX; ``` 或者直接授予 `DBA` 角色以排除权限问题(适用于测试环境): ```sql GRANT DBA TO <username>; ``` #### 2. **主表已存在** - 如果前一次的 Data Pump 操作未正确完成,主表可能仍存在于数据中,导致当前操作无法重新创建该表。 - 解决方法:手动清理残留的主表。查找并删除主表(默认名称为 `SYS_EXPORT_SCHEMA_01`、`SYS_EXPORT_FULL_01` 等): ```sql DROP TABLE <master_table_name>; ``` 可通过以下语句查找当前存在的主表: ```sql SELECT owner, table_name FROM dba_tables WHERE table_name LIKE 'SYS_EXPORT%'; ``` #### 3. **存储空间不足** - 主表存储在用户的默认表空间中,如果该表空间不足,将导致主表创建失败。 - 解决方法:检查并扩展相关表空间的大小,或者更改用户的默认表空间为具有足够空间的表空间: ```sql ALTER USER <username> DEFAULT TABLESPACE <new_tablespace>; ``` #### 4. **版本兼容性问题** - 在某些 Oracle 版本中(如 11.2.0.3),可能存在 Data Pump 相关的 Bug,尤其是在 RAC 环境下运行并行作业时。 - 解决方法:升级到更高版本的 Oracle 数据,如 11.2.0.4 或 12c 及以上版本。此外,应用最新的补丁集(Patch Set)可以修复潜在的问题。 #### 5. **RAC 环境下的配置问题** - 在 RAC 环境中,若目录对象(DIRECTORY)不是共享存储路径,或者并行参数设置不当,也可能导致主表加载失败。 - 解决方法:确保目录对象指向共享 ASM 或 NFS 存储,并且所有节点都能访问该路径。同时,避免使用过高的 `PARALLEL` 值,建议根据实例数合理设置: ```bash expdp system/oracle@orcl schemas=sh directory=dp_shared_dir parallel=3 cluster=y dumpfile=expsh%U.dmp reuse_dumpfiles=y ``` #### 6. **日志文件或转储文件写入失败** - 如果日志文件或转储文件路径不可写或不存在,会导致主表初始化失败。 - 解决方法:确认 DIRECTORY 对象对应的物理路径存在且可读写,且 Oracle 用户对该路径有适当的权限: ```sql SELECT * FROM dba_directories WHERE directory_name = 'DP_SHARED_DIR'; ``` --- ### 示例:完整清理与重试流程 ```bash # 清理残留主表 SQL> DROP TABLE SYS_EXPORT_SCHEMA_01; # 检查并确保目录对象可用 SQL> SELECT * FROM dba_directories WHERE directory_name = 'DP_SHARED_DIR'; # 授予必要的权限 SQL> GRANT READ, WRITE ON DIRECTORY dp_shared_dir TO system; # 重新执行导出命令 $ expdp system/oracle@orcl schemas=sh directory=dp_shared_dir parallel=3 cluster=y dumpfile=expsh%U.dmp reuse_dumpfiles=y ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值