您的位置:首页 > 运维架构 > Linux

Linux 编程学习笔记----文档管理系统

2015-09-17 21:45 726 查看
本文从网络上完成的第

Linux在文件系统管理。

1.VFS文件系统概述

linux採用VFS来管理文件系统,并且linux设计的原则之中的一个就是everything is file。因此文件管理系统是linux设计最核心的体现。

VFS的全称是Virtual File System (虚拟文件系统)。

整体上说 Linux 下的文件系统主要可分为三大块:一是上层的文件系统的系统调用。二是虚拟文件系统 VFS(Virtual Filesystem Switch),三是挂载到 VFS 中的各实际文件系统。比如 ext2。jffs 等。

如图:



VFS 是一种软件机制,或许称它为 Linux 的文件系统管理者更确切点。与它相关的数据结构仅仅存在于物理内存其中。所以在每次系统初始化期间,Linux 都首先要在内存其中构造一棵 VFS 的文件夹树(在 Linux 的源码里称之为 namespace)。实际上便是在内存中建立对应的数据结构。VFS 文件夹树在 Linux 的文件系统模块中是个非常重要的概念,希望读者不要将其与实际文件系统文件夹树混淆,在笔者看来。VFS 中的各文件夹其主要用途是用来提供实际文件系统的挂载点。当然在
VFS 中也会涉及到文件级的操作,本文不阐述这样的情况。

下文提到文件夹树或文件夹。假设不特别说明,均指 VFS 的文件夹树或文件夹。

图是一种可能的文件夹树在内存中的影像:



2.文件系统的注冊

这里的文件系统是指可能会被挂载到文件夹树中的各个实际文件系统,所谓实际文件系统。即是指VFS 中的实际操作终于要通过它们来完毕而已,并不意味着它们一定要存在于某种特定的存储设备上。比方在有的 Linux 机器下就注冊有 "rootfs"、"proc"、"ext2"、"sockfs" 等十几种文件系统。

2.1 数据结构

在 Linux 源码中。每种实际的文件系统用下面的数据结构表示:

struct file_system_type
{
const char *name;
int fs_flags;
struct super_block *(*read_super) (struct super_block *, void *, int);
struct module *owner;
struct file_system_type * next;
struct list_head fs_supers;
};
注冊过程实际上将表示各实际文件系统的 struct file_system_type 数据结构的实例化,然后形成一个链表,内核中用一个名为 file_systems 的全局变量来指向该链表的表头。

2.2 注冊 rootfs 文件系统

在众多的实际文件系统中,之所以单独介绍 rootfs 文件系统的注冊过程。实在是由于该文件系统 VFS 的关系太过密切,假设说 ext2/ext3 是 Linux 的本土文件系统。那么 rootfs 文件系统则是 VFS 存在的基础。一般文件系统的注冊都是通过 module_init 宏以及 do_initcalls() 函数来完毕(读者可通过阅读module_init 宏的声明及 arch\i386\vmlinux.lds 文件来理解这一过程),可是
rootfs 的注冊却是通过 init_rootfs() 这一初始化函数来完毕。这意味着 rootfs 的注冊过程是 Linux 内核初始化阶段不可切割的一部分。

init_rootfs() 通过调用 register_filesystem(&rootfs_fs_type) 函数来完毕 rootfs 文件系统注冊的。当中rootfs_fs_type 定义例如以下:

struct file_system_type rootfs_fs_type = { \
name:		"rootfs", \
read_super:	ramfs_read_super, \
fs_flags:	FS_NOMOUNT|FS_LITTER, \
owner:		THIS_MODULE, \
}


注冊之后的 file_systems 链表结构例如以下图所看到的:



3.VFS 文件夹树的建立

既然是树。所以根是其赖以存在的基础。本节阐述 Linux 在初始化阶段是怎样建立根结点的。即 "/"文件夹。这当中会包含挂载 rootfs 文件系统到根文件夹 "/" 的详细过程。构造根文件夹的代码是在 init_mount_tree() 函数 (fs\namespace.c) 中。

首先。init_mount_tree() 函数会调用 do_kern_mount("rootfs", 0, "rootfs", NULL) 来挂载前面已经注冊了的 rootfs 文件系统。这看起来似乎有点奇怪,由于依据前面的说法。似乎是应该先有挂载文件夹,然后再在其上挂载对应的文件系统,然而此时 VFS 似乎并没有建立其根文件夹。没关系。这是由于这里我们调用的是 do_kern_mount(),这个函数内部自然会创建我们最关心也是最关键的根文件夹(在
Linux 中,文件夹相应的数据结构是 struct dentry)。

