设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间07 现在时间07 缓存数据 没有什么事情是简单轻松的,唯有不断努力,才能更加顺利。

没有什么事情是简单轻松的,唯有不断努力,才能更加顺利。

查看: 1405|回复: 3

Linux动静态库的制作与使用

[复制链接]

  离线 

TA的专栏

  • 打卡等级:即来则安
  • 打卡总天数:15
  • 打卡月天数:0
  • 打卡总奖励:212
  • 最近打卡:2023-08-27 07:22:45
等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
32
主题
24
精华
0
金钱
306
积分
64
注册时间
2023-8-12
最后登录
2025-5-31

发表于 2024-5-15 16:56:19 来自手机 | 显示全部楼层 |阅读模式
目录


  • 一、静态库

    • 1.1 静态库的制作
    • 1.2 静态库的生成
    • 1.3 静态库的发布
    • 1.4 静态库的使用
    • 1.5 静态库的安装

  • 二、动态库

    • 2.1 动态库的制作
    • 2.2 动态库的生成
    • 2.3 动态库的发布
    • 2.4 动态库的使用
    • 2.5 动态库是如何被加载和共享的?

  • 三、再来认识地址

    • 3.1 逻辑地址的引入
    • 3.2 CPU 是如何知道指令位置的
    • 3.3 一个库函数是如何被找到并且执行的

  • 四、结语

一、静态库

对于我们自己写的一份源代码,别人如果想使用,可以有以下两种做法:第一种就是将我们的源代码直接给别人拷贝一份,但是如果你觉得自己的代码写的非常厉害,不想让别人知道,或者别人嫌拷贝太麻烦了,那么就需要采用第二种做法;第二种做法就是,将我们自己写的源代嘛想办法打包成库,然后将这个库和对应的头文件提供给使用者。注意,头文件是必不可少的,头文件就相当于该库中方法的使用说明书,如果不提供头文件,别人大概率是不知道该库是如何使用的。

1.1 静态库的制作
  1. // add.h
  2. #pragma once

  3. int add(int x, int y);
复制代码
  1. // add.c
  2. #include "add.h"

  3. int add(int x, int y)
  4. {
  5.     return x + y;
  6. }
复制代码
  1. // sub.h
  2. #pragma once

  3. int sub(int x, int y);
复制代码
  1. // sub.c
  2. #include "sub.h"

  3. int sub(int x, int y)
  4. {
  5.     return x - y;
  6. }
复制代码
  1. // mul.h
  2. #pragma once

  3. int mul(int x, int y);
复制代码
  1. // mul.c
  2. #include "mul.h"

  3. int mul(int x, int y)
  4. {
  5.     return x * y;
  6. }
复制代码
  1. // div.h
  2. #pragma once

  3. extern int myerrno; // 声明一个可以被其它源文件使用的变量

  4. int div(int x, int y);
复制代码
  1. // div.c
  2. #include "div.h"

  3. int myerrno = 0; // 定义

  4. int div(int x, int y)
  5. {
  6.     if(y == 0)
  7.     {
  8.         myerrno = -1;
  9.         return myerrno;
  10.     }
  11.     return x / y;
  12. }
复制代码
1.2 静态库的生成

静态库的生成指令:
1.png

静态库本质上就是对
  1. .o
复制代码
文件进行打包,所以首先要将
  1. .c
复制代码
文件编译成
  1. .o
复制代码
文件,然后进行打包。
  1. ar
复制代码
是 gnu 归档工具,可以使用它来生成一个静态库,它执行的工作就是把一个或者多个
  1. .o
复制代码
文件打包,生成一个
  1. .a
复制代码
库文件。
  1. -rc
复制代码
表示 replace and creat,
  1. .a
复制代码
库文件中如果有待打包的
  1. .o
复制代码
文件就替换,没有就创建。
静态库生成示意图:
2.jpeg


1.3 静态库的发布

发布库:
3.png

4.png

发布库就是把
  1. lib
复制代码
目录拷贝给别人。

1.4 静态库的使用

首先创建一个
  1. test
复制代码
目录,先讲上面发布的
  1. lib
复制代码
目录拷贝到
  1. test
复制代码
目录中,然后在
  1. test
复制代码
目录中创建一个
  1. main.c
