`
jakielong
  • 浏览: 228455 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Unix/Linux环境下创建和使用静/动态库

阅读更多

库的作用

  大体上库的存在,有两方面的原因,一是代码的复用,二是声明和实现的分离。将功能相近的使用模块封装成库,使代码的复用、管理和分发变得简单了许多,例如著名的开源图形库ncurses,你可以自行编译,更可以直接使用已经编译好的现成的库文件。另外,由于库是二进制文件,某种意义上讲,将功能的实现部分隐藏了起来,这就为商业代码的保护提供了一种方式。
  库文件按照链接方式和时机,可以分为动态库和静态库,下面分别介绍它们在Linux环境中的创建和使用方法。

静态链接库

  静态库是指在程序的链接阶段,其中被用到的代码会被直接链接到可执行文件中的库。静态链接的可执行程序包含了其所需的全部库函数:所有库函数都连接到程序中。 这类程序是完整的,其运行不需要外部库的支持。 静态链接程序的优点之一是其安装之前不需要做环境准备工作 。
  Linux中,静态库的扩展名通常为.a,它仅仅是一些目标文件(.o)的归档(archive)或者说打包,另外,为了链接时能够快速地定位其中的符号(函数、变量名等),静态库还会包含一个对其中符号的索引。
  创建静态库的过程十分简单,除编译所必须的工具之外,要用到的命令只有两个ar和ranlib。ar可以将各个目标文件进行归档,ranlib对ar生成的归档文件(即静态库文件)进行索引。假设现在有这样几个源文件:plus.c, sub.c及相应的头文件,另外还有一个用来调用库文件中函数的主文件main.c,它们的内容分别是:
plus.c,

#include "plus.h"
int
plus(int a, int b)
{
    return a + b;
} 

 sub.c,

#include "sub.h"
int
sub(int a, int b)
{
    return a - b;
} 

 main.c,

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "plus.h"
int
main(int argc, char **argv)
{
    int a = plus(1, 2);
    printf("%d\n", a);
    return 0;
} 

 下面将plus.c和sub.c编译,制作成静态库libmath.a,依次执行:

$ cc main.c -L. -lmath -o main

$ ./main

动态链接库

  动态库和静态库相似,也是各个目标文件的集合。但相比静态链接的程序,动态链接可执行程序要小得多:这类程序运行时需要外部共享 函数库的支持,因此好像并不完整。除了程序体小之外,动态链接允许程序包指定必须的库,而不必将库装入程序包内。动态链接技术还允许多个运行中的程序共享一个库,这样就不会出现同一代码的多份拷贝共占内存的情况了。由于这些原因,当前多数程序采用动态链接技术。
  在Linux中的扩展名通常为.so。但在链接时,并不会被链接到可执行文件中,而是在执行时(需要时)由操作系统的动态加载模块动态地加载到内存,并链接到可执行文件地址空间的相应位置。
  动态链接库的创建也分为编译和”归档”两个阶段,但不同的是在这两个阶段需要使用一些不同的命令选项。首先,需要将源文件编译成一种成为位置无关码(PIC: Position Independent Code)的目标文件,这种代码可以被加入到内存的任何位置却不需要加载器对其进行重定位,关于这种格式可以参考《链接器与加载器》和《程序员的自我修养–链接装载与库》中较为详尽的描述。接下来需要将这些位置无关码“归档”为.so文件。整个过程只需一个工具即可,即gcc。还是上面的源文件,执行以下命令:

$ cc -c -fpic plus.c sub.c
$ cc -shared -o libmath.so *.o

这样,包含plus.o和sub.o的动态库文件libmath.so就创建完成了。其中cc(gcc的符号链接)命令的-fpic或-fPIC选项使之创建位置无关的目标文件,使用-shared选项可以创建最终的动态库文件。
  使用动态库文件有两种方法,一种是让操作系统的动态加载模块(如ld-linux.so.2)在执行时加载动态库。另一种是在代码中使用dl库动态加载库文件。
  先介绍下第一种方法。使用这种方法需要在编译可执行文件时指明库名及其路径(对于自己的编写的动态库而言):


$ cc main.c -L. -lmath -o main

  这时可执行文件main就被创建了,上面的命令并没有将libmath.so中相应的目标代码链接到main中(你可以对比一下这里的main和静态链接的main的大小),只是在库中查找和确认main.c中用到的符号而已。但通常这个main现在还无法正常执行,这涉及到系统查找动态库的路径问题,系统通常只在某些指定的目录(标准路径)下查找所需的库文件,若在标准路径中无法找到所需的库,则会到环境变量LD_LIBRARY_PATH(如果存在的话)指定的目录下查找,若仍无法找到就会报错。因为libmath.so在main的当前目录中,而当前目录通常并不在标准路径之列。为了使libmath.so能够被找到和加载,你可以把它放到标准路径中,但更好的方法是将其所在目录加入到LD_LIBRARY_PATH变量中。执行下面的命令:


$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. # 添加当前目录
$ export LD_LIBRARY_PATH # 将环境变量导出,使其在子shell中可用
$ ./main
3

  
  下面介绍使用dl库加载动态库,dl库中函数很少很简练,看main.c代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include "plus.h"
int
main(int argc, char **argv)
{
    void * lib_handle = dlopen("./libmath.so", RTLD_LAZY);
    if(lib_handle)
    {
        int (*plus_ptr)(int, int) = dlsym(lib_handle, "plus");
        if(plus_ptr)
        {
            int a = plus_ptr(1, 2);
            printf("%d\n", a);
        }
        dlclose(lib_handle);
    }
    return 0;
} 

 main.c中,首先使用dlopen打开需要的动态库,其中参数RTLD_LAZY指明仅当需要调用该库时才进行加载。dlopen返回一个句柄,dlsym使用该句柄和符号来取得相应函数的地址,这里使用int (*)(int, int)函数指针来接收plus函数的地址。接下来使用得到的函数指针调用相应的函数,最后通过dlclose函数来关闭句柄。编译这个程序需要使用dl动态链接库,因此需要使用gcc的-ldl选项:

$ cc main.c -ldl -o main

$ ./main

so与.a文件的对比

  最后附上libmath.a和libmath.so文件的内容,使用objdump的-d选项查看的:
libmath.a,

In archive libmath.a:

plus.o:     file format elf32-i386
Disassembly of section .text:
00000000 <plus>:
   0:	55                   	push   %ebp
   1:	89 e5                	mov    %esp,%ebp
   3:	8b 45 0c             	mov    0xc(%ebp),%eax
   6:	8b 55 08             	mov    0x8(%ebp),%edx
   9:	8d 04 02             	lea    (%edx,%eax,1),%eax
   c:	5d                   	pop    %ebp
   d:	c3                   	ret    

sub.o:     file format elf32-i386
Disassembly of section .text:
00000000 <sub>:
   0:	55                   	push   %ebp
   1:	89 e5                	mov    %esp,%ebp
   3:	8b 45 0c             	mov    0xc(%ebp),%eax
   6:	8b 55 08             	mov    0x8(%ebp),%edx
   9:	89 d1                	mov    %edx,%ecx
   b:	29 c1                	sub    %eax,%ecx
   d:	89 c8                	mov    %ecx,%eax
   f:	5d                   	pop    %ebp
  10:	c3                   	ret

libmath.so:

libmath.so:     file format elf32-i386
Disassembly of section .init:
00000324 <_init>:
 324:	55                   	push   %ebp
 325:	89 e5                	mov    %esp,%ebp
 327:	53                   	push   %ebx
 328:	83 ec 04             	sub    $0x4,%esp
......
0000044c <plus>:
 44c:	55                   	push   %ebp
 44d:	89 e5                	mov    %esp,%ebp
 44f:	8b 45 0c             	mov    0xc(%ebp),%eax
 452:	8b 55 08             	mov    0x8(%ebp),%edx
 455:	8d 04 02             	lea    (%edx,%eax,1),%eax
 458:	5d                   	pop    %ebp
 459:	c3                   	ret
 45a:	90                   	nop
 45b:	90                   	nop

0000045c <sub>:
 45c:	55                   	push   %ebp
 45d:	89 e5                	mov    %esp,%ebp
 45f:	8b 45 0c             	mov    0xc(%ebp),%eax
 462:	8b 55 08             	mov    0x8(%ebp),%edx
 465:	89 d1                	mov    %edx,%ecx
 467:	29 c1                	sub    %eax,%ecx
 469:	89 c8                	mov    %ecx,%eax
 46b:	5d                   	pop    %ebp
 46c:	c3                   	ret
 46d:	90                   	nop
 46e:	90                   	nop
 46f:	90                   	nop
......

Disassembly of section .fini:

000004a8 <_fini>:
 4a8:	55                   	push   %ebp
 4a9:	89 e5                	mov    %esp,%ebp
 4ab:	53                   	push   %ebx
 4ac:	83 ec 04             	sub    $0x4,%esp
 4af:	e8 00 00 00 00       	call   4b4 <_fini+0xc>
 4b4:	5b                   	pop    %ebx
 4b5:	81 c3 40 1b 00 00    	add    $0x1b40,%ebx
 4bb:	e8 d0 fe ff ff       	call   390 <__do_global_dtors_aux>
 4c0:	59                   	pop    %ecx
 4c1:	5b                   	pop    %ebx
 4c2:	c9                   	leave
 4c3:	c3                   	ret

  可见,相对.a,.so有很多额外的代码段,其中比较重要的是<_init>段和<_fini>段。它们分别进行一些前期和后期处理工作,例如<_init>通常在dlopen返回之前执行一些.so库中的一些初始化工作(C++中就可能是全局构造或者静态对象的构造函数)。

转自:http://www.dutor.net/index.php/2010/07/dynamic-static-library/

分享到:
评论

相关推荐

    Unix/linux进程池管理

    在Unix/Linux环境下,创建进程池通常使用`fork()`系统调用,通过复制父进程来创建子进程。`fork()`成功后,父进程继续处理新的请求,而子进程则成为工作进程。另外,`pthread`库也可以用于创建线程池,但线程池与...

    Understanding unix/linux programming源代码

    《理解Unix/Linux编程源代码》是Bruce Molay撰写的一本实战型教程,旨在帮助读者深入理解和掌握Unix/Linux系统下的编程技巧。这本书通过丰富的实例和源代码解析,将复杂的系统编程概念化,使得初学者和有经验的...

    Unix/Linux 编程实践教程

    《Unix/Linux 编程实践教程》是一本深入探讨Unix和Linux操作系统编程的宝贵资源,旨在帮助读者理解系统组件的工作原理并掌握实际编程技巧。通过本书的学习,你可以深入了解这两个广泛使用的开源操作系统,从而提升你...

    UNIX/LINUX平台C函数库手册

    在UNIX和LINUX操作系统中,C函数库是编程的基础,为开发者提供了丰富的系统调用和标准库函数。这篇手册详细介绍了这些函数库,是C语言学习者和实践者的重要参考资料。下面将对其中的关键知识点进行深入阐述。 1. **...

    unix/linux下c/c++ 函数速查手册

    在Unix/Linux环境下,C和C++编程是系统级开发的核心技术。这个"UNIX/Linux下C/C++函数速查手册"提供了全面的C和C++函数参考,帮助开发者快速找到所需的函数信息,提升开发效率。以下是一些关键的知识点: 1. **...

    unix/linux进程池管理

    在Unix/Linux操作系统中,进程池(Process Pool)是一种高效的资源管理策略,用于处理大量并发请求。它通过预先创建一组固定数量的子进程来提供服务,而不是每次请求到来时都创建新的进程,这样可以减少进程创建和...

    linux/unix系统编程手册附录代码

    《Linux/Unix系统编程手册》是一本深受程序员和系统管理员喜爱的经典著作,它详细阐述了在Linux和Unix操作系统上进行低级编程的各种技术。附录中的代码是书中的实例,旨在帮助读者深入理解和掌握这些系统编程的核心...

    Linux/UNIX系统编程手册课后习题代码

    《Linux/UNIX系统编程手册》是一本经典的教材,它深入浅出地介绍了Linux和UNIX操作系统下的编程接口。这本书涵盖了各种核心API,包括文件操作、进程管理、网络通信、信号处理等,是学习系统级编程的重要参考资料。...

    linux下C语言开发笔记整理

    Linux下C语言开发笔记整理涵盖了从基础知识到网络通信的多个方面,主要围绕在Unix/Linux系统环境下使用C语言进行软件开发的各项技术与理论。以下是从文件提供的信息中提炼的知识点。 ### Unix/Linux系统基本命令和...

    Unix和Linux下的Oracle数据库管理

    本文将深入探讨在Unix和Linux环境下如何有效地管理和维护Oracle数据库。 一、Oracle数据库基础 Oracle数据库是一种关系型数据库管理系统(RDBMS),它支持SQL语言,提供了数据存储、查询、更新和管理的强大功能。在...

    Linux和Unix下的动态库和静态库编写方法

    总的来说,理解和掌握Linux和Unix下的静态库和动态库的创建和使用方法,对于进行系统级编程和软件开发至关重要。正确选择和管理库可以帮助优化程序性能,减少资源消耗,同时提高代码的复用性和维护性。

    UNIX-Linux系统编程 光盘部分 下载

    6. **线程编程**:多线程是现代程序设计的常见模式,理解和使用pthread库进行线程管理,如线程创建、同步(互斥锁、条件变量)、通信(线程间信号)等。 7. **内存管理**:了解动态内存分配(malloc、calloc、...

    UnixLinux实践教程 源码

    9. **编译与构建**:GCC是Unix/Linux下的主要编译器,Makefile用于自动化编译过程,而编译链接时的动态库和静态库也是需要理解的部分。 10. **课后习题解答**:这部分通常包含对上述知识点的实践应用,通过解决实际...

    Linux_UNIX系统编程手册(上).pdf

    1. **Shell和命令行**:讲解如何使用Bash shell以及命令行工具,这是Linux和UNIX系统中的基本操作环境。 2. **文件I/O**:详述文件操作函数,如open(), read(), write(), close()等,以及文件描述符的概念。 3. **...

    ch08unix-linux编程实践.rar

    本章主要探讨了在Unix/Linux环境下进行程序设计的关键概念和技术,包括系统调用、进程管理、文件I/O、网络编程等方面。 1. **系统调用**:Unix/Linux系统的功能通过系统调用来实现,如创建进程、读写文件、网络通信...

    UNIX∕Linux系统与软件开发环境(本科)指导书.doc

    在UNIX/Linux环境下进行C语言编程时,集成MySQL数据库能够提供数据存储和处理能力。要进行MySQL数据库开发,首先需要安装相应的软件包,包括MySQL服务器(如mysql-server-3.23.54a-11)、客户端(如mysql-3.23.54a-...

    unix_linux c不错的函数库,请下载.zip

    9. **线程与同步**:在Unix/Linux下,`pthread`库提供了线程创建、同步和互斥锁等功能,如`pthread_create`、`pthread_join`、`pthread_mutex_lock`等。 10. **网络编程**:`socket`函数用于创建套接字,`bind`、`...

    UNIX Linux 系统下C 语言程序设计

    在UNIX和Linux系统中,C语言作为一种强大的编程工具,被广泛用于系统开发、软件构建以及各种应用程序的编写。本文将深入探讨在这些操作系统环境下进行C语言程序设计的基础知识和实践技巧。 一、编译器与环境搭建 在...

    unix环境高级编程

    《Unix环境高级编程》是Unix/Linux系统开发领域的一本经典著作,它深入讲解了在Unix/Linux环境下进行系统级编程的各种技术和方法。对于任何希望在这一领域深入研究的程序员来说,这本书都是不可或缺的参考资料。 ...

Global site tag (gtag.js) - Google Analytics