上一章介绍了如何通过文件来使用磁盘,但在真实的系统中,用户都是通过“目录树”来使用磁盘的。本章将介绍如何将整个磁盘变成一颗“目录树”,而且让这颗“目录树”在各种系统上(Linux或Windows)都好使。
1、目录
1.1 目录的样子
用户利用目录来管理文件,再利用文件来使用磁盘,这样在用户看来一个磁盘就显得井然有序了。目录,表示一个文件集合。
用户打开目录就能够知道目录下有哪些文件。因此目录需要记录自己名下有哪些文件。上一章介绍了如何通过目录项找到文件的 inode ,然后通过 inode 找到文件在磁盘中的存放位置。目录只要存放自己名下的所有文件的目录项或者 inode 就可以了。显然文件的 inode 太大,目录项较小,且较为灵活,所以目录存放目录项更为合适。
由于目录存放了自己名下所有文件的目录项,因此也可以将目录看成一个文件,看成一个存放文件目录项的文件。 为了防止混淆,本章会将目录也叫做目录文件。既然目录文件也是一个文件,那么目录文件也应该存放在磁盘中,并且操作系统也是根据目录项和 inode 来寻找目录文件。i_mode 是 inode 中的一个成员(字段),它存放了 inode 所指向的文件的文件类型:FIFO文件、字符设备文件、目录文件、普通文件等。利用 i_mode 可以将普通文件和目录文件区分开来,操作系统在识别本次访问的文件为目录文件时操作系统只需要按照目录文件存放的内容去解析目录文件即可。
1.2 目录的使用
从1.1节的介绍可以看出,目录的使用关键在于目录的解析。本文将以解析 “/my/a” 为例介绍目录的使用方法。如下图所示:

图中的数字表示解析的顺序,首先操作系统要从磁盘中读入根目录文件的 inode ,然后根据 inode 找到根目录文件(也就是图中标识的根目录),然后在将其中的my目录项读入内存,找到 my目录文件的 inode … 。操作系统获得一个文件都是从根目录开始解析最后找到所需文件的盘块位置,可以看出解析的关键在于根目录的 inode 。因此根目录的 inode 必须存放在磁盘的固定位置。
2、文件系统
(对于文件系统,特别是文件系统组成与定义,我没有找到一个通俗的、详细的介绍。本节只能暂时根据课程所讲以及自己的理解来介绍文件系统。在找到之后关于文件系统好的介绍后再补充本节内容。若大家对文件系统有什么更好的理解,也希望大家能够不吝赐教,可以发在评论区中 O(∩_∩)O )
文件系统的工作就是要将磁盘抽象成“目录树”的结构,让用户不再关心扇区,盘块等概念。在上一节中已经建立起一个简单的“目录树”,可以看出操作系统无法单靠自身将这颗“目录树”建立,它还需要有磁盘一些硬性的规定,比如需要磁盘规定根目录的 inode 所在的位置。
为了让不同系统的计算机都能建立出“目录树”结构,磁盘必须做一些硬性的规定(或者说布局)。这样不同的操作系统就能根据磁盘的这些规定解析出磁盘,并建立出用于使用磁盘的“目录树”。下图为 Linux0.11 所使用的磁盘的布局:

根据以上的磁盘布局结合第1节所讲,一颗“目录树”就可以建立起来了。
3、目录解析代码实现
open() 函数是实现目录解析的关键,在上一章中只是简单的介绍了 open() 如何将文件名映射成文件对于的 inode,本节会对 open() 函数进行更加详细的分析。
open() 的是通过 sys_open() 实现的,在 sys_open() 中调用了 open_namei() ,在 open_namei() 中又调用了 dir_namei() ,最后 dir_namei() 中又调用了 get_dir() ,而 get_dir() 是真正完成目录解析的程序:
// 搜索指定路径名的文件名(或目录)的 inode
// pathname - 路径名
static struct m_inode * get_dir(const char * pathname)
{
char c;
const char * thisname;
struct m_inode * inode;
struct buffer_head * bh;
int namelen,inr,idev;
struct dir_entry * de;
if (!current->root || !current->root->i_count)
panic("No root inode");
if (!current->pwd || !current->pwd->i_count)
panic("No cwd inode");
if ((c=get_fs_byte(pathname))=='/') { // 若为绝对路径,则从根目录开始解析
// current->root 是根目录的 inode,根目录的inode放在磁盘的固定位置
// init进程 ( setup((void *) &drive_info); )将根目录的 inode 读入内存,
// 之后所有子进程都继承了 init 进程的根目录的 inode
inode = current->root;
pathname++;
} else if (c)//第一个字符不为'/',则使用相对路径解析
inode = current->pwd;
else
return NULL; /* empty name is bad */
inode->i_count++; // 将 inode 的引用计数加1
while (1) { // 正式开始解析目录
thisname = pathname;
if (!S_ISDIR(inode->i_mode) || !permission(inode,MAY_EXEC)) {
iput(inode);
return NULL;
}
// 获取文件名或目录名
for(namelen=0;(c=get_fs_byte(pathname++))&&(c!='/');namelen++)
/* nothing */ ;
if (!c)
return inode;
// find_entry() 将从目录中读取目录项
if (!(bh = find_entry(&inode,thisname,namelen,&de))) {
iput(inode);
return NULL;
}
inr = de->inode;// inr 是目录项中的索引节点号
idev = inode->i_dev;
brelse(bh);
iput(inode);
if (!(inode = iget(idev,inr))) // iget(),再读下一层目录
return NULL;
}
}