笔记:关于链接库那点事儿
2021年7月31日
根据《程序员的自我修养-链接、装载与库(潘爱民著)》整理
1)总线。
北桥:高速设备(内存,缓存,CPU,PCIbridge)。南桥:低速设备(磁盘,USB,键盘,鼠标)。
2)内存。
增加中间曾层,内存映射,地址隔离,分段,分页。
3)线程。
程序执行最小单元:线程ID,指令指针,寄存器集合,堆栈。
线程三种状态:运行,就绪,等待
Linux Task:
- fork:复制当前进程,只产生本任务的镜像
- exec:使用新的可执行镜像覆盖当前的镜像
- clone:创建子进程并从指定位置开始执行(产生新线程)
4)线程同步
信号量(任意线程获取和释放)
互斥锁(谁拥有谁释放)
临界区(进程内同步代码块)
条件变量(唤醒等待的线程)
5)线程过度优化
a. 阻止编译器为了提高速度缓存变量到寄存器:volatile x;
b. 阻止编译器调整操作 volatile 变量的指令次序 (volatile 无法阻止CPU动态调整执行顺序)
CPU 乱序执行的问题:volatile 无法阻止CPU动态调整执行顺序:
采用 barrier() 指令 (lwsync),阻止 CPU 将 barrier 之前的指令交换到 barrier 之后。
6)多线程内部情况
用户线程:内核线程
- 1:1
CreateThread
clone(thread_fun, thread_stack, CLONE_VM, 0);
默认模型。真并发。数量限制,开销大 - N:1 线程切换快,无限的线程数量。性能提高不显著。
- M:N
7)编译和链接
a.预编译(展开全部的宏,处理全部预编译指令,删除注释):$ gcc -E
b. 编译(cc 语法分析,构建,生成汇编代码):
直接:
c. 汇编(as 汇编代码转为机器指令产生目标文件):
直接:
d. 链接(ld relocation 符号解析重定位,给程序地址打补丁,使其指向正确的地址。):
8)目标文件(可执行文件)格式COFF
Linux ELF,Windows PE-COFF。格式查看:
文件头 0x00000000
.text 段 代码段 机器代码指令
.data 段 数据段 已初始化的全局变量和局部静态变量
.bss 段 未初始化的全局变量和局部静态变量(0)。预留位置,没有内容,不占空间。
内容查看:$ objdump -h foobar.o
分析代码段:$ objdump -s -d foobar.o
内容查看:$ readelf -h foobar.o
段表结构:$ readelf -S foobar.o
9)可执行文件(映像文件:进程)装载
每个程序有自己的虚拟地址空间。常用部分放内存,不常用部分放磁盘。在操作系统(动态装载,页映射)看来:进程最关键的特征是拥有独立的虚拟地址空间:
a. 创建独立的虚拟地址空间
b. 读取可执行文件头,建立虚拟地址空间和可执行文件的映射关系
c. 将CPU的指令寄存器设置成可执行文件的入口地址,运行。缺页中断。加载执行
10)动态链接
静态链接缺点:空间浪费,更新困难。链接时重定位:Link Time Relocation
Linux 动态共享对象(DSO)。Windows DLL。位置无关代码(-fPIC),装载时重定位:Load Time Reloc (Win基址重置Rebasing)。
查看DSO是否为 PIC(无输出则为PIC):
TEXTREL(重定位表地址)
11)运行时加载
dlopen(sopathfile, flag)
- sopathfile: ‘/’开头绝对路径直接加载。
- 相对路径加载(搜索)次序:
i. LD_LIBRARY_PATH 指定的一系列目录
ii. 查找 /etc/ld.so.cache 里面指定的共享库路径
iii. /lib, /usr/lib
sopathfile: 0. 返回全局符号表句柄。全局符号表:程序可执行文件及已经装载的所有共享模块和通过dlopen(file, RTLD_GLOBAL) 方式已经打开的符号。
dlsym(handle, symbol)
返回函数地址,变量地址,常量的值(配合char * dlerror()==NULL 成功)。
dlclose(handle)
卸载。
编译运行:
12)Linux共享库组织
ABI 兼容。命令规则:libname.so.X.Y.Z
共享库名字:libfoo.so.2 (必须保留主版本号。libfoo.so.1和libfoo.so.2是完全不同的共享库! )
共享库的链接名XXX(自动查找最新的XXX):libfoo.so.2.6.1 = libXXX.so.2.6.1
编译路径查找:-L共享库路径
当共享库同时存在动态和静态版本时(如libc.a, libc.so.1.2.3), $ ld -static -lc查找静态库(libc.a),$ ld -Bdynamic -lc (默认)查找动态库libc.so.X。
此版本号交会。当只存在低于要求的次版本号共享库时。
Linux共享库符号版本机制。
13)共享库查找
优先级:LD_PRELOAD->LD_LIBRARY_PATH->ld.so.cache->/usr/lib->/lib
a.永久添加共享库:
/etc/ld.so.conf 添加搜索路径,运行 ldconfig 调整 SO-NAME和更新 /etc/ld.so.cache。ld.so.cache为针对查找共享库特殊设计的文件结构。
b.临时添加共享库:
环境变量 LD_LIBRARY_PATH=冒号分隔的一系列路径
或者:
LD_PRELOAD(/etc/ld.so.preload):里面指定的共享库和目标文件全部都会加载。
LD_DEBUG:打印整个装载过程: LD_DEBUG=files ./HelloWorld.out
查看依赖:
14)共享库创建
libfoo1.c libfoo2.c 创建libfoo.so.1.2.3
单步:
libfoo1.c libfoo2.c -lbar1 -lbar2
多步:
去掉调试(-S)和符号(-s)信息(binutils):
或:
15)共享库的构造函数和析构函数(仅GCC)
attribute((constructor)) libfoo_init(…); main() 之前执行或 dlopen返回前执行
attribute((destructor)) libfoo_fini(…); exit() 时执行或 dlclose返回前执行
构造优先级(执行次序):
attribute((constructor(100))) libfoo_init1()
attribute((constructor(200))) libfoo_init2()
attribute((constructor(300))) libfoo_init3()
析构优先级(执行次序):
attribute((destructor(300))) libfoo_fini3()
attribute((destructor(200))) libfoo_fini2()
attribute((destructor(100))) libfoo_fini1()
16)Windows 上的共享库
windows 上的函数族和Linux映射:
如果跨平台,统一开发Linux的API,需要包装Win32 API。下面的开源库提供了Win32 API的封装版本 :
如果用 VS 开发,程序可以静态链接 libdl.lib,如果使用 mingw 编译,可以静态链接到 libdl.a。(Linux上通过 -ldl 动态链接到Linux自身的 libdl.so)
下面提供示例代码演示如何动态绑定动态库api: