笔记:关于链接库那点事儿
Posted 车斗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了笔记:关于链接库那点事儿相关的知识,希望对你有一定的参考价值。
笔记:关于链接库那点事儿
2021年7月31日
根据《程序员的自我修养-链接、装载与库(潘爱民著)》整理
1)总线。
北桥:高速设备(内存,缓存,CPU,PCIbridge)。南桥:低速设备(磁盘,USB,键盘,鼠标)。
2)内存。
增加中间曾层,内存映射,地址隔离,分段,分页。
3)线程。
程序执行最小单元:线程ID,指令指针,寄存器集合,堆栈。
线程三种状态:运行,就绪,等待
Linux Task:
-
fork:复制当前进程,只产生本任务的镜像
-
exec:使用新的可执行镜像覆盖当前的镜像
-
clone:创建子进程并从指定位置开始执行(产生新线程)
if ((pid=fork()) == 0) { // 子进程 } else { // 主进程 }
4)线程同步
信号量(任意线程获取和释放)
互斥锁(谁拥有谁释放)
临界区(进程内同步代码块)
条件变量(唤醒等待的线程)
5)线程过度优化
a. 阻止编译器为了提高速度缓存变量到寄存器:volatile x;
b. 阻止编译器调整操作 volatile 变量的指令次序 (volatile 无法阻止CPU动态调整执行顺序)
x = 0;
Thread1 Thread2
lock(); lock();
x++; x++;
unlock(); unlock();
CPU 乱序执行的问题:volatile 无法阻止CPU动态调整执行顺序:
x = y = 0;
Thread1 Thread2
r1 = y; y = 1;
x = 1; r2 = x;
采用 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
hello.c -o hello.i
b. 编译(cc 语法分析,构建,生成汇编代码):
$ gcc -S hello.i -o hello.S
直接:
$ gcc -S hello.c -o hello.S
c. 汇编(as 汇编代码转为机器指令产生目标文件):
$ as hello.S -o hello.o
直接:
$ gcc -c hello.c -o hello.o
d. 链接(ld relocation 符号解析重定位,给程序地址打补丁,使其指向正确的地址。):
$ ld /path/to/a.o /path/to/b.o -L/path/to/lib -lm
8)目标文件(可执行文件)格式COFF
Linux ELF,Windows PE-COFF。格式查看:
$ file foobar.o
文件头 0x00000000
.text 段 代码段 机器代码指令
.data 段 数据段 已初始化的全局变量和局部静态变量
.bss 段 未初始化的全局变量和局部静态变量(0)。预留位置,没有内容,不占空间。
内容查看:$ objdump -h foobar.o
分析代码段:$ objdump -s -d foobar.o
内容查看:$ readelf -h foobar.o
段表结构:$ readelf -S foobar.o
extern “C” {
// C++ 编译器把这里的代码当作 C 语言代码处理
}
9)可执行文件(映像文件:进程)装载
每个程序有自己的虚拟地址空间。常用部分放内存,不常用部分放磁盘。在操作系统(动态装载,页映射)看来:进程最关键的特征是拥有独立的虚拟地址空间:
a. 创建独立的虚拟地址空间
b. 读取可执行文件头,建立虚拟地址空间和可执行文件的映射关系
c. 将CPU的指令寄存器设置成可执行文件的入口地址,运行。缺页中断。加载执行
10)动态链接
静态链接缺点:空间浪费,更新困难。链接时重定位:Link Time Relocation
Linux 动态共享对象(DSO)。Windows DLL。位置无关代码(-fPIC),装载时重定位:Load Time Reloc (Win基址重置Rebasing)。
$ gcc -fPIC -shared -o Lib.so Lib.c
$ reade;f -l Lib.so
查看DSO是否为 PIC(无输出则为PIC):
$ readelf -d foo.so |grep TEXTREL
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)
卸载。
/* test.c */
#include <stdio.h>
#include <dlfcn.h>
int main(int argc, chat *argv[]) {
void * hdl;
double (*func)(double);
char *error;
hdl =dlopen(argv[1], RTLD_NOW);
if (!hdl) exit(1);
func=dlsym(hdl, “sin”);
if ((error = dlerror()) != NULL) {
dlclose(hdl);
exit(-1);
}
func(3.1416/2);
dlclose(hdl);
return 0;
}
编译运行:
$ gcc -o test test.c -ldl
$ ./test /lib/libm-2.6.1.so
12)Linux共享库组织
ABI 兼容。命令规则:libname.so.X.Y.Z
X – 主版本号:重大升级。2.y.z不兼容1.y.z
Y – 次版本:增量(兼容)升级。1.4.z兼容1.3.z,1.2.z,1.1.z,1.0.z
Z – 发布版本:不添加任何接口。只是 BUG FIX。
共享库名字:libfoo.so.2 (必须保留主版本号。libfoo.so.1和libfoo.so.2是完全不同的共享库! )
libfoo.so.2 -> libfoo.so.2.6.1
$ readelf -d Lib.so
共享库的链接名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共享库符号版本机制。
/lib:系统运行必不可少最关键的库。
/usr/lib:非系统关键,但是可能开发时会用到。
/usr/local/lib:第3方程序库安装位置。
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_LIBRARY_PATH=/path1:/path2:/path3 /opt/myapp/test
或者:
$ /lib/ld-linux.so.2 -library-path /path1:/path2:/path3 /opt/myapp/test
LD_PRELOAD(/etc/ld.so.preload):里面指定的共享库和目标文件全部都会加载。
LD_DEBUG:打印整个装载过程: LD_DEBUG=files ./HelloWorld.out
查看依赖:
$ ldd /path/to/your.so.2
14)共享库创建
libfoo1.c libfoo2.c 创建libfoo.so.1.2.3
单步:
$ gcc -shared -fPIC -Wl,soname,libfoo.so.1 -o libfoo.so.1.2.3 \\
libfoo1.c libfoo2.c -lbar1 -lbar2
多步:
$ gcc -c -g -Wall -o libfoo1.o libfoo1.c
$ gcc -c -g -Wall -o libfoo1.o libfoo2.c
$ ld -shared -soname libfoo.so.1 -o libfoo.so.1.2.3 libfoo1.o libfoo2.o -lbar1 -lbar2
去掉调试(-S)和符号(-s)信息(binutils):
$ strip libfoo.so
或:
$ gcc -Wl,-s 或 -Wl,-S
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()
以上是关于笔记:关于链接库那点事儿的主要内容,如果未能解决你的问题,请参考以下文章