在这个场景里,do_kern_mount() 做的工作主要是:

1)调用 alloc_vfsmnt() 函数在内存里申请了一块该类型的内存空间(struct vfsmount *mnt),并初始化其部分成员变量。

2) 调用 get_sb_nodev() 函数在内存中分配一个超级块结构 (struct super_block) sb。并初始化其部分成员变量,将成员 s_instances 插入到 rootfs 文件系统类型结构中的 fs_supers 指向的双向链表中。

3) 通过 rootfs 文件系统中的 read_super 函数指针调用 ramfs_read_super() 函数。还记得当初注冊rootfs 文件系统时,其成员 read_super 指针指向了 ramfs_read_super() 函数,參见上图.

4) ramfs_read_super() 函数调用 ramfs_get_inode() 在内存中分配了一个 inode 结构 (struct inode) inode。并初始化其部分成员变量,当中比較重要的有 i_op、i_fop 和 i_sb:

inode->i_op = &ramfs_dir_inode_operations;
inode->i_fop = &dcache_dir_ops;
inode->i_sb = sb;


这使得将来通过文件系统调用对 VFS 发起的文件操作等指令将被 rootfs 文件系统中对应的函数接口所接管。

5) ramfs_read_super() 函数在分配和初始化了 inode 结构之后,会调用 d_alloc_root() 函数来为 VFS的文件夹树建立起关键的根文件夹 (struct dentry)dentry。并将 dentry 中的 d_sb 指针指向 sb。d_inode 指针指向 inode。

6) 将 mnt 中的 mnt_sb 指针指向 sb。mnt_root 和 mnt_mountpoint 指针指向 dentry。而 mnt_parent指针则指向自身。

这样。当 do_kern_mount() 函数返回时,以上分配出来的各数据结构和 rootfs 文件系统的关系将例如以下图所看到的。



图中 mnt、sb、inode、dentry 结构块下方的数字表示它们在内存里被分配的先后顺序。限于篇幅的原因,各结构中仅仅给出了部分成员变量,读者能够对比源码依据图中所看到的按图索骥,以加深理解。

最后。init_mount_tree() 函数会为系统最開始的进程(即 init_task 进程)准备它的进程数据块中的namespace 域。主要目的是将 do_kern_mount() 函数中建立的 mnt 和 dentry 信息记录在了 init_task 进程的进程数据块中,这样全部以后从 init_task 进程 fork 出来的进程也都先天地继承了这一信息,在后面用sys_mkdir 在 VFS 中创建一个文件夹的过程中,我们能够看到这里为什么要这样做。为进程建立
namespace 的主要代码例如以下:

namespace = kmalloc(sizeof(*namespace), GFP_KERNEL);
list_add(&mnt->mnt_list, &namespace->list);  //mnt is returned by do_kern_mount()
namespace->root = mnt;
init_task.namespace = namespace;
for_each_task(p) {
get_namespace(namespace);
p->namespace = namespace;
}
set_fs_pwd(current->fs, namespace->root, namespace->root->mnt_root);
set_fs_root(current->fs, namespace->root, namespace->root->mnt_root);


该段代码的最后两行便是将 do_kern_mount() 函数中建立的 mnt 和 dentry 信息记录在了当前进程的 fs结构中。

以上讲了一大堆数据结构的来历,事实上终于目的只是是要在内存中建立一颗 VFS 文件夹树而已,更确切地说, init_mount_tree() 这个函数为 VFS 建立了根文件夹 "/",而一旦有了根,那么这棵数就能够发展壮大,比方能够通过系统调用 sys_mkdir 在这棵树上建立新的叶子节点等,所以系统设计者又将 rootfs 文件系统挂载到了这棵树的根文件夹上。关于 rootfs 这个文件系统,读者假设看一下前面图 2 中它的file_system_type
结构,会发现它的一个成员函数指针 read_super 指向的是 ramfs_read_super。单从这个函数名称中的 ramfs,读者大概能推測出这个文件所涉及的文件操作都是针对内存中的数据对象,其实也的确如此。

