文件系统+软硬连接+动静态库
- 1.理解文件系统
- 1.1磁盘的物理结构
- 1.2磁盘的存储结构
- 1.3磁盘的逻辑结构
- 1.4文件系统
- 2.软硬链接
- 2.1什么是软硬链接
- 2.2软硬链接的作用
- 3.动静态库
- 3.1什么是库
- 3.1静态库和静态链接
- 3.2动态库和动态链接
- 3.2.1通过环境变量找到动态库路径
- 3.2.2把动态库拷贝到默认路径下
- 3.2.3添加到配置文件中
- 3.2.4建立软链接
- 3.3动静态库的加载原理
- 3.3.1静态库加载原理
- 3.3.2动态库加载原理
自我名言:只有努力,才能追逐梦想,只有努力,才不会欺骗自己。
喜欢的点赞,收藏,关注一下把!
文件分为:被打开的文件和没被打开的文件,前面两篇博文主要讲解的是进程与被打开文件的关系。今天这篇博客讲的是没被打开的文件如何在磁盘上存放,文件系统如何管理文件,以及软硬连接。
1.理解文件系统
问:没有被打开的文件,该如何被操作系统管理呢?
没有被打开的文件,只能在磁盘上静静的放着,磁盘上有大量的文件,也必须被管理(静态管理)起来,方便我们随时打开。这些工作是由文件系统做的。
在理解文件系统之前,我们必须搞懂磁盘上的文件是怎么存的。
我们似乎还没有见过磁盘,这里先补充一些磁盘的有关知识。
1.1磁盘的物理结构
我们目前很少看见磁盘了,使用最多的就是SSD固定硬盘。
磁盘是我们计算机中唯一的一个机械结构!
磁盘是一个机械结构+外设,所有磁盘访问速度很慢(相对于CPU和内存来说)。
但是在企业端,磁盘依旧是存储主流。磁盘具有造价低,存储量大等特点。。。
注意:磁头和盘面没有接触! 距离就像一架波音747在距离地面一米高速飞行。
为什么这样说呢?因为盘片一秒可以上万转。
当盘面高速旋转时,磁头会漂浮起来。
磁盘还需要防止抖动。虽然盘面看起来光滑的,但是表明是存储数据的地方,如果磁盘抖动导致磁头把盘面刮花就会丢失数据,就好像以往,看碟片出现花纹情况,就是出现了数据丢失。
1.2磁盘的存储结构
磁盘的存储是分为那个磁道,那个盘面,那个扇区的。
问:磁盘寻址的时候,基本单位是bit,还是byte?
注意,磁盘寻址的时候,基本单位既不是bit,更不是byte,而是一块块扇区。也就是说数据都在扇区放着。
每一块扇区都是512byte。靠近圆心存储密度急,远离圆心密度疏。
如果在单面上,如何定位一个扇区呢?
首先要确定在那个磁道。
其次确定在对应磁盘的哪一个扇区。
那如何确定在那个磁道呢?
其实磁头来回摆动的时候,就是在确定在哪一个磁道。
如何确定在哪一个扇区?
盘面在高速旋转的时候,就是让磁头定位扇区。
一个盘片有两个盘面,两个盘面都可以存储数据,那另一面怎么存储数据?
有多少盘面就有多少磁头。并且这些磁头是共进退的。
那如何在磁盘中,定位任何一个扇区呢?
1.先定位在哪一个磁道/柱面(因此磁头是共进退的,磁道一旦确认,所以磁头就不动了)。
2.在定位哪一个磁头(也就是确认在哪一个盘面)。
3.最后定位在哪一个扇区。
磁道(track),柱面(cylinder),磁头(head),扇区(sector)。
因此,磁盘中定位任何一个扇区,采用的硬件级别定位方式:CHS定位法!
1.3磁盘的逻辑结构
还记得磁带吗?卷起来是圆的,扯出来是线性结构。
磁盘物理上也是圆形,在逻辑上想象成线性结构。
假设这个磁盘只有两个盘片,四个盘面,三个柱面
我们把整个磁盘从逻辑上看做一个sector arr[n],把对磁盘的管理,变成对数组进行管理。
请问现在要找到一个扇区,该如何找到呢?
这时,只需要直到这个扇区的"下标",就可以定位一个扇区!
而在操作系统内部,称这种地址为LBA地址(逻辑块地址)。
假设盘面:4,磁道/柱面:10,扇区:100,扇区大小:512byte。
4 * 10 * 100 * 512=总容量(byte)
4 * 10 * 100=下标范围
在物理层面上磁盘定位一个扇区采用的是CHS定位法,那么操作系统如何将逻辑上LBA地址(线性地址)转化成CHS对应方案呢?
假设下标123
123/1000=0 —> 0号盘面 H
123/100=1 —> 1号磁道 C
123%100=1 —> 1号扇区 S
操作系统以这样的算法将LBA地址转化成磁盘能认识的CHS地址,进而访问磁盘的地址。
为什么OS要进行逻辑抽象呢?直接用CHS不行吗?
1.便于管理(是管理一个数组号,还是三维立体结构好呢?)
2.不想让操作系统代码和硬件强耦合(如果底层用的是SSD,或者其他硬件存呢?OS只需把LBA转化成其他的就行了)
虽然磁盘的访问基本单位是512byte,但是依旧很小!!(想读取4KB内容需要8次IO)。知道木桶原理就知道,访问速度根据最低来进行的。因此OS内的文件系统定制的进行多个扇区的读取---->1KB,2KB,4KB为基本单位。所以哪怕只想读取1bit,也必须要将4KB加载到内存,进行读取或者修改,如果有必要再写回磁盘!
操作系统的文件系统一般默认以4KB大小(也就是8个扇区)为基本单位来进行系统和磁盘IO的过程。
内存也是被划分成为4KB大小的空间被OS进行内存管理------页框
磁盘中的文件尤其是可执行文件,按照4KB大小划分好的块-----页帧
把文件加载到内存就是把数据放到页框里。
假设磁盘500G,我们看如何存储文件的。
假设一个磁盘划分4个盘,C/D/E/F盘,其实就是把整个磁盘划区。
如果能把100G大小空间管理好,直接把办法对其他区复制粘贴整个磁盘就可以管理好。
现在对每个区分组。
如果我把5G大小空间管理好,对其他分组进行复制粘贴,这100G也能管理好。
具体来看看文件系统是如何对空间进行管理的
1.4文件系统
Boot Block:分区的开头,是分区表。存放各种分区的情况,如OS系统在那个分区,图形化界面在那个区等。
Super Block:保存的整个文件系统的信息,整个分区有多少分组,起始块号,结束块号是多少等等,而且并不是每个分组都有这个Super Block,只存在于一个或者多个分组。
那为什么Super Block不放在分区而放在分组呢?
这样做是为了备份。万一这个文件系统坏掉了,可以把其他分组保存的文件系统的信息拷贝过来,这样就完成了文件系统的修复。
文件=内容+属性
注意:Linux的文件属性和文件内容是分批存储的。
文件属性存储在Inode,Inode是固定大小,一个文件一个Inode,记录文件的几乎所有属性,但文件名并不在Inode中存储!
文件内容存储在data blocks中,随着应用类型变化,大小也在变化。
Inode为了进行区分彼此,每一个I弄得都有自己的ID!
ls -li //查看Inode编号
查找一个文件的时候,统一使用的是Inode编号。
先去Inode Bitmap查看这个位置是否被占用,再去Inode属性里查找block这个数组,数组保存文件内容保存在data blocks那些数据块中,
下标0-11,直接指向存放数据的数据块。
下标12-14,指向的虽然也是一个数据块,但是数据块不光可以保存数据也可以保存其他的block id。
这样大型文件也可以存储。
删除文件也是使用Inode
只需要去把Inode Bitmap把该位置1改成0,就去把Block Bitmap把该位置变成0就好了。
但是在Linux下,我们查找,删除文件都用的是文件名而不是Inode啊?
问:目录是文件吗?
Linux一切皆文件,因此目录也是文件。
既然是文件就有自己的Inode和date block,Inode放的是目录的属性,那数据块里放的是什么?
其实放的是当前目录下文件名和Inode的映射关系!
因此目录下创建和删除文件必须要有写的权限。
如果有一次误删了重要的文件,我该怎么做呢?
最好什么都不要做,如果对其他文件进行了读写,Inode会被覆盖。
想恢复文件怎么做呢?
我们需要知道文件的Inode,去Inode Bitmap把该位置由0变1,然后再去Inode找到数据块的映射关系,根据映射关系,把Block Bitmap对应的位图中由0变1,这样文件内容就回来了。
如何知道Inode呢?
我们只知道文件名并不知道Inode,其实OS也考虑到这点,所以OS内包含了一个日志信息,当删除一个文件,OS会把对应删除的文件与Inode对应关系临时存储在日志中,注意这个日志也会有保存时间的。
2.软硬链接
2.1什么是软硬链接
软链接
ln -s myfile.txt soft_file.link
硬链接
ln myfile.txt hard_file.link
对软硬链接发现了什么?
发现软链接有独立的Inode,硬链接和被链接的文件具有相同的Inode。
软硬链接的区别:是否具有独立的Inode。
软链接具有独立的Inode:可以被当作独立的文件看待。
硬链接没有独立的Inode,那硬链接该如何理解呢?或者建立一个硬链接究竟是做了什么?
首先知道,建立硬连接根本没有创建文件!因为没有给硬链接分配独立的Inode,既然没有创建文件,那一定没有自己的属性集合和内容集合,你用的一定是别人的Inode和内容。
这里就回答了:建立硬链接就是在指定路径下,新增文件名和inode的映射关系!
当删除myfile.txt时,只是将该Inode的映射关系减1。但是Inode还在因此还可以通过hard_file.link与Inode的映射关系找到该文件的内容。
那什么时候一个文件算被真正删除呢?
当一个文件的硬链接数变成0的的时候,这个文件才算真正被删除!
软链接有自己独立的Inode和内容,当我们删除myfile.txt,软链接就出错,但是我们知道Inode还在。
由此可见软链接内容保存的不是与文件Inode的映射关系。
其实软链接里面保存的是与文件所处路径的映射关系。
当把文件删除时,这个映射关系就出错了。
软链接其实就相当于windows桌面上的快捷方式。
2.2软硬链接的作用
软链接的应用场景:相对于快捷方式,否则执行一个进程需要找到该进程所在路径才能运行进程,而有了软链接直接运行软链接就可以执行当前进程。
unlink soft_file.link //删除链接文件
硬链接的应用场景:
一个普通文件创建,硬链接数为1,而创建目录硬链接数为2,为什么?
一个普通文件,本身就有一个文件名和自己Inode的映射关系。因此为1;
目录也是一个文件,具有独立的Inode,首先就有一个映射关系。这是一个硬链接。进入目录之后发现
.(当前目录)与新建目录lesson1的Inode是一样的。所以当我们执行一个进程的时候通常是./a.out其实就相对于lesson1/a.out,(. 也是一个文件名),.就相对于当前目录lesson1的硬链接,因此lesson1的硬链接数为2。
当在lesson1目录下在创建一个empty目录,发现硬链接数由2变成3了。
这是因为,empty目录下有一个…(上层目录),这个文件与lesson1的Inode一样,相对于又是一个硬链接,因此lesson1的硬链接数变成3。
Linux为什么不允许普通用户给目录创建硬链接,而可以创建软链接呢?
其实.和. .不就是给目录建立的硬链接嘛,不过这是由操作系统自己创建的硬链接,OS自己弄不怕出错,而怕用户创建硬链接出错。
3.动静态库
关于动静态库的知识,在前面【Liunx】开发工具第5章节细说了一些内容,这里主要是实现动静态库。
静态库(.a结尾):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
动态库(.so结尾):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
3.1什么是库
程序在经过预处理,编译,汇编,之后,就要链接了,链接就是将.o文件和库链接起来形成可执行程序。
下面我们将演示类似与库的思想。
问:如果要写一个库需要包含main函数吗?
其实并不需要,如果库里面已经帮忙写好了,那我们在main函数中写的代码有什么用呢?
这里创建main函数,主要为了演示代码的正确性。
首先建立一些文件,写简短的add,sub函数的实现。
gcc -c main.c //形成同名的.o文件
.o文件,是重定位目标二进制文件
现在三个.c文件都已经经过预处理,编译,汇编变成了.o文件。下面就该链接了。
把所有.o文件链接起来有两种方式
gcc -o mymath main.o my_add.o my_sub.o //直接把.o链接起来
gcc -o mymatc main.c my_add.c my_sub.c //先把.c文件形成对于的.o文件,再去链接
当所有.o文件链接好了,就形成了可执行程序。
假如写库的人给不想给用库的人自己的.c文件,那就给用库的人提供自己的.o可重定位目标二进制目标文件,让用库的人来用自己的代码来进行链接就行了。
在预处理阶段就报错了,发现找不到头文件,因此我们也把头文件引入。
这样编译就没报错了。
下面将所有.o文件链接起来
结果是对的。证明编译是没问题的。
虽然现在还不懂写库,但是未来我可以给对方提供.o文件(方法的实现),.h文件(都有什么方法),然后对方就可以编译了。------>库的思想。
.h文件好处理,上面都是没有经过编译的字符类的,有没有它能自己看自己查。
但是如果有一万个.c文件,难道要把这一万个.c文件都编译成了.o文件,在加上头文件一万个,就有两万个文件,就全部直接给用库的人,那用库的人在写命令行的时候是不是太麻烦了。
因此我们可以尝试将所有的".o文件"打包,给对方提供一个库文件即可。
库文件就是多个.o文件打包合并成一个文件,这个文件就是库,然后打包工具和打包方式的不同就有了动态库和静态库。
库的本质:就是.o文件的集合。
3.1静态库和静态链接
生成静态库
ar -rc libmymath.a my_add.o my_sub.o
ar是gnu归档工具,rc表示(replace and create)
将my_add.o和my_sub.o归并到libmymath.a。
交付库=库文件.a .so+匹配的头文件都给别人
.PHONY:outputoutput:mkdir -p mylib/includemkdir -p mylib/libcp -f *.a mylib/libcp -f *.h mylib/include
写好库之后就可以把库发布出去了。
tar czf mylib.tgz mylib //打包
然后别人下载,安装。
cp mylib.tgz ../test //相当于下载
tar xzf mylib.tgz //解压
上面就用cp已经tar命令模仿库的下载的过程。但是还没有安装。
安装的本质:就是把对应的软件安装到特定的目录下,就是把它对应的可执行程序拷贝到指定的系统能够找到的目录。
这里先试试不安装,直接用。
这里报找不到头文件的错误,但是mylib里面明明就有我的头文件啊。
其实这是因为,#include"my_add.h" 会在当前路径下找,但是由于我的库在mylib目录下,也就是说不在当前目录,因此需要指明头文件在哪。
gcc -o mymath main.c -I./mylib/include //-I指明头文件
报找不到库在哪
gcc -o mymath main.c -I./mylib/include -L./mylib/lib
//指明头文件在哪,指明库在哪
但是还是报同样的错误,但这次肯定不是找不到库在哪里了,那这事因为什么呢?
这是因为,如果要链接第三方的库,必须指明库的名称。
但我以前写代码的时候,从来没有指明库的名称!!
gcc —>C
g++ —>C++
编译器默认可以识别C,C++自带的库。
指明了名称,为什么还有问题?
原因在于,库名称不对,
库名称:去掉前缀,去掉后缀,剩下的才是库的名称。
gcc -o mymath main.c -I./mylib/include -L./mylib/lib -lmymath
查看可执行程序依赖的动态库列表
ldd mytest
file mytest
发现我的库明明是静态库,为什么这里是动态链接?
gcc默认的是动态链接的(建议行为),对于一个特定库来说,究竟是动态库还是静态库,取决于你提供的是动态库还是静态库,动态库和静态库都给gcc默认是动态链接。
形成一个执行成程序,可能不仅仅依赖一个库。
对于多个库,gcc也是一个个进行链接的。
对于执行一个进程,需要再命令行写一大推的命令,显得太麻烦了。
我们可以把库安装到指定路径下。
ls /usr/include //头文件
ls /lib64 //库文件
sudo cp mylib/include/* /usr/include //安装头文件
sudo cp mylib/lib/*.a /lib64 //安装库文件
我明明将头文件和库文件安装到指定目录下了,为什么还报错?这里肯定是是把相应的文件加载到相应的位置。
还记得上面提过,使用第三方库要指定库名称。
这里加上库名称就好了。
删除
sudo rm /usr/include/my_ *
sudo rm /lib65/libmymath.a
3.2动态库和动态链接
生成动态库
shared: 表示生成共享库格式
fPIC:产生位置无关码(position independent code)
库名规则:libxxx.so
位置无关码,等到动态库加载原理细说。
gcc -c -fPIC my_add.c
gcc -c -fPIC my_sub.c
gcc -shared -o libmymath.so my_add.o my_sub.o
这里没有再像静态库那样麻烦再打包什么的,而是直接把动态库拷贝过去,都是一样的效果。
gcc -o mymath main.c -I./mylib/include -L./mylib/lib -lmymath
加载共享库时出错:libmymath.so:无法打开共享对象文件:没有这样的文件或目录。为什么出现找不到的错误?
我不是已经告诉库文件,路径和库名称了嘛。
告诉谁了?
我其实只是告诉gcc了,当把程序编译完,和gcc还有关系吗?
和gcc没有关系了。
程序运行起来,OS和shell也是需要知道库再哪里的!而自己库根本没有在系统指定路径下,因此OS无法找到。
这里介绍四种解决方法。
3.2.1通过环境变量找到动态库路径
程序在加载进程时,除了在默认路径下找,也会在LD_LIBRAY_PATH路径下找。
echo $LD_LIBRARY_PATH //把路径信息打印到显示器
把库路径添加到环境变量里。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wdl/linux/test_10_28/mylib/lib
然后运行就没问题了。
但是当我重新登录shell时,发现又不能运行了。
这是因为我们修改的只是子进程的环境变量,当子进程退出,在重新登录shell,如果不对该子进程的环境变量进行修改,它默认用的是父进程的也就是shell的环境变量。
3.2.2把动态库拷贝到默认路径下
sudo cp mylib/include/* /usr/include //安装头文件
sudo cp mylib/lib/*.so /lib64 //安装库文件
3.2.3添加到配置文件中
cd /etc/ld.so.conf.d
这些都是系统默认给我们配置好的文件。
sudo touch lib.conf
sudo vim lib.conf
当配置文件写好保存退出
发现还是找不到错误,这是因为写完配置文件需要更新一下。
sudo ldconfig
这样就没问题了。
如果要删除刚才的配置文件,在删除之后也需要sudo ldconfig更新一下。
3.2.4建立软链接
最简单的方法就是建立软链接
ln -s /home/wdl/linux/test_10_28/mylib/lib/libmymath.so libmymath.so
在当前目录下,给我们的库建立一个软链接,然后运行./mymath可以正确运行,说明在搜索动态库的时候,就能够默认在当前目录下搜索。
如果不想在当前目录下建立软链接,就想在系统中可以用它。可以把这个软链接建立到对应的系统路径下。
sudo ln -s /home/wdl/linux/test_10_28/mylib/lib/libmymath.so /lib64/libmymath.so
3.3动静态库的加载原理
3.3.1静态库加载原理
其实静态库并不需要加载,在编译可执行程序的时候,只是把静态库里面的代码拷贝一份给可执行程序,需要一份就拷贝一份,需要多份就拷贝多份。
那把库代码拷贝到可执行程序的哪里呢?
拷贝到代码区。可执行程序文件本身也有逻辑地址,静态库在该文件的代码区从0x00000000 到0xffffffff编址。
但是当可执行程序加载到内存中,这些重复的代码就造成了内存空间的浪费。
静态库是以绝对编制的方式在该文件的代码区进行编制的。
3.3.2动态库加载原理
静态链接是将静态库的代码通过绝对编制的方式拷贝到可执行程序的代码区。而动态链接是将动态库里指定函数的地址,写入到我们的可执行程序中。
这个地址是偏移地址,就是相对于libc.so(动态库)的地址。
假设动态链接调用的是printf函数,就将printf函数在动态库中的偏移量写入到可执行程序中。
当执行到printf时,经过页表读取时发现在可执行程序中这个这个printf代码不存在。
但是在编译的时就在可执行程序表明这是一个外部地址,需要访问动态库库。因此OS先暂停执行printf,而先将动态库加载到内存,然后将库中的内容经过页表映射到进程的共享区。
动态库经过页表映射到共享区,立马就决定了这个库的起始地址。
虽然在可执行程序中printf语句只是说我们用了那些库。但是不用担心,在链接的时候,printf中已经填写好了printf在库的偏移量!
因此当想调用printf的时候,库一旦完成了加载,映射的过程之后。想跳转执行动态库中的方法,直接在自己的上下文中跳转。
库加载进来,然后就知道了这个库在共享区的地址(也是这个库的起始地址),这时只要拿着printf在链接时的形成的printf在库中的偏移量,再加上这个库的起始地址,就找到这个代码在共享区的地址,然后去调用这个函数,调用结束之后,再返回代码处向后继续执行。
这就是动态库访问,加载的过程。