0
点赞
收藏
分享

微信扫一扫

文件系统

写在前面

这个博客还是属于基础IO模块,不过前面的博客我们学到的所有的东西都在内存中,谈的都是文件与进程的关系.那么是不是计算机中所有的文件都被打开了吗?那些没有被打开的文件究竟如何管理呢?这是我们今天要解释的.

文件系统

我们先回答一个简单的问题,那些没有被打开文件在哪里?肯定是静静的在磁盘上躺着.那么这些没有被打开的文件有什么特点呢?这里就像我们的快递点,里面的快递非常多,也非常杂,我们需要把这些快递进行管理.

这就有点意思了,既然这些文件又这么多问题,我们为何还要保留这些文件呢?这就像快递一样,我们不知道人什么时候来取快递,也就是这些文件随时可能会被打开.问题是我们如何能够快速的去到属于自己的快递呢?这就是快递点老板的作用了,他把这些快递按照一定的规则进行归置,并告诉你的取件码是是什么.这里的老板就相当于我们的OS,他把文件按照一定的规则经行处理,以便我们可以快速的打开这个文.我们把这一模块称为文件系统.

磁盘

在了解文件系统之前,需要提前认识一下磁盘,也就是硬件,需要简单了解组成.如果不认识磁盘,理解文件系统是非常难的.

物理结构

这里我有点担心大家可能会对这一点难以理解,主要就是这里面涉及到硬件,里面的结构非常复杂.这里我们想说一下,从某种意义而言,磁盘是我们计算机唯一的机械设备.我们现在笔记本上面一般很少用磁盘了,大多是SSD,也就是固态硬盘.SSD和磁盘的存储原理不一样.这里先来点其他的知识,SSD相对于磁盘效率高,容量少,也意味之造价贵,而且SSD一般具有读写次数的上限.磁盘的最大特点就是单价便宜,磁盘在互联网公司中非常重要,假设一台服务器配备10块磁盘,每块磁盘造价1000元,该公司存在20w台服务器.这里光是磁盘都要花费2个亿.使用SS就更加费钱了.最关键的是磁盘可不是可以永久使用的,一般3到5年就是损坏,也就是只有大公司才可以自建机房,大家要是有兴趣可以去查看自建机房要考虑哪些条件,例如,地钱,电费(工业电费),空调费,维护费...

我们先去看看一下磁盘拆开的样子,大家可以去二手市场买一些淘汰的磁盘,自己拆拆看看.

文件系统_静态库

盘面

光盘大家应该都知道吧,小时候我就喜欢用这个去看鬼片.如果我们仔细观察就会知道,一般我们很爱护光盘银色的那一面,害怕刮花,这是因为这一面是存储着数据的.同样的,我们要谈的盘面和光盘一样,他也是存储数据的,这里我们先不说这个数据是如何存储的.不同于光盘只能一面存储数据,盘面的两面都可以存储数据.

文件系统_数据_02

文件系统_静态库_03

磁头

大家知道以前的黑胶唱片吗?想想那个可以播放黑胶唱片的机器,我也不知道它的名字叫什么.上面是不是有一个类似针的东西在唱片上滑动.那个东西就类似我们今天的磁头.

文件系统_数据_04

注意,磁头和盘面是不挨着的,他们离的非常近,但是绝对不能挨着.想一想无论我们肉眼看到盘面是如何的光滑,但是在高倍望远镜还是棱角分明.如果挨着了就会刮花盘面.大家或许有拆过机械硬盘的经历,这里我强烈不推荐拆刚买的硬盘,由于磁头和盘面里的非常近,也就是有一点灰尘都不行,这也是我们要求要是拆的话必须在无尘环境下.或许你有时候不小心碰撞了一下机械硬盘,发现不能用了,这是因为整个机械硬盘都是一个一个构件固定的,或许你碰撞了一下,磁头就会刮花盘面.

文件系统_数据_05

在解释一下,我们前面说了盘面是两面都有数据的,那么我们磁头是如何写另外一面的数据的?这个我没有和大家说,对于每一个盘面都有一个磁盘,也就是一个盘(不知道这个称呼准不准确)拥有两个磁头.

数据存储

大家可能会感到既然磁头和盘面不接触,那么请问它是如何把数据存储进去的?这里我先问问为何叫磁盘?这个磁就很有意思了,我们可以这么理解肯定是由于某个东西具有磁性所以叫做磁盘,那么这里盘面就是一个具有磁性的东西.或者说是盘面有类似小指南针的东西.这里就要谈另外的东西了,我们数据的存储磁盘上存储的是01,那么这就有意思了,开和关可以代表01,那么南极和北极(磁性)也能代表01,也就是磁头可以改变盘面上指南针的南北极,进而把数据存进来.

物理存储结构

我们先来解释上面的物理结构是如何工作的.上面我们说了数据是如何存储的,这里解释我们如何把数据写到整个盘面.对于那些比较老的计算机,你一开始会发现你的主机在疯狂的响,这个时候有可能是你的磁盘的是在高速的旋转,注意这个高速是相对于人而言的.这个时候如果磁头不动,我们可以在盘面上写一个圆周的数据.但是如果你的磁头是沿着一条直径来回反复的运动,此时你就可以写整个盘面了.

扇区

这个时候我们就要看看数据的物理存储结构了.我们把盘面上的一个同心圆称之为磁道.同时被两条半径之间的磁道称之为扇区.下面就是盘面的俯视图.

文件系统_动态库_06

同样的,我们把多个盘面距离圆心相同距离的同心圆共同组成的柱子叫做一个柱面.

文件系统_数据_07

这个时候我要下一个结论,磁盘上存储的基本单位是扇区,一般而言,一个扇区的大小是 512字节 .我们发现每一面都是这样的,所以我们只要把这一面搞定就可以了.我们磁头在寻址的时候只需要知道是某一个面某一个磁道某一个扇区.知道哪一面是哪一个磁头决定的,哪一个磁道是哪一个柱面确定的,哪一个扇区也是该磁头决定的.一旦我们找到一个存储单元,我们可以按照同样的方法寻找道其他的扇面.我们把这寻址称呼为CHS寻址.等下我们会用一个例子和大家分析一下如何寻址.

这个大家可能有点疑惑,外面的同心圆扇区必定比里面的同心圆扇区面积大,难道都是 512字节 吗?是的,这个和生产有关,这里不做解释.

抽象存储结构

我们已经明白了数据存储的物理结构,这个时候我们需要把他抽象一下.大家有没有玩过磁带,就是下面的这种.

文件系统_静态库_08

小的时候我感觉把里面的磁带扯出来非常好玩,今天我们就是要扯一下磁带.首先我们知道磁带谁原本不是圆形的吗?当我们全部扯出就变成了直线的.这个时候我们就要把盘片想成也可以被扯成直线的磁带(大家一定要这样想,舍弃其他的不适感).那么我们想问一下这个直线看着像什么?这不就是一个数组吗?此时我们对磁盘的管理变成对数组的管理.这个就非常简单的了.

文件系统_动态库_09

也就是我们从找到扇区变成了找到数组的下表就可以了,这个不就是先描述在组织吗!这个地址就称之为LBA地址,也就是逻辑块地址.未来你想往磁盘中写入,我们只需要知道LBA就可以了.OS会把LBA地址转化位CHS地址,然后通过CHS地址来进行写入数据.这个就涉及到LBA地址转化位CHS地址了.这我们举一个例子,这个非常简单.

假设我们这里有四个扇面,每一个扇面有1000个扇区,此时共有4000个扇区.我们把下表1234的LBA地址转化为CHS地址.

文件系统_数据_10

我们开始CHS寻址,假设每一个盘面有20个磁道.

  1. H 盘面 1234/1000 = 1 也就是在 第2面
  2. C磁道 1234%1000 =234 => 234/20 = 11 在第12个磁道中
  3. S 扇区 123%1000 = 234=> 234%20=14 这个在第15个扇区