复制代码
进行测试。目录结构如下图所示:
5.png
  1. // main.c
  2. #include "add.h"
  3. #include "sub.h"
  4. #include "mul.h"
  5. #include "div.h"
  6. #include <stdio.h>

  7. int main()
  8. {
  9.     printf("1 + 2 = %d\n", add(1, 2));
  10.     printf("1 - 2 = %d\n", sub(1, 2));
  11.     printf("1 * 2 = %d\n", mul(1, 2));
  12.     printf("1 / 2 = %d\n", div(1, 2));
  13.     return 0;
  14. }
复制代码
6.png

编译
  1. main.c
复制代码
时报错,说找不到对应的头文件。此时就需要再来认识一下包含头文件的两种方式了。在使用库中的头时,一般用
  1. <>
复制代码
来包含头文件,
  1. <>
复制代码
表示到系统指定目录下去查找头文件。在使用自己写的头文件时,一般使用
  1. ""
复制代码
,表示在当前源文件的统计目录下查找头文件,找打了就用,没找到再去系统指定目录下进行查找,所以对于库提供的头文件我们也可以使用
  1. ""
复制代码
进行包含。但是上面代码中我们使用的就是
  1. ""
复制代码
,并且
  1. add.h
复制代码
就在
  1. lib/include
复制代码
目录下,
  1. lib
复制代码
目录和
  1. main.c
复制代码
同处
  1. test
复制代码
目录下,为什么会报错呢?因为用
  1. ""
复制代码
包含的头文件,会告诉编译器在
  1. main.c
复制代码
的同级目录下进行查找,也就是在
  1. test
复制代码
目录下进行查找,并不会深入到
  1. test
复制代码
中的
  1. lib
复制代码
目录去查找。
7.jpeg

解决上面报错的方法有三种。第一种,将我们发布的
  1. lib
复制代码
库中的头文件拷贝到系统的指定路径下;第二种,在代码中补全路径,如
  1. #include "/lib/include/add.h"
复制代码
;第三种,在执行
  1. gcc
复制代码
指令编译的时候加上
  1. -I
复制代码
选项,指定编译器搜索头文件的路径。
第三种解决方案示意图:
8.png

此时编译仍然没有成功,但是没有报头文件找不到的错误了。现在是链接出错,可以编译形成
  1. .o
复制代码
文件,如下图所示:
9.png

链接报错还是因为
  1. gcc
复制代码
在进行编译链接的时候,只会去默认路径下查找打包形成的库文件,不会去我们的
  1. lib/mymathlib
复制代码
目录下查找,这样就导致
  1. gcc
复制代码
编译器找不到我们打包的库
  1. libmymath.a
复制代码
,最终链接时就会报错。
10.png

解决链接有两种方法。方法一:将我们的库拷贝到系统的指定路径下,并不能完全解决,还需要指定库的名称,下面会讲;方法二:在使用
  1. gcc
复制代码
的时候添加对应的选项。方法二示意图,如下所示:
11.png

其中
  1. -L
复制代码
选项指定了库的搜索路径,
  1. -l
复制代码
选项指定了待搜索的库的名称。 为什么在搜索头文件的时候仅需指定路径呢?因为在代码中已经写了头文件的具体名称,所以仅需指定头文件的路径即可。而一个路径下可以有多个库,如果只指定路劲,编译器还是不知道该去链接哪个库,因此还要在后面使用
  1. -l
复制代码
选项指定待链接的库的具体名称,注意:去掉前缀
  1. lib
复制代码
和 后缀
  1. .a
复制代码
才是一个库的名称,建议
  1. -l
复制代码
后面紧跟库的名称。一般在使用第三方库的时候,可能不需要带
  1. -I
复制代码
或者
  1. -L
复制代码
,但是
  1. -l
复制代码
指定库的名称是一定需要到,因为
  1. gcc
复制代码
默认只能找到系统调用和语言层面的库。
小Tips:在动态库和静态库都有的情况下,
  1. gcc
复制代码
默认链接动态库,如果系统中只提供静态库,
  1. gcc
复制代码
则只能对该库进行静态链接。如果有需要,
  1. gcc
复制代码
可以链接多个库。

1.5 静态库的安装

12.png

库的安装本质上就是把头文件和库文件拷贝到系统的特定目录下。还可以通过在指定目录下创建软链接的方式,如下图所示:
13.jpeg

小Tips:此时包含头文件前面应该加上软链接的名字,如:
  1. #include <myinc/add.h>
