在 Linux 下制做动态连接库,“标准” 的作法是编译成位置无关代码(Position Independent Code,PIC),而后连接成一个动态连接库。常常遇到的一个问题是 -fPIC 是否是必需,由于好像不加常常也能正常运行,只是建立 .so 的时候会有一个警告。函数
搜索、试验了一下,答案彷佛是这样:操作系统
(1) 一般的建议是始终加上 -fPIC 生成位置无关代码;指针
(2) AMD64 下,必须使用位置无关代码,不然链接失败:code
relocation R_X86_64_32S against `a local symbol' can not be used when making a shared object; recompile with -fPIC进程
(3) IA32 下,链接成功,但有警告:内存
warning: creating a DT_TEXTREL in object.it
这样的 .so 文件能够彻底正常工做。io
可执行文件在连接时就知道每一行代码、每个变量会被放到线性地址空间的什么位置,所以这些地址能够都做为常数写到代码里面。对动态库,这就不行了,这要等到加载时才知道。无非下面两种方法:编译
(1) 可重定位代码(relocatable code):Windows DLL 以及不使用 -fPIC 的 Linux SO。table
生成动态库时假定它被加载在地址 0 处。加载时它会被加载到一个地址(base),这时要进行一次重定位(relocation),把代码、数据段中全部的地址加上这个 base 的值。这样代码运行时就能使用正确的地址了。
(2) 位置无关代码(position independent code):使用 -fPIC 的 Linux SO。
这样的代码自己就能被放到线性地址空间的任意位置,无需修改就能正确执行。一般的方法是获取指令指针(如 IA32 的 EIP 寄存器)的值,加上一个偏移获得全局变量/函数的地址。
PIC vs. relocatable:
(1) PIC 的缺点主要就是代码有可能长一些。例如 IA32,因为不能直接使用 [EIP+constant] 这样的寻址方式,甚至不能直接将 EIP 的值交给其余寄存器,要用到 GOT(global offset table)来定位全局变量和函数。这样致使代码的效率略低。
(2) PIC 的加载速度稍快,由于不须要作重定位。
(3) 多个进程引用同一个 PIC 动态库时,能够共用内存。这一个库在不一样进程中的虚拟地址不一样,但操做系统显然会把它们映射到同一块物理内存上。对于可重定位代码,则必须为每一个库都在物理内存中复制一份副本,由于须要修改其中的地址。固然,主流现代操做系统都启用了分页内存机制,这使得重定位时可使用 COW(copy on write)来节省内存(32 位 Windows 就是这样作的);然而,页面的粒度仍是比较大的(例如 IA32 上是 4KiB),至少对于代码段来讲能节省的至关有限。
注:对于 AMD64,因为 AMD64 实现了 [RIP+constant] 的寻址方式,第 (1) 点不成立。
这样,把动态库编译成 PIC 只有好处没有坏处,于是 Linux AMD64 要求用于生成动态库的目标文件必须使用 -fPIC 编译也合情合理了。