注意,我不太清楚上面的算法是不是正确的,这里我把思想传递给大家就可以了.这个时候继续抽象,我感觉一次性访问512字节太小了,此时我们以8个扇区为单位,也就是4kb为一个存储单元进行IO了,也就是IO的基本单位是4kb.

磁盘管理

我们感觉磁盘512g(后面我们把它改成500g)实在是太大了,这个时候还要继续抽象.

分区

比如说我们的国家面积实在是太大了,统一管理不过来.所以我们划分了一个一个省,这个时候感觉省份还是挺大的,我们在划分一个一个市.同样的,我们也可以把整个磁盘也进行划分,这个过程就称之为分区.再想一想,在现实生活中,由于地域的文化等差异,我们的管理方式可能会有点不同,但是在计算机中,我们可是没有差异的,但我们可以管理好一份去区域的时候,我们是可以复制粘贴的.

文件系统_静态库_11

inode

好了,现在可以说一说一块小区域是如何管里的了.这个Linux做的非常细.

解耦和

和上面一样我们把磁盘逐次分为很多的的小区域.假设我们电脑是500g,我们把他划分为4kb的数组,我们看看你又多少.

文件系统_静态库_12

我们说了磁盘的基本单位是扇区,一般是512字节,OS访问磁盘的单位是4kb,也就是8个扇区.我们想问一下,为何要这样做?主要有两个理由.

  • 提高效率 512字节有点少,如果我们一次IO可以操纵4kb,可以减少IO次数,前面的磁头寻址是暴力遍历整个磁盘的,花费时间很长
  • 不要让软件(OS)设计和硬件(磁盘)具有强相关性,换句话说就是解耦合

我们先来解释一下解耦合.前面我们说了,在谈缓冲区的时候,如果我们自己送书,自己既干快递员的事,也干自己的事.如果你要是把快递给快递员,你会关心快递员是如何把书送到的吗?不关心,我们只需要书到了就可以了.这里自己送就是强耦合,快递员送就是解耦合.

我带大家理解上面的那句话.OS不关心磁盘的基本单位是多少?我们只需要保证OS访问磁盘的基本单位是4kb就可以了.这样即使我们磁盘的厂商后面把单位改成了多少,我都不关心,反正我们的代码都不会更改,我们只认4kb.

Block group

好了.我们继续谈上面的分区,我们逐渐把磁盘进行划分,直到划分成了一个一个块组(Block group),注意,现在我们不关心组的大小是什么,此时我们将正式认识.

文件系统_数据_13

我们就可以把整个磁盘管理变化成了对一个快组的管理.

文件系统_数据_14

我先来解释一下什么是是Boot Block.当电脑开机的时候,计算机会进行自检.这个地方保存的是开机信息.那么我们接下类要看一个块组里面的东西是什么?

文件系统_静态库_15

我们在前面一直强调,文件是包含文件内容和文件属性,他们都是数据,也就是他们都要存储.Linux采用的是属性和内容分开存储的方案.这个时候我们把内容保存在一个个block中,也就是4kb,属性放在另外一块空间,这个空间有128字节,我们把这个空间称之为inode.一般而言,内容是不断增多的,属性大小一般是稳定的,我们都把属性给抽象出来了,后面修改的属性也就是修改数据的大小.

  • Data blocks 这个区域主要是以块为单位,也就是4kb.进行文件内容的存储,我们给内容分配的空间是按照4kb为单位的,也就是即使你只用1kb,OS也给你分配4kb.
  • inode table  以128字节为基本单位,每一个保存的是文件的属性,这个属性包含文件的创建时间等属性...
  • Block Bitmap 位图,01代表是否block被使用,比特位的位置代表哪个block被使用
  • inode Bitmap 也是位图,记录inode的使用条件.原理和上面一样,
  • Group Descriptor Table:块组描述符,该块组中inode的起始编号是什么,有多少inode被使用...当然可以一个一个计算,但是终究还是有点麻烦.
  • Super Block :存放文件系统本身的结构信息.记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量.Super Block的信息被破坏,可以说整个文件系统结构就被破坏了

