综述
文件系统在内存中是通过dentry和inode来表现的,换句话说:文件系统本身是一个虚拟的概念,但是我们可以通过它在内存中的表现形式dentry和inode来进行研究。Inodes是底层的对象的表现形式(也是目录的表现形式),dentry是一个由d_name、指向一个inode的d_node、一个指向父dentry的d_parent组成。
Dentry 和Inode
加入我们有如下的结构:
/
|
foo
| \
bar bar2
在上面所示的结构中,可以提取到如下信息:
- 有4个inode,分别是foo,bar,bar2,/
- 有3个denry, 分别是bar–>foo、bar2–>foo、foo–>/
对于上述的关系,做一个简单的说明。对于一个d_name
为bar的dentry来说,该dentry拥有一个指向底层文件名为bar的指针d_inode
、一个指向父dentry的指针d_parent
。对于root dentry来说,d_parent
指向自己。
Dentry 和Inode之间的映射关系
这里需要注意的是。从dentry 到 inode之间的映射关系是由d_inode
指针来维护的,一个inode可以对应多个dentry,多个dentry可以对应一个inode。dentry到inode是多对一的关系。在同一文件系统中,一个文件可以被多个dentry所引用(我们通常称之为:硬链接);这种映射关系有这样的特性:除非当当前文件的所有dentry引用被删除,否则当前file是不会被删除的。
文件和目录的操作者是进程,当然了其实质是操作相应的数据结构。在进程操作的数据结构中包含了指向dentry的指针。其所代表的文件在进程打开期间同样也是无法删除的,除非文件不再被任何dentry所引用。
Inode 和superblock的关系
另外,Inode也同superblock之间保存有密切的联系。在inode的结构体中有一个指向superblock的指针i_sb
。superblock是一个描述了当前文件系统状态的结构体,通常是存储在物理设备上。
不同文件系统之间的关系
但是从进程(或则叫用户角度)来看:该进程不可能同时只和同一个文件系统发生关系,相应的会同各种不同的文件系统有关联;不同的文件系统之间也存在着种种关联,这就需要一个结构来对这种关系加以规范,这种结构就是由vfsmount struct
所代表的mountpoint结构。
除了需要同父子vfsmounts关联之外,每一个vfsmount都包含有如下信息:
- mnt_root:一个指向vfsmount root dentry的指针
- mnt_mountpoint: 一个指向当前vfsmount所被挂载点的dentry指针
vfsmount和底层文件系统之间的关系同样也是多对一的关系。可以将同一个文件系统mount到不同的地方,这样的一个显著结果是:被挂在在不同地方的文件系统拥有同样的dentries、inodes和superblock
举例说明
不同的进程隶属不同的命名空间。当某一进程使用clone
并配合CLONE_NEWNS
标识创建一个新的task时,文件系统会赋予该进程一分其父dentry的vfsmount关系树。命名空间的root是由task->namespace->root
指针来指明的(尽管task->fs->root task->fs->rootmnt
看起来更像实际的起始位置,但是在拥有chroot
的情况下,他们二者还是有很大不同的)。
当我们看到了一个绝对路径"/foo/bar"
,
- 遍历
task->fs->rootmnt
所指向的vfsmount以及task->fs->root
所指向的dentry - 开始查找一个名为foo的并且保存有
d_parent
的dentry - 检查当前dentry是否有相应的mount信息;如果在当前的dentry上存储有mont信息,那么首先查看下到底mount在什么地方,并使用当前的vfsmount替换dentry上原有的vfsmount,替换其root指向的dentry
- 对于bar重复step 2 的操作,并产生新的vfsmount和dentry
step 3比较晦涩,这稍作介绍。在step 2 中所找到的dentry 实际上是可能被多方引用到多个命名空间中的;对于引用该dentry的每一方来说,可能就是不同文件系统的挂载点(或者啥都米有)。所有即便是我们知道了dentry上的所有信息,依然无法确定该dentry是否有被mount到啥地方,因此我们推出了vfsmount。因此通常的做法是去查找当前保存在dentry hash table中的vfsmount,所查找到的vfsmount结果向我们展示了当前dentry所mount了什么dentry
我们也可以在已经挂在了文件系统的dentry上重新挂载一个新的文件系统,此时会将前一次挂载的文件系统隐藏。所有当我们在Dentry中发现了被挂在的vfsmount时,需要重复的去搜索新的vfsmount以及判定当前dentry的root dentry是否又被挂载的什么位置;一直重复这个动作直到发现一个没有任何挂载的root dentry为止。这个流程,我们可以通过研读kernel/fs/namei.c:follow_mount()
来明确。
在上述的每一个阶段中,所遍历到的dentry并不是我们所需要的信息,相反我们所需要的信息是成对的dentry和vfsmount。所以在struct nameidata
结构体中保存了指向dentry和vfsmount的指针,可以用来进行回溯。
struct nameidata {
struct path path;
struct qstr last;
struct path root;
struct inode *inode; /* path.dentry.d_inode */
unsigned int flags;
unsigned seq, m_seq;
int last_type;
unsigned depth;
char *saved_names[MAX_NESTED_LINKS + 1];
};
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};
题外话
fs_struct 和struct namespace
对于定义在include/linux/sched.h中的task_struct
,其中抱恨了struct fs_struct
和struct namespace
结构。
struct fs_struct {
atomic_t count;
rwlock_t lock;
int umask;
struct dentry * root, * pwd, * altroot;
struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};
struct namespace {
atomic_t count;
struct vfsmount * root;
struct list_head list;
struct rw_semaphore sem;
};
sys_chroot()
函数调用set_fs_root
时,仅仅支护改变fs->root
和fs->rootmnt
,但是并不会改变当前实际工作的目录(使用pwd
命令所打印出的信息)。尽管我们执行了chroot指令,但是的状态其实是继承在执行该指令之前状态(打开了那些文件、任务啊等等)
struct vfsmount
详细可查阅代码:include/linux/mount.h:
struct vfsmount
{
struct list_head mnt_hash;
struct vfsmount *mnt_parent; /* fs we are mounted on */
struct dentry *mnt_mountpoint; /* dentry of mountpoint */
struct dentry *mnt_root; /* root of the mounted tree */
struct super_block *mnt_sb; /* pointer to superblock */
struct list_head mnt_mounts; /* list of children, anchored here */
struct list_head mnt_child; /* and going through their mnt_child */
atomic_t mnt_count;
int mnt_flags;
char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
struct list_head mnt_list;
};