文章目录
一、静态库
我们设计了一套方法,给别人用
1.给源文件
2.把我们的源代码打包成库=库+.h(头文件是方法的说明书)
比如说main.c里面的都是库的源代码,我们想使用的话就是把这五个直接变成.o一编译就好了
静态库就是把上面的a-d.o打包起来形成一个libxxx.o,然后和main.o一链接就形成了对应的可执行程序了
1.1ar命令
ar命令就是将我们的.o打包成.a的一个命令
ar -rc 静态库 xxx.o xxxx.o
ar是gnu归档工具,rc表示(replace and create),如果存在就替换,如果不存在就创建
1.2形成发布库
lib=libmymath.a
$(lib):mymath.o
ar -rc $@ $^
mymath.o:mymath.c
gcc -c $^
.PHONY:clean
clean:
rm -rf *.o *.a lib
.PHONY:output
output:
mkdir -p lib/include
mkdir -p lib/mymathlib
cp *.h lib/include
cp *.a lib/mymathlib
我们写了一段加减乘除的代码,声明放在了mymath.h,定义放在了mymath.c
前半部分是将.o打包形成库
后半部分是将头文件和库分离
1.3使用库
[wyx@hcss-ecs-000a test]$ cat main.c
#include "mymath.h"
int main()
{
printf("2+3=%d\n",Add(2,3));
return 0;
}
main.c:1:20: fatal error: mymath.h: No such file or directory
#include "mymath.h"
^
compilation terminated.
当我们像日常的逻辑一样使用库的时候,却发现报错报的是找不到这个库
1.gcc会在默认的路径(/usr/include/)下去找
2.gcc会在当前路径下区查找
drwxrwxr-x 4 wyx wyx 4096 Sep 26 10:00 lib
-rw-rw-r-- 1 wyx wyx 79 Sep 26 10:01 main.c
但是mymath.h不在当前目录,在lib的下级目录里面,必须是得和源代码在同一级路径下
1.3.1指定目录搜索头文件
gcc main.c -I ./lib/include/
-I去指定目录下搜索头文件
/tmp/ccUimNNH.o: In function `main':
main.c:(.text+0xf): undefined reference to `Add'
collect2: error: ld returned 1 exit status
虽然还是会报错,但已经报的不是头文件找不到的错了
报的是找不到这个add方法,就是链接的时候报错,其实就是找不到方法的实现,那就是找不到静态库
1.3.2指定目录搜索库
系统只会去默认的路径下去寻找库
gcc main.c -I ./lib/include/ -L ./lib/mymathlib/ -lmymath
1.-L告诉系统我们要链接库,库在哪个地方
2.链接库我们要显性的指定库名称 ,为什么,因为一个目录下有很多的库,gcc不知道你要链接哪一个库
为什么头文件不需要呢,因为你已经在程序里面指明是哪个头文件,所以gcc可以去根据头文件名去目录下去寻找
3.-l指明我们要链接哪一个库
4.-l后面必须跟库真实名,库真实名要去掉前缀lib,去掉后缀.a
5.第三方库,往后使用的时候,必定是要使用gcc -l
1.3.3ldd命令
ldd命令是显示一个可执行程序或共享库(动态库)所依赖的共享库列表
wyx@hcss-ecs-000a test]$ ldd a.out
linux-vdso.so.1 => (0x00007ffcb9967000)
libc.so.6 => /lib64/libc.so.6 (0x00007f5c588cb000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5c58c99000)
可是这里我们却没有看到我们上面所写的静态库
gcc对默认的库链接时是动态链接
如果系统中只提供静态链接,gcc则只能对该库进行静态链接
动静态库都提供的时候默认肯定是动态链接的,你想静态链接就带-static
如果系统中需要链接多个库,则gcc可以链接多个库
1.4库的安装
[wyx@hcss-ecs-000a test]$ sudo cp lib/include/mymath.h /usr/include/
[wyx@hcss-ecs-000a test]$ ls /usr/include/mymath.h
/usr/include/mymath.h
[wyx@hcss-ecs-000a test]$ sudo cp lib/mymathlib/libmymath.a /lib64
[wyx@hcss-ecs-000a test]$ ls /lib64/libmymath.a
/lib64/libmymath.a
我们将自己写的库或者第三方库拷贝到系统默认的搜索路径下的这种操作叫作库的安装
1.5建立软链接
`sudo ln -s /home/wyx/lesson9_library/test/lib/include/ /usr/include/myhead
`ll /usr/include/myhead`
lrwxrwxrwx 1 root root 43 Sep 26 19:11 /usr/include/myhead -> /home/wyx/lesson9_library/test/lib/include/
头文件建立软连接
sudo ln -s /home/wyx/lesson9_library/test/lib/mymathlib/ /lib64/libmymath.a
ll /lib64/libmymath.a
lrwxrwxrwx 1 root root 45 Sep 26 19:13 /lib64/libmymath.a -> /home/wyx/lesson9_library/test/lib/mymathlib/
gcc main.c -lmymath
库文件建立软链接
虽然在系统路径下建立了软链接,但是在编译的时候我们还是需要指明库的名称以便寻找到具体的库
二、动态库
动态库和静态库的前置都一样,都是要把源文件先形成.o 才能执行后续的操作
fPIC:产生与位置无关码
这个我们后面会说到
2.1形成库
[wyx@hcss-ecs-000a lesson9_library]$ gcc -fPIC -c mylog.c
[wyx@hcss-ecs-000a lesson9_library]$ gcc -fPIC -c myprint.c
先把源文件变成.o文件
ar命令是专门形成静态库的,使用gcc命令形成动态库
gcc默认内置可以形成动态库
-shared形成共享库
gcc -shared -o libmymethod.so *.o
-rwxrwxr-x 1 wyx wyx 8144 Sep 26 19:36 libmymethod.so
-rw-rw-r-- 1 wyx wyx 1880 Sep 26 09:54 libmymath.a
不带-shared是形成可执行程序,但是我们要形成库就要带-shared
动态库是带可执行权限的,静态库不带
因为静态库的链接是直接把静态库的文件拷贝到程序里面,其他东西静态库是不管的,静态库是不会加载到内存的
动态库是要和你的可执行程序产生关联,当你的可执行程序要执行的时候势必要跳转到动态库 ,所以你的动态库必须要被加载,只有可执行程序这样的文件才能快速被加载
可执行权限就是你的文件是否会以可执行程序的形式加载到内存
动态库不是不能执行,只是不能单独执行
2.2发布库
dt-lib=libmymethod.so
static-lib=libmymath.a
.PHONY:all
all: $(static-lib) $(dt-lib)
$(static-lib):mymath.o
ar -rc $@ $^
mymath.o:mymath.c
gcc -c $^
$(dt-lib):myprint.o mylog.o
gcc -shared -o $@ $^
myprint.o:myprint.c
gcc -fPIC -c $^
mylog.o:mylog.c
gcc -fPIC -c $^
.PHONY:clean
clean:
rm -rf *.o *.a mylib
.PHONY:output
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp *.h mylib/include
cp *.a mylib/lib
cp *.so mylib/lib
通过这样的形式我们就能完成对动静态库的一起发布了
[wyx@hcss-ecs-000a test]$ tree mylib
mylib
├── include
│ ├── mylog.h
│ ├── mymath.h
│ └── myprint.h
└── lib
├── libmymath.a
└── libmymethod.so
2.3使用库
#include "mymath.h" //静态库
//动态库
#include "myprint.h"
#include "mylog.h"
int main()
{
//静态库
printf("2+3=%d\n",Add(2,3));
//动态库
Print();
Log("hello Log");
return 0;
}
gcc main.c -I ./mylib/include -L ./mylib/lib -lmymethod -lmymath
动态库在编译的时候我们也要指明对应的路径要不然系统默认去系统的路径下去搜索
[wyx@hcss-ecs-000a test]$ ./a.out
./a.out: error while loading shared libraries: libmymethod.so: cannot open shared object file: No such file or directory
但是我们在运行的时候就会直接报错
[wyx@hcss-ecs-000a test]$ ldd a.out
linux-vdso.so.1 => (0x00007ffeea7fd000)
libmymethod.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00007f996fabb000)
/lib64/ld-linux-x86-64.so.2 (0x00007f996fe89000)
我们查看这个可执行程序的共享库信息时就会发现这个动态库并没有找到
但是我们不是编译的时候告诉gcc这个库在哪里了吗
但是我们只告诉了编译器,gcc确实形成了可执行
一旦形成可执行就和编译器就没关系了
动态库在哪里,你也得告诉系统--加载器
加载的时候C语言为什么能够找到你呢
libc.so.6 => /lib64/libc.so.6 (0x00007f996fabb000)
动态库加载的时候也要告诉路径,系统会在默认的路径下去搜索C语言的动态库
2.4解决加载找不到动态库的方法
2.4.1拷贝到系统的默认的库路径
sudo cp /home/wyx/lesson9_library/test/mylib/lib/libmymethod.so /lib64
[wyx@hcss-ecs-000a test]$ ldd a.out
linux-vdso.so.1 => (0x00007ffd5e5a6000)
libmymethod.so => /lib64/libmymethod.so (0x00007fda57a89000)
libc.so.6 => /lib64/libc.so.6 (0x00007fda576bb000)
/lib64/ld-linux-x86-64.so.2 (0x00007fda57c8b000)
2.4.2建立软链接
sudo ln -s /home/wyx/lesson9_library/test/mylib/lib/libmymethod.so /lib64/libmymethod.so
[wyx@hcss-ecs-000a test]$ ll /lib64/libmymethod.so
lrwxrwxrwx 1 root root 55 Sep 26 20:33 /lib64/libmymethod.so -> /home/wyx/lesson9_library/test/mylib/lib/libmymethod.so
这样我们就能够直接找到了
[wyx@hcss-ecs-000a test]$ ldd a.out
linux-vdso.so.1 => (0x00007ffe83ff9000)
libmymethod.so => /lib64/libmymethod.so (0x00007fa5803c5000)
libc.so.6 => /lib64/libc.so.6 (0x00007fa57fff7000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa5805c7000)
2.4.3库路径添加到环境变量LD_LIBRARY_PATH
LD_LIBRARY_PATH
这个环境变量是专门用来给用户提供搜索用户自定义库的路径的
所以将我们的动态库路径添加到这个环境变量系统就能找到了
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wyx/lesson9_library/test/mylib/lib
[wyx@hcss-ecs-000a test]$ ldd a.out
linux-vdso.so.1 => (0x00007ffeadbd9000)
libmymethod.so => /home/wyx/lesson9_library/test/mylib/lib/libmymethod.so (0x00007f938c936000)
libc.so.6 => /lib64/libc.so.6 (0x00007f938c568000)
/lib64/ld-linux-x86-64.so.2 (0x00007f938cb38000)
这也是一种办法
2.4.4/etc/d.so.conf.d建立动态库路径的配置文件
在/etc/d.so.conf.d建立动态库路径的配置文件,然后重新ldconfig即可
ls /etc/ld.so.conf.d/
bind-export-x86_64.conf kernel-3.10.0-1160.119.1.el7.x86_64.conf kernel-3.10.0-957.el7.x86_64.conf mariadb-x86_64.conf
这些文件是系统维护自己的动态库时的搜索路径文件
在这个路径下建立一个这样的.conf文件保存你的路径系统自然而然就能找到了
sudo vim lesson9_library.conf
sudo ldconfig
把你的路径放到这样的文件,还需要将配置文件重新加载一下,系统就能找到了
不需要库名字,因为你的可执行程序在编译的时候就知道你要链接哪一个库了
2.4.5小结
实际情况,我们用的库都是别人的成熟的库,都采用直接安装到系统的方式
2.5共享库
[wyx@hcss-ecs-000a test]$ rm -f ./mylib/lib/libmymath.a
[wyx@hcss-ecs-000a test]$ ./a.out
当我们把静态库删除了之后我们会发现程序还可以跑因为静态库已经拷贝到我们的程序里了
[wyx@hcss-ecs-000a test]$ gcc -o main main.c -I ./mylib/include/ -L ./mylib/lib -lmymethod
[wyx@hcss-ecs-000a test]$ gcc -o test main.c -I ./mylib/include/ -L ./mylib/lib -lmymethod
当我们建立完两个可执行文件的时候删掉对应的动态库,这两个程序都跑不了了
[wyx@hcss-ecs-000a test]$ rm ./mylib/lib/libmymethod.so
[wyx@hcss-ecs-000a test]$ ./test
./test: error while loading shared libraries: libmymethod.so: cannot open shared object file: No such file or directory
[wyx@hcss-ecs-000a test]$ ./main
./main: error while loading shared libraries: libmymethod.so: cannot open shared object file: No such file or directory
说明这一个共享库一定是会被两个不相关的程序都会使用到的
1.常见的动态库被所有的可执行程序(动态链接的),都要使用,动态库--共享库
2.动态库在进程运行的时候,是要被加载的(静态库没有)
所以,动态库在系统加载之后,会被所有进程共享
动态库把代码共享出来所有的进程共享这一个库,可以让代码只有一份,大大节省内存,这就叫作共享库
三、动态库加载

1.当我们的程序想要调用共享库时,共享库就需要被加载到内存,填进页表的物理地址处,映射进地址空间的共享区
2.当我们的正文代码需要调用库里面的函数时,只需要跳转到共享区,然后去执行,再返回给正文代码,此时你就能满足在你的地址空间上完成对库的访问了
3.结论:建立映射,从此以后,我们执行的任何代码,都是在我们的进程地址空间中进行执行
系统运行中,一定会存在多个动态库-os管理起来--先描述再组织--系统中,所有库的加载情况,os非常清楚
3.1程序没有加载前的地址

1.程序编译后之后,程序内部有地址的概念
2.代码在编译的时候,天然的就对代码和数据有编址了,不会使用函数名了,转而使用call这个地址
3.可执行程序在没有被加载到内存的时候,就已经被分为了很多段(堆栈未初始化数据等)
4.这些都是老的,实际上可执行程序在进行编译链接时形成可执行程序时,内部的编址已经变成现如今的平坦模式-我们在磁盘上看到的可执行程序它的顺序的排布问题和我们加载到内存里和地址空间里对应的规则 基本上是一致的
编译器也要考虑操作系统
5.为了方便编译代码,先看main函数,还要看到其他的函数,不能边编译边给你解析,所以函数都是要先声明的
6.这上面的地址已经是虚拟地址,所以我们的可执行程序还没有加载到内存的时候,也就是在磁盘的时候,已经按虚拟地址的方式给我们加载好的,这也叫逻辑地址
7.小结:可执行程序没有加载之前,在磁盘的时候可执行程序内部采用逻辑地址的方式进行编址
3.2程序被加载后的地址
1.当我们可执行程序加载到内存的时候,每一条指令就占据了物理地址,所以每条代码就占据了两套地址 ,一是可执行程序内部的逻辑地址,二是物理地址
如何理解呢?
我们站在一个大的学校操场,全校师生站在一起(内存),由于内存里排列的地址是不固定的,也就是你们都站在操场随便站,此时唯一能区分你的就是学号(物理地址),回到班级以后(可执行程序),在班级里你有对应的座位号,老师上课的时候就不会喊你某某班的多少学号,而是喊你多少号,这就是在内部使用的逻辑地址。
2.程序加载到内存后,如何执行第一条指令呢?可执行程序在形成的时候里面包含着entry(入口地址) ,由于是在可执行程序内部在编译期间就已经形成的,所以入口地址也是逻辑地址,内存中是虚拟地址
3.一个进程内部包含了自己的cwd工作目录和exe(能找到自己的可执行程序),所以当我们加载这个可执行程序时就把入口地址放到CPU寄存器EIP 当中
4.因为已经时虚拟地址了,所以直接就去正文段执行了,就可以通过页表映射去寻找物理地址了,可执行程序不是有必要一次性加载到内存,可以先将入口地址加载,当我们在页表要填物理地址的时候就会触发缺页中断,程序就被加载进来了,就可以填上右侧的物理地址了,因为加载之后是以页位单位的,所以就加载了一大批的指令
5.然后读取一条指令,这条指令的长度我们就知道了,我们只需要让入口地址+长度就可以取执行下一条指令了
比如我们读取call 4也就是跳转到函数,CPU内读取到的指令,内部可能有数据,可能也有地址,着也是虚拟地址
6.读取程序当中的地址,CPU内部处理的地址,到二次访问读到的全部都是虚拟地址 ,CPU读到的是虚拟地址,但是要真正的找到你,再怎么虚拟也是指令间进行定位,但是我们必须要读取你指令,通过页表将虚拟地址转化成物理地址,找到你的这条指令又是虚拟地址,然后重复就循环起来了
7.CPU读取到的只有虚拟地址比如0x112233,通过页表转化你才能找到物理地址,而物理地址里面存着指令,就是虚拟地址只存地址不存指令,我们找到物理地址后,读取指令的长度,虚拟地址加指令的长度就能找到下一条虚拟地址
3.3动态库的地址--fPIC

1.当我们可执行程序内部调用库方法比如printf,因为采用的是逻辑地址,cpu读到虚拟地址以后,就在正文代码去找到共享区去找,共享库加载进来开辟物理内存,共享库映射到共享区里面
2.关键是共享区大了,具体映射到哪里呢 ,这个printf是提前被编译好的,如果是固定的绝对地址,我有十个八个共享库加载进来,保不齐就能把你这个绝对地址占了,所以动态库被加载到固定地址空间中的位置是不可能的
3.库在形成的时候,让自己的内部函数不要采用绝对地址,只表示每个函数在库中的偏移量即可 ,所以在编译的时候,printf所采用的不是绝对地址而是偏移量
4.当我们把库加载进来,在地址空间里就可以随便放,操作系统只需要记住库的起始地址 ,找到这个函数只需要加函数的偏移量就可以了
5.fPIC与位置无关码--直接用偏移量对库中函数进行编址