linux --> 文件系统十问

文件系统十问
 
参考:http://djt.qq.com/article/view/620
 
关于Linux文件系统相关的问题:
1、机械磁盘随机读写时速度非常慢,操作系统是采用什么技巧来提高随机读写的性能的?
2、touch一个新的空文件占用磁盘空间吗? 占用的话占用多少?
3、新建一个空目录占用磁盘空间吗?占用多少?和新建一个文件相比,哪个占用的更大?
4、你知道文件名是记录在磁盘的什么地方吗?
5、文件名最长多长?受什么制约? 
6、文件名太长了会影响系统性能吗?为什么会产生影响?
7、一个目录下最多能建立多少个文件?
8、新建一个内容大小1k的文件,实际会占用多大的磁盘空间?
9、向操作系统发起读取文件2Byte的命令,操作系统实际会读取多少呢?
10、我们使用文件时要怎么样来能提高磁盘IO速度? 
一、磁盘构成及分区
1、磁盘物理结构
  本文只讨论机械磁盘,SSD不在本文讨论范围之内。对于管理磁盘,分磁盘面、磁头、磁道、柱面和扇区。
    磁盘面:磁盘是由一叠磁盘面组成,见下左图。
    磁头(Heads):每个磁头对应一个磁盘面,负责该磁盘面上的数据的读写。
    磁道(Track):每个盘面会围绕圆心划分出多个同心圆圈,每个圆圈叫做一个磁道。
    柱面(Cylinders):所有盘片上的同一位置的磁道组成的立体叫做一个柱面。
    扇区(Sector):以磁道为单位管理磁盘仍然太大,所以计算机前辈们又把每个磁道划分出了多个扇区,见下右图
linux --> 文件系统十问linux --> 文件系统十问
  Linux上可以通过fdisk命令,来查看当前系统使用的磁盘的物理信息。
linux --> 文件系统十问
  可以看出我的磁盘有255个heads,也就是说共有255个盘面。3263个cylinders,也就是说每个盘面上都有3263个磁道, 63sectors/track说的是每个磁道上共有63个扇区。命令结果也给出了Sector size的值是512bytes。那么算一下该磁盘的大小,结果是26.8G,和磁盘的总大小相符。
  255盘面  * 3263柱面 * 63扇区 * 每个扇区512bytes = 26839088640byte。
 
2、分区
  分区是操作系统对磁盘进行管理的第一步,例如Windows下的C、D、E、F盘。如果让你把整块磁盘分成C、D等分区,你会怎么分呢? 
  方案一: 255个盘面,C盘是0-100盘面, D盘是101-200个盘面,……
  方案二:3263个柱面,C盘0-1000个柱面,D盘1001-20001个柱面,……
  先说下磁盘IO时的过程。第一步(寻道时间),首先是磁头径向移动来寻找数据所在的磁道。第二步(旋转延迟),找到目标磁道后通过盘面旋转,将目标扇区移动到磁头的正下方。第三步(存取时间),向目标扇区读取或者写入数据。到此为止,一次磁盘IO完成,故:
         单次磁盘IO时间 = 寻道时间 + 旋转延迟 + 存取时间。
  对于旋转延时,现在主流服务器上经常使用的是1W转/分钟的磁盘,每旋转一周所需的时间为60*1000ms/10000转=6ms,故其旋转延迟为(0-6ms)。对于存取时间,一般耗时较短,为零点几ms。对于寻道时间,现代磁盘大概在3-15ms,其中寻道时间大小主要受磁头当前所在位置和目标磁道所在位置相对距离的影响。 
  采用第一种,磁头就需要在3000多个track间不停地跳来跳去,这样磁盘的寻道时间就会翻倍,磁盘性能就会下降。
      采用第二种,假如对于磁盘C,只需要在磁头在1-1000个磁道间移动就可以了,大大降低了寻道时间。
所以,方案二的分区方式可以降低磁盘IO时间中的寻道时间部分,所有的操作系统采用的都是方案二,没有用方案一的。
注:实际上分区并不是从0开始的,磁盘的第一个磁道对应的柱面会被用来安装引导加载程序以及磁盘分区表。
  回到开篇问题1,操作系统是采用什么技巧来降低随机读写的性能问题的呢?操作系统通过按磁道对应的柱面划分分区,来降低磁盘IO所花费的的寻道时间 ,进而提高磁盘的读写性能。

