41.Linux/Unix 系统编程手册(下) -- 共享库基础
共享库是一种将库函数打包成一个单元使之能够在运行时被多个进程共享的技术。
这种技术能够节省磁盘空间和RAM。1.目标库链接器实际上是由一个单独的链接器程序ld来完成的。当使用cc(或gcc)命令链接一个程序时,编译器会在幕后调用ld.在Linux上总是应该通过gcc间接的调用链接器,因为gcc能够确保使用正确的选项来调用ld并将程序与正确的库文件链接起来。很多情况下,源代码文件也可以被多个程序共享。因此要降低工作量的第一步就是将这些源码文件只编译一次,然后在需要的时候将他们链接进不同的可执行文件。其缺点是在链接的时候仍然需要为所有目标文件命名。为解决这一问题,可以将一组目标文件组织成一个被称为对象库的单元。对象库分为2种:静态的和动态的。2.静态库静态库也被称为归档文件,好处:1.可以将一组经常被用到的目标文件组织进单个库文件,这样就可以使用它来构建多个可执行程序并且在构建各个应用的时候无需重新编译原来的源代码文件。2.链接命令变得简单了。在链接命令行中只需要指定静态库的名称即可,而无需一个个的列出目标文件了。链接器知道如何搜索静态库并将可执行程序需要的对象抽取出来。创建和维护静态库:从结果上说,静态库实际上就是一个保存所有被添加到其中的目标文件的副本文件。这个归档文件还记录着每个目标文件的各种特性,包括文件的权限,数字用户,组ID以及最后修改时间。根据惯例,静态库的名称形式为 libname.a 。使用 ar(1) 命令能够创建和维护静态库 :ar options archive object-file ...options 参数是由一些列的字母构成,其中一个是操作代码,其他是能够影响操作的执行的修饰符。下面是常用的操作码:1. r(替换, 创建)ar r libdemo.a foo.o boo.o2. t(列出目标文件) ar t libdemo.a 3. d(删除)使用静态库:将静态库与程序链接起来的方式有2种 : 1.在链接命令中指定静态库的名称,如gcc -g -o prog prog.o libdemo.a2.将静态库放在链接器搜索的其中一个标准目录中(如 /usr/lib) ,然后使用 -l选项指定库名(即库的文件名出去了 lib 前缀和 .a 后缀)gcc -g -o prog prog.o -ldemo如果库不在链接器搜索的目录中,那么可以只要 -L 选项指定链接器应该搜索额外的目录gcc -g -o prog prog.o -L目录 -ldemo3.共享库将程序与静态库链接起来,得到的可执行文件会包含所有被链接进程序的目标文件的副本。当有几个不同的可执行程序使用了同样的目标模块时,每个可执行程序都会拥有自己的目标目标模块的副本。这种代码冗余有几个缺点:1.多个副本会浪费磁盘空间2.每个程序会独立的在虚拟内存中保存一份目标模块的副本,从而提高了系统在虚拟内存的整体使用量3.如果要修改静态库中的一个目标模块,那么所有使用那个模块的可执行程序都必须要重新进行链接以合并这个变更。共享库就是设计来解决这些缺点的。共享库的关键思想是目标模块的单个副本由所有需要这些模块的程序共享。目标模块不会被链接进可执行程序中。相反,当第一个需要共享库中的模块的程序启动的时候,库的单个副本就会在运行时被加载进内存。而后面使用同一共享库的其他程序启动时,它们会使用已经被加载进内存的库的副本。还有以下其他优势:1.由于整体程序的大小变小了,因此在一些情况下,程序可以完全被加载进内存中从而更快的启动程序。第一个加载共享库的程序实际上在启动时会花费更多的时间。2.由于目标模块没有被复制进可执行文件中,而是在共享库中集中维护,因此修改目标模块时,无需重新链接程序就能看到变更。缺点:1.共享库比静态库更复杂2.共享库在编译时必须要使用位置独立的代码,这在大多数架构上都会有性能开销,因为它额外需要一个寄存器。3.在运行时必须要执符号重定位。在符号重定位期间,需要将对共享库中的每个符号(变量或者函数)的引用修改成符号在虚拟内存中的实际运行时位置。由于存在这个重定位的过程,与静态库相比,一个使用共享库的程序或多或少需要花费一些时间执行这个过程。4.创建一个共性库gcc -g -c -fPIC -Wall a.c b.c c.cgcc -g -shared -o libfoo.so a.o b.o c.o -shared 命令创建了一个包含目标模块的共享库。根据惯例,共享库的前缀为 lib,后缀为 .so(shared object)5.位置独立的代码:-fPIC 选项指定编译器应该生成位置独立的代码,这会改变编译器生成执行特定操作的代码的方式。包括访问全局,静态和外部变量,访问字符串常量,以及获取函数的地址。这些变更使得代码可以在运行时被放置在任意一个虚地址处。这一点对于共享库来讲是必须的,因为在链接的时候是无法知道共享库代码位于内存的何处的。(一个共享库在运行时所处的内存位置依赖于很多因素,如加载这个库的程序已经占用的内存数量和这个程序已经加载的其他共享库。)为了确定一个既有目标文件在编译时是否使用了 -fPIC 选项,可以使用如下命令:nm mod1.o | grep _GLOBAL_OFFSET_TABLE_readelf -s mod1.o | grep _GLOBAL_OFFSET_TABLE_6.使用共享库使用一个共享库需要做2件事情,而使用静态库则不用:1.由于可执行文件不再包含它所需的目标文件的副本,因此它必须要通过某种机制找出在运行时所需的共享库。这是通过在链接阶段将共享库的名称嵌入可执行文件中来完成的。(在 ELF中,库依赖性是记录在可执行文件的 DT_NEEDED 标签中的)一个程序所依赖的所有共享库列表被称为程序的动态依赖列表。在运行时必须要存在某种机制解析嵌入的库名---即找出与在可执行文件中指定的名称对应的共享库文件--接着如果库不在内存中的话就将库加载进内存。将程序与共享库链接起来时会自动将库的名字嵌入可执行文件中。2.动态链接,即在运行时解析内嵌的库名。这个任务是由动态链接器(也称为动态加载器或运行时链接器)来完成的。动态链接器本身也是一个共享库,其名称为 /lib/ld-linux.so.2, 所有使用共享库 ELF 可执行文件都会用到这个共享库。路径名 /lib/ld-linux.so.2 通常是一个指向动态链接器的可执行文件的符号链接。这个文件的名称为 ld-version.so,其中 version 表示安装在系统上的 glibc 的版本---如 ld.2.11.so。动态链接器会检查程序所需的共享库清单并使用一组预先定义好的规则来在文件系统上找出相关的库文件。其中一些规则则指定了一组存放共享库的标准目录。如很多共享库位于 /lib 和 /usr/lib。LD_LIBRARY_PATH 环境变量:通知动态链接器一个共享库位于一个非标志的目录中的一种方法是将该目录添加到 LD_LIBRARY_PATH 环境变量中以分号分割的目录列表中。如果定义了LD_LIBRARY_PATH,那么动态链接器在查找标准目录之前会先查找该环境变量列出的目录中的共享库。静态链接和动态链接比较:通常,术语链接用来表示链接器ld将一个或者多个编译过的目标文件组成一个可执行文件。有时候会使用术语静态链接从动态链接中将运行时加载可执行文件所需的共享库这一步骤区分开来。(静态链接有时也被称为链接编辑,像ld这样的静态链接器有时候被称为链接编辑器)。每个程序---包括那些使用共享库的程序---都会经历一个静态链接的阶段。在运行时,使用共享库的程序会经历额外的动态链接阶段。7.共享库别名 soname到目前为止介绍的所有例子中,嵌入到可执行文件以及动态链接器在运行时搜索的名称是共享库文件的实际名称,这被称为真实名称(real name)。但可以---实际上经常这样做---使用别名来创建共享库,这种别名称为 soname(ELF中的 DT_SONAME标签)。如果共享库拥有一个 soname, 那么在静态链接阶段会将 soname 嵌入到可执行文件中,而不会使用真实名称,同时后面的动态链接器子啊运行时也会使用这个 soname 来搜索库。引入 soname 的目的是为了提供一层更间接,使得可执行程序能够在运行时使用与链接时使用的库不同(但兼容)的共享库。gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.cgcc -g -shared -Wl,-soname,libbar.so -o libfoo.so mod1.o mod2.o mod3.o-Wl,-soname,以及 libbar.so 选项是传给链接器的指令将共享库 libfoo.so 的soname 设置为 libbar.so如果要确定一个既有共享库 soname,可以使用如下命令:objdump -p libfoo.so | grep SONAMEreadelf -d libfoo.so | grep SONAME动态链接器无法找到名为 libbar.so 共享库。当使用 soname 时,还必须做一件事:必须要创建一个符号链接将 soname 指向库的真实名称,并且必须要将这个符号链接放在动态链接搜索的其中一个目录中。8.使用共享库的有用工具ldd(1) : 列出动态依赖显示了一个运行所需的共享库objdump : 能够获取各类信息---包括反汇编的二进制机器码---从一个可执行文件,编译过的目标以及共享库中。它还能够显示这些文件中各个 ELF 节的头部信息。nm : 会列出模板库或者可执行程序中定义的一组符号。这个命令的一种用途是找出哪些库定义了一个符号。9.共享库版本和命名规则一般来说,一个共享库互相连续的两个版本是互相兼容的,这意味着每个模块中的函数对外呈现的调用接口是一致的,并且函数语义是等价的(即它们能取得同样的结果)。这种版本号不同但互相兼容的版本称为共享库的次要版本。但有时候需要创建一个库的新主版本---即与上一个版本不兼容的版本。真实名称,soanme,以及链接器名称共享库的每一个不兼容版本是通过一个唯一的主要版本标识符来区分的,这个主要版本标识符是共享库的真实名称的一部分。根据惯例,主要版本标识符由一个数字构成,这个数字是随着库的每个不兼容版本的发布而顺序递增。除了主要版本标识符之外,真实名称还包含一个次要版本标识符,它是用来区分库的主要版本中兼容的次要版本。真实名称的格式为 libname.so.major-id.minor-id。与主要版本标识符一样,次要版本标识符可以是任意字符串。但根据惯例,它要么是一个数字,要么是由2个点分割的数字,其中第一个数字标识了次要版本,第二个数字标识该次要版本中的补丁号或修正号。libdemo.so.1.0.1共享库的soname包括相应的真实名称中的主要版本标识符,但不包括次要版本标识符。因此,soname的形式为 libname.so.major-id。通常,会将 soname 创建为包含真实名称的目录中的一个相对符号链接。下面是一些 soname 的例子,libdemo.so.1 -> libdemo.so.1.0.2对共享库的某个特定的主要版本来说,可能存在几个库文件,这些库文件是通过不同的次要版本标识符来区分的。通常,每个库的主要版本的 soname会指向在主要版本中的最新的次要版本。这种配置使得在共享库的运行时操作期间版本化语义能够正确工作。由于静态链接阶段会将 soname 的副本嵌入到可执行文件中并且 soname 符号链接后面可能会被修改指向一个更新的(次要)版本的共享库,因此可以确保可执行文件在运行时能够加载库的最新的次要版本。此外,由于一个库不同的主要版本的 soname 不同,因此它们能够和平的共存并且被需要它们的程序访问。除了真实名称和 soname 之外,通常还会为每个共享库定义第三个名称:链接器名称,将可执行文件与共享库链接起来时会用到这个名称。链接器名称是一个只包含库名同时不包含主要或次要版本标识符的符号链接,因此其形式为 libname.so 。有了链接器名称后就可以构建能够自动使用共享库的正确版本(即最新版本)的独立于版本的链接命令了。一般来说,链接器名称与它所引用的文件位于同一目录中,它既可以链接到真实名称,也可以链接到库的最新主要版本的 soname。通常,最好使用指向 soname 的连接,因此对 soname 所做的变更会自动反应到链接器名称上。libdemo.so ->libdemo.so.2使用标准规范创建一个共享库:1.gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c//接着创建共享库,真实名称为 libdemo.so.1.0.1, soname 为 libdemo.so.12.gcc -g -shared -W1,-soname,-libdemo.so.1 -o libdemo.so.1.0.1 mod1.o mod2.o mod3.o//接着为 soname 和链接器名称创建恰当的符号链接3.ln -s libdemo.so.1.0.1 libdemo.so.1ln -s libdemo.so.1 libdemo.so//接着可以使用 ls 来验证配置4.ls -l libmode.so* | aws '{print $1, $9, $10, $11}'//接着可以使用链接器名称来构建可执行文件5.gcc -g -Wall -o prog prog.c -L. -ldemo10.安装共享库一般来说,共享库及其关联的符号链接会被安装到其中一个标准库目录中,标准库目录包括:1./usr/lib, 大多数标准库安装目录2./lib, 应该将系统启动时用到的库安装在这个目录中(因为在系统启动时可能还没挂载/usr/lib)3./usr/local/lib,应该将非标准或实验性的库安装在这个目录中(对于 /usr/lib 是一个由多个系统共享的网络挂载但需要只在本机安装一个库的情况则可以将库房在这个目录中)4.其中一个在 /etc/ld.so.conf 中列出的目录ldconfig:ldconfig(8)解决了共享库的两个潜在问题:1.共享库可以位于各种目录中,如果动态链接器需要通过搜索所有这些目录找出一个库并加载这个库,那么整个过程将非常慢2.当按照了新版本的库或者删除了旧版本的库,那么 soname 符号链接就不是最新的。ldconfig 通过执行2个任务来解决这些问题。1.它搜索一组标准的目录并创建或更新一个缓冲文件 /etc/ld.so.cache 使之包含在所有这些目录中的主要库版本(每个库的主要版本的最新的次要版本)列表。动态链接器在运行时解析库名称时会轮流使用这个缓冲文件。为了构建这个缓冲,ldconfig会搜索在 /etc/ld.so.conf 中指定的目录,然后搜索 /lib 和 /usr/lib 。 /etc/ld.so.conf文件由一个目录路径名(应该是绝对路径名)列表构成,其中路径名之间用换行,空格,制表符,逗号或者冒号分割。在一些发行版中,/usr/local/lib 目录也位于这个列表中。ldconfig -p 会显示 /etc/ld.so.cache 的当前内容2.它检查每个库的各个主要版本的最新次要版本(即具有最大的次要版本号的版本)以找出嵌入的 soname,然后在同一目录中为每个 soname 创建(或更新)相对符号链接。为了能够正确的执行这些动作,ldconfig要求库的名称要根据前面介绍的规范来命名。在默认情况下,ldconfig会执行上面两个动作,但可以使用命令行选项来指定它指向其中一个动作:-N 会防止缓存重建-X 选项会阻止soname符号链接的创建-v 选项会使得 ldconfig 输出描述其所执行的动作信息每当安装了一个新的,或者删除了一个既有的库,以及 /etc/ld.so.conf 修改后,都应该重新运行 ldconfig11.在目标文件中指定库搜索目录到目前为止已经介绍了2种通知动态链接器共享库的位置的方式:1.使用 LD_LIBRARY_PATH 环境变量2.将共享库安装到其中一个标准库目录中(/lib,/usr/lib,或者 /etc/ld.so.conf 中列出其中的一个目录)3.在静态编译阶段可以在可执行文件中插入一个在运行时搜索共享库的目录列表.这种方式对于库位于一个固定但不属于动态链接器搜索的标准位置的位置中时,非常有用。要实现这种方式需要在创建可执行文件时使用 -rpath 链接器选项。gcc -g -Wall -W1,-rpaht,/home/mtk/pdir -o prog prog.c libdemo.so命令:
ar
nm
readelf
objdump
ldd //列出动态依赖
1.创建静态库// 1.先将目标文件编译成 .o 文件gcc -c foo.c boo.c // 2.用 .o 文件,创建生成 libdemo.a 静态库ar r libdemo.a foo.o boo.o // 3.将库文件与主程序文件链接gcc -o main main.c libdemo.a//如果 libdemo.a 在标准目录中gcc -o main main.c -ldemo//也可以指定 目录gcc -o main main.c -L指定的目录 -ldemo2.创建共享库//1.指定编译参数 -fPIC 生成位置独立的代码, 创建 .o 目标文件gcc -g -c -fPIC foo.c boo.c//2.生成 .so 共享文件gcc -g -shared -o libdemo.so boo.o foo.o//3.将共享库与主程序一起编译gcc -g -Wall -o main main.c libfoo.so报错,./main: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory为了使用一个共享库,就需要做2件事:1.链接阶段,将共享库的名称嵌入可执行文件中来(在ELF中,库依赖是记录在可执行文件的 DT_NEEDED 标签中) // 静态链接,生成 .so 文件的过程2.运行阶段,必须存在某种机制来解析嵌入的库名---即找出可执行文件中指定的名称对应的共享库,如果不在就加载进内存。// 动态链接, 查找 .so 文件的过程原因是:动态链接器找的时候,会在标准目录查找,而 libfoo.so 并不在标准库//4.通知动态链接器一个共享库位于一个非标准目录的一种方法是,将目录添加到 LD_LIBRARY_PATH 中。LD_LIBRARY_PATH=. ./main3.共享库别名 soname//1.指定编译参数 -fPIC 生成位置独立的代码, 创建 .o 目标文件gcc -g -c -fPIC -Wall foo.c boo.c//2.生成别名共享库 libbieming.sogcc -g -shared -Wl,-soname,libbieming.so -o libdemo.so foo.o boo.o//3.将共享库与主程序一起编译gcc -g -o main main.c libdemo.so//4.给动态链接器指定非标准目录,报错。原因是 : 动态链接器无法找到名为 libbieming.so 的共享库LD_LIBRARY_PATH=. ./main//5. 为别名库创建软连接,到真实库ln -s libdemo.so libbieming.so//6.再次运行,成功LD_LIBRARY_PATH=. ./main
41.Linux/Unix 系统编程手册(下) -- 共享库基础相关推荐
- linux/unix系统编程手册11-15
title: linux/unix编程手册-11_15 date: 2018-05-27 11:53:07 categories: programming tags: tips linux/unix编 ...
- Linux/Unix系统编程手册 第三章:系统编程概念
本章介绍系统编程的基础概念和一些后续章节用到的函数及头文件,并说明了可移植性问题. 系统调用是受控的内核入口,通过系统调用,进程可以请求内核以自己的名义去执行某些动作,比如创建子进程,执行I/O操作, ...
- Linux/UNIX系统编程手册gg
Linux系统: "所见皆文件" 一个比较好的博客 一.Linux基础操作 Linux系统目录: bin:存放二进制可执行文件 boot:存放开机启动程序 dev:存放设备文件: ...
- Linux/Unix系统编程 五:进程
进程是一个可执行程序的实例. 一.linux系统进程管理 1.进程管理的作用 判断机器健康状态 查看系统中所有进程 杀手进程 2.查看系统进程 1.ps -aux BSD操作系统格式: TTY说明: ...
- Linux系统编程手册-源码的使用
Linux系统编程手册-源码的使用 转自:http://www.cnblogs.com/pluse/p/6296992.html 第三章后续部分重点介绍了后面章节所要使用的头文件及其实现,主要如下: ...
- Linux/Unix系统下nginx+php安装简明教程
本文转载自Linux/Unix系统下nginx+php安装简明教程,请保留转载信息~ 一.安装nginx: 1. 安装pcre库,nginx的rewrite模板需用到pcre库: mkdir -p / ...
- 在Linux/Unix系统下用iconv命令处理文本文件中文乱码问题
iconv命令是运行于linux/unix平台的文件编码装换工具.当我们在linux/unix系统shell查看文本文件时,常常会发现文件的中文是乱码的,这是由于文本文件的编码与当前操作系统设置的编码 ...
- 5w字总结 Unix系统编程学习笔记(面试向)(Unix环境高级编程/Unix环境程序设计)
文章目录 一.计算 C语言的数据表示与处理 计算 C语言的基本运算操作 内存表和符号表 类型转换 函数类型的分析 指令 复合指令 句法 函数 函数激活(Activation Record) 函数激活定 ...
- ①Linux简明系统编程(嵌入式公众号的课)---总课时12h
10.09 注意:这个是Linux高级编程的简明教程,是Linux应用程序的开发,而不是底层程序的开发. 内容是关于操作系统和网络编程的吗? Linux简明系统编程 〇.课程思维导图 〇.会用到的头文 ...
- unix服务器备份文件命令,linux/unix系统间文件备份脚本
这是我之前写的一个shell脚本,用于linux/unix系统间的文件自动备份. 脚本的主要功能有:用于不同服务器间当天的文件的同步或拷贝:需要expect命令支持,RHEL和AIX测试正常,需要ex ...
最新文章
- git 只merge部分_[Skill]俩小时掌握多人开发中git的主要用法
- 设计模式之强大的接口适配器模式,继承Thread or 实现Runnable?
- 六面 Google,失败经验分享…
- 区块链BaaS云服务(19)趣链“联邦计算BitXmesh”
- Spring Cloud(八)使用Zipkin进行分布式链路跟踪
- android内核模块签名,android安装内核module,提示Required key not available
- python调用C函数库
- spring整合mongodb集群
- 分区操作后索引的状态
- Linux系统LVM增加新硬盘实现根文件系统扩容
- cf round480D Perfect Groups
- Nginx安装及配置文件解释
- mysql 嵌套查询多表_MySql嵌套查询+关联查询+多表查询+对应案例 超详细,一看就会!!!...
- kX3552常用插件集
- 如何用python编写抢票软件哪个好_又到疯狂抢票的高峰期,用python写一个简单的12306抢票软件...
- 程序员的奋斗史(二十八)——寒门再难出贵子?
- centos7 zabbix短信告警(阿里短信平台)
- 别光顾着吃瓜,今天来讲讲微博为何总宕机
- Transformer的PyTorch实现(超详细)
- 我的STC89C52单片机