从还有一个角度而言。由于 VFS 本身就是内存中的一个数据对象,所以在其上的操作仅限于内存,那也是很合乎逻辑的事。

在接下来的章节中,我们会用一个详细的样例来讨论怎样利用 rootfs所提供的函树为 VFS 添加一个新的文件夹节点。

VFS 中各文件夹的主要用途是为以后挂载文件系统提供挂载点。所以真正的文件操作还是要通过挂载后的文件系统提供的功能接口来进行。

4. VFS 下文件夹的建立

为了更好地理解 VFS,以下我们用一个实际样例来看看 Linux 是怎样在 VFS 的根文件夹下建立一个新的文件夹 "/dev" 的。

要在 VFS 中建立一个新的文件夹,首先我们得对该文件夹进行搜索。搜索的目的是找到将要建立的文件夹其父文件夹的相关信息,由于"皮之不存,毛将焉附"。比方要建立文件夹 /home/ricard,那么首先必须沿文件夹路径进行逐层搜索。本例中先从根文件夹找起,然后在根文件夹下找到文件夹 home。然后再往下,便是要新建的文件夹名 ricard,那么前面讲得要先对文件夹搜索。在该例中便是要找到 ricard 这个新文件夹的父文件夹,也就是 home 文件夹所相应的信息。

当然。假设搜索的过程中发现错误,比方要建文件夹的父文件夹并不存在,或者当前进程并无对应的权限等等。这样的情况系统必定会调用相关过程进行处理,对于此种情况,本文略过不提。

Linux 下用系统调用 sys_mkdir 来在 VFS 文件夹树中添加新的节点。

同一时候为配合路径搜索,引入了以下一个数据结构:

struct nameidata {
struct dentry *dentry;
struct vfsmount *mnt;
struct qstr last;
unsigned int flags;
int last_type;
};


这个数据结构在路径搜索的过程中用来记录相关信息,起着类似"路标"的作用。当中前两项中的 dentry记录的是要建文件夹的父文件夹的信息。mnt 成员接下来会解释到。

后三项记录的是所查找路径的最后一个节点(即待建文件夹或文件)的信息。 如今为建立文件夹 "/dev" 而调用 sys_mkdir("/dev", 0700)。当中參数 0700 我们不去管它,它仅仅是限定将要建立的文件夹的某种模式。sys_mkdir 函数首先调用 path_lookup("/dev",
LOOKUP_PARENT, &nd);来对路径进行查找,当中 nd 为 struct nameidata nd 声明的变量。在接下来的叙述中。由于函数调用关系的繁琐,为了突出过程主线,将不再严格依照函数的调用关系来进行描写叙述。

path_lookup 发现 "/dev" 是以 "/" 开头,所以它从当前进程的根文件夹開始往下查找,详细代码例如以下:

nd->mnt = mntget(current->fs->rootmnt);
nd->dentry = dget(current->fs->root);


记得在 init_mount_tree() 函数的后半段以前将新建立的 VFS 根文件夹相关信息记录在了 init_task 进程的进程数据块中,那么在这个场景里,nd->mnt 便指向了图 3 中 mnt 变量。nd->dentry 便指向了图 3 中的 dentry 变量。

然后调用函数 path_walk 接着往下查找。找到最后通过变量 nd 返回的信息是 nd.last.name="dev",nd.last.len=3,nd.last_type=LAST_NORM。至于 nd 中 mnt 和 dentry 成员,在这个场景里还是前面设置的值,并无变化。这样一圈下来,仅仅是用 nd 记录下相关信息,实际的文件夹建立工作并没有真正展开。可是前面所做的工作却为接下来建立新的节点收集了必要的信息。

好,到此为止真正建立新文件夹节点的工作将会展开,这是由函数 lookup_create 来完毕的,调用这个函数时会传入两个參数:lookup_create(&nd, 1)。当中參数 nd 便是前面提到的变量,參数1表明要建立一个新文件夹。

这里的大体过程是:新分配了一个 struct dentry 结构的内存空间,用于记录 dev 文件夹所相应的信息,该dentry 结构将会挂接到其父文件夹中。也就是上图 中 "/" 文件夹相应的 dentry 结构中,由链表实现这一关系。接下来会再分配一个 struct inode 结构。

