一种磁盘文件系统的设计

近日和朋友聊到存储系统设计的相关技术,结合以前我在互联网公司做分布式系统的一些经验,本篇就粗略地讲一讲如何设计一个简单的磁盘文件系统。

磁盘的结构:

传统的磁盘结构是像下面这个样子的,它有一个或多个盘片,用于存储数据。盘片多采用铝合金材料;中间有一个主轴,所有的盘片都绕着这个主轴转动。一个组合臂上面有多个磁头臂,每个磁头臂上面都有一个磁头,负责读写数据。


image.png

磁盘一般有一个或多个盘片。每个盘片可以有两面,即第一个盘片的正面为0面,反面为 1 面;第二个盘片的正面为 2 面…依次类推。磁头的编号也和盘面的编号是一样的,因此有多少个盘面就有多少个磁头。盘面正视图如下图,磁头的传动臂只能在盘片的内外磁道之间移动。因此不管开机还是关机,磁头总是在盘片上面。关机时,磁头停在盘片上面,抖动容易划伤盘面造成数据损失,为了避免这样的情况,所以磁头都是停留在起停区的,起停区是没有数据的。


image.png

每个盘片的盘面被划分成多个狭窄的同心圆环,数据就存储在这样的同心圆环上面,我们将这样的圆环称为磁道 (Track)。每个盘面可以划分多个磁道,最外圈的磁道是0号磁道,向圆心增长依次为1磁道、2磁道…磁盘的数据存放就是从最外圈开始的。


image.png

根据硬盘的规格不同,磁道数可以从几百到成千上万不等。每个磁道可以存储数 Kb 的数据,但是计算机不必要每次都读写这么多数据。因此,再把每个磁道划分为若干个弧段,每个弧段就是一个扇区 (Sector)。扇区是硬盘上存储的物理单位,现在每个扇区可存储 512 字节数据已经成了业界的约定。也就是说,即使计算机只需要某一个字节的数据,但是也得把这个 512 个字节的数据全部读入内存,再选择所需要的那个字节。


image.png

柱面是我们抽象出来的一个逻辑概念,简单来说就是处于同一个垂直区域的磁道称为柱面 ,即各盘面上面相同位置磁道的集合。需要注意的是,磁盘读写数据是按柱面进行的,磁头读写数据时首先在同一柱面内从 0 磁头开始进行操作,依次向下在同一柱面的不同盘面(即磁头上)进行操作,只有在同一柱面所有的磁头全部读写完毕后磁头才转移到下一柱面。因为选取磁头只需通过电子切换即可,而选取柱面则必须通过机械切换。数据的读写是按柱面进行的,而不是按盘面进行,所以把数据存到同一个柱面是很有价值的。

磁盘被磁盘控制器所控制(可控制一个或多个),它是一个小处理器,可以完成一些特定的工作。比如将磁头定位到一个特定的半径位置;从磁头所在的柱面选择一个扇区;读取数据等。


image.png

现代硬盘寻道都是采用CHS(Cylinder Head Sector)的方式,硬盘读取数据时,读写磁头沿径向移动,移到要读取的扇区所在磁道的上方,这段时间称为寻道时间(seek time)。因读写磁头的起始位置与目标位置之间的距离不同,寻道时间也不同。磁头到达指定磁道后,然后通过盘片的旋转,使得要读取的扇区转到读写磁头的下方,这段时间称为旋转延迟时间(rotational latencytime)。然后再读写数据,读写数据也需要时间,这段时间称为传输时间(transfer time)。

根据上文的信息,我们可以得出磁盘容量的计算公式为:
磁盘容量 = 总盘面数 x 每盘面柱面数 x 每磁道扇区数 x 512

注:以上信息来源于网络。

文件系统的设计:

磁盘在物理上是一个很复杂的混和结构,对软件开发者来说,基本上不需要直接接触它们,我们所有的访问都在磁盘控制器的接口之一,使用与内存相似的统一寻址,只不过磁盘支持持久化,且性能相对于内存有数量级差。我们可以考虑使用静态链表来设计文件系统。

静态链表:

静态链表是一种利用顺序存储结构来进行链式存储的特殊结构,使用它可以在一段连接的存储空间模拟离散的链式结构。它通过将整块大内存划分为小的块,块与块之间通过指针连接,这个小的块,我称为之存储单元。

文件系统结构定义:

在存储单元之上,一个基本的文件系统需要有逻辑的目录、文件,通常一个块不可能存储完整个目录或文件,因此它们实际上可能需要跨多个块。下面是存储单元和目录结构的定义:

存储单元结构定义:

    头区(保留32字节):
        本单元数据区大小(4字节)
        数据类型(4字节):目录 / 文件
        数据区偏移(4字节):32
        父级存储单元地址(4字节)
        数据区的下一单元地址(4字节)
        本单元的下一空闲单元地址(4字节)
    数据区(大小由头区指定):
        目录数据 / 文件数据

目录结构定义:

    项目数(4字节)
    项目1(保留16字节):
        项目类型(4字节):目录 / 文件
        项目名称(4字节):目录名 / 文件名
        项目所在存储单元地址(4字节)
    项目2(保留16字节):
        项目类型(4字节):目录 / 文件
        项目名称(4字节):目录名 / 文件名
        项目所在存储单元地址(4字节)

文件结构定义:
纯文件内容

此外,还需要有空闲单元结构等信息,这里增加一个全局磁盘信息定义:

    存储单元大小
    存储单元入口地址
    空闲单元入口地址
文件系统内容布局:

下面以一个64K的磁盘,假设起始地址从0开始,最大地址64K-1,对于如下的逻辑存储:

/ 
  |------目录1 
  |           |------目录2 
  |           |           |------文件3 (大小3.1K)
  |           |           
  |           |------文件2 (大小100)
  |          
  |------文件1 (大小100)

假设定义每单元大小为4K,在4K字节中,需要存储单元头32字节,其余4K-32可存储目录或文件内容,假设在下面的设计中,每个单元若存储文件内容最多不超过3K。且假设定义全局磁盘信息在0单元,其实际物理布局如下:

全局信息地址:0
    存储单元大小:4K
    存储单元入口地址:4K
    空闲单元入口地址:32K

单元地址:4K (/目录)
    头区:
        本单元数据区大小:36(4 + 2x16)
        数据类型:目录
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        目录数据,内容如下:
            项目数:2
            项目1:
                项目类型:目录
                项目名称:目录1
                项目所在存储单元地址:8K
            项目2:
                项目类型:文件
                项目名称:文件1
                项目所在存储单元地址:12K

单元地址:8K (目录1)
    头区:
        本单元数据区大小:36(4 + 2x16)
        数据类型:目录
        数据区偏移:32
        父级存储单元地址:4K
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        目录数据,内容如下:
            项目数:2
            项目1:
                项目类型:目录
                项目名称:目录2
                项目所在存储单元地址:16K
            项目2:
                项目类型:文件
                项目名称:文件2
                项目所在存储单元地址:20K

单元地址:12K (文件1)
    头区:
        本单元数据区大小:100
        数据类型:文件
        数据区偏移:32
        父级存储单元地址:4K
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        文件数据,内容如下:
            abcdefg....(100字节)

单元地址:16K (目录2)
    头区:
        本单元数据区大小:20(4 + 1x16)
        数据类型:目录
        数据区偏移:32
        父级存储单元地址:8K
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        目录数据,内容如下:
            项目数:1
            项目1:
                项目类型:文件
                项目名称:文件3
                项目所在存储单元地址:24K

单元地址:20K (文件2)
    头区:
        本单元数据区大小:100
        数据类型:文件
        数据区偏移:32
        父级存储单元地址:8K
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        文件数据,内容如下:
            abcdefg....(100字节)

单元地址:24K (文件3)
    头区:
        本单元数据区大小:3K
        数据类型:文件
        数据区偏移:32
        父级存储单元地址:16K
        数据区的下一单元地址:28K
        本单元的下一空闲单元地址:0
    数据区:
        文件数据,内容如下:
            abcdefg....(3K字节)

单元地址:28K (文件3)
    头区:
        本单元数据区大小:100
        数据类型:文件
        数据区偏移:32
        父级存储单元地址:16K
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        文件数据,内容如下:
            abcdefg....(100字节)

单元地址:32K
    头区:
        本单元数据区大小:0
        数据类型:0
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:36K
    数据区:
        无

单元地址:36K
    头区:
        本单元数据区大小:0
        数据类型:0
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:40K
    数据区:
        无

单元地址:40K
    头区:
        本单元数据区大小:0
        数据类型:0
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:44K
    数据区:
        无

单元地址:44K
    头区:
        本单元数据区大小:0
        数据类型:0
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:48K
    数据区:
        无

单元地址:48K
    头区:
        本单元数据区大小:0
        数据类型:0
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:52K
    数据区:
        无

单元地址:52K
    头区:
        本单元数据区大小:0
        数据类型:0
        数据区偏移:32
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:56K
    数据区:
        无

单元地址:56K
    头区:
        本单元数据区大小:0
        数据类型:0
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:60K
    数据区:
        无

单元地址:60K
    头区:
        本单元数据区大小:0
        数据类型:0
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        无

操作设计:

一个合格的文件系统至少应支持以下操作:

格式化:

包括低级格式化和高级格式化,低级格式化由厂商提供,由控制器提供支持,主要用于消除数据,重建扇区存储结构。高级格式化由操作系统提供,主要目的是向固定扇区写入特定结构,这些结构包括磁盘或分区元信息表,管理整个磁盘或分区的全局和入口信息。
对我们的简单系统来说,需要初使化全局信息地址和根目录,以及各存储单元,初使化为:

全局信息地址:0
    存储单元大小:4K
    存储单元入口地址:4K
    空闲单元入口地址:8K

单元地址:4K (/目录)
    头区:
        本单元数据区大小:0
        数据类型:目录
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        目录数据,暂无

单元地址:8K
    头区:
        本单元数据区大小:0
        数据类型:0
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:12K
    数据区:
        无

......

单元地址:60K
    头区:
        本单元数据区大小:0
        数据类型:0
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        无
新增目录:

首先需要找到父目录所在的单元位置,由于整个文件系统是一个树型结构,需要从根目录位置开始寻找。若在根目录添加,或者说找到了父目录的单元地址,操作如下:

  1. 在全局信息地址中找一个空闲单元,如8K,修改空闲单元入口地址为12K。
全局信息地址:0
    存储单元大小:4K
    存储单元入口地址:4K
    空闲单元入口地址:12K
  1. 修改4K所在的根目录存储单元内容,如下:
单元地址:4K (/目录)
    头区:
        本单元数据区大小:20(4 + 2x16)
        数据类型:目录
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        目录数据,内容如下:
            项目数:1
            项目1:
                项目类型:目录
                项目名称:目录名称
                项目所在存储单元地址:8K
  1. 修改8K的存储单元内容,如下:
单元地址:8K (目录名称)
    头区:
        本单元数据区大小:4
        数据类型:目录
        数据区偏移:32
        父级存储单元地址:4K
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        目录数据,内容如下:
            项目数:0
删除目录:

删除目录是上面新增目录的逆操作,操作步骤:

  1. 首先必须提供待删除目录的确定地址,如删除上面新增的目录8K,不需要特意的做数据区的擦除工作。
  2. 从8K的数据单元头区找到父级存储单元地址,如4K,然后读取4K数据区的内容,删除对应的子目录,然后重新写回数据区。
  3. 将8K的数据单元重新加入到空闲单元中,需要先取出修改全局信息地址中的空闲单元入口地址,将8K存储单元链向它,并将8K地址更新到空闲单元入口地址。
遍历目录:

遍历通常实现为一个递归的过程,对每个递归单元来说,逻辑如下:

  1. 判断提供的存储单元的头区数据类型,如果非目录,返回不再执行后续步骤。
  2. 根据数据区偏移找到目录的数据结构,这里一个列表,读取其内容。
  3. 顺序遍历这个列表,判断项目类型,如果是文件,打印文件名,如果是目录,则递归。
  4. 判断数据区的下一单元地址是否为0,非0表示目录列表未展示完全,还需要读取其他的单元,继续回到步骤1执行。
新增文件:

首先需要提供父目录位置,即目录存储单元地址,以在根目录添加文件为例,操作如下:

  1. 在全局信息地址中找一个空闲单元,如8K,修改空闲单元入口地址为12K。
全局信息地址:0
    存储单元大小:4K
    存储单元入口地址:4K
    空闲单元入口地址:12K
  1. 修改4K所在的根目录存储单元内容,如下:
单元地址:4K (/目录)
    头区:
        本单元数据区大小:20(4 + 2x16)
        数据类型:目录
        数据区偏移:32
        父级存储单元地址:0
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        目录数据,内容如下:
            项目数:1
            项目1:
                项目类型:文件
                项目名称:文件名称
                项目所在存储单元地址:8K
  1. 修改8K的存储单元内容,如下:
单元地址:8K (文件名称)
    头区:
        本单元数据区大小:0
        数据类型:文件
        数据区偏移:32
        父级存储单元地址:4K
        数据区的下一单元地址:0
        本单元的下一空闲单元地址:0
    数据区:
        无
写入文件:

需要提供文件的存储单元地址,以向上面的8K地址写入abcdefg内容为例,操作如下:

  1. 更新8K的头区单元数据区大小为7。
  2. 更新8K的数据区的内容为abcdefg。
读取文件:

需要提供文件的存储单元地址,以向上面的8K地址读取为例,操作如下:

  1. 取得8K的头区单元数据区大小,如7。
  2. 读取8K的数据区内容,读取长度7。
  3. 检查数据区的下一单元地址,确保是否是最后数据区,若不为0,切换存储单元地址,继续执行步骤1。
删除文件:

是新增文件的反操作,操作如下:

  1. 首先必须提供待删除文件的确定地址,如删除上面新增的文件8K,不需要特意的做数据区的擦除工作。
  2. 从8K的数据单元头区找到父级存储单元地址,如4K,然后读取4K数据区的内容,删除对应的子项,然后重新写回数据区。
  3. 将8K的数据单元重新加入到空闲单元中,需要先取出修改全局信息地址中的空闲单元入口地址,将8K存储单元链向它,并将8K地址更新到空闲单元入口地址。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容