- 指令:ln -s 要链接的文件名 链接文件名
- 功能:建立软链接
链接文件可以使用rm来删除,但是最好用指令去解除链接文件。
- 指令:unlink 链接文件名
- 功能:删除链接文件
那么软连接掉地有什么用呢?
如上图中蓝色框中所示的可执行程序,它处于比较深的路径下,如果要执行该程序,就要像红色框中那样,输入很长的路径名。
在当前目录下建立深路径下可执行程序的软连接文件,此后直接执行软链接文件就可以。
软链接文件就像我们在windows下桌面上的快捷方式一样。
例如,点开谷歌浏览器的快捷方式图标的属性,可以看到有一个目标,目标中有一串路径。
- 桌面的快捷方式就是软链接文件。
- 属性中目标中的路径就是被链接的文件。
链接文件和被链接文件的inode是不相同的,如上图蓝色框中所示。
将链接关系中的被链接文件删除以后,链接文件就出错了,并且在闪烁。
- 创建的文件是一个普通文件,并不是可执行文件。
- 创建的文件和原来的文件inode不同,原来的是917833,现在是917844。
- 只因文件名相同,链接关系就恢复正常了。
从上面的现象可以得出一个结论:软链接和inode无关,只和路径名有关。
也就是说,建立软链接后,并不是链接文件和另一个文件的inode绑定了,只是和文件名绑定了。
- 指令:ln 链接文件名 被链接文件名
- 功能:没有选项-s,建立硬链接
inode相同,意味着它俩是一个文件,因为inode是一个文件时唯一标识,而且一个文件只有一个。所以说,硬链接创建的文件并不是一个独立的新文件,它是被链接文件的别名。
硬连接的本质:在指定路径下,新增文件名和inode编号的映射关系!!!
此时就不再是文件名和inode一一对应了,而是一个inode对应多个文件名,这是文件名都标识一个文件,只是叫法不同。
上图中,红色框中的数字表示文件的硬连接数,也就是一个inode有几个文件名字和它映射。
删除硬链接:
同样的,可以使用rm删除,也可以使用unlink指令来删除。
- 原本这个文件的硬链接数是2,并且inode是917832。
- 删除掉硬链接文件以后,硬链接数变成了1,但是仍然存在inode为917832的文件。
这说明,文件没有被删掉,那么该如何删除这个文件呢?因为硬链接文件和链接文件其实是一个文件。
- 每当一个文件名和这个inode建立映射关系的时候,引用计数加1。
- 也就是硬连接数每加1,引用计数就加1。
所以在删除这个文件的时候,使用unlink只是让硬链接数和引用计数减了1。只有引用计数减到0,这个文件的inode的位图才会被清0,这个文件才会被删除。
硬链接的作用:
- 目录一经创建,它的硬链接数就是2。
- 普通文件一经创建,它的硬连接数是1。
我们知道,每个目下都有两个隐藏文件,分别是一个点和两个点,一个点表当前目,两个点表示上级目录。
- dir这个目录名和917833这个inode构成一个映射关系。
- dir目录下的一个点又和917833这个inode构成一个映射关系。
所以一个目录一被创建,硬链接数就是2,因为有两个映射关系到这个inode上。
- dir目录本身的名字和inode917833是一个映射。
- ./dir中的一个点和inode也是一个映射。
- ./dir/mydir中的两个点和inode也是一个映射。
一个点和两个点同样是目录名,只是名字是点而已。
给目录建立硬链接的时候,shell反馈目录不能建立硬链接,这是为什么?
- 如果给根目录建立了硬链接,那么就会造成死循环,从下级目录中的根目录和最上级目录的根目录之间的死循环。
所以为了避免这种情况,操作系统直接就不让用户给目录创建硬链接,至于(.和…)这俩硬链接,是操作系统自己创建的。
最后,软硬链接的区别:链接文件是否有独立的inode。
先来简单回顾一下动静态库,详细内容可以看本喵的这篇文章动静态链接。
所以见到一个库,掐头去尾才是它的库名。使用gcc进行编译的时候,默认是采用的动态链接,如果要使用静态链接需要加上选项-static。
静态链接形成的可执行程序比静态链接形成的要大,如上图中红色框所示。
库源码及头文件:
将上诉代码接口给同学使用,并且不暴露源码,此时我们就可以选择将它制作成库。
制作静态库:
现在站在制作库的一方来看问题。
对源码进行预处理,编译,汇编,形成以.o为后缀的目标文件,再将这些文件交给同学。
现在将两个源文件编译成了两个以.o为后缀的目标文件,如上图蓝色框所示,它们是二进制形式的机器码,并不是源码。
将.o为后缀的目标文件以及头文件交给同学,让他去链接生成可执行程序。
同学给我们对的反馈如上图,成功链接了,并且成功执行了可执行程序。
库的原理和上面类似,只是将所有的.o为后缀的文件打包在了一起,形成了一个库,在使用的时候直接使用这个库就可以。
- 指令:ar -rc libname.a [所有待打包.o]
- 作用:将所有待打包的.o文件制作成静态库。
该指令中,ar是gnu的归档工具(Archive files),rc表示replace和create。
将要给同学的两个.o文件进行打包,制作成一个静态库。
我们可以写一个这样的Makefile,直接一步make到位,生成一个库文件。
- 将所用到的头文件全部放在myliba/include目录下。
- 将静态库文件放在myliba/lib目录下。
操作如上图所示。
- 指令:ar -tv 库文件名
- 功能:列出静态库中的文件和详细信息。
正是我们打包的.o文件。
再使用打包工具tar,将库和头文件一起打包,如上图所示,将压缩包发生给同学。
此时我们的库就制作成了。
使用静态库:
此时我们站在使用者的角度,也就是那个同学的角度。
我们接收到了库的压缩包(和从网上下载一样),然后解压,得到了里面的文件。
进行make的时候,发现报错了,所以接下来就是怎么使用它,有俩种方法可以用。
这种方法类似于我们安装软件,使用gcc的时候,它会自动去系统默认路径下搜索所需要的头文件和库文件,也就是C语言标准库所在的路径。
- 头文件默认搜索路径:/usr/include;
- 库文件默认搜索路径:/usr/lib64;
我们可以将同学给的头文件和库文件安装在gcc的默认搜索路径下。
如上图所示,将库和头文件安装到默认搜索路径下,当然在复制的过程中需要使用root权限。
但是在make的时候,发现,还是报错了,这次不是找不到头文件,而是找不到使用接口的定义,说明库出问题了。是因为gcc不知道该用这个默认路径下的哪个库。
在使用gcc编译的时候告诉编译器要使用的库名(掐头去尾后的库名),此时就能编译成功了,而且成功调用了同学给我们的库。
- -l选项:指定使用的库名(掐头去尾后的库名)。
- 注意:l可以和名字合在一起,如-lmylib。
我们平时可是没有加过-l选项的,各种库函数,比如printf这是都是可以调用的,为什么我们自己的库还需要告诉gcc库名呢?
- 官方提供的标准库编译器是能够认识的,所以不用特别指出。
这种方式不建议使用,因为第三方库没有经过检测,会污染其他库和头文件。
比较好的是下一种方式。
选项 | 作用 |
---|---|
-I(大写i) | 指定头文件路径 |
-L | 指定库文件路径 |
-l(小写L) | 指定库(掐头去尾后的库名) |
如上图所示,这样一来,一个第三方库就可以正常使用了。
源码和头文件还是和制作静态库时的一样,要求也是一样,但是此时是给同学制作一个动态库。
制作动态库:
和制作静态库一样,将所有.o文件打包在一起,但不使用ar打包,而是使用gcc来打包。
站在制作者的角度:
位置无关码是什么呢?
如上图所示,假设绿色的星星在进行百米赛跑,在跑道的中间,也就是50米处有一个箭头标志。
描述星星的位置:
- 绝对位置:80米处
- 相对位置:箭头右侧30米处
如果这不是100米的跑道,而是变成了200米,500米呢?星星的绝对位置会变化,但是它的相对位置永远不变,处于箭头右侧30米处。
位置无关码就类似于箭头右侧30米处的30。 本质上它是一个偏移量,相对于库基地址的偏移量。
选项 | 作用 |
---|---|
-fPIC | 生成位置无关码 |
-shared | 生成动态库 |
此时一个动态库就制作成了,同样的我们可以将生成动态库的步骤写到Makefile中。
每一个.o文件文件都会生成一个位置无关码。
如此一来,动态库就制作成功了。
使用动态库:
此时站在使用者的角度:
动态库的使用方法有四种:
- 将头文件复制到/sur/include路径下,将动态库文件安装在/usr/lib64路径下.
- 并且在编译的时候使用-l选项告诉gcc使用的动态库名称。
同样,这种做法是最不推荐的。
不适用第一种方法,那么就需要告诉gcc头文件的路径,库的路径,还有库名。
- 使用-I,-L,-l选项告诉gcc相应的信息后,编译成功了。
- 执行可执行程序的时候报错动态库不存在。
- 查看可执行程序的链接属性,发现找不到动态库。
信息都告诉gcc了,怎么编译不成功呢?
回顾:
- 静态链接:将用到的库函数在编译的时候赋值到了源码中,所以编译成功就可以执行。
- 动态链接:在编译的时候,只是将库的位置无关码(地址偏移量)复制到了源码中,等在运行可执行程序的时候再去动态库中根据偏移量找到应用的库函数。
仔细分析一下:
所以接下来的任务就是告诉操作系统我的动态库在哪里。
- 在执行程序的时候,操作系统会从环境变量LD_LIBRARY_PATH中读取动态库的路径。
将自己的动态库路径放入到环境变量中,再执行刚刚生成的可执行程序,发现可以成功执行了,而且使用的是动态库中的函数接口。
这种做法并不能永久生效,因为每次启动shell的时候,它都会从配置文件中重新加载环境变量,我们这里给LD_LIBRARY_PATH赋值只是暂时的。
在使用gcc编译的时候和上面一样,在告诉操作系统库文件路径时不再放入环境变量中,而是放入系统配置文件中。
- /etc/ld.so.conf.d/
- 在这个路径中有很多配置文件
此时我们再创建一个配置文件,名字随意,后缀是.conf的就行,在里面把我们动态库的路径放进去。这里的所有操作都需root权限。
- 使用sudo config指令更新刚刚添加的配置文件
此时操作系统就能找到我们对动态库了,而且这是一个永久的办法,不像将路径加到环境变量中那样是暂时的。
- 路径:/usr/lib64
在这个系统默认搜索库路径下,有很多的库文件,还有很多的软链接库文件。
将同学给我们的动态库软链接到系统默认搜索路径下,如上图所示。此时系统就能找到库了。
gcc在编译的时候,只是将库中的库函数生成了一个位置无关码(相对偏移量)放在了程序中,在程序执行的时候,需要操作系统先将整个库加载到内存中,然后再根据位置无关码调用这个库函数。
- 进程被创建以后,将对应的代码加载到内存中。
- 进程虚拟地址空间的代码段通过页表映射到了内存中,其中就包括生成的位置无关码。
- 操作系统将指定的动态库也加载到了内存中,由于占据内存空间,所以给它分配了地址。
- 当进程执行到调用库函数时,操作系统根据位置无关码,在动态库基地址的基础上偏移一定量的地址找到要调用的库函数,并且调用。
以上就是动态库的加载和调用过程。
- 在gcc进行编译时,编译器将要调用的库函数复制到了程序中,形成了可执行程序。
- 进程被创建后,操作系统将复制了库函数的可执行程序加载到内存中去执行。
静态库的加载和操作系统没有关系,它是编译器完成的,就是将库函数源码复制一份到我们对源码中。
所以使用静态库的程序都比使用静态库的程序要大,所占用的内存多。
再来看一个现象:
- 此时只有静态库,没有动态库。
- 告诉gcc各种信息,进行编译,采用的是静态库。
- 查看可执行程序动态库信息,发现并没有我们的静态库。
- 查看可执行程序的文件信息,发现依然是动态链接,并没有说静态链接。
这就奇怪了,我们明明用的静态库,但是还是说动态链接,这是为什么?
除非使用了-static选项,否则gcc就会进程动态链接,并且认为它编译的程序都是动态链接,即使调用了静态库。
- Access:文件访问时间。
- Modify:文件内容修改时间。
- Change:文件属性修改时间。
根据字面意思就可以知道他们代表着什么,一般情况下,修改文件内容,Modife和Change的时间都会改变,内容变了,属性一般都会发生变化。
Access的时间并不是访问一次就会变化,因为访问频率高的话,需要不停修改这个时间,也就意味着和磁盘会有频繁的访问,到降低效率。
- 访问文件的次数累积到一定量时,Access的时间会改变。
- 访问文件的时间达到一定量时,Access的时间也会改变。
学习完这篇文章后,对硬链接和软链接要有清楚的认识,并且要掌握动静态库的制作和使用,因为们以后会用到这些东西。结合前面两篇文章,基础IO部分就结束了。