微信扫一扫

028-83195727 , 15928970361
business@forhy.com

Linux下动态链接库技术实现“消息映射表”

linux,C,C++,.SO,动态库加载2016-11-17

题目:

利用动态链接库技术实现具有热插拔能力的“消息映射表”程序。程序在功能上表现为一个计算器程序,主菜单中提示:

Press A:Adding;

Press S:  Subtracting

Press M:  Multiplying

Press D:  Dividing

用户键入A则启动加法子程序,提示用户输入两个操作数,并输出计算结果。用户键入S则启动减法子程序。以此类推。子程序执行完毕之后再回到主菜单状态。

使该程序有热插拔能力是指可以通过配置文件在不改变主程序的前提下动态增加新的菜单项和新的功能(例如增加乘方、开方、指数、对数运算等。)


设计思路:首先,设计并实现程序所需的动态链接库,其次,在主函数中开辟一个线程监控配置文件是否被改变,如果配置文件发生改变则重新读取配置文件中内容。最后,根据用的不同输入加载不同的动态库和函数。根据设计思路,本文从动态库生成、配置文件设计、配置文件监控和动态链接库动态加载四个方面展开介绍。

1、动态链接库的生成

        要想使用动态链接库技术必须要有动态库,因此本节主要介绍linux下动态库的编译与链接。下面以题目中计算器程序为例为大家讲解。文件名为calculate1.c。

#include<stdio.h>
intAdd(int x,int y)
{
int tmp = x + y;
	printf("%d + %d = %d\n", x,y,tmp);
return tmp;
}
intSub(int x,int y)
{
int tmp = x - y;
	printf("%d - %d = %d\n", x,y,tmp);
return tmp;
}
intMultiply(int x,int y)
{
int tmp = x * y;
	printf("%d * %d = %d\n", x,y,tmp);
return tmp;
}
intDivigcc -c -fPIC calculate1.c	gcc -shared -fPIC calculate1.o -o a.sode(int x,int y)
{
int tmp = x / y;
	printf("%d / %d = %d\n", x,y,tmp);
return tmp;
}

在linux下可以输入以下命令:
gcc -c -fPIC calculate1.c
gcc -shared -fPIC calculate1.o -o a.so

在这里解释一下编译命令
-c 就是将源文件calculate.c编译成成目标文件calculate.o。calculate.o为不完整的机器代码,因为这里只是简单的将高级语言转换成机器语言,确定不了函数的起始地址。
-fPIC PIC全称是Position Independent Code(位置无关的代码)。PIC使.so文件的代码段变成真正意义上的共享,PIC在编译阶段告诉编译器产生于位置无关的代码,也就是说在生成的代码中全部使用相对地址,没有绝对地址,可以被加载器加载到内存的任何位置都可以正确执行。如果编译时不加-fPIC,则加载.so代码时需要重定位,重定位就会修改代码内容,造成不便。
另外,由于平台的差异,GCC对shared支持不一样,有的shared包含了-fPIC,有的没有包含。因此,最好显式的加上fPIC。
-o  就是生成目标文件或可执行文件。
运行完上述命令即可看到文件目录下生成a.so文件。

2 、配置文件设计

消息

菜单项提示

所处动态库

函数入口

A

Adding

a.so

Add

S

Subtracting

a.so

Sub

M

Multiplying

a.so

Multiply

D

Dividing

a.so

Divide

P

Power

b.so

Pow

3、配置文件监控与读取

配置文件监控主要用到了linux中inotify机制,它是一个内核用于通知用户空间程序文件系统变化的机制。
在用户态,inotify 通过三个系统调用和在返回的文件描述符上的文件 I/O 操作来使用,使用 inotify 的第一步是创建 inotify 实例:
int fd = inotify_init();

其中,每一个 inotify 实例对应一个独立的排序的队列。

       文件系统的变化事件被称做 watches 的一个对象管理,每一个 watch 是一个二元组(目标,事件掩码),目标可以是文件或目录,事件掩码表示应用希望关注的 inotify 事件,每一个位对应一个 inotify 事件。Watch 对象通过 watch描述符引用,watches 通过文件或目录的路径名来添加。目录 watches 将返回在该目录下的所有文件上面发生的事件。

下面函数用于添加一个 watch:

int wd = inotify_add_watch(fd, path, mask);

     其中,fd 是 inotify_init() 返回的文件描述符,path 是被监视的目标的路径名(即文件名或目录名),mask 是事件掩码, 可以通过修改事件掩码,来改变希望被通知的inotify 事件。Wd 是 watch 描述符。
size_t len = read(fd, buf, BUF_LEN);
      文件事件用一个 inotify_event 结构表示,它通过由 inotify_init() 返回的文件描述符使用通常文件读取函数 read 来获得。
 inotify_event 结构体
struct inotify_event {
        __s32           wd;/* watch descriptor */
        __u32           mask;/* watch mask */
        __u32           cookie;/* cookie to synchronize two events */
        __u32           len;/* length (including nulls) of name */
	char            name[0];/* stub for possible name */
};