复制代码
这种形式。

二、动态库


2.1 动态库的制作
  1. // myprintf.h
  2. #pragma once

  3. #include <stdio.h>

  4. void Print();
复制代码
  1. // myprintf.c
  2. #include "myprintf.h"

  3. void Print()
  4. {
  5.     printf("Hello Linux\n");
  6. }
复制代码
  1. // mylog.h
  2. #pragma once

  3. #include <stdio.h>

  4. void Log(const char* info);
复制代码
  1. // mylog.c
  2. #include "mylog.h"

  3. void Log(const char* info)
  4. {
  5.     printf("log: %s\n", info);
  6. }
复制代码
2.2 动态库的生成

14.jpeg

小Tips:在编译生成
  1. .o
复制代码
文件的时候,要加上
  1. -fPIC
复制代码
选项,该选项表示产生位置无关码(position independent code)。将
  1. .o
复制代码
文件打包生成动态库,继续使用
  1. gcc
复制代码
,需要带
  1. -shared
复制代码
选项,表示生成共享库格式。其次需要注意动态库的命名规则是
  1. libxxx.so
复制代码
。动态库是可执行程序的一种,将来是需要被加载到内存的,因此它带了
  1. x
复制代码
选项,而静态库的使用本质是把静态库中的二进制代码拷贝一份去使用,静态库是不需要被加载到内存的,因此静态库没有可执行权限。

2.3 动态库的发布
  1. dy-lib=libmymethod.so
  2. static-lib=libmymath.a
  3. .PHONY:all
  4. all:$(dy-lib) $(static-lib)

  5. $(static-lib):add.o sub.o mul.o div.o
  6.                 ar -rc $@ add.o sub.o mul.o div.o

  7. $(dy-lib):myprintf.o mylog.o
  8.                 gcc -shared -o $@ $^

  9. myprintf.o:myprintf.c
  10.                 gcc -fPIC -c $^
  11. mylog.o:mylog.c
  12.                 gcc -fPIC -c $^
  13. add.o:add.c
  14.                 gcc -c $^
  15. sub.0:sub.c
  16.                 gcc -c $^
  17. mul.o:mul.c
  18.                 gcc -c $^
  19. div.o:div.c
  20.                 gcc -c $^
  21. .PHONY:clean
  22. clean:
  23.                 rm -rf *.o *.a mylib *.so
  24. .PHONY:output
  25. output:
  26.                 mkdir -p mylib/include
  27.                 mkdir -p mylib/lib
  28.                 cp *.h mylib/include
  29.                 cp *.a mylib/lib
  30.                 cp *.so mylib/lib
复制代码
15.png

小Tips:上面不是单纯的发布动态库,而是将动静态库同时发布。

2.4 动态库的使用
  1. #include "myprintf.h"
  2. #include "mylog.h"
  3. #include <stdio.h>

  4. int main()
  5. {
  6.     Print();
  7.     Log("Hello log function!");
  8.     return 0;
  9. }
复制代码
16.png

上图中按照静态库的使用方法去使用动态库,可以成功生成可执行文件,但是可执行文件在运行的时候出错了。
17.png

在使用
  1. ldd
复制代码
查看可执行程序运行所需的共享库时发现,
  1. libmymethod.so
复制代码
后面指向
  1. not found
复制代码
。为什么会这样呢?我们在使用
  1. gcc
复制代码
进行编译的时候,不是已经通过
  1. -L
复制代码
  1. -l
复制代码
选项告诉编译器动态库所在的路径和名字,为什么还是找不到呢?原因正如前面所述,我们仅仅是告诉了编译器所需的动态库在哪里,而可执行程序运行靠的是加载器,上面的
  1. not found
复制代码
表示加载器不知道动态库在哪里。这也从侧面印证了静态库是不会加载到内存中的,所以使用静态库只需要告诉编译器静态库在哪里即可。
解决该问题的方法有四种。第一种,将库文件拷贝到系统默认的库路径(
  1. /lib64
复制代码
  1. /usr/lib64
复制代码
);第二种,在系统默认的库路径(
  1. /lib64
复制代码
  1. /usr/lib64
复制代码
)下建立软链接;第三种,将自己库所在的路径,添加到系统的环境变量
  1. LD_LIBRARY_PATH
