df 命令的实现

df 命令的模拟实现

通过 strace 命令查看 df 主要使用了如下的系统调用:open、fstat、read、statfs

我这里实际上是模拟实现的 df --block-size=4096 这个命令,也就是说以 4096 字节为块大小来显示磁盘使用情况。

这里最为关键的是 statfs 这个结构体,该结构体的某些字段被用作 df 命令的输出字段:

struct statfs {
long f_type; /* type of filesystem (see below) */
long f_bsize; /* optimal transfer block size */
long f_blocks; /* total data blocks in file system */
long f_bfree; /* free blocks in fs */
long f_bavail; /* free blocks avail to non-superuser */
long f_files; /* total file nodes in file system */
long f_ffree; /* free file nodes in fs */
fsid_t f_fsid; /* file system id */
long f_namelen; /* maximum length of filenames */
};
比如:df --block-size=4096 的输出如下(纵向列出):
Filesystem
/dev/sda1
4K-blocks
5077005  f_blocks 字段
Used
145105  f_blocks 字段 -f_bfree 字段
Available
4669841  f_bavail 字段
Use%
4% (f_blocks-f_bfree)/ f_blocks*100% 来计算磁盘使用率。
Mounted on
/

模拟实现的代码如下:

清单 5. 模拟实现代码

#include
#include
#include
#include
#include
#include
#define SIZE1 100
#define FN "/etc/mtab"
#define SPACE ' '
int displayapartition(char * pt,char * pt1);
int main(void){
char tmpline[SIZE1];
FILE * fp;
char * pt1;
char * pt2;
char * pt3;
if( (fp = fopen(FN,"r")) == NULL ){
fprintf(stderr,"%s \n",strerror(errno));
exit(5);
}
while( fgets(tmpline, SIZE1, fp) != NULL ){
pt1=strchr(tmpline, SPACE);
pt2=pt1+sizeof(char);
*pt1='\0';
pt3=strchr(pt2,SPACE);
*pt3='\0';
if(strstr(tmpline,"/dev") != NULL ){
displayapartition(tmpline,pt2);
}
}
return 0;
}
int displayapartition(char * pt,char * pt1){
struct statfs buf;
statfs(pt1,&buf);
int usage;
usage=ceil((buf.f_blocks-buf.f_bfree)*100/buf.f_blocks);
printf("%s ",pt);
printf("%ld ",buf.f_blocks);
printf("%ld ",buf.f_blocks-buf.f_bfree);
printf("%ld ",buf.f_bavail);
printf("%d%% ",usage);
printf("%s ",pt1);
printf("\n");
return 0;
}

df 命令实现的说明

下面解释一下这个程序:

·首先,该程序定义了一个函数 displayapartition, 这里先定义它的函数原型。

·然后我们从主程序说起:首先定义了一个 char tmpline[SIZE1] 数组,该数组用来存放从宏定义常量 FN 代表的文件中,打开后存入文件的每行记录。

·接着定义了一个文件流指针和 3 个字符串指针。

·接下来打开文件 FN 并把结果赋值给文件流变量 fp, 如果打开失败就退出。

·下面从打开的文件流中读出 SIZE1 个字符到临时数组 tmpline。比如读出一行数据为:/dev/sda1 / ext3 rw 0 0  将把 /dev/sda1 放入数组 tmpline,把加载点 / 放入指针 pt2,同时判断字符串 tmpline 是否包含 /dev 字符串,这样来判断是否是一个磁盘文件,如果是的话就调用子函数 displayapartition,不是则返回。

·子函数 displayapartition 是做什么的呢?该函数接受 2 个参数,一个是行 /dev/sda1 / ext3 rw 0 0 中的第一列比如:/dev/sda1 也就是实际磁盘作为 pt 指针,一个是行中的第二列比如:/ 也就是挂载点作为 pt1 指针。然后子函数通过 pt1 指针,读取挂载上的文件系统信息到 buf 数据结构里面。

·根据开头介绍过的 statfs 结构体,buf.f_blocks 表示打开的文件系统的总数据块,buf.f_blocks-buf.f_bfree 表示已经使用的数据块,buf.f_bavail 表示非超级用户可用的剩余数据块,磁盘使用率就是前面列出过的计算表达式:(f_blocks- f_bfree)/ f_blocks*100%。通过子函数就可以打印出 df 需要显示的所有信息到标准输出了。

小结

本文依次讲述了 cp、rm、mkdir、tac、df 命令的主要功能实现代码,当然每个命令还有很多参数,我这个模拟实现代码甚至连主要功能的很多细节都没有实现,比如 df 命令的输出头我没有打印出来,这牵涉到打印头和输出格式化等很多细节。所以,从这里我们就可以推断出,真实的源代码肯定是考虑得非常全面、严谨和健壮的。我这里只是抛砖引玉,希望能给爱好 Linux 的朋友们提供一种理解 Linux 系统的思路。


相关内容