这里我们有点疑惑,既然Super Block是记录文件整个文件系统的,为何SB为何会在块组中,不应该再Boot block中吗 ?理论是如此.但是电脑如果异常关机等到我们下一次开机时,询问是否重新修复文件系统,你一点文件就会被修复好.这个就是SB的作用. 注意不是所有的组都有, 主要是 为了做数据备份.

inode

OS是如何保证文件的唯一性的?每一个文件都有一个inode编号,这个编号是具有唯一性.一般而言,一个文件一个inode.我们先来看看inode编号.

文件系统_动态库_16

此时我们就明白了,文件的唯一性是inode确定的.那么请问一个文件是如何自己的内容产生联系的,要知道属性和内容是分开的.这个时候我们就需要知道inode的本质了,它是一个结构体,里面肯定会保存一个类似blocks[15]的数组,其中下标[0,11]直接保存指向文件内容的blocks编号.这个时候我们计算一下,我们有10个元素,也就是10×4kb=40kb,感觉文件内容的空间是不是有点少?这个时候我们就要数组的其他元素了[12, 14]中也是也是block文件编号,不过指向的block的内容又是指向block的,这就像一个二级索引.如果还是不够的话,我们可以建立三级索引.

这里像问问大家,文件名是不是属性?肯定是啊?但是 inode里面不保存不会保存文件名?也就是Linux下没有文件名的概念.这就和我们看到的现象有偏差了,我们之前用的都是文件名,你这一说,把之前的都给否定了.这是因为我们还没有谈目录.Linux中目录是文件吗?是的,既然是文件,那么内容和属性是什么?属性就是inode,这里就不说了.我们谈谈内容,目录作为一个文件,它的内容保存的是一个映射关系,是文件名和inode为映射.此时我们就明白了.我们要找到文件必须找到inode编号,要找到inode就是通过文件名.

我们再说一下是如何删除文件的,OS会把删除的文件通过文件名通过映射找到inode,找到结构体里面的inode Bitmap和Block Bitmap ,把属于自己的位由1置为0就可以了,这就是我们下载文件需要那么长的时间,删除文件只需要几分钟甚至几秒.

软硬链接

我们先看一个现象,我们有点疑惑下面指令出来的那个1是什么意思.我们也从来没有和大家说过.

[bit@Qkj 11_16]$ ls -li

文件系统_动态库_17

软链接

我们看一下下面的指令.我们创建一个软链接.

[bit@Qkj 11_16]$ ln -s test.c test.c.soft

文件系统_数据_18

所谓的创建软连接就是创建了一个新的inode,不过我们疑惑这里有什么作用呢?我们想用一个例子和大家演示.

[bit@Qkj 11_16]$ mkdir -p ./d1/d2/d3/d4

文件系统_静态库_19

文件系统_动态库_20

文件系统_静态库_21

这个时候我们就明白软连接可以连接那些藏得很深的程序,在Windows环境下这个尤为明显.我们都知道快捷方式,所谓的快捷方式就是一个软连接.软件不可能安排到桌面,里面藏得非常深.那么新的软连接是产生一个文件的,内容是什么呢?指向文件所在路径 (也就是目标文件)

文件系统_动态库_22

硬链接

我们谈了这么多,还是没有和大家说1代表是什么,不要着急.我们来创建一个硬链接.

[bit@Qkj 11_16]$ ln test.c test.c.hard

文件系统_静态库_23

此时你就会明显的发现我们由1变化成了2.这个1或者2代表的是我们的硬链接数.除此之外我们还发现我们的inode是没有发生变化的,这个就有意思,也就是创建硬链接是不会创建新的inode的.注意unlink指令是可以删除连接的,这里就不谈的.

我们再分析下硬链接数是什么?但是有的数字变化了.1变成了2,这里面就是硬连接数.它就是一个引用计数,只计算硬连接数

文件系统_数据_24

为何普通文件的硬连接数是 1,而目录是 默认是2

[bit@Qkj 11_16]$ mkdir d1

文件系统_数据_25

这里就是**.** 的作用了,这个**.**就是一个文件名,只不过看起来有点寒蝉罢了,就是当前目录的硬连接,所以这里默认是2

文件系统_动态库_26

这也是./a.out可以执行成功的原因.不就是当前路径.那么**..**是什么?我们知道,它是上级目录,也就是上级目录的硬链接需要+1./

文件系统_动态库_27

动静态库

前面我们说了库这个词,那是我第一次正式接触库的概念,我们下载编译器的时候,会自动下载相应的库,我们之前包含的标准库的头文件,为何编译器可以知道头文件所在的路径,原因就是编译器下载的时候,.像库这些文件的路径已经默认确定好了.头文件是包含函数声明,库是函数的实现.

库分为动态库和静态库,等会我们感性的认识一下它们两个的区别.在Linux, .so后缀就是动态库,Windows下是.dll.静态库在Linux是.a,Windows下是.lib.

  • 动态库 我们只是包含头文件,遇到标准库的函数,就去库里面找 动态链接
  • 静态库 编译器遇到了库里面的函数,编译器自动把这个函数给复制到你的可执行程序中 静态链接

注意,Linux默认是连接动态库,我们也推荐的是动态库.如果我们想要静态库连接,下面也行,加上一个选项就可以了

[bit@Qkj 08_08]$ gcc main.c -o s_a.out -static

文件系统_数据_28

今天我们将从两角度来对动静态库做一个完整的解析,包含了制作和使用.

库的制造

我们先来给大家制造一下动静态库,这个以后我们如果写了很好的项目就要可以自己制作,这我们模拟一下.

制造静态库

我们知道Linux下静态库的后缀是.a.如果我们程序是静态链接,一般我们程序的体积是非常大的.想问一下库需不要main函数呢?不需要,库是我们提供给用户的,main函数是是用户做的.我们先来写几个文件.

文件系统_数据_29

文件系统_静态库_30

现在我们已经得到几个源文件,每一个源文件里面保存这一个函数,直接开始制作静态库.

文件系统_静态库_31

我们知道所谓的链接不就是所有的.o形成一个可执行程序吗,如果我把所有的.o文件给别人,别人也可以直接使用.但是.o文件实在是太多了.我们要归档一下,也就是打包一下.,ar指令就是这个作用.

ar - rc 静态库名(xxx.a) 各个.o文件

文件系统_数据_32

文件系统_数据_33

此时我们的.a文件就是一个静态库,后面等到我们发布一下就可以使用了.它的本质就是几个.o的集合,以后有人用的的话,把相应的.o文件拷贝进去就可以了,仅此而已.

发布库

我们先把库给大家发布出来.如果你想把库给别人用你要给别人什么呢?这里有两个,一个是库文件,一个是头文件.那么发布库的过程就是作这个的.下面的Makefile就是这么作的.

文件系统_静态库_34

制作动态库

相比较于静态库,动态库的制作有点复杂, 但是原理是一样的.只不过在gcc形成.o文件的时候加上一个指令,这个指令叫做与位置无关码,后面会谈.这个与地址无关码也就是动态库可以加载到物理内存的任意位置,而且只有一份.

文件系统_静态库_35

打包的时候我们加上一个选项,表示是形成动态库.

文件系统_静态库_36

库的使用

这个时候我们就可以得到两个库了.现在我们开始全真模拟当我们拿到一个库的时候我们应该如何使用.

文件系统_静态库_37

静态库使用

现在我们要站在使用者的角度来看了,首先试试静态库的.

#include "mymath.h"
#include "myprint.h"

int main()
{
int start = 0;
int end = 100;
int result = addToVal(start, end);
printf("result : %d\n", result);

Print("hello word");
return 0;
}

文件系统_静态库_38

这个时候当然会出错,首先我们得头文件都没有用对.谁在找头文件,编译器在找,本质是进程再找,就是当前路径,我们知道上面得做法是不对得,但是这里我们就这么做.这里先中断一下.系统的头文件一般在 这个路径下

