诡异的df算法

来源:互联网 发布:联通群发短信软件 编辑:程序博客网 时间:2024/05/02 00:29

df命令,接触过Linux/Unix的人都用过吧?不过为什么有的数字总是算不对呢?

例如下面,/data那个文件系统,
total = 1404203532
used = 1203335028
available = 129539124
use% = 91%

但是,total - used = 200868504 <> available
而且,used / total = 86% <> use%

view plaincopy to clipboardprint?
  1. -bash-3.00$ df  
  2. Filesystem           1K-blocks      Used Available Use% Mounted on  
  3. /dev/mapper/vg00-lvol00  
  4.                        2031952    187976   1740760  10% /  
  5. /dev/sda2               497861     17285    454872   4% /boot  
  6. /dev/mapper/vg00-lvol05  
  7.                      1404203532 1203335028 129539124  91% /data  
  8. none                   2074584         0   2074584   0% /dev/shm  
  9. /dev/mapper/vg00-lvol04  
  10.                       10095152   6955380   2626956  73% /home  
  11. /dev/mapper/vg00-lvol02  
  12.                       10095152   2091108   7491228  22% /usr  
  13. /dev/mapper/vg00-lvol03  
  14.                       10095152    229120   9353216   3% /var  
  15. apams01-vip1:/mnt/mdraw1  
  16.                      2097015808 1785570528 311445280  86% /nas_mdraw  



然而,/nas_mdraw那个文件系统的统计结果又是对的。2097015808 - 1785570528 = 311445280,1785570528 / 2097015808 = 86%

因此,df的计算方法到底如何,显得有点诡异。不过,本文也不准备卖关子,正确答案是:

df命令的输出清单的第1列是代表文件系统对应的设备文件的路径名(一般是硬盘上的分区);第2列给出分区包含的数据块(1024字节)的数目;第3,4列分别表示已用的和可用的数据块数目。用户也许会感到奇怪的是,第3,4列块数之和不等于第2列中的块数。这是因为缺省的每个分区都留了少量空间供系统管理员使用。即使遇到普通用户空间已满的情况,管理员仍能登录和留有解决问题所需的工作空间。清单中 Use%列表示普通用户空间使用的百分比,即使这一数字达到100%,分区仍然留有系统管理员使用的空间。最后,Mounted on列表示文件系统的安装点。

而这个缺省的预留空间,默认值是5%,这部分空间,普通用户不能使用,上面的/data文件系统就是这种情况,91%-86%=5%;另外,在root创建文件系统的时候是可以改变其大小的,如果mkfs的时候设置预留空间为0,那么普通用户将能够使用该设备的所有空间,上面的/nas_mdraw文件系统就是这种情况。

如果本文到此为止的话,那么没有多大实质意义,网上相关的解释到处都是,甚至上面整段话都是来自搜索引擎的。于是,我们从df的源代码用到的结构体开始,简单分析一下df的算法,利用其开源的接口,逐步写出一个综合df、du等功能的统计文件系统内各目录使用情况的程序。

首先介绍一下,df的源代码在Coreutils包中,Linux的许多基本命令,例如ls、cp等等,都包含在这个包中。大家可以到GNU的官方网站下载最新版本:
http://www.gnu.org/software/coreutils/

下载解包之后,很容易就能够在src目录找到df.c。简单跟踪一下的话,会发现df在输出结果之前会将读到的数据写到一个struct fs_usage结构体中,这个结构体在lib目录下的fsusage.h中定义:

view plaincopy to clipboardprint?
  1. struct fs_usage  
  2. {  
  3.   uintmax_t fsu_blocksize;      /* Size of a block.  */  
  4.   uintmax_t fsu_blocks;         /* Total blocks. */  
  5.   uintmax_t fsu_bfree;          /* Free blocks available to superuser. */  
  6.   uintmax_t fsu_bavail;         /* Free blocks available to non-superuser. */  
  7.   bool fsu_bavail_top_bit_set;  /* 1 if fsu_bavail represents a value < 0.  */  
  8.   uintmax_t fsu_files;          /* Total file nodes. */  
  9.   uintmax_t fsu_ffree;          /* Free file nodes. */  
  10. };  



大部分定义看英文描述就足够清晰了,除了那个bool类型成员。不过,我调试的时候还真没见过其取值为1的情况,于是暂且忽略吧。最后两个成员是对应inode的,正好df有个-i参数,不过也不常用。

前面四个成员正是对应常用df命令的,与结果对应的是:

view plaincopy to clipboardprint?
  1. total     = fsu_blocksize * fsu_blocks;  
  2. used      = fsu_blocksize * (fsu_blocks - fsu_bfree);  
  3. available = fsu_blocksize * bavail;  
  4. use%      = used *100 / (used + available);  



也就是说,计算百分比的那个分母是剔除了预留给root的空间的,而预留空间的计数方法是

view plaincopy to clipboardprint?
  1. reserved  = fsu_blocksize * (fsu_bfree - fsu_bavail);  



收集这些数据的接口也定义在同一个头文件下,其原型是:

view plaincopy to clipboardprint?
  1. int get_fs_usage (char const *file, char const *disk, struct fs_usage *fsp);  



这样一来,只要照抄df.c的相应代码,df能够显示的数据,我们也能够照样计算了,还能多计算一列预留空间。

列的算法知道了,下一步的问题是,如何像df那样,读出每行的数据,也就是获得每个文件系统的信息?