上一章介绍了如何通过文件来使用磁盘,但在真实的系统中,用户都是通过“目录树”来使用磁盘的。本章将介绍如何将整个磁盘变成一颗“目录树”,而且让这颗“目录树”在各种系统上(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 所使用的磁盘的布局:
整个磁盘被划分为一个一个盘块(1KB)。引导块存放在引导程序(Linux0.11 中的 bootsect.s 就是一个引导程序),规定引导块位于文件系统所在设备的第一个块,大小固定,并且无论使用有引导程序引导块都必须存在。超级块用于存放设备上的文件系统结构的信息,并说明各部分大小,超级快的大小和在磁盘中的位置也是被规定好的。操作系统在挂载(mount)一个磁盘时都会将超级块的内容读入。下图为超级快的结构:
i节点位图用于说明 inode 是否已经被使用,逻辑块位图用于说明数据区盘块的使用情况。i节点存放了文件的 inode ,根目录的 inode 也存放在这里,且放在固定的位置。数据区存放的就是文件和目录了(目录里面存放的是目录项,文件里面存放的具体文件的内容)。更详细的介绍请参考《Linux内核完全剖析——基于0.12内核》第12章:文件系统。
根据以上的磁盘布局结合第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;
}
}
本章对应的实验链接:实验8:Proc文件系统的实现
4 问题与思考
本节内容为自己在学习过程遇到的问题以及自己对问题的思考。理解有误的地方,还请各位大佬指正。
4.1 什么是文件系统,什么是根文件系统
文件系统是一个系统,因此会有多个部分组成:一部分在操作系统软件中是程序,另一部分在磁盘中包括超级块、inode等(图2.1 磁盘的布局中的内容)。这两个部分相互配合。程序部分主要工作为解析、查找文件名,找到文件名对应inode,按照特定的规则(不同的文件系统对文件有不同的组织方式)从磁盘这种读写文件。
理解了文件系统由多个部分组成、多个部分相互配合后,就能够理解根文件系统是什么了。在做根文件系统移植的时候,是将根文件写入到磁盘中(或者flash中),也就说根文件系统属于文件系统中位于磁盘的那部分。在烧录操作系统后,系统第1次运行时,它为软件部分提供一个基础的,可以解析的“目录树”,这颗“树”就是Linux操作系统所必备的根目录及其根目录下所必备的一些文件。
参考
本章内容中的图2.1、图2.2 截取自《Linux内核完全剖析——基于0.12内核》第12章。
[1]操作系统_哈尔滨工业大学_中国大学MOOC
[2]《Linux内核完全剖析——基于0.12内核》
[3]百度百科——文件系统