Inode 中的 i_sb 和 dentry 中的 d_sb 分别都指向上图 中的 sb,这样看来,在同一文件系统下建立新的文件夹时并不须要又一次分配一个超级块结构,由于毕竟它们都属于同一文件系统,因此一个文件系统仅仅相应一个超级块。

这样,当调用 sys_mkdir 成功地在 VFS 的文件夹树中新建立一个文件夹 "/dev" 之后,在上图的基础上,新的数据结构之间的关系便例如以下图 所看到的。下图 中颜色较深的两个矩形块 new_inode 和 new_entry 便是在sys_mkdir() 函数中新分配的内存结构。至于图中的 mnt,sb,dentry,inode 等结构,仍为上图 中对应的数据结构,其相互之间的链接关系不变(图中为避免过多的链接曲线,忽略了一些链接关系。如 mnt
和 sb,dentry之间的链接。读者可在上图的基础上參看下图)。



须要强调一点的是,既然 rootfs 文件系统被 mount 到了 VFS 树上,那么它在 sys_mkdir 的过程中必定会參与进来,其实在整个过程中。rootfs 文件系统中的 ramfs_mkdir、ramfs_lookup 等函数都曾被调用过。

5.在 VFS 树中挂载文件系统

在本节中,将描写叙述在 VFS 的文件夹树中向当中某个文件夹(安装点 mount point)上挂载(mount)一个文件系统的过程。

这一过程可简单描写叙述为:将某一设备(dev_name)上某一文件系统(file_system_type)安装到VFS文件夹树上的某一安装点(dir_name)。

它要解决的问题是:将对 VFS 文件夹树中某一文件夹的操作转化为详细安装到其上的实际文件系统的相应操作。

比方说。如果将 hda2 上的根文件系统(如果文件系统类型为 ext2)安装到了前一节中新建立的 "/dev" 文件夹上(此时,"/dev" 文件夹就成为了安装点),那么成功安装之后应达到这种目的,即:对 VFS 文件系统的 "/dev" 文件夹运行 "ls"
指令。该条指令应能列出 hda2 上 ext2 文件系统的根文件夹下全部的文件夹和文件。非常显然,这里的关键是怎样将对 VFS 树中 "/dev" 的文件夹操作指令转化为安装在其上的 ext2 这一实际文件系统中的相应指令。所以。接下来的叙述将抓住怎样转化这一核心问题。

在叙述之前。读者最好还是自己设想一下 Linux 系统会怎样解决这一问题。记住:对文件夹或文件的操作将终于由文件夹或文件所相应的 inode 结构中的 i_op 和 i_fop 所指向的函数表中相应的函数来运行。所以,无论终于解决方式怎样。都能够设想必定要通过将对
"/dev" 文件夹所相应的 inode 中 i_op 和 i_fop 的调用转换到 hda2 上根文件系统 ext2 中根文件夹所相应的 inode 中 i_op 和 i_fop 的操作。

初始过程由 sys_mount() 系统调用函数发起,该函数原型声明例如以下:

asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type,
unsigned long flags, void * data);


当中。參数 char *type 为标识将要安装的文件系统类型字符串,对于 ext2 文件系统而言,就是"ext2"。

參数 flags 为安装时的模式标识数。和接下来的 data 參数一样,本文不将其做为重点。

为了帮助读者更好地理解这一过程。笔者用一个详细的样例来说明:我们准备将来自主硬盘第 2 分区(hda2)上的 ext2 文件系统安装到前面创建的 "/dev" 文件夹中。那么对于 sys_mount() 函数的调用便详细为:

sys_mount("hda2","/dev ","ext2",…)。


该函数在将这些来自用户内存空间(user space)的參数复制到内核空间后,便调用 do_mount() 函数開始真正的安装文件系统的工作。

相同,为了便于叙述和讲清楚主流程,接下来的说明将不严格依照详细的函数调用细节来进行。

do_mount() 函数会首先调用 path_lookup() 函数来得到安装点的相关信息,如同创建文件夹过程中叙述的那样,该安装点的信息终于记录在 struct nameidata 类型的一个变量其中,为叙述方便。记该变量为nd。

在本例中当 path_lookup() 函数返回时。nd 中记录的信息例如以下:nd.entry = new_entry; nd.mnt = mnt;这里的变量如图 3 和 4 中所看到的。

然后。do_mount() 函数会依据调用參数 flags 来决定调用下面四个函数之中的一个:do_remount()、 do_loopback()、do_move_mount()、do_add_mount()。