buf 是一个 inotify_event 结构的数组指针,BUF_LEN 指定要读取的总长度,buf 大小至少要不小于 BUF_LEN,该调用返回的事件数取决于 BUF_LEN 以及事件中文件名的长度。Len 为实际读去的字节数,即获得的事件的总长度。
以下为配置文监控的完整代码:
void* displayInotifyEventThread(void* arg)
{
int inotifyFd;
int wd;
int j;
int flags;
char buf[BUF_LEN];
ssize_t numRead;
char*p;
struct inotify_event *event;
typedefvoid(*pp)();
	pp read_config =(pp)arg;
	inotifyFd = inotify_init();
if(inotifyFd ==-1)
{
        printf("inotify initual failed\n");
}
	wd = inotify_add_watch(inotifyFd,path,IN_ALL_EVENTS);
if(wd ==-1)
{
        printf("inotify_add_watch error\n");
}
if(DEBUG)
    	printf("Watching %s using wd %d\n",path,wd);
while(1)
{
        numRead = read(inotifyFd,buf,BUF_LEN);
if(numRead ==-1)
{
            printf("read error\n");
}
if(DEBUG)
        	printf("Read %ldbytes from inotify fd\n",(long)numRead);
		read_config();
}
	usleep(10*1000);
}


配置文件的读取比较简单在这里就不多阐述了。以下为配置文件读取代码
void* readConfig()
{
int fd;
int filecount;
char filebuf[4096]={0};
	fd = open(path,O_RDONLY);
	filecount = read(fd,filebuf,4096*sizeof(char));
if(fd !=-1)
{
int proccount =0;
char str[5][50];
MsgMapTable MMT;
		memset(&MMT,0,sizeof(MMT));
int k =0;
for(int i =0; i <5; i++)
{
			memset(str[i],0,50);
}
for(int i =0;i < filecount;i++)
{
if(filebuf[i]!= EOF)
{
if(filebuf[i]!='\n'&& filebuf[i]!=' ')
{
					proccount++;
}else{
					memcpy(str[k],filebuf+i-proccount,proccount);
					k++;
					proccount =0;
}
if(k >3)
{
					memcpy(MMT.OPMODE,str[0],strlen(str[0]));
					memcpy(MMT.OPMODEMEAN,str[1],strlen(str[1]));
					memcpy(MMT.LIBNAME,str[2],strlen(str[2]));
					memcpy(MMT.FUNNAME,str[3],strlen(str[3]));
					string key = MMT.OPMODE;
					msg_map_tables[key]= MMT;
//msg_map_tables.insert(map<string,MsgMapTable>::value_type(key,MMT));
for(int i =0; i <5; i++)
{
						memset(str[i],0,50);
}
if(DEBUG)
{
						cout<<"readConfig:key is "<<key<<"\n";
						cout<<msg_map_tables[key].OPMODE<<" "<<msg_map_tables[key].OPMODEMEAN<<" "<<msg_map_tables[key].LIBNAME<<" "<<msg_map_tables[key].FUNNAME<<"\n";
}
					memset(&MMT,0,sizeof(MMT));
					k =0;
}
}
}
}
}

4、动态链接库动态加载

      动态链接库的加载用到了dlopen()函数,该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。这样可以通过一个配置文件,来控制加载模块的过程。这种机制使得在系统中添加或者删除一个模块时,都不需要重新编译了。
void*dlopen(constchar*file,int mode);
   功能:打开指定的动态链接库文件,并将它读入内存,返回一个句柄给调用进程。
   参数:
       –file: 库文件名称
       –mode: 符号解析时间、作用范围
               RTLD_LAZY: 暂缓决定,等有需要时再解析出符号
               RTLD_NOW: 立即决定,返回前解出所有符号
               RTLD_GLOBAL:动态库中定义的符号可被其后打开的其他库重定位
               RTLD_LOCAL:与RTLD_GLOBAL作用相反,动态库中定义的符号不能被其后打开的其它库重定位,缺省RTLD_LOCAL
void*dlsym(void*handle,constchar*name)
功能:根据动态链接库操作句柄与符号,返回符号对应的地址,可以是函数也可以是变量。
参数:
handle : dlopen返回的动态链接库句柄
name :符号名称,可为函数名或变量名称

int dlclose(void*handle)
功能:关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载


以下为动态链接库加载的完整代码:
void searchMap(char* opMode)
{
char lib_path[20]="./";
map<string ,MsgMapTable>::iterator l_it;
    l_it=msg_map_tables.find(opMode);
if(l_it == msg_map_tables.end())
{
    	printf("opMode is error\n");
return;
}
if(DEBUG)
    	cout<<"l_it->second.LIBNAME is "<<l_it->second.LIBNAME;
	strcat(lib_path,l_it->second.LIBNAME);
void*lib = dlopen(lib_path, RTLD_LAZY);
if(DEBUG)
		cout<<"\nl_it->second.FUNNAME is "<<l_it->second.FUNNAME;
calculate_t calculate  =(calculate_t)dlsym(lib, l_it->second.FUNNAME);
if(DEBUG)
		cout<<"\nnum1 = "<<num1<<" num2 = "<<num2<<"\n";
	calculate(num1,num2);
	dlclose(lib);
}

运行结果

未加载b.so





加载b.so