[关闭]
@yiltoncent 2016-01-13T08:27:30.000000Z 字数 3619 阅读 9649

为什么同样的文件在不同的文件系统中大小不一样?'du'、'ls -l'与'ls -s'的区别辨析

LINUX


背景

上周在公司时候发现一个问题,一个文件夹里面有200个Dummy文件,用数字1-200来标示,以及一个index.html文件,在windows下面查看文件大小分别是20488B以及6B,占用空间是24KB以及4KB,总共是4804KB

我们有一块linux板子,里面挂载了两种不同的文件系统,一种是ramfs,一种是jffs2。假设包含这些文件的文件夹叫dum,那么在不同文件系统里面对这个文件夹操作duls -s以及ls -l得到的结果有些差别。详细如下:

ramfs

file du ls -s ls -l
Dummy* 24KB 24KB 20488B
index.html 4KB 4KB 6B
dum 4804KB

jffs2

file du ls -s ls -l
Dummy* 21KB 20KB 20488B
index.html 1KB 0KB 6B
dum 4101KB

提出问题

从上面的表格中,我们可以问出三个问题
1. 为什么同样的文件夹dum在不同的文件系统中du操作的结果不一样?
2. 为什么同样的文件,在不同的文件系统中duls -s的结果不一样?
3. 为什么同样的文件在同一个操作系统中,duls -s的结果不一样?

本文将由这三个问题引出并一一深入解释。在进入问题解答之前,我们要学习一点预备知识。

预备1: duls -s以及ls -l操作的不同。

其中ls -l很好理解,这个操作的结果在两个文件系统中的结果是一样的,乃至与windows系统中文件大小也是一样的。不容易理解的是duls -s的区别。这个留着后面再说。

预备2: 磁盘扇区(sector)与文件系统块(block)

这两个概念很多人容易混淆,其实我觉得很容易理解。sector是针对硬件,如硬盘,它是物理盘的属性。block是文件系统概念,是操作系统分配磁盘空间时操作的最小单位。下面引用一篇文章,我觉得说得很详细,可以拓展参考一下。
一般情况下,磁盘的sector大小为512字节。但像flash这样的设备比较特殊:

闪存的最小寻址单位是字节,而不是磁盘上的扇区sector。这意味着我们可以从一块闪存的任意偏移(offset)读数据,但并不表明对闪存写操作也是以字节为单位进行的。将磁盘文件系统(ext2,FAT)运行在闪存上的很自然的方法就是在文件系统和闪存之间提供一个闪存转换层(Flash Translation Layer), 它的功能就是将底层的闪存模拟成一个具有512字节扇区大小的标准块设备(block device)。对于文件系统来说,就像工作在一个普通的块设备上一样,没有任何的差别引用链接http://www.ibm.com/developerworks/cn/linux/l-jffs2/

而文件系统的block大小并非固定的,一般有1024B、2048B、4096B等等,不同的文件系统支持的block大小不一样,一般在生成文件系统的时候可以指定block的大小。对于存储大文件来说,block划分大一点,文件读取效率高;对于存储小文件来说,block划分小一点,空间利用率高。

我们的jffs2文件系统正是基于闪存的,而ramfs则是基于内存的。

问题1

从前面index.html文件du结果可知,ramfsjffs2的block大小分别是4KB和1KB。
这一定程度上解释了为什么文件夹dum在不同的文件系统中du操作的结果不一样。不同文件系统的Block大小不一样,一般来说对于同样的文件所占用的磁盘空间也不一样;除非文件大小正好是两者block大小的公倍数。如文件大小正好是8KB,那么两个文件系统的du结果都应该是8KB。

问题2

对于ramfs,两个文件duls -s的结果都是一样的。然而对于jffs2,两个文件duls -s的结果都不一样。这个是令人费解的。在网上搜索了半天也没有满意的答案,就只能自己看源码找原因了。在公司里面因为是嵌入式平台,lsdu程序都集成在busybox中。通过代码分析,发现两个程序都是对struct statst_blocks进行操作处理。基本思路是:

  1. sum = size * 512;
  2. sum += display_unit/2; //rounding
  3. sum /= display_unit;
  4. print(sum);

根据上面公式,我们分别计算Dummyindex.htmlduls -s操作下的大小,以及对整个dum文件夹的du操作结果。
首先,20488B占用的sector大小为ROUND(20488/512)=41,6B占用的sector大小为ROUND(6/512)=1.

  1. du Dummy
  2. sum = 41*512
  3. sum = 41*512+1K/2 = 42*512;
  4. sum = 42*512/1K = 21 //21个单位1K的block
  5. ls -s Dummy
  6. 41>>1 = 20 //20个单位为1KB的block
  7. du index.html
  8. sum = 1*512
  9. sum = 1*512+1K/2 = 1K
  10. sum = 1K/1K = 1 //1个单位为1K的block
  11. ls -s index.html
  12. 1>>1 = 0 //

以上所述解释圆满的解释了第二个问题

问题3

问题2解决了之后,问题3也顺势解决了

  1. du dum/
  2. sum = (41*200+1)*512 //200个Dummy文件,一个index.html文件
  3. sum = (41*200+1)*512+1K/2=8202*512
  4. sum = 8202*512/1K = 4101 //4104个单位为1K的block

回头看ramfs

将上述分析适用到ramfs文件系统中,也能得到正确答案,以为佐证。但在分析之前,首先要回顾一下两种文件系统的block大小区别,这个在问题1中已经提到了:ramfs文件系统的block大小为4KB,占用sector大小为8。即ramfs对于存储空间的分配不是以1个sector为单位,而是以8个sector为单位,即使这个文件只有1个字节也要分配8个。为什么要这么做呢?我觉得是为了管理效率,如果分的太细碎的话,管理成本过大。而对于flash设备,因为一般嵌入式系统的存储空间都是受限的,因此存储效率为先,能存储更多的内容是文件系统设计的首要考虑,所以分配的时候以1个sector为单位。
下面稍微计算一下:
20488B占用的sector大小为ROUND(20488/(8*1024))*8=48,6B占用的sector大小为ROUND(6/(8*512))*8=8.

  1. du Dummy
  2. sum = 48*512
  3. sum = 48*512+1K/2 = 49*512;
  4. sum = 49*512/1K = 24 //21个单位1K的block
  5. ls -s Dummy
  6. 48>>1 = 24 //24个单位为1KB的block
  7. du index.html
  8. sum = 8*512
  9. sum = 8*512+1K/2 = 9*512
  10. sum = 9*512/1K = 4 //4个单位为1K的block
  11. ls -s index.html
  12. 8>>1 = 4 //

分析

其实到这里,基本已经结束了,但在PC平台上的通用lsdu代码与嵌入式的代码有所不同,我在ubuntu上用sudo apt-get source -d coreutils获取了源代码。这里我们也分析一番代码,增强认识。

To be continued

总结

即使是你认为司空见惯很简单的一个东西,也许内里都复杂的一塌糊涂。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注