目录
站在库提供者的角度
静态库
制作静态库
静态库的内容
发布静态库
用户使用静态库
找头文件
找库文件
动态库
制作动态库
动态库报错
动态库是怎么被加载的?
地址相关问题
程序加载前的地址
程序加载后的地址
如何执行第一条指令?
动态库的地址
站在库提供者的角度
有两种方法
静态库
制作静态库
ar是一个生成静态库的命令,目的是把所有.o文件打包成一个.a文件。
rc是表示在.a文件中创建或替换.o文件。
静态库的内容
把源文件变成.o文件然后打包成一个库,这就是静态库。
发布静态库
当别人需要使用我们的静态库时,只用把lib给他就行了。
用户使用静态库
找头文件
gcc编译时,会去系统默认头文件目录和当前目录找对应头文件,加一个I后面跟指定目录就是告诉系统顺便去这里找一下头文件。
找库文件
gcc编译时,会去系统默认库路径下找库文件,加L后面跟路径就是告诉系统来这个路径找库文件,加l后面直接跟库的名字就是告诉系统我要链接这个库。
总结:第一步找头文件,第二步找库文件,第三步指定哪个库。
如果不想这么麻烦,我们可以把头文件和库文件放到系统默认路径中,或者在系统默认路径建立软链接。
放到默认路径相当于安装。
在系统默认路径下添加一个软链接。
动态库
在链接之前,动静态库都一样,都需要把源文件变成.o文件。
制作动态库
先准备一些源文件和头文件。
一次生成两个库。
生成静态库。
生成动态库。
把源文件编译成.o文件时要加fPIC,把.o文件打包成库时要加shared。
清理和发布,生成一个目录进行存放。
发布形成自己的库。
这个库给别人使用。
main.c要包头文件。
使用静态库。
使用动态库。
同时依赖多个库。
动态库报错
关于动态库的报错。
已经告诉了你在哪里,为什么会找不到?
原因在于你告诉的是编译器,编译完后变成可执行程序就和编译器没关系了。
这里报的是加载错误,就是你还得告诉系统,告诉加载器,不仅编译要有路径,加载也要有路径。
一个有四种做法,
第一:直接把库拷贝(安装)到系统路径中。
第二:在系统路径建立软链接。
第三:添加环境变量,这个环境变量是用来搜索用户自定义库的路径。
把动态库所在的路径添加到环境变量中。
第四:设置配置文件。
进入这个目录,这里面存放的都是配置文件。
创建一个配置文件,把动态库的路径写进去。
重新加载配置文件。
环境变量是临时的,配置文件是永久的。
动态库是怎么被加载的?
动态库也是文件,先从磁盘加载到物理内存中,进程的页表会把动态库的位置和自己的共享区建立映射。
当进程要用到动态库时,会判断是否已经加载,如果已经加载了,那么页表直接映射到自己的共享区即可。
库里面的全局数据会不会因为多进程而受到影响?
不会,会发生写时拷贝。
地址相关问题
程序加载前的地址
程序编译之后,内部就具备地址。
平坦模式:程序内部的地址编排和虚拟地址基本一致。(编译器要考虑操作系统)
当程序在磁盘中还未加载时就已经具备了地址,这个地址叫逻辑地址,相当于虚拟地址,也就是在磁盘中还未加载时就已经照顾到虚拟地址了。
objdump -S 将程序反汇编。
程序加载后的地址
程序加载到物理内存后也会自动有对应的物理地址。
如何执行第一条指令?
进程会记录自己在哪个工作目录下,进程也能找到自己的可执行程序。
第一步,读取可执行程序,CPU的pc寄存器会获取对应的入口地址。这个入口地址是程序加载前就有的,是逻辑地址所以相当于虚拟地址。
第二步,用虚拟地址去页表找物理地址,但此时并没有建立映射,此时会触发缺页中断,然后加载程序,建立映射。
第三步,系统知道指令的长度,每读完一条指令,PC存的地址就往后加,就这样不断向后执行。
如果读到call指令,CPU会发现这是函数调用,CPU读到的指令内部可能有地址,这个地址是逻辑地址,也就是虚拟地址,所以就直接用虚拟地址去找物理地址。物理地址不存在就缺页中断加载程序默认就有对应的物理地址了,然后建立映射。
动态库的地址
程序用到库函数,触发缺页中断库会加载,库的虚拟地址和物理地址建立映射,虚拟地址在共享区。
库的地址不能是固定的,只能是任意位置加载。
库内部函数的地址是偏移量。
加载库的地址是库的起始地址,当用到库函数时就用起始地址加偏移量找到对应虚拟地址然后找到映射。
保证库可以在虚拟内存任意位置加载。
因为静态库会直接拷贝到可执行程序里。静态库编译的时候就是按虚拟地址编的,位置是确定的。