复制代码
中,该环境变量就是专门用来搜索动态库的;第四种,如果想让我们的库和系统、语言自带的库一样,在程序运行的时候可以自动被找到,那我们可以在
  1. /etc/ld.so.conf.d
复制代码
路径下添加一个
  1. .conf
复制代码
结尾的配置文件,该配置文件里面的内容就是我们自己动态库所在的路径。添加完后执行
  1. ldconfig
复制代码
指令,将所有的配置文件重现加载一下,然后程序就能够正常运行啦。
18.jpeg

19.jpeg

小Tips:这样添加,当系统重启后新添加的境变量就没有了,如果想让系统启动时自动添加该路径到
  1. LD_LIBRARY_PATH
复制代码
环境变量中,可以通过修改
  1. ~/.bash_profile
复制代码
中的配置去实现,具体如下图所示:
20.png

21.png

小Tips:加载器是不需要知道库的名字的,只需要知道库的路径即可。

2.5 动态库是如何被加载和共享的?

动态库在进程运行的时候是需要被加载到内存的,常见的动态库被所有的可执行程序(动态链接的),都要使用,因此,动态库在系统中加载之后,会被所有进程共享。
22.jpeg

首先我们需要知道,一个进程可以链接多个动态库,同理,当系统中存在多个进程的时候,那么此时系统中一定是存在多个动态库的。操作系统一定会通过“先描述,再组织”的方式将系统中所有的动态库管理起来。所以对操作系统而言,所有库的加载情况,它非常清楚。A.exe 在编译链接的时候采用的是动态库,A 进程在运行的时候,CPU 按照从上往下的顺序执行代码,遇到了一个库函数,假设就为
  1. printf
复制代码
,此时操作系统发现
  1. printf
复制代码
所在的动态库并没有被加载到内存中,因此就会将这个动态库加载到内存,因为动态库也是文件,也有 inode,所以这本质上就是文件的加载,将动态库加载到内存之后,操作系统会在 A 进程的页表上建立该动态库与 A 进程地址空间中共享区的映射关系,然后 CPU 就又代码段跳转到共享区去执行动态库中关于
  1. printf
复制代码
的代码,执行完后跳转会代码段继续执行后续代码。与此同时,B.exe 经过编译链接(用动态库),然后被加载到内存,成为 B 进程,CPU 在执行 B 进程代码的时候,也遇到了
  1. printf
复制代码
函数,此时因为在 A 进程执行的时候,就把
  1. printf
复制代码
所在的动态库加载到了内存,所以此时操作系统并不会再去把这个动态库加载一遍,而是直接在 B 进程的页表中建立映射关系。此时一个动态库被加载到内存中,就同时被两个进程所使用,因此动态库也被叫做共享库。
一个问题:现在我们知道了动态库是可以被多个进程共享的。那动态库中的全局变量例如
  1. errno
复制代码
该怎么办?我们知道,
  1. errno
复制代码
是 C 语言为我们提供的一个错误码,一般在调用库函数失败的时候,该错误码会被设置,那动态库是被共享的,岂不意味着
  1. errno
复制代码
也可能是被多个进程共享的,那在 A 进程中执行库函数失败,假设
  1. errno
复制代码
被设置成 1,在 B 进程中
  1. errno
复制代码
也是 1 嘛?这显然是不合理的。实际上,当要修改
  1. errno
复制代码
的时候,操作系统会通过引用计数去判断该动态库是否被多个进程共享,如果该库被多个进程共享,操作系统会发生写时拷贝。

三、再来认识地址


3.1 逻辑地址的引入

一个
  1. .c
复制代码
源文件在被编译成为
  1. .exe
复制代码
可执行程序的时候,会加上地址。可以这样来理解,一个
  1. .c
复制代码
源文件首先会编译成为汇编文件,将我们的 C 语言转化成一条条汇编指令,接着会把汇编指令转化成机器码,对应的汇编文件和机器码文件其实都已经加上了地址,最终
  1. .exe
复制代码
中也是包含地址的。
  1. .exe
复制代码
文件本质上就是由各种段构成的,现如今的
  1. .exe
复制代码
文件中的编址都采用平坦模式,即
  1. .exe
复制代码
文件已经按照程序地址空间的格式进行分段编址。

3.2 CPU 是如何知道指令位置的