二、 目录与文件

1、引子    
  文件系统不就是目录和文件吗?先来来创建个空目录和空文件吧,查看结果如下图:
linux --> 文件系统十问
  第五列显示的是占用的空间大小,那么提几个小小的问题吧。
    (1)为什么目录占用的空间是4096?
    (2)为什么空文件占用的空间却是0?
    (3)如果空文件真占用0byte空间,那么该文件的文件名、创建者以及权限-rw-rw-r—等文件夹相关的信息都存到哪儿去了? 
2、空文件占用空间

 输入df –i查看inodes信息:

linux --> 文件系统十问

touch一个空的文件后再次df -i查看inodes信息:

linux --> 文件系统十问

  这个实验证明操作系统“欺骗”了我们,它消耗掉了一个inode。那么inode的节点大小是多少呢,使用dumpe2fs命令可以帮助我们查看到这个东东的实际大小。

linux --> 文件系统十问

  在输出的结果中我们可以找到下面这行:

linux --> 文件系统十问

  它告诉我们每个inode的大小是256Byte。当然这个大小每台机器都会不一样,它实际上是在系统格式化磁盘的时候决定的。

  好了,开篇第二个问题也有答案了。原来新建一个空的文件是会占用磁盘空间的,实际占用的是256Byte。哦,不,准确的说法应该是一个inode size,具体的值是在格式化时决定的。

  再说说新建空目录吧,前面说了新建空目录会占用4KB的磁盘空间。那么仅仅如此吗? 我们同样在新建目录前后都使用df –i来监视系统inode的占用。

linux --> 文件系统十问

  原来目录也是会占用一个inode节点的,第三个问题也有了答案了,新建一个空目录会占用磁盘空间4KB + inode size。 哦,这个在你的系统上也不一定是4K,它实际上一个block size。同样在dumpe2fs下可以看到。

linux --> 文件系统十问

  只不过我的磁盘在格式化时采用的是4KB的大小,呵呵!

3、神秘的空目录的4KB

  空目录占用的那4KB,这些空间是用来存什么的呢?cd到新建的目录下查看。

linux --> 文件系统十问

  我们再新建两个空的文件,再查看下目录的空间占用情况。
linux --> 文件系统十问

  因为空文件不占用block,所以这里显示的仍然是目录占用的block,和之前大小没有变化。使用php脚本创建100个文件名长度为32Byte的空文件。

linux --> 文件系统十问

linux --> 文件系统十问

  这时我们发现目录占用的磁盘空间变大了,成了3个Block了。这就解答了我们开篇的第四个问题,文件名是存储在目录占用的block中的。另外新建了个空目录,创建了100个文件名长度为32*3个空文件,该临时目录占用的磁盘空间如下:

linux --> 文件系统十问

  占用的block数目为什么没有变成3倍?其实Linux文件系统关于文件的结构体中除了文件名以外,还有其它的一些字段的,文件名变长3倍不会导致结构体变大3倍的,这点可以参考Linux系统内核相关书籍。

  好了,到现在开篇问题6也有了答案了。文件名长了当然会对系统性能产生影响,因为这可能会导致更多的磁盘IO。很多程序员都喜欢将文件命名为有意义的长串,使人一看文件名就知道用途。当然我没说这样不好,但是如果你的文件数量相当大的时候,你就要考虑你的文件名是否导致你的目录block占用太多了。占用的空间倒是小事,磁盘很便宜,但是你得考虑下在目录下查找文件时操作系统的感受,操作系统可需要用你你提供的文件名进行字符串比较,而且运气不好的话需要将其名下所有block都搞一遍才行啊。(当然了,你的文件名长度不变态,而且数量没有达到十万数量级的话实际上这个开销也不会太大,但是这个开销你还是知道的为好)

  至于开篇问题5,文件名最长多长。实际上Linux操作系统就是为了避免程序员不节制地使用长文件名,强加了个限制,不得超过255byte

  另外,大家有没有经验,在目录下文件很多的时候,我们使用ls命令时会很慢。现在大家知道原因了吧,这时实际上操作系统在读取当前目录的所有block,如果block比较多的话,可能得需要多次IO操作才能完成这个简单的ls命令。

  我在自己的电脑某个目录下创建了一100W个空文件,ls命令1分钟还没出结果,被我ctrl+c掉了。在自己的项目中可不要这么干,虽然操作系统可以cache住你的目录数据,使你下次调用时会块很多,但我还是建议你单个目录下文件数目不要过万。否则你的程序在重启后首次运行时可能会出现性能不佳的情况。

  好了,回到开篇问题7,你有答案了吗?一个目录下最多能建多少个文件,这个最多其实是受限于你目录所在分区的inode数量,你有100W个inode,你最多就可以新建100W个文件。但是,上面说了,单个目录下文件数量最好不要过万,否则会带来系统性能的问题。

