@yiltoncent
2016-01-13T16:27:30.000000Z
字数 3619
阅读 10049
LINUX
上周在公司时候发现一个问题,一个文件夹里面有200个Dummy
文件,用数字1-200
来标示,以及一个index.html
文件,在windows下面查看文件大小分别是20488B
以及6B
,占用空间是24KB
以及4KB
,总共是4804KB
。
我们有一块linux板子,里面挂载了两种不同的文件系统,一种是ramfs
,一种是jffs2
。假设包含这些文件的文件夹叫dum
,那么在不同文件系统里面对这个文件夹操作du
、ls -s
以及ls -l
得到的结果有些差别。详细如下:
file | du | ls -s | ls -l |
---|---|---|---|
Dummy* | 24KB | 24KB | 20488B |
index.html | 4KB | 4KB | 6B |
dum | 4804KB |
file | du | ls -s | ls -l |
---|---|---|---|
Dummy* | 21KB | 20KB | 20488B |
index.html | 1KB | 0KB | 6B |
dum | 4101KB |
从上面的表格中,我们可以问出三个问题
1. 为什么同样的文件夹dum
在不同的文件系统中du
操作的结果不一样?
2. 为什么同样的文件,在不同的文件系统中du
和ls -s
的结果不一样?
3. 为什么同样的文件在同一个操作系统中,du
和ls -s
的结果不一样?
本文将由这三个问题引出并一一深入解释。在进入问题解答之前,我们要学习一点预备知识。
du
、ls -s
以及ls -l
操作的不同。du
: Summarize disk usage of each FILE, recursively for directories. 统计每个文件占用磁盘空间的大小。ls -s
: print the allocated size of each file, in blocks. 显示每个文件分配的磁盘空间的大小,以block为单位。ls -l
: use a long listing format. 显示文件的实际大小。其中ls -l
很好理解,这个操作的结果在两个文件系统中的结果是一样的,乃至与windows系统中文件大小也是一样的。不容易理解的是du
与ls -s
的区别。这个留着后面再说。
这两个概念很多人容易混淆,其实我觉得很容易理解。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
则是基于内存的。
从前面index.html
文件du
结果可知,ramfs
和jffs2
的block大小分别是4KB和1KB。
这一定程度上解释了为什么文件夹dum
在不同的文件系统中du
操作的结果不一样。不同文件系统的Block大小不一样,一般来说对于同样的文件所占用的磁盘空间也不一样;除非文件大小正好是两者block大小的公倍数。如文件大小正好是8KB,那么两个文件系统的du
结果都应该是8KB。
对于ramfs
,两个文件du
和ls -s
的结果都是一样的。然而对于jffs2
,两个文件du
和ls -s
的结果都不一样。这个是令人费解的。在网上搜索了半天也没有满意的答案,就只能自己看源码找原因了。在公司里面因为是嵌入式平台,ls
和du
程序都集成在busybox
中。通过代码分析,发现两个程序都是对struct stat
的st_blocks
进行操作处理。基本思路是:
ls -l
: 取得文件的struct stat
结构,对st_blocks
成员右移1位,相当于除以2。为什么要除以2呢?因为我们前面提到了磁盘sector大小为512B[闪存有一个转换层],这里的st_blocks
实际是指磁盘sector的大小。我们以KB为单位,所以块的st_blocks
大小需要除以2。为了证明这点,我在ubuntu机器上面做了一下测试。
从上面截图可以看到,IO块大小为4096,而块为8,这说明这儿的块大小是512B。
这里其实就暴露了一个设计问题,在struct stat
中表示磁盘块的术语和表示文件系统中IO块的术语是一个词:block
。这很容易让人迷糊,这儿给大家阐述清楚了,希望能有所帮助。
du
: 这个处理比较复杂一点,简单来说:如果du
操作的对象是整个文件夹,就需要将所有文件的st_blocks
全部加起来称为size
,另外有一个参数display_unit
是表示显示单位的,我们简单假定display_unit
就是1K,计算如下:
sum = size * 512;
sum += display_unit/2; //rounding
sum /= display_unit;
print(sum);
根据上面公式,我们分别计算Dummy
和index.html
在du
和ls -s
操作下的大小,以及对整个dum
文件夹的du
操作结果。
首先,20488B
占用的sector
大小为ROUND(20488/512)=41
,6B
占用的sector
大小为ROUND(6/512)=1
.
du Dummy
sum = 41*512
sum = 41*512+1K/2 = 42*512;
sum = 42*512/1K = 21 //21个单位1K的block
ls -s Dummy
41>>1 = 20 //20个单位为1KB的block
du index.html
sum = 1*512
sum = 1*512+1K/2 = 1K
sum = 1K/1K = 1 //1个单位为1K的block
ls -s index.html
1>>1 = 0 //
以上所述解释圆满的解释了第二个问题
问题2解决了之后,问题3也顺势解决了
du dum/
sum = (41*200+1)*512 //200个Dummy文件,一个index.html文件
sum = (41*200+1)*512+1K/2=8202*512
sum = 8202*512/1K = 4101 //4104个单位为1K的block
将上述分析适用到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
.
du Dummy
sum = 48*512
sum = 48*512+1K/2 = 49*512;
sum = 49*512/1K = 24 //21个单位1K的block
ls -s Dummy
48>>1 = 24 //24个单位为1KB的block
du index.html
sum = 8*512
sum = 8*512+1K/2 = 9*512
sum = 9*512/1K = 4 //4个单位为1K的block
ls -s index.html
8>>1 = 4 //
其实到这里,基本已经结束了,但在PC平台上的通用ls
和du
代码与嵌入式的代码有所不同,我在ubuntu
上用sudo apt-get source -d coreutils
获取了源代码。这里我们也分析一番代码,增强认识。
To be continued
即使是你认为司空见惯很简单的一个东西,也许内里都复杂的一塌糊涂。