在我们当前的样例中,系统会调用 do_add_mount() 函数来向 VFS 树中安装点 "/dev " 安装一个实际的文件系统。

在 do_add_mount() 中,主要完毕了两件重要事情:一是获得一个新的安装区域块。二是将该新的安装区域块增加了安装系统链表。

它们各自是调用 do_kern_mount() 函数和 graft_tree() 函数来完毕的。

这里的描写叙述可能有点抽象,诸如安装区域块、安装系统链表等。只是不用着急,由于它们都是笔者自定义出来的概念。等一下到后面会有专门的图表解释,到时便会清楚。

do_kern_mount() 函数要做的事情,便是建立一新的安装区域块。详细的内容在前面的章节 VFS 文件夹树的建立中已经叙述过。这里不再赘述。

graft_tree() 函数要做的事情便是将 do_kern_mount() 函数返回的一 struct vfsmount 类型的变量增加到安装系统链表中,同一时候 graft_tree() 还要将新分配的 struct vfsmount 类型的变量增加到一个hash表中,其目的我们将会在以后看到。

这样。当 do_kern_mount() 函数返回时。在图 4 的基础上,新的数据结构间的关系将如图 5 所看到的。

当中,红圈区域里面的数据结构便是被称做安装区域块的东西,当中最好还是称 e2_mnt 为安装区域块的指针。蓝色箭头曲线即构成了所谓的安装系统链表。

在把这些函数调用后形成的数据结构关系理清楚之后,让我们回到本章节開始提到的问题,即将 ext2 文件系统安装到了 "/dev " 上之后。对该文件夹上的操作怎样转化为对 ext2 文件系统相应的操作。从图 5上看到。对 sys_mount() 函数的调用并没有直接改变 "/dev " 文件夹所相应的 inode (即图中的 new_inode变量)结构中的 i_op 和 i_fop 指针,并且 "/dev " 所相应的 dentry(即图中的 new_dentry 变量)结构仍然在 VFS 的文件夹树中,并没有被从当中隐藏起来,相应地。来自
hda2 上的 ext2 文件系统的根文件夹所相应的 e2_entry 也不是如当初笔者所想象地那样将 VFS 文件夹树中的 new_dentry 取而代之,那么这之间的转化究竟是怎样实现的呢?

请读者注意以下的这段代码:

while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry));


这段代码在 link_path_walk() 函数中被调用,而 link_path_walk() 终于又会被 path_lookup() 函数调用,假设读者阅读过 Linux 关于文件系统部分的代码。应该知道 path_lookup() 函数在整个 Linux 繁琐的文件系统代码中属于一个重要的基础性的函数。简单说来,这个函数用于解析文件路径名。这里的文件路径名和我们平时在应用程序中所涉及到的概念同样,比方在 Linux 的应用程序中 open 或 read 一个文件 /home/windfly.cs
时。这里的 /home/windfly.cs 就是文件路径名,path_lookup() 函数的责任就是对文件路径名中进行搜索,直到找到目标文件所属文件夹所相应的 dentry 或者目标直接就是一个文件夹。笔者不想在有限的篇幅里详解这个函数。读者仅仅要记住 path_lookup() 会返回一个目标文件夹就可以。

上面的代码非常地不起眼。以至于初次阅读文件系统的代码时常常会忽略掉它,可是前文所提到从 VFS 的操作到实际文件系统操作的转化却是由它完毕的,对 VFS 中实现的文件系统的安装可谓功不可没。如今让我们细致剖析一下该段代码:d_mountpoint(dentry) 的作用非常easy,它仅仅是返回 dentry 中 d_mounted 成员变量的值。这里的dentry 仍然还是 VFS 文件夹树上的东西。假设 VFS 文件夹树上某个文件夹被安装过一次,那么该值为 1。

对VFS 中的一个文件夹可进行多次安装,后面会有样例说明这样的情况。在我们的样例中,"/dev"
所相应的new_dentry 中 d_mounted=1,所以 while 循环中第一个条件满足。

以下再来看__follow_down(&nd->mnt, &dentry)代码做了什么?到此我们应该记住。这里 nd 中的 dentry 成员就是下图中的 new_dentry,nd 中的 mnt成员就是下图中的 mnt。所以我们如今能够把 __follow_down(&nd->mnt, &dentry) 改写成__follow_down(&mnt, &new_dentry),接下来我们将 __follow_down()
函数的代码改写(仅仅是去处掉一些不太相关的代码,而且为了便于说明。在部分代码行前加上了序号)例如以下:



static inline int __follow_down(struct vfsmount **mnt, struct dentry **dentry)
{
struct vfsmount *mounted;
[1]	mounted = lookup_mnt(*mnt, *dentry);
if (mounted) {
[2]		*mnt = mounted;
[3]		*dentry = mounted->mnt_root;
return 1;
}
return 0;
}


代码行[1]中的 lookup_mnt() 函数用于查找一个 VFS 文件夹树下某一文件夹近期一次被 mount 时的安装区域块的指针,在本例中终于会返回图 5 中的 e2_mnt。至于查找的原理,这里粗略地描写叙述一下。

记得当我们在安装 ext2 文件系统到 "/dev" 时,在后期会调用 graft_tree() 函数,在这个函数里会把图 5 中的安装区域块指针 e2_mnt 挂到一 hash 表(Linux 2.4.20源码中称之为 mount_hashtable)中的某一项,而该项的键值就是由被安装点所相应的
dentry(本例中为 new_dentry)和 mount(本例中为 mnt)所共同产生,所以自然地。当我们知道 VFS 树中某一 dentry 被安装过(该 dentry 变成为一安装点),而要去查找其近期一次被安装的安装区域块指针时,相同由该安装点所相应的 dentry 和 mount 来产生一键值,以此值去索引 mount_hashtable,自然可找到该安装点相应的安装区域块指针形成的链表的头指针。然后遍历该链表。当发现某一安装区域块指针,记为 p,满足下面条件时:

(p->mnt_parent == mnt && p->mnt_mountpoint == dentry)


P 便为该安装点所相应的安装区域块指针。当找到该指针后。便将 nd 中的 mnt 成员换成该安装区域块指针,同一时候将 nd 中的 dentry 成员换成安装区域块中的 dentry 指针。在我们的样例中。e2_mnt->mnt_root成员指向 e2_dentry,也就是 ext2 文件系统的 "/" 文件夹。

这样,当 path_lookup() 函数搜索到 "/dev"时。nd 中的 dentry 成员为 e2_dentry,而不再是原来的 new_dentry,同一时候 mnt 成员被换成 e2_mnt,转化便在不知不觉中完毕了。

如今考虑一下对某一安装点多次安装的情况。相同作为样例。我们如果在 "/dev" 上安装完一个 ext2文件系统后。再在其上安装一个 ntfs 文件系统。

在安装之前,相同会对安装点所在的路径调用path_lookup() 函数进行搜索,可是这次因为在 "/dev" 文件夹上已经安装过了 ext2 文件系统,所以搜索到最后。由 nd 返回的信息是:nd.dentry = e2_dentry, nd.mnt = e2_mnt。

由此可见,在第二次安装时。安装点已经由 dentry 变成了 e2_dentry。接下来。相同地,系统会再分配一个安装区域块。如果该安装区域块的指针为
ntfs_mnt。区域块中的 dentry 为 ntfs_dentry。

ntfs_mnt 的父指针指向了e2_mnt,mnfs_mnt 中的 mnt_root 指向了代表 ntfs 文件系统根文件夹的 ntfs_dentry。然后,系统通过 e2_dentry和 e2_mnt 来生成一个新的 hash 键值,利用该值作为索引,将 ntfs_mnt 增加到 mount_hashtable 中,同一时候将 e2_dentry 中的成员 d_mounted 值设定为 1。这样。安装过程便告结束。

读者可能已经知道,对同一安装点上的近期一次安装会隐藏起前面的若干次安装。以下我们通过上述的样例解释一下该过程:

在先后将 ext2 和 ntfs 文件系统安装到 "/dev" 文件夹之后。我们再调用 path_lookup() 函数来对"/dev" 进行搜索,函数首先找到 VFS 文件夹树下的安装点 "/dev" 所相应的 dentry 和 mnt,此时它发现dentry 成员中的 d_mounted 为 1。于是它知道已经有文件系统安装到了该 dentry 上,于是它通过 dentry 和 mnt 来生成一个 hash 值,通过该值来对 mount_hashtable 进行搜索。依据安装过程。它应该能找到 e2_mnt
指针并返回之。同一时候原先的 dentry 也已经被替换成 e2_dentry。回头再看一下前面已经提到的下列代码:while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry)); 当第一次循环结束后, nd->mnt 已经是 e2_mnt,而 dentry 则变成 e2_dentry。