4、文件的block

  再做个关于文件的实验。我新建了个空目录,并在其下新建了个文件,里面只写了一个空格数据,保存后du命令显示如下:

linux --> 文件系统十问

  这8K里有4K是目录的,也就可以算出操作系统为只包含一个空格的文件分配了4KB。其实文件的block比较简单的了,不像目录的block里会存很多文件系统的结构体,文件的block里只会保存文件的数据。上面这个实验表明,操作系统分配空间时是以block为最小单位。也就是说只要你的文件数据不为空,操作系统就至少会给你分配一个block来存储,直到你超过了4KB,操作系统再给你分配下一个block。所以对于开篇问题8,新建一个内容大小为1k的文件,实际会占用1个block(一般为4k)和一个inode(一般为256byte)。

  其实文件系统在向磁盘发起IO请求的时候,也是以block size为单位的。哪怕你只向操作系统发起读取文件的2Byte,但是操作系统会一次性给你读取4KB回来。因此磁盘IO真的是很慢,而且我们只要访问了这2Byte,确实很有可能接下来继续访问这2byte后面的内容,这也就是程序局部性原理,所以操作系统索性一次性就多读取些回来了。呵呵,这就是开篇问题9的答案。
这就像我们去逛超市,逛一次真的是很浪费时间,这可要比坑爹的磁盘IO也慢许多了。我们总不会逛了一圈超市就买了一个苹果就回来了吧,我们肯定会多买些东西为家里以后的需求准备着,反正买一堆东西比买一个苹果也没多花多少时间,何乐为不为呢,就是这个道理。

  再说说开篇问题10,怎么样设计你的文件能提高一些IO速度呢?那就是如果你知道你的要新建的文件大概会占用多大的空间的话,比如1M。那么你新建文件时就顺便和操作系统说一下,让它帮你将文件的size预留下来。这样实际上操作系统时会尽可能为你分配连续的block,这样你再读取这个文件时,磁头就省去很多寻道时间了,IO速度就显得快多了。

 
三、写在后面的话

  前面我们说的都是基于我自己的文件系统,情形是一个block size是4KB,一个inode size是256byte,包括我虚拟机上的inode数量才只有140多万个。这些值实际上不是固定的,你完全可以在格式化你的硬盘的时候设置成其它的值。设置的原则就是看你的硬盘的容量,以及你的用途。

  如果你的文件都是大于4KB,甚至是几M,几G的文件,那么建议你的block还是尽可能的大一点吧,这样inode里就能少记几个地址。
  如果你的文件大部分都是1K以下的,那么确实使用4K的block会造成一点点浪费,你可以适当考虑把你的block设置得小一点。
  另外,要关注你的文件系统的inode。操作系统在查看目录和文件占用的磁盘空间信息时把inode节点的占用给隐藏起来了,其用意在于为用户提供一个白盒的环境,把数据占用的空间交给我们来认知,而把inode信息隐藏起来为了降低我们理解操作系统的难度。而实际上,我们作为非普通用户的开发人员应该具备这个知情权。这个东东直接关系到你文件系统能创建文件数量。否则哪天等你发现线上机器磁盘还剩大把大把的空间,但就是inode使用光了,那时候就只有重新格式化或者迁移服务器了。这两个操作想想都觉得苦逼啊,还是能避免就尽量避免吧。   
上一篇:微言Netty:分布式服务框架


下一篇:Dubbo阿里Alibaba开源的分布式服务框架