最近在学习Unix下系统编程,书看的还比较仔细,但是合上书后总是有种雾里看花朦朦胧胧的感觉。俗话说实践出真知,学习编程怎么能不动手呢。既然是学习系统编程那就写一些系统命令来巩固知识,消除朦胧的感觉吧!选中PWD命令,有如下几个原因: 1、 可以加深对Linux文件系统组织结构的理解 2、可以加深对目录结构的理解 3、 可以加深对挂载点和链接的理解
注:关于LINUX文件系统的相关基础知识,大家可以先看看《Linux文件系统详解》:http://www.armjishu.com/bbs/viewtopic.php?id=1754&flag=1578 Unix下一切皆文件,也就是说掌握好了文件也就掌握了Unix的一切(菜鸟说法不必当真)。掌握了上述三个知识点也就算基本掌握了Unix文件系统的相关知识。有点遗憾的是文件状态和权限相关知识在这个例子中没有涉及。选好了命令下一步就是写了,下面是我的写作计划: 1、 Unix的文件系统的内部结构,主要是超级块、inode相关知识 2、 目录结构,当然是目录相关知识了 3、 编写PWD命令 PWD命令的作用就是显示当前工作目录的绝对路径,就是从/开始到当前目录。用法很简单,没有其他参数。在命令行敲入pwd:
pwd命令.jpg
1、Unix文件系统的内部结构 文件系统可以用来存储文件内容、文件属性(所有者、日期等)和目录,这些不同类型的数据是如何存储在磁盘上的呢?Linux使用了一个简单的办法。如下图所示它将磁盘分成了3部分:超级块、inode表和数据区。
文件的内部结构
超级块,通常是文件系统的第一个块,用来存放文件系统本身的信息。例如每个区域的大小、未被使用的磁盘块的信息等;不同版本的Unix的超级块的内容和结构稍有不同。 inode表,每个文件都有一些属性,如大小、文件所有者和最近修改时间等。这些属性被记录在inode表中,所有的inode都有相同的大小。文件系统中的每个文件都对应一个inode; 数据区,文件内容的内容保存在这个区域。磁盘上所有块的大小都是一样的,如果文件包含了超过一个块的内容,则文件内容会存放在多个磁盘块中。 当创建一个新文件时,内核将文件内容存放在数据区,文件属性存放在inode中,文件名存放在目录中。当对一个文件进行相关操作时,内核先在目录中找到文件名,然后根据目录中inode获得文件的属性,最终找到文件的内容。从上述过程可以看出,目录至少需要包含inode和文件名。
2、目录结构 用户看到的文件系统是目录和子目录的集合,也就是目录树。每个目录能够包含文件和子目录,每个子目录有一个父目录,这棵树的结构常用线条连接的方框图来表示。在文件系统内部,目录是一个包含文件名与inode节点对的列表的文件。从用户角度看到的是一个文件名的列表,而从系统的角度看到的是一个被命名的指针的列表。如下图所示:
目录树的两种不同视图
一般我们都说文件存放在某个目录中,但是由上面的分析可知目录中存放的只是文件在inode表的入口,而文件内容则存储在数据区。'文件x在目录a中 ',意味着在目录a中有一个指向inode402的链接,这个链接所附加的文件名为x。简短的说,目录包含的是文件的引用,每个引用被称为链接。 链接有两种,一种是硬链接,另一种是符号链接。这里需要注意的一点是Unix下文件没有文件名,但是链接有名字,通常所说的文件名其实就是链接名。硬链接就是把链接名和inode链接起来,也就是将文件名和文件本身链接起来。符号链接就是通过名字引用文件,而不是inode,也就是将名字和名字链接起来。 从图1我们可以看到,在系统角度,每个目录都有两个链接:'.'和'..'。这两个链接是干什么用的呢?内核在每个目录都设置了一个指向目录本身的inode,这个入口被称为'.'。'..',是指向父目录inode的入口。
3、编写PWD命令
3.1、PWD分析 通过前面基础知识的学习,我们知道了Unix文件系统是树形结构,节点被称为inode,指针的集合被称为目录,叶子节点被称为链接。那么要想取得当前工作目录的绝对路径只需追踪链接,读取目录,一个目录接着一个目录沿着树向上追踪,每步查看'.'的inode,然后在父目录中查找该inode的名字,直到到达树的根部。这就是PWD命令的工作原理,知道了原理那么就可以得出PWD的实现算法了: 1、得到'.'的inode,称其为n(使用stat) 2、进入'..'(使用chdir) 3、找到节点号为n的inode链接的名字(使用opendir、readdir、closedir) 4、重复上述三个步骤,直到到达书的根部 这里需要注意两个问题,第一个问题就是如何知道已经到达树的顶端。在Unix 文件系统的根目录中,'.' 和 '..'指向同一个inode。因此当PWD命令重复循环直到一个目录的'.' 和 '..'的inode相同时,就可以认为已经到达文件树的根部了。第二个问题就是如何以正确的顺序显示目录名字。学过算法的同学都知道,处理这种问题用递归再合适不过了。通过一个递归的程序逐步到达树的顶端来一个接一个地显示目录名,从而避免了字符串的管理。
3.2、相关数据结构和系统调用 在动手写代码之前需要了解一下两个数据结构:DIR和struct dirent。DIR是一个不透明的结构,每个平台都有自己的DIR实现,用户不需要明白DIR的具体结构。也就是说,看到源码中的DIR就只需要明白这是个目录指针就可以了。struct dirent是一个目录入口的结构,在Linux下定义如下:
view plaincopy to clipboardprint?
- struct dirent{
- long d_ino; /* inode number 索引节点号 */
- off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
- unsigned short d_reclen; /* length of this d_name 文件名长 */
- unsigned char d_type; /* the type of d_name 文件类型 */
- char d_name [NAME_MAX+1]; /* file name文件名,最长255字符 */
- }
struct dirent{long d_ino; /* inode number 索引节点号 */off_t d_off; /* offset to this dirent 在目录文件中的偏移 */unsigned short d_reclen; /* length of this d_name 文件名长 */unsigned char d_type; /* the type of d_name 文件类型 */char d_name [NAME_MAX+1]; /* file name文件名,最长255字符 */}
与目录操作有关的函数在dirent.h头文件中声明。它们以DIR结构为目录操作的基础,其使用方法与文件流(FILE *)类似。目录数据项本身在struct dirent结构中返回。我们实现PWD命令需要用到的目录操作有opendir、readdir和closedir,下面就介绍下这三个函数:
DIR *opendir(const char *name) opendir函数用来打开一个目录并创建一个目录流,如果成功则返回一个指向DIR结构的指针,该指针用于读取目录数据项。
struct dirent *readdir(DIR *dirp) readdir函数返回一个指针,指针指向的结构保存着目录流dirp中下一个目录项的相关资料。后续的readdir调用将返回后续的目录项。
int closedir(DIR *dirp) closedir函数用来关闭一个目录流并释放与之相关的资源,成功返回0,失败返回-1。
3.3、编译运行 进入到存放spwd.c文件的目录下,使用gcc编译源文件,生成可执行文件(我这里命名为spwd),然后把可执行文件拷贝到/bin/下就可以像使用pwd命令一样使用自己写的pwd命令了。