分类: LINUX
2011-03-07 11:26:00
原文地址:http://www.ibm.com/developerworks/cn/linux/l-cn-commands/index.html
本文章中的示例代码是在 CentOS 5.4 64 位环境下运行通过的,在其它 unix 系统上没有测试过。
Linux 操作系统中的命令实际上是编译好的可执行程序,比如说 ls 这个命令,这个文件位于 /bin 目录下面,当我们用 file /bin/ls 命令查看的时候会有以下输出:
这个命令通过调用 stat 系统调用和 /usr/share/file/magic.mgc 文件来决定文件的类型。如上的 /bin/ls 是一个 ELF 格式的动态链接的 64 位的可执行文件。
系统调用是用户程序和操作系统内核之间的接口,我们可以使用操作系统提供的系统调用来请求分配资源和服务。我们可以通过 man 2 章节来查找 Linux 提供的系统调用的具体使用方法。有关文件操作的常见系统调用命令有:open、creat、close、read、write、lseek、opendir、readdir、mkdir、stat 等等。
大家也都知道 cp 这个命令主要的作用就是把一个文件从一个位置复制到另一个位置。比如现在 /root 目录下有一个 test.txt 文件,如果我们用 cp test.txt test2.txt 命令的话,在同一个目录下面就会生成一个同样内容的 test2.txt 文件了。
那么 cp 命令是怎么实现的呢,我们看如下代码:
该程序的主要实现思想是:打开一个输入文件,创建一个输出文件,建立一个 BUFFERSIZE 大小的缓冲区;然后在判断输入文件未完的循环中,每次读入多少就向输出文件中写入多少,直到输入文件结束。
让我来详细的讲述一下这个程序:
rm 命令主要是用来删除一个文件。
该命令的实现代码如下:
其中程序的关键是 unlink 系统调用,unlink 函数原型包含在
我们从这个程序的创建过程来分析这个程序。
这个命令的模拟程序是怎么写出来的呢?
首先,我们可以在机器上 touch test 建立一个 test 文件,然后调用 strace rm test 命令来查看 rm 命令具体使用了那些系统调用。
通过查看,我们看到主要使用的系统调用如下:
我们可以看到起主要作用的就是 unlink(“test”) 这个系统调用。
让我们来分析一下这些输出的含义:
这里如果我们建立一个目录 test1,然后用 rm test1 去删除这个目录会有什么结果呢?
我们看到有如下输出:
rm: cannot remove `test1': Is a directory |
这时我们用 strace 命令来追踪一下,发现输出主要是如下不同。
unlink("test") = -1 EISDIR (Is a directory) |
这里说明了删除不掉的原因是 unlink 系统调用报错,unlink 它认为 test 是一个目录,不予处理。
那么怎么删除一个目录呢?应该是用 rmdir 系统调用,这样就不会出现上述的问题了。
再让我们来看看 mkdir 的实现。
完整的代码如下:
这段代码也比较简单,我这里就不逐行解释了,主要说以下几点:
首先 mkdir 函数是定义于
而 fprintf 函数是位于
mkdir 的函数原型如下:
int mkdir(const char *pathname, mode_t mode); |
mode 声明为 mode_t 类型。
那么 mode_t 数据类型是什么数据类型,应该从哪个文件去查看它的定义呢?
让我们逐步查找一下。
首先从文件 /usr/include/sys/stat.h 中找到 mode_t 类型
/usr/include/sys/stat.h -> typedef __mode_t mode_t;
说明 mode_t 只是对 __mode_t 的一种定义。
然后从 /usr/include/bits/types.h 中找到 __mode_t 类型
/usr/include/bits/types.h -> __STD_TYPE __MODE_T_TYPE __mode_t;
说明 __mode_t 也只是对 __MODE_T_TYPE 的一种定义。
/usr/include/bits/typesizes.h -> #define __MODE_T_TYPE __U32_TYPE
说明 __MODE_T_TYPE 是对 __U32_TYPE 的一种定义。
/usr/include/bits/types.h -> #define __U32_TYPE unsigned int
最后 __U32_TYPE 是一种无符号的整数的定义。
从上述推导可以看出,mode_t 实际上也就是一种无符号整数。
另外如下结构 struct stat 定义中的 st_mode 成员变量也是使用的 mode_t 类型的变量。
从 man 2 stat 中可以找到结构 struct stat 的定义,如下:
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for filesystem I/O */ blkcnt_t st_blocks; /* number of blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */ }; |
该结构也是我们在后面的 tac 命令实现中需要用到的结构体。我们需要用到结构体中的 st_size 成员,该成员反映了被读取的文件描述符对应的文件的大小。
tac 命令主要用来以倒序的方式显示一个文本文件的内容,也就是先显示最后一行的内容,最后显示第一行的内容。代码如下:
让我们来运行一下该程序:
程序的运行情况如下,假设编译后的可执行文件名为 emulatetac,有一个文本文件 test.txt。
# gcc emulatetac.c -o emulatetac # cat test.txt 1 2 3 a b # ./emulatetac test.txt b a 3 2 1 |
可以看出文件内容以倒序方式显示输出了。
下面逐行讲解:
通过 strace 命令查看 df 主要使用了如下的系统调用:open、fstat、read、statfs
我这里实际上是模拟实现的 df --block-size=4096 这个命令,也就是说以 4096 字节为块大小来显示磁盘使用情况。
这里最为关键的是 statfs 这个结构体,该结构体的某些字段被用作 df 命令的输出字段:
比如: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 / |
模拟实现的代码如下:
下面解释一下这个程序:
本文依次讲述了 cp、rm、mkdir、tac、df 命令的主要功能实现代码,当然每个命令还有很多参数,我这个模拟实现代码甚至连主要功能的很多细节都没有实现,比如 df 命令的输出头我没有打印出来,这牵涉到打印头和输出格式化等很多细节。所以,从这里我们就可以推断出,真实的源代码肯定是考虑得非常全面、严谨和健壮的。我这里只是抛砖引玉,希望能给爱好 Linux 的朋友们提供一种理解 Linux 系统的思路。