站在先前的讲过的文件系统的角度来看,下图test这个文件有自己独立的inode,在他的inode对应的block数据块当中存放的是test这个可执行程序的路径(包括文件名)。
并且如果此时将bin1/bin2/bin3/test的这个程序移动到其他位置,软连接则会无效。
一个文件可以既被软链接,又被硬链接,对文件的修改都会同步到链接的文件当中。
库的命名:
取消前缀lib,去掉.之后的内容,剩下就是库的名字。
动态链接 – >动态库 libc.so
静态链接 – >静态库 libc.a
感性认知:
动态链接类似于每次要钱就找父母要一次钱。
静态链接类似家里的小金库就是你的,需要自己就可以满足。
gcc编译默认是动态链接的,相同的代码动态链接的程序小于静态链接的程序
//分别生成动静态链接的程序
[ljh@VM-0-11-centos 2.27]$ gcc test.c -o test_dynamic
[ljh@VM-0-11-centos 2.27]$ gcc test.c -static -o test_static
[ljh@VM-0-11-centos 2.27]$ ll
total 876
-rwxrwxr-x 1 ljh ljh 8360 Feb 27 11:28 a.out
-rw-rw-r-- 1 ljh ljh 27 Feb 27 11:12 log.txt
-rw-rw-r-- 1 ljh ljh 68 Feb 27 11:28 test.c
-rwxrwxr-x 1 ljh ljh 8360 Feb 27 11:29 test_dynamic
-rwxrwxr-x 1 ljh ljh 861288 Feb 27 11:29 test_static
我们通过上面代码可以发现test_static的程序远远大过test_dynamic的程序,他们两个程序用的库一个是libc.so,一个是libc.a
ldd命令可以查看文件依赖的动态库:
[ljh@VM-0-11-centos 2.27]$ ldd test_dynamic
linux-vdso.so.1 => (0x00007fff813cd000)
libc.so.6 => /lib64/libc.so.6 (0x00007fa5e59ce000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa5e5d9c000)
习惯: 一般,为了更好地支持开发,第三方库和语言库,都必须提供两个库,一个是静态库,一个是动态库,方便程序员进行二进制文件的生成。
动态链接vs静态链接
动态链接:体积小,节省资源(磁盘,内存),一旦库丢失,程序就不可以执行了。
静态链接:体积大,浪费资源(磁盘,内存),可能用C语言写10个可执行程序,会出现大量的重复的代码。但库丢失(不安装),不影响。
专注于打包动静态库与使用动静态库
静态库:
目标:实现一个加减法的静态库。
分析:别人要知道我提供的库是要什么方法,我们当然可以把.c*,.h全部发给别人,但是如果今天我们不想给.c,我们可以将.c全部编译成.o,.o*打包成库,然后和.h放在一个文件夹里放给别人就可以了。
源代码需要私密性保护,头文件可以告知有什么方法
步骤:
1. 编写.c和.h文件
[ljh@VM-0-11-centos bin11]$ ll
total 16
-rw-rw-r-- 1 ljh ljh 56 Feb 27 11:55 Add.c
-rw-rw-r-- 1 ljh ljh 53 Feb 27 11:55 Add.h
-rw-rw-r-- 1 ljh ljh 55 Feb 27 11:55 Sub.c
-rw-rw-r-- 1 ljh ljh 53 Feb 27 11:55 Sub.h
2. 采用ar -rc命令打包
r:replace替换,create创建
我们下面创建一个库名为mymath的静态库,打包成lib发送给对端即可。注意是将.o文件打包。
[ljh@VM-0-11-centos bin11]$ gcc -c *.c
[ljh@VM-0-11-centos bin11]$ ar -rc libmymath.a *.o
[ljh@VM-0-11-centos bin11]$ ll
total 36
-rw-rw-r-- 1 ljh ljh 56 Feb 27 11:55 Add.c
-rw-rw-r-- 1 ljh ljh 53 Feb 27 11:55 Add.h
-rw-rw-r-- 1 ljh ljh 1240 Feb 27 12:31 Add.o
drwxrwxr-x 2 ljh ljh 4096 Feb 27 12:01 lib
-rw-rw-r-- 1 ljh ljh 2920 Feb 27 12:31 libmymath.a
-rw-rw-r-- 1 ljh ljh 55 Feb 27 11:55 Sub.c
-rw-rw-r-- 1 ljh ljh 53 Feb 27 11:55 Sub.h
-rw-rw-r-- 1 ljh ljh 1240 Feb 27 12:31 Sub.o
drwxrwxr-x 3 ljh ljh 4096 Feb 27 12:33 test
[ljh@VM-0-11-centos bin11]$ mkdir lib
[ljh@VM-0-11-centos bin11]$ cp *.h *.a lib
[ljh@VM-0-11-centos bin11]$ tree lib
lib
|-- Add.h
|-- libmymath.a
`-- Sub.h
0 directories, 3 files
有了lib,我们把lib发送给别人就可以,我们这里在本机创建多一个文件夹做测试。
3. 测试lib
[ljh@VM-0-11-centos bin11]$ mkdir test
[ljh@VM-0-11-centos bin11]$ cd test
[ljh@VM-0-11-centos test]$ cp ../lib . -rf
李四今日拿到张三发的lib文件,看了看里面的.h文件,发现实现了Add,Sub,想拿来使用,于是他写了下面这段代码,但是却报错了。
[ljh@VM-0-11-centos test]$ vim main.c
[ljh@VM-0-11-centos test]$ cat main.c
#include
#include"Add.h"
#include"Sub.h"
int main()
{
printf("%d\n",Add(10,20));
printf("%d\n",Sub(10,20));
}
[ljh@VM-0-11-centos test]$ gcc main.c -o main
main.c:2:16: fatal error: Add.h: No such file or directory
#include"Add.h"
^
compilation terminated.
[ljh@VM-0-11-centos test]$ pwd
/home/ljh/2.27/bin11/test
No such file or directory,李四查阅了原因,头文件只会在默认路径或main.c的同路径下查找/home/ljh/2.27/bin11/test
,也就是这个路径,而.h放在了/home/ljh/2.27/bin11/test/lib
下,李四这个时候理解了,查阅了方法,发现gcc带选项 -I ./lib
即可.
随即他写下这段代码:李四看到报出了链接错误,那么肯定是找到了文件,但是却没有链接上,李四冥思苦想,上网查找资料。
[ljh@VM-0-11-centos test]$ gcc main.c -o main -I ./lib/
/tmp/ccBm9pNY.o: In function `main':
main.c:(.text+0xf): undefined reference to `Add'
main.c:(.text+0x2f): undefined reference to `Sub'
collect2: error: ld returned 1 exit status
李四最终发现库文件的路径没有指定,也就是.h他能找到了,但是.c找不到就是库没有找到,而gcc下 -L ./lib
就能指定这个库,李四随即把代码改成下面,但是还是报了错误。
[ljh@VM-0-11-centos test]$ gcc main.c -o main -I ./lib/ -L ./lib/
/tmp/ccHZnoAe.o: In function `main':
main.c:(.text+0xf): undefined reference to `Add'
main.c:(.text+0x2f): undefined reference to `Sub'
collect2: error: ld returned 1 exit status
李四想了想,引入的库文件路径已经有了,头文件也已经能找到了,是不是要给出具体的库文件名呢?,gcc下 -l mymath
后加库名称,代码改了之后,李四发现就能够正常运行了。
[ljh@VM-0-11-centos test]$ gcc main.c -o main -I ./lib/ -L ./lib/ -lmymath
[ljh@VM-0-11-centos test]$ ./main
30
-10
注意1:
若发生以下错误,则是打包的时候错误的将.c文件给打包了。
[ljh@VM-0-11-centos test]$ gcc main.c -o main -I ./lib/ -L ./lib/ -lmymath
./lib//libmymath.a: error adding symbols: Archive has no index; run ranlib to add one
collect2: error: ld returned 1 exit status
注意2:
上面我们提供的静态库但是编译的时候是默认动态 链接,链接会变成静态链接。虽然file显示静态链接,但是ldd没有显示我们的staticlib库,说明对这个库采用的是静态链接的方式。
file
命令文件是否是链接状态
[ljh@VM-0-11-centos zhangsan]$ ls
main main.c stlib
[ljh@VM-0-11-centos zhangsan]$ file main
main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=fc61dd09c0e83aa9d34292bb5dfcfd2322b5337f, not stripped 这里显示动态
但是ldd当中没有,说明staticlib库没有被动态链接。
[ljh@VM-0-11-centos zhangsan]$ ldd main
linux-vdso.so.1 => (0x00007ffec1fd9000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff39e814000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff39ebe2000)
总结:
gcc选项:
-I + path 给定路径或默认路径当中查找
,默认路径一般是 /usr/include。
-L + path:告诉gcc除了默认路径和当前路径,也要到我们的指定路径下找库文件
,系统默认的默认库在/lib或/lib64当中。
-l +库名称
例如ls/lib64,我们能看到很多库,但是具体是要哪一个库我们是要去指定的。
为什么在C语言编译的时候,从来没有明显的使用-L,-I,-i
等选项呢?
因为库文件和头文件在默认路径下gcc能找到,并且gcc是编译C代码的,默认就链接了libc,所以我们一个选项都不用带就可以编译程序。
我们的程序如何不使用这些选项也能编译程序
头文件和库文件分别拷贝到默认路径下(库的安装)。但是通常第三方库要调用的时候还是要带-l
表明要链接哪一个库。
进行ar打包并创建stlib文件夹,只需要把stlib发送给别人就可以了。
代码:
[ljh@VM-0-11-centos dystlib]$ cat Makefile
lib:Add.o Sub.o
ar -rc libstaticlib.a *.o
mkdir stlib
cp *.h stlib
cp libstaticlib.a stlib
Add.o:Add.c
gcc -c $^
Sub.o:Sub.c
gcc -c $^
结果:
[ljh@VM-0-11-centos dystlib]$ cd zhangsan
[ljh@VM-0-11-centos zhangsan]$ cp ../stlib/ .
[ljh@VM-0-11-centos zhangsan]$ tree stlib
stlib
|-- Add.h
|-- libstaticlib.a
`-- Sub.h
0 directories, 3 files
[ljh@VM-0-11-centos zhangsan]$ gcc main.c -o main -I ./stlib/ -L ./stlib/ -lstaticlib
[ljh@VM-0-11-centos zhangsan]$ ./main
30
-10
动态库的打包:
生成一个加减法的动态库dy_lib
动态链接时:
当我们有若干个进程都需要同一个同一个动态库的内容的时候,我们的内存当中只需要存一份实体。
静态链接时:
每个进程执行程序都会加载到代码区当中,内存当中对一份代码的实现可能存在多份实体。
位置无关码:每个进程都有一个共享区,共享区的不同位置可以映射到相同的动态库代码,动态库代码也可以加载到内存当中的任意位置(页表的转换),每个进程就可以在打开的动态库文件里面执行对应的函数。
保证库当中有多少行代码,怎么执行都不会出错,与位置无关,即使有函数调用也能正常运行。总之不影响库的执行。
[ljh@VM-0-11-centos dy_lib]$ cat Makefile
lib:Add.o Sub.o
gcc -shared *.o -o libmymath.so
mkdir dylib
cp ./*h dylib
cp libmymath.so dylib
Add.o:Add.c
gcc -fPIC -c $^
Sub.o:Sub.c
gcc -fPIC -c $^
.PHONY:clean
clean:
rm -rf dylib
使用上与静态相比略有一点差异,会报错
cannot open shared object file: No such file or directory。
错误示范
[ljh@VM-0-11-centos zhangsan]$ cp -rf ../dylib/ .
[ljh@VM-0-11-centos zhangsan]$ ls
dylib main.c
[ljh@VM-0-11-centos zhangsan]$ tree dylib
dylib
|-- Add.h
|-- libmymath.so
`-- Sub.h
0 directories, 3 files
[ljh@VM-0-11-centos zhangsan]$ gcc main.c -o main -I ./dylib/ -L ./dylib/ -lmymath
[ljh@VM-0-11-centos zhangsan]$ ./main
./main: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
分析问题:
当我们运行的时候与编译器gcc已经没有关系了。运行的时候是系统要帮我们找到运行时要使用的动态库!!!
相当于我们给了编译器明确的路径,编译器知道了,并且编译器借助环境变量生成了一个正确的路径,但是系统不会找编译器要,我们还需要自己指明路径给编译器。
为什么之前动态连接的其他程序,可以直接运行呢?
因为库在默认路径下,系统可以找到。
解决方法:
第一种解决方案:
环境变量LD_LIBRARY_PATH,我们只需要把库的目录路径导入到环境变量后就可以了。
[ljh@VM-0-11-centos dy_lib]$ cd zhangsan
[ljh@VM-0-11-centos zhangsan]$ ls dylib
Add.h libmymath.so Sub.h
[ljh@VM-0-11-centos zhangsan]$ ./main
./main: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
[ljh@VM-0-11-centos zhangsan]$ pwd 查询当前目录
/home/ljh/2.27/dystlib/dy_lib/zhangsan
[ljh@VM-0-11-centos zhangsan]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ljh/2.27/dystlib/dy_lib/zhangsan/dylib 追加导入环境变量
[ljh@VM-0-11-centos zhangsan]$ ./main
30
-10
然后我们可以把先前写的动静态库放到同一个文件夹(lib)当中,就可以供别人使用。
[ljh@VM-0-11-centos zhangsan]$ ls
lib main.c
打印文件内容
[ljh@VM-0-11-centos zhangsan]$ tree lib
lib
|-- Add.h
|-- libmymath.a
|-- libmymath.so
`-- Sub.h
0 directories, 4 files
[ljh@VM-0-11-centos zhangsan]$ gcc main.c -o main -I ./lib/ -L ./lib/ -lmymath -static
main就可以是静态编译了
[ljh@VM-0-11-centos zhangsan]$ file main
main: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=02ddaebbe1c007e6bfcd54f5b91e3128a136ebac, not stripped
当然这里的库也可以放在二级目录,三级目录。
第二种找到动态库的方法:把自己写的.so文件拷贝到系统的共享库路径下,一般指/usr/lib64。
第三种找到动态库的方法:ldconfig 配置/etc/ld.so.conf.d/,在root用户ldconfig更新
在/etc/ld.so.con.d/路径下创建一个后缀.conf的文件,然后把lib的目录地址写入,有多个可以换行写入即可。
注意要再ldconfig刷新,就可以正常运行动态链接的文件。
编写lib文件的路径
[root@VM-0-11-centos ~]# echo "/home/ljh/2.27/dystlib/dy_lib/zhangsan/lib" > /etc/ld.so.conf.d/ljh_tmp.conf
[root@VM-0-11-centos ~]# logout
刷新
[root@VM-0-11-centos ~]# ldconfig
[root@VM-0-11-centos ~]# logout
[ljh@VM-0-11-centos zhangsan]$ ./main30
-10
第一种方法只在未关闭会话时有效,第二种只在设置user下有效,而第三种方法可以永久使用。
-Wl.option:
此选项传递 option 给连接程序; 如果 option 中间有逗号, 就将 option 分成多个选项, 然 后传递给会连接程序。
gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.1
这样子我们ldd main 的时候显示的就是libhello.so.0 了;
ldconfig -n .
就是让:用此选项时,ldconfig仅扫描命令行指定的目录,不扫描默认目录(/lib、/usr/lib),也不扫描配置文件/etc/ld.so.conf所列的目录。gcc main.c -L . -lhello -o main
如这样,那么我们需要用ln -s 再弄一个软连接,ln libhello.so.0 -s libhello.so
,就会生成如下,但是此时我们需要添加环境变量。 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:`pwd`
这样带来的好处,就是若是小版本的更新,可以不用编译程序,而程序就能够用到最新的动态库。
gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.2
上述的图有误,实际上再次执行一次ldconfig -n .
可以让指向最新的版本。后续运行不需要重新编译源文件。实际上的图类似下面,进行一个简单的更正~
注意:通常环境变量的设置会在/etc/profile.d/ 的路径创建文件,这样能解耦,不需要直接修改 /etc/profile 文件,效果相同。 同理动态库的设置 /etc/ld.so.conf.d/ 底下创建新的文件即可。
库的理解:
当我们使用第三方库,下载别人的库,如果是源文件,我们可以自己编译生成库再使用,如果是别人帮我们编写好了,我们就可以安装实际就帮我们拷贝到了默认路径下。
!_!