上面说过,可执行程序内部是有逻辑地址的,在可执行程序加载到内存之后,每一条指令还会有自己对应的物理地址,因为物理内存它本身就是有地址的,无论可执行程序是否加载到内存中。此时可执行程序已经被加载到了内存,CPU 是如何知道该可执行程序的第一条指令在哪儿的呢?在编译形成可执行程序的时候,除了形成代码段、数据段、.bss 段外,还会形成一个文件头,这里面就存储了可执行程序的入口地址,这个地址是逻辑地址(虚拟地址)。在 CPU 中有一个寄存器,一般管它叫做 PC 指针,它里面存储的就是接下来要执行指令的地址。实际上,最初并不急着把可执行程序全部加载到内存,只需要将可执行文件的头部加载到内存即可,CPU 通过头部获取到可执行程序的入口地址,然后拿着该地址去查页表,发现并没有建立内存映射,此时操作系统会发生缺页中断,将对应的程序加载到内存,接下来就好办了,CPU 通过内置的指令集,先天就知道每条指令的长度,然后他会按顺序往后执行,遇到函数调用指令,或者一些跳转指令,也是根据虚拟地址去页表中查找映射关系,发生缺页中断。因此可以得出一结论,CPU 是通过虚拟地址转物理地址去执行可执行程序中的指令,访问可执行程序中的变量。

3.3 一个库函数是如何被找到并且执行的

结合上面两点,可以得出,可执行程序内部有逻辑地址,CPU 是通过虚拟地址转物理地址去执行指令的。那一个可执行程序是如何加载并使用动态库的呢?以程序中调用
  1. printf
复制代码
函数为例,按照上面两小节的说法,在可执行程序中,
  1. printf
复制代码
函数有一个固定的逻辑地址,假设为
  1. 0x11223344
复制代码
,而 CPU 是通过虚拟地址查找物理地址去执行指令,即通过
  1. 0x11223344
复制代码
这个虚拟地址去映射找到物理地址,然后执行
  1. printf
复制代码
函数,那是否意味着,在程序地址空间角度,动态库需要被加载到动态区的固定位置,这样才能保证
  1. printf
复制代码
函数的地址是
  1. 0x11223344
复制代码
。如果按照上面两小节说的,对于一个进程来说,动态库是必须加载到固定的位置,但是这几乎是不可能的,因为一个可执行程序可能同时使用多个库,每个库的大小不一,并且每个库中都独立编址,可能该进程还使用了 B 库中的某个函数,该函数在 B 库中为编址也是
  1. 0x11223344
复制代码
。所以很难做到将一个动态库加载到固定位置。因此我们需要想办法让库可以在虚拟内存中共享区的任意位置进行加载,实现方法是,在动态库内部,不采用绝对编址,而是采用相对编址,对于动态库中的函数只需要知道其在库中的偏移量即可
  1. 0x11223344
复制代码
就不再表示
  1. printf
复制代码
的绝对地址,而是它相对于这个库起始位置的偏移量,此时就可以实现把库加载到虚拟内存共享区的任意位置。之后,操作系统只需要记住每一个库在虚拟内存中的起始地址即可,当要执行某个库函数的时候,只需要用该函数所在库的起始地址加上该函数的相对地址(也就是偏移量),就可以知道该函数在程序地址空间中的虚拟地址,然后再拿着这个虚拟地址去查页表,找到该函数在物理内存中的地址,然后执行库函数。
  1. -fPIC
复制代码
选项,就是让编译器在形成动态库文件的时候,直接用偏移量对库中的函数进行编址。静态库是直接拷贝到可执行程序中的,无需加载到物理内存中,因此静态库中的函数就被当做了我们自己写的函数一样,直接采用绝对编址。

四、结语

以上就是Linux动静态库的制作与使用的详细内容,更多关于Linux动静态库的资料请关注晓枫资讯其它相关文章!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
24
积分
28
注册时间
2022-12-26
最后登录
2022-12-26

发表于 2024-5-25 04:36:39 | 显示全部楼层
感谢楼主分享。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
20
积分
20
注册时间
2022-12-23
最后登录
2022-12-23

发表于 2024-11-17 16:55:59 | 显示全部楼层
路过,支持一下
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
12
积分
4
注册时间
2024-6-19
最后登录
2024-6-19

发表于 2025-3-31 20:32:58 | 显示全部楼层
顶顶更健康!!!
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼
4楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表