Linux动静态库

静态库

将库的实现预编译成.o格式,然后将这些.o文件打包成.a文件,这份文件必须以lib开头,这就是静态库的实现。

下面代码演示建立并调用静态库

头文件static.h

cpp 复制代码
#include<stdio.h>
#include <stdlib.h>
extern int syserrno;
int mydiv(int a,int b);

源文件static.c

cpp 复制代码
#include"static.h"
//简单除法实现
int syserrno=0;

int mydiv(int a,int b){
    if(b==0){
        syserrno=-1;
        return -1;
    }
    return a/b;
}

makefile

cpp 复制代码
lib=libdiv.a
$(lib):static.o//这里是为了方便修改
	ar -rc $@ $^//将.o文件打包成lib

static.o:static.c
	gcc -c static.c

.PHONY:clean
clean:
	rm -rf *.o *.a lib
.PHONY:output
output://为头文件和静态库建立目录分别存放
	mkdir -p lib/include
	mkdir lib/divlib
	cp *.h lib/include
	cp *.a lib/divlib

调用make、make output后文件结构如下:


手动gcc完成静态库链接(也可写在makefile中,这里做演示)

gcc的选项解释

-I 的作用是在编译的时候告诉编译器头文件所在路径,这一步还没有链接

-L的作用是告诉链接器库的路径,所以-L后跟着库所在路径

-l为 链接器指定具体的库文件,因为库所在路径下可能有多个库。


静态链接的其它方式:

  • 将静态库安装到系统指定路径下,因为gcc默认在系统路径和当前路径下搜索库文件,其它路径找不到

  • 在系统路径下建立静态库的软链接


动态库

将.c带fPIC选项预编译为.o文件,然后带shared选项将.o文件编译为动态库文件

makefile动静态库整合版本

cpp 复制代码
libs=libdiv.a
libd=libadd.so

all:$(libs) $(libd)//这里是为了同时执行动静态库的实现

$(libs):static.o
	ar -rc $@ $^

static.o:static.c
	gcc -c $^

$(libd):dynamic.o
	gcc -shared -o $@ $^
	
dynamic.o:dynamic.c
	gcc -fPIC -c $^ -o $@

.PHONY:clean
clean:
	rm -rf *.o *.a *.so lib
.PHONY:output
output:
	mkdir -p lib/include
	mkdir -p lib/divlib
	cp *.h lib/include
	cp *.a lib/divlib
	cp *.so lib/divlib

动态库的编译同样要带-I-L-l选项告诉编译器库和头文件的位置

和静态库不同的是,静态库在编译后可直接被可执行程序加载,而动态库是运行程序时被加载,所以加载器也需要知道要链接的库的位置

让加载器找到动态库的方法

  1. 拷贝到系统默认的库路径 /lib64 /usr/lib64/

  2. 在系统默认的库路径 /lib64 /usr/lib64/下建立软连接

  3. 将自己的库所在的路径,添加到系统的环境变量LD_LIBRARY_PATH中

    cpp 复制代码
    export LD_LIBRARY_PATH=static_depot/lib/divlib:$LD_LIBRARY_PATH
  4. /etc/ld.so.conf.d 建立自己的动态库路径的配置文件,然后重新ldconfig即可

    cpp 复制代码
    sudo vim /etc/ld.so.conf.d/mylib.conf
    /home/doggy/mylib/
    sudo ldconfig

动态库是如何被多个进程加载的?

操作系统通过 inode 找到文件在磁盘上的数据块将数据从磁盘读入物理内存(物理页帧)页表负责把虚拟地址映射到那些物理页帧。

同理,动态库也是磁盘中的文件,当cpu执行调用库指令时,库被加载到内存中(因为已经链接过了加载器知道去哪找),并通过页表映射到虚拟地址空间的共享区中,库代码在共享区中运行完毕后,cpu回到正文部分继续执行指令。

动态库加载到内存后,如果又一进程调用该库,操作系统识别到内存已经加载了动态库,直接为该进程页表添加映射,实现动态库的复用,因此动态库比静态库更能节省空间。

动态库的代码段被所有进程共享(只读),但全局变量等数据段每个进程各自有独立副本,通过写时拷贝机制实现互不冲突


再谈虚拟地址空间

程序没有加载前的地址(虚拟地址)

程序编译好后有内置地址(虚拟地址),这种地址遵照程序的逻辑结构 ,上面存储着指令,每条指令都有自己的长度。cpu在设计时已经认识各种指令(cpu指令集),所以能够按顺序执行这些指令而不是把指令当做数据。虚拟地址和物理地址的最小寻址单元都是字节地址宽度相同,所以两者之间可以建立映射。

程序加载后的地址

程序开始运行,指令被加载到内存,同时具虚拟地址和物理地址

cpu如何执行到第一条指令?

虚拟地址有一个入口地址,cpu有个EIP寄存器专门用于存储虚拟地址,程序开始运行时,EIP存放的是入口地址,发现入口地址没和物理内存关联,此时触发缺页中断,页表将两种地址做映射。由此cpu根据虚拟地址找到物理地址。

所以cpu不是直接去内存中从上到下执行,而是跟着指令走,也就是跟着虚拟地址来走,因此代码加载是靠缺页中断按需进行的。

动态库的加载问题

动态库编译后不能产生固定的地址空间,因为动态库不知道自己内部的函数此后会被加载到虚拟地址空间的哪个位置。比方说,动态库中的某个函数要调用同库的另一个函数,它不能写死一个绝对地址,因为动态库在不同进程的虚拟地址空间中被映射到的位置不同,同一份指令必须对所有进程

都通用,所以必须用相对偏移。

而可执行程序不存在这个问题,因为每个进程拥有自己独立的虚拟地址空间,可执行程序每次加载到固定位置,所以地址可以直接写死。

因此制作动态库时要通过 -fPIC 编译 .c 生成 .o,让机器码中的地址引用全部变成相对偏移(相对于当前指令地址),而不是绝对地址。这样无论动态库被映射到哪个虚拟地址,指令都能正确找到目标。

静态库则不同,它在链接时就被复制进了可执行程序,地址在编译时就全部确定下来了,不需要 -fPIC。

相关推荐
赵民勇1 小时前
Linux file命令详解
linux·运维
li-xun2 小时前
LINUX DO 社区注册机制调整与公益 AI 服务动态
linux·运维·人工智能
j_xxx404_2 小时前
MySQL表操作硬核解析:从 CREATE TABLE 到磁盘文件、ALTER TABLE 与 DDL 风险
运维·服务器·数据库·c++·mysql·adb·ai
ba_pi2 小时前
k8s删除pod
linux·容器·kubernetes
AskHarries2 小时前
权限模型:Shell、Browser、文件读写的安全边界
服务器·前端·网络
wuminyu2 小时前
Java锁机制之park和unpark源码剖析
java·linux·c语言·jvm·c++
团象科技2 小时前
外贸站选海外服务器 拆解跨境运营中常被忽略的核心性能细节
运维·服务器
皆圥忈2 小时前
文件描述符与重定向
linux
实心儿儿2 小时前
Linux —— 线程池(2)
linux