目录
1. 软硬链接
1.1 创建软链接
1.2 创建硬链接
1.3 硬链接数和unlink
2. 动静态库
2.1 制作静态库
2.2 查看和打包静态库
2.3 使用静态库
2.3.1 安装在默认搜索路径
2.3.2 告知路径+库路径+库名
2.4 制作动态库
2.5 使用动态库
2.5.1 安装在默认搜索路径
2.5.2 路径放在环境变量
2.5.3 路径放在配置文件
2.5.4 软链接到默认搜索路径
2.6 动态库加载和调用
2.7 静态库加载和调用
本篇完。
1. 软硬链接
上一篇:
inode等知识也讲了,现在我们讲讲硬连接数的关联:软硬链接。
软硬链接的区别:
- 软链接:是一个独立文件,有自己独立的 inode 和 inode 编号。
- 硬链接:不是一个独立的文件,它和目标文件使用的是同一个 inode。硬链接就是单纯的在 Linux 指定的目录下,给指定的文件新增 文件名 和 inode 编号的映射关系。
我们可以通过如下命令,创建一个文件的软硬链接:
ln -s 文件名 链接文件名 // 创建软连接
ln 文件名 链接文件名 // 创建硬链接
1.1 创建软链接
这里创建linux_15目录,进去然后在里面创建一个 test.c 文件,随便写点东西:
编译运行:
软连接是给可执行程序的软连接,我们给test.exe 创建一个软连接,可以使用下面的指令:
ln -s 文件名 链接文件名 // 创建软连接
此时运行软连接依旧能执行程序。ll -i:
将链接关系中的被链接文件删除以后,链接文件就出错了,并且在闪烁。
再创建一个普通的同名文件,链接就恢复正常了。
- 创建的文件是一个普通文件,并不是可执行文件。
- 创建的文件和原来的文件inode不同,原来的是920674,现在是92835。
- 只是文件名相同,链接关系就恢复正常了。
从上面的现象可以得出一个结论:软链接和inode无关,只和路径名有关。
也就是说,建立软链接后,并不是链接文件和另一个文件的inode绑定了,只是和文件名绑定了。
可以发现test.exe和它的软链接的inode 是不同的。软链接有什么用途呢?:
我们创建一个比较深的路径,在里面写个程序:
编译运行:
如果我想在原来的linux_15运行呢:
这样也能运行,不过路径名太长太深的时候就很麻烦了,此时我们可以在linux_15路径下给test2.exe创建一个软连接,并且尝试运行:
成功运行,这就是软连接的作用。是不是有点像 Windows 下的 快捷方式? 软链接就是 Linux 下的快捷方式 。
上面我们演示的是让软链接链接一个可执行程序,未来我们可以用它来链接头文件、库文件,动静态库,这样就可以不需要让我们冗余的在去某些地方找这些库了。
1.2 创建硬链接
我们先给test.c创建一个硬链接看看:
可以发现他们的inode是一样的,并且打印test.c的硬链接也打印了test.c的内容,
此时我们在给test.exe创建硬链接看看,注意到此时test.exe的硬链接数为1。
test.exe的硬链接也可以运行,并且他们的硬链接数都为2了。
硬链接就是单纯的在 Linux 指定的目录下,给指定的文件新增文件名和 inode 编号的映射关系,你可以理解为起别名。
删掉test.exe的硬链接:
test.exe还可以运行,但是硬链接数变为了1,
1.3 硬链接数和unlink
前一篇:
了解过引用计数的就知道,此时这个删除就和引用计数类似了。
我们在删除文件时操作系统干了两件事情:
① 在目录中将对应的记录删除,
② 将硬连接数-1,如果为0,则将对应的磁盘释放。
删除的话可以直接 rm,但是我们还是建议使用专门的 取消链接 的指令:unlink
我们把存在的软硬链接都删除:
成功删除,到了最后发现,为什么d1目录的硬链接数是3?
我们把上面的东西全删了,重新创建一个文件和目录:
为什么创建普通文件,硬链接数默认是 1 ,目录硬链接数默认是 2 呢 ?
因为 普通文件的文件名本身就和自己的 inode 具有映射关系,而且只有一个。
我们知道,任意一个目录一定存在一个点或两个点: . ..
那么 ./ 为什么表示的是当前路径呢?因为 . 表示的当前文件,./ 就是当前所处的路径。
默认一个空目录创建一个 自己的名字 和 一个点,所以两个文件名指向它,所以是 2。
那么 .. 又是什么呢?.. 指向的是上级路径。
这就是为什么我们 cd .. 可以回到上级目录的原因,因为它可以指向上级目录。
2. 动静态库
动静态库即 动态库 (Dynamic Library) 与 静态库 (Static Library) 。
① 动态库:库文件,以.so为后缀(Windows中为.dll):程序在运行的时才去链接动态库的代码,多个程序共享使用库的代码。
② 静态库:库文件,以.a为后缀(Windows中为.lib):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
linux_7学的;
2.1 制作静态库
我们先站在设计库的工程师的角度,学如何形成静态库。
我们直接实操式地讲解,下面我们会写一段简单的、包含头文件和源文件的代码。
像以前的函数声明和实现分开一样,写两个头文件和两个源文件,再写个main.c:
此时如果有同学(客户之类的)想使用上面的接口函数,并且写了下面的使用代码:
如果要将上诉代码接口给同学使用,并且不暴露源码,此时我们就可以选择将它制作成库。
制作静态库:
现在站在制作库的一方来看问题。
对源码进行预处理,编译,汇编,形成以.o为后缀的目标文件,再将这些文件交给同学。
现在将两个源文件编译成了两个以.o为后缀的目标文件,如上图所示,它们是二进制形式的机器码,并不是源码。
我们先试着链接生成可执行程序。
成功链接。
将.h和.o为后缀的目标文件以及头文件交给同学,让他去链接生成可执行程序。此时可以制作一个动态库给同学,库的原理和上面类似,只是将所有的.o为后缀的文件打包在了一起,形成了一个库,在使用的时候直接使用这个库就可以。
指令:ar -rc 静态库名字 所有待打包.o
ar是gnu归档工具,-rc表示(replace and create),静态库名字前面要加lib,后缀是.a
2.2 查看和打包静态库
成功制作了一个静态库。可以通过指令来查看我们制作的库:
- 指令:ar -tv 库文件名
- 功能:t:列出静态库中的文件,v:verbose 详细信息
如果要给同学的话,要给所有的头文件和还有静态库,是不是太麻烦了?
此时我们可以把他们放到一个目录下,此时我们可以写一个这样的Makefile,和前面制作静态库的步骤一起,直接一步make到位,生成一个库文件。
- 将所用到的头文件全部放在outputlib/include目录下。
- 将静态库文件放在outputlib/lib目录下。
复习一下Makefile:
和前面制作静态库的步骤一起,直接一步make到位,生成一个库文件:
make:
再使用打包工具tar,将库和头文件一起打包,即这个outputlib目录,就能将压缩包发生给同学。
2.3 使用静态库
此时我们站在使用者的角度,也就是那个同学的角度。
我们接收到了库的压缩包(和从网上下载一样),然后解压,得到了里面的文件:
(这里创建一个test目录,把main.c和mylib.tgz移动进去,然后解压)
运行下main.c:
发现报错了,没有找到mymath.h?接下来就是学学正确使用它,有俩种方法可以用。
2.3.1 安装在默认搜索路径
①安装在系统中
这种方法类似于我们安装软件,使用gcc的时候,它会自动去系统默认路径下搜索所需要的头文件和库文件,也就是C语言标准库所在的路径。
- 头文件默认搜索路径:/usr/include;
- 库文件默认搜索路径:/usr/lib64;
切换到root用户将头文件和库文件安装在gcc的默认搜索路径下:
发现还是报错了,这次不是找不到头文件,而是找不到使用接口的定义,说明库出问题了。是因为gcc不知道该用这个默认路径下的哪个库。
在使用gcc编译的时候告诉编译器要使用的库名(掐头去尾后的库名),此时就能编译成功了。
- -l选项:指定使用的库名(掐头去尾后的库名)。
成功编译且运行,我们平时可是没有加过-l选项的,各种库函数,比如printf这是都是可以调用的,为什么我们自己的库还需要告诉gcc库名呢?
- 官方提供的标准库编译器是能够认识的,所以不用特别指出。
这种方式其实就是库的安装,这种方式不建议使用,因为第三方库没有经过检测,会污染其他库和头文件,所以这里把复制过去的文件删了。
切换到普通用户,用第二种方法:
2.3.2 告知路径+库路径+库名
② 告诉编译器头文件路径,库路径,库名
gcc选项 作用 -I(大写i) 指定头文件路径 -L 指定库文件路径 -l(小写L) 指定库(掐头去尾后的库名)
现在试试第二种方法:
成功站在第三方的角度使用了静态库。
2.4 制作动态库
有了前面写的文件,制作动态库就简单点了。源码和头文件还是和制作静态库时的一样,要求也是一样,但是此时是给同学制作一个动态库。这里进入上级目录,然后make clean一下。
制作动态库:
和制作静态库一样,将所有.o文件打包在一起,但不使用ar打包,而是使用gcc来打包。
① 在使用gcc形成.o文件的时候,再加一个选项-fPIC,生成位置无关码:
成功生成.o文件,位置无关码是什么呢?
如上图所示,假设一火柴人在进行百米赛跑,在跑道的中间,也就是50米处有一个箭头标志。
如果这不是100米的跑道,而是变成了200米,500米呢?火柴人的绝对位置会变化,但是火柴人的相对位置永远不变,处于箭头右侧30米处。此时如果跑到两端变长变短也与我无瓜了。
位置无关码就类似于箭头右侧30米处的30。 本质上它是一个偏移量,相对于库基地址的偏移量。
② 形成库文件:使用gcc,加-shared选项,告诉gcc生成动态库而不是可执行程序,-o后面是动态库的名字,前面加lib,后缀是.so
此时一个动态库就制作成了,同样的我们可以将生成动态库的步骤和前面的静态库一起写到Makefile中。动静态库都要形成 .o,到底是位置有关还是位置无关?最终带来的结果就是不一样。所以名字要区分开来,最后弄好.so文件的移动和删除就行了:
.PHONY:all
all:librtx.a librtx.so
librtx.a:mymath.o myprint.o
ar -rc librtx.a mymath.o myprint.o
mymath.o:mymath.c
gcc -c mymath.c -o mymath.o
myprint.o:myprint.c
gcc -c myprint.c -o myprint.o
librtx.so:mymath_s.o myprint_s.o
gcc -shared mymath_s.o myprint_s.o -o librtx.so
mymath_s.o:
gcc -fPIC -c mymath.c -o mymath_s.o
myprint_s.o:
gcc -fPIC -c myprint.c -o myprint_s.o
.PHONY:output
output:
mkdir -p outputlib/include
mkdir -p outputlib/lib
cp -rf *.h outputlib/include
cp -rf *.a outputlib/lib
cp -rf *.so outputlib/lib
.PHONY:clean
clean:
rm -rf *.o *.a *so outputlib
make clean再make:
此时动静态库和静态库就一起制作完成了。
2.5 使用动态库
make output就和静态库一起放到一个目录下了:(这里把mytest程序删了)
同样将制作的动态库打包,形成压缩包发送给同学。
(这里创建一个test2目录,把test里的main.c拷贝过去,再压缩包移动进去,然后解压)
使用动态库:
此时站在使用者的角度:
动态库的使用方法有四种:
2.5.1 安装在默认搜索路径
① 将头文件和库文件安装在系统默认搜索路径中,这里不演示了,和静态库的情况一样。
- 将头文件复制到/sur/include路径下,将动态库文件安装在/usr/lib64路径下.
- 并且在编译的时候使用-l选项告诉gcc使用的动态库名称。
这种做法是最不推荐的。
2.5.2 路径放在环境变量
② 将库文件路径放在环境变量里
不适用第一种方法,先试试告诉gcc头文件的路径,库的路径,还有库名。
gcc选项 作用 -I(大写i) 指定头文件路径 -L 指定库文件路径 -l(小写L) 指定库(掐头去尾后的库名)
和静态库的gcc选项一下:(输gcc指令的时候就想到gcc是默认使用动态链接的)
输入ldd+程序名就验证了默认是使用动态链接的:
为什么程序运行不了?
回顾:
- 静态链接:将用到的库函数在编译的时候赋值到了源码中,所以编译成功就可以执行。
- 动态链接:在编译的时候,只是将库的位置无关码(地址偏移量)复制到了源码中,等在运行可执行程序的时候再去动态库中根据偏移量找到应用的库函数。
- 我们将头文件路径,库文件路径,库文件名告诉了gcc。
- 编译完成以后,和gcc就没有关系了,接下来的执行是操作系统的事情。
- 那么操作系统就必须得知道所使用的动态库在哪里。
所以接下来的任务就是告诉操作系统我的动态库在哪里。
- 在执行程序的时候,操作系统会从环境变量LD_LIBRARY_PATH中读取动态库的路径。
(ld加载的意思,library库的意思,path路径的意思)将自己的动态库路径放入到环境变量中:
再执行刚刚生成的可执行程序,发现可以成功执行了,而且使用的是动态库中的函数接口。这种做法并不能永久生效,因为每次启动shell的时候,它都会从配置文件中重新加载环境变量,我们这里给LD_LIBRARY_PATH赋值只是暂时的。
为了不影响下面使用动态库的其它操作,这里重新登录一下xshell,就回到解放前了。
2.5.3 路径放在配置文件
③ 将库文件路径放在配置文件中
在使用gcc编译的时候和上面一样,在告诉操作系统库文件路径时不再放入环境变量中,而是放入系统配置文件中。
- /etc/ld.so.conf.d/
- 在这个路径中有很多配置文件
随意看看一个配置文件,发现里面放的也是路径:
此时我们进入/etc/ld.so.conf.d/里创建一个配置文件,名字随意,后缀是.conf的就行,在里面把我们动态库的路径放进去。这里的所有操作都需root权限。这里换到root用户,学了sudo可以用sudo。
成功写入,此时切换到rtx2,回到原来路径:
此时再运行时,发现还是找不到动态库,是因为没更新配置文件,切换到root更新再换回来:
此时操作系统就能找到我们对动态库了,而且这是一个永久的办法,不像将路径加到环境变量中那样是暂时的。
切换到root删掉再玩玩:
到这也学了个 更新配置文件的指令:ldconfig
但是这三种方法都这么麻烦,有没有简单点的?第4种就来了:
2.5.4 软链接到默认搜索路径
④ 软链接到系统默认搜索路径中
(依旧不太建议使用,感觉用第二种就好了)
在这个系统默认搜索库路径/usr/lib64下,有很多的库文件,还有很多的软链接库文件:
root用户将动态库软链接到系统默认搜索路径下,此时系统就能找到库了。
ln -s 文件名 链接文件名 // 创建软连接
这里要带上绝对路径:(这里两个绝对路径最后的文件名应该是一样的)
成功运行。
当然上面的各种使用动静态库的方法还有优化的地方或者别的方法,这里就不再演示了。
2.6 动态库加载和调用
gcc在编译的时候,只是将库中的库函数生成了一个位置无关码(相对偏移量)放在了程序中,在程序执行的时候,需要操作系统先将整个库加载到内存中,然后再根据位置无关码调用这个库函数。
- 进程被创建以后,将对应的代码加载到内存中。
- 进程虚拟地址空间的代码段通过页表映射到了内存中,其中就包括生成的位置无关码。
- 操作系统将指定的动态库也加载到了内存中,由于占据内存空间,所以给它分配了地址。
- 当进程执行到调用库函数时,操作系统根据位置无关码,在动态库基地址的基础上偏移一定量的地址找到要调用的库函数,并且调用。
以上就是动态库的加载和调用过程。
2.7 静态库加载和调用
- 在gcc进行编译时,编译器将要调用的库函数复制到了程序中,形成了可执行程序。
- 进程被创建后,操作系统将复制库函数的可执行程序加载到内存中去执行。
静态库的加载和操作系统没有关系,它是编译器完成的,就是将库函数源码复制一份到我们对源码中。
如果使用静态库,同一个库函数被使用了十次,编译器就会在对应位置复制十分,使用100次,就会复制一百次。
如果使用动态库,同一个库函数被使用十次,就会有十个位置无关码,使用100次就会有100个,在执行的时候,操作系统根据偏移量在内存中仅有的一个动态库中调用相应的函数。
所以使用静态库的程序都比使用静态库的程序要大,所占用的内存多。
本篇完。
下一篇:零基础Linux_16(基础IO_文件)笔试选择题:文件描述符+ionde和动静态库。
再下一篇是进程地址通信的内容了,语言开始向C++转变,环境学用一下VSCode。