[bit@Qkj uselib]$ ls /usr/include/

库文件一般在这个路径下

[bit@Qkj uselib]$ ls /lib64/

那么我们第一种做法就是把将自己的头文件和库文件.a拷贝到上面的两个路径下.注意这里还是会报错,不同的是链接错误

文件系统_数据_39

之前我们没有用过第三方库,gcc和g++默认就认识是哪一个库,但是这里我们必须告知用的哪一个库.

文件系统_数据_40

我们把库和头文件拷贝到目录上叫做库的安装,但是我们现阶段不推荐这种做法, 你的代码就这么好吗?非常容易污染库.

我们再来看第二种做法.在使用库时候指定头文件搜索路径. 此时已经找到了头文件,也就是编译可以通过.

[bit@Qkj uselib]$ gcc mytest.c -o mybin -I ./lib-static/include/ -L ./lib-static/lib/

这个时候还是有点问题,此时我们已经找到了库,不过你我们需要告知链接那一个库.

[bit@Qkj uselib]$ gcc mytest.c -o mybin -I ./lib-static/include/ -L ./lib-static/lib/ -lmystaticlib

文件系统_数据_41

动态库使用

上面的我们能都是静态库的使用,这里说一下动态库.动态库也是可以把库文件和头文拷贝到相应的目录下,这里就不谈了.我么直接谈第二种做法.

文件系统_数据_42

上面你会发现有问题,这里为何不可以执行呢?我们看看这个可执行程序的依赖的库.

[bit@Qkj uselib]$ ldd mybin

文件系统_动态库_43

这个使用我们发现我们的可执行程序的竟然没有链接到库.在编译的我不是说了库在哪里吗?为何还是找不到.这个就和大家解释一下, -I -L选项是gcc编译器的,我都帮你形成了可执行程序了,你还让我管后面执行程序如何执行,不是难为我吗?这是什么事!

再谈这个之前,我们静态库的时候怎么没有这个问题呢?因为他在形成可执行程序的时候已经把相应的代码拷贝进去了,之后不依赖库了,这个就不需要在运行的时候查找了.那么为什么动态库有这个问题?这个问题一会再说.现在最重要的是解决这个问题.如何解决这个问题,我们让可执行程序找到动态库就行了,我和大家说三个方法.

  1. 拷贝动态库 到 lib64/
  2. 导入环境变量
  3. 系统配置文件
  4. 其他方式

第一个就不和大家分析了,我们直接看第二个.我们之前从没有见过,这是C或者C++默认有的

文件系统_动态库_44

上面的环境变量有一点问题,一旦我们退出,环境变量会被重新读写,我们前面设计的没有了.这里要求永久有效,修改配置文件,系统里面有一个路径,这个路径下面如果你定义了静态库,系统在执行程序的时候也会扫描这个文件,我们在这里创建一个文件,把动态库的绝对路径拷贝进去就可以了.重启一下配置文件.

文件系统_数据_45

你说了这么多,感觉都很麻烦,能不能说一个人性化的方式,这里软连接可以了,我们在lib64下创建一个软连接就可以.

文件系统_静态库_46

共享区

我们把上面的用下,你会发现都是可以的.此时我们用法都已经解决了.

文件系统_静态库_47

我们回答前面的问题,为何可执行程序在动态链接的时候我们需要做这么事情,这是由于程序和动态库是分开加载的.我们在虚拟地址空间一直有一个地方没有和大家谈,在栈区和堆区中间的哪一部分是什么?他叫做共享区.当一个程序要连接一个动态库的时候,这个时候动态库如果没有被加载,此时从硬盘中加载到物理内存的任意位置,我们通过页表把这个库映射到共享区.如果还有一个进程链接这个库,我们直接通过共享区去查找这个库是不是存在,如果存在了,直接用就可以了.此时我们把库加载到任意位置叫做与位置无关,而且动态库只有一份被加载到内存.可以回答这个问题了,如果程序要加载动态库,我们前提是找到这个库,这就是我们运行的时候为何要有这么多的操作.

举报

相关推荐

0 条评论