此时因为 e2_dentry 中的成员 d_mounted 值为 1,所以 while 循环的第一个条件满足,要继续调用 __follow_down() 函数,这个函数前面已经剖析过。当它返回后
nd->mnt 变成了 ntfs_mnt,dentry 则变成了 ntfs_dentry。

因为此时 ntfs_dentry 没有被安装过其它文件。所以它的成员 d_mounted 应该为 0,循环结束。

对 "/dev" 发起的 path_lookup() 函数终于返回了 ntfs 文件系统根文件夹所相应的 dentry。这就是为什么 "/dev" 本身和安装在其上的 ext2 都被隐藏的原因。假设此时对 "/dev" 文件夹进行一个 ls 命令。将返回安装上去的 ntfs 文件系统根文件夹下全部的文件和文件夹。

6. 安装根文件系统

有了前面章节 4 的基础。理解 Linux 下根文件系统的安装并不困难,由于无论怎么样,安装一个文件系统到 VFS 中某一安装点的过程原理毕竟都是一样的。

这个过程大致是:首先要确定待安装的 ext2 文件系统的来源,其次是确定 ext2 文件系统在 VFS中的安装点,然后便是详细的安装过程。

关于第一问题,Linux 2.4.20 的内核另有一大堆的代码去解决,限于篇幅,笔者不想在这里去详细说明这个过程,大概记住它是要解决到哪里去找要安装的文件系统的就能够了。这里我们最好还是就觉得要安装的根文件系统就来自于主硬盘的第一分区 hda1.

关于第二个问题。Linux 2.4.20 的内核把来自于 hda1 上 ext2 文件系统安装到了 VFS 文件夹树中的"/root" 文件夹上。事实上。把 ext2 文件系统安装到 VFS 文件夹树下的哪个安装点并不重要(VFS 的根文件夹除外),仅仅要是这个安装点在 VFS 树中是存在的。而且内核对它没有另外的用途。

假设读者喜欢,尽能够自己在 VFS 中创建一个 "/Windows" 文件夹。然后将 ext2 文件系统安装上去作为将来用户进程的根文件夹,没有什么不能够的。

问题的关键是要将进程的根文件夹和当前工作文件夹设定好。由于毕竟仅仅用用户进程才去关心现实的文件系统。要知道笔者的这篇稿子但是要存到硬盘上去的。

在 Linux 下,设定一个进程的当前工作文件夹是通过系统调用 sys_chdir() 进行的。

初始化期间。Linux 在将 hda1 上的 ext2 文件系统安装到了 "/root" 上后,通过调用 sys_chdir("/root") 将当前进程,也就是 init_task 进程的当前工作文件夹(pwd)设定为 ext2 文件系统的根文件夹。记住此时 init_task进程的根文件夹仍然是图 3 中的 dentry,也就是 VFS 树的根文件夹。这显然是不行的。由于以后
Linux 世界中的全部进程都由这个 init_task 进程派生出来。无一例外地要继承该进程的根文件夹。假设是这样。意味着用户进程从根文件夹搜索某一文件夹时。实际上是从 VFS 的根文件夹開始的,而其实却是从 ext2 的根文件開始搜索的。

这个矛盾的解决是靠了在调用完 mount_root() 函数后,系统调用的以下两个函数:

sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");


其主要作用便是将 init_task 进程的根文件夹转化成安装上去的 ext2 文件系统的根文件夹。有兴趣的读者能够自行去研究这一过程。

所以在用户空间下,很多其它地情况是仅仅能见到 VFS 这棵大树的一叶,并且还是被安装过文件系统了的,实际上对用户空间来说还是不可见。我想,VFS 很多其它地被内核用来实现自己的功能,并以系统调用的方式提供过用户进程使用,至于在其上实现的不同文件系统的安装,也仅仅是当中的一个功能罢了。

应用层的开发不须要关心VFS源代码的详细实现。仅仅须要知道VFS对外的各类文件系统的接口函数就可以。

Reference

http://www.ibm.com/developerworks/cn/views/linux/libraryview.jsp

来源于网络,转载请注明出处:http://blog.csdn.net/suool/article/details/38172057
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: