分类:
2013-05-15 10:36:23
原文地址:文件系统挂载相关数据结构 作者:tq08g2z
文件系统挂载
Linux也是用系统的根文件系统:它由内核在引导阶段直接安装,并拥有系统初始化脚本以及最基本的系统程序。作为一个目录树,每个文件系统都拥有自己的根目录(root directory)。安装文件系统的目录称为挂载点(mount point)。已安装文件系统属于安装目录的一个子文件系统。已安装文件系统的整个子树位于安装点之下。
已装载文件系统命名空间
在传统的Unix系统中,只有一个已安装文件系统树:从系统的根文件系统开始,每个进程通过指定合适的路径名可以访问已安装文件系统的任何文件。也就是说根目录/是固定的,对应于最开始设置的dentry,inode和设备。而linux2.6内核,则每一个进程可拥有自己的已安装文件系统树——叫做进程的已装载文件系统命名空间(mnt_namespace)。
通常大多数进程共享同一个已装载文件系统命名空间,相同的已安装文件系统树,即位于系统的根文件系统且被init进程使用的已安装文件系统树。不过,如果clone()系统调用以CLONE_NEWNS标志创建一个新进程,那么进程将获取一个新的已装载文件系统命名空间。这个新的已装载文件系统命名空间随后由子进程继承。
当进程安装或卸载一个文件系统时,仅修改它的已装载文件系统命名空间。因此,所做的修改对共享同一已装载文件系统命名空间的所有进程是可见,并且也只对它们可见。进程可以通过系统调用pivot_root()来改变它的已装载文件系统命名空间的根文件系统。
进程用一个统一的nsproxy结构来管理它的各种命名空间:
struct task_struct {
……
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct
*files;
/* namespaces */
struct nsproxy
*nsproxy;
……
};
进程的命名空间由进程描述符的nsproxy字段指向的nsproxy结构描述:
---------------------------------------------------------------------
include/linux/ns_proxy.h
struct nsproxy {
atomic_t count;
struct uts_namespace
*uts_ns;
struct ipc_namespace
*ipc_ns;
struct mnt_namespace
*mnt_ns;
struct pid_namespace
*pid_ns;
struct net *net_ns;
};
---------------------------------------------------------------------
而进程的已装载文件系统命名空间则由nsproxy结构的mnt_ns字段指向的mnt_namespace结构描述:
---------------------------------------------------------------------
include/linux/mnt_namespace.h
struct mnt_namespace {
/* 引用计数器(共享已安装文件系统命名空间的进程数)*/
atomic_t count;
struct vfsmount * root; /* 已安装文件系统描述符 */
struct list_head list; /* 所有已安装文件系统描述符表的头 */
wait_queue_head_t poll;
int event;
};
---------------------------------------------------------------------
List字段是双向链表的头,该表聚集了属于已安装文件系统命名空间的所有已安装文件系统。root字段表示已安装文件系统,它是这个命名空间的已安装文件系统的根目录。
文件系统的挂载
对于大多数传统的Unix系统来说,一个文件系统只能挂载一次,而对于linux,则同一个文件系统可以被多次挂载。但不管被挂载多少次,它又的的确确是唯一的,都仅有一个超级快对象。
挂载的文件系统形成一个层次:一个文件系统的挂载点可能是另一个文件系统的目录,而第二个文件系统又挂载在第三个文件系统上,等等。把多个文件系统挂载在一个单独的挂在点上也是可能的。已经使用先前安装下的文件和目录的进程可以继续正常执行,但在同一挂载点上新的安装隐藏前面挂载的文件系统。当最顶层的安装被卸载时,下一层的挂载操作再一次变得可见。
对于每个挂载操作,内核必须在内存中保存挂载点和挂载标志,以及要挂载文件系统与其他已挂载文件系统之间的关系。这样的信息保存在挂载文件系统描述符中;每个描述符是一个vfsmount类型的结构体,其定义如下:
---------------------------------------------------------------------
include/linux/mount.h
struct vfsmount {
struct list_head
mnt_hash; /* 用于散列链表的指针 */
/* 指向父文件系统,这个文件系统挂载在其上 */
struct vfsmount
*mnt_parent; /* fs we are mounted on */
/* 指向本文件系统挂载点目录的dentry */
struct dentry
*mnt_mountpoint; /* dentry of mountpoint
*/
/* 指向挂载目录树根目录的dentry */
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 */
/* 用于已安装文件系统链表mnt_mounts的指针 */
struct list_head
mnt_child; /* and going through their
mnt_child */
int mnt_flags;
/* 4 bytes hole on
64bits arches */
const char
*mnt_devname; /* Name of device e.g.
/dev/dsk/hda1 */
/* 已安装文件系统描述符的mnt_namespace链表的指针 */
struct list_head
mnt_list;
struct list_head
mnt_expire; /* link in fs-specific
expiry list */
struct list_head
mnt_share; /* circular list of shared
mounts */
struct list_head
mnt_slave_list;/* list of slave mounts */
struct list_head
mnt_slave; /* slave list entry */
struct vfsmount
*mnt_master; /* slave is on
master->mnt_slave_list */
/* 指向安装了文件系统的进程的已安装文件系统命名空间的指针 */
struct mnt_namespace
*mnt_ns; /* containing namespace */
int mnt_id; /* mount identifier */
int mnt_group_id; /* peer group identifier */
/*
* We put mnt_count & mnt_expiry_mark at
the end of struct vfsmount
* to let these frequently modified fields in a
separate cache line
* (so that reads of mnt_flags wont ping-pong
on SMP machines)
*/
atomic_t mnt_count; /* 引用计数器 */
/* 如果文件系统标记为到期,那么就设置该标志为true(如果设置了该标志,并且没有任何人使用它,那么它就可以自动卸载这个文件系统)*/
int mnt_expiry_mark; /* true if marked for expiry */
int mnt_pinned;
int mnt_ghosts;
#ifdef CONFIG_SMP
int __percpu
*mnt_writers;
#else
int mnt_writers;
#endif
};
---------------------------------------------------------------------
vfsmount结构会被保存在几个双向循环链表中:
由父文件系统vfsmount描述符的地址和挂载点目录的目录项对象的地址索引的散列表。散列表存放在mount_hashtable数组(fs/namespace.c, static struct list_head *mount_hashtable
__read_mostly;)中,其大小取决于系统中RAM的容量。表中每一项是具有同一散列值的所有描述符形成的双向循环链表的头。描述符的mnt_hash字段包含指向链表中相邻元素的指针。
对于每一个已挂载文件系统命名空间,所有属于此命名空间的已挂载文件系统描述符形成了一个双向链表。mnt_namespace结构的list字段存放链表的头,vfsmount描述符的mnt_list字段包含链表中指向相邻元素的指针。
对于每一个已安装的文件系统,所有已安装的子文件系统形成了一个双向循环链表。链表的头存放在vfsmount描述符的mnt_mounts字段;子描述符的mnt_child字段存放指向链表中相邻元素的指针。vfsmount_lock自旋锁(fs/namespace.c, __cacheline_aligned_in_smp
DEFINE_SPINLOCK(vfsmount_lock);)保护vfsmount对象的链表免受同时访问。
描述符的mnt_flags字段存放几个标志的值,用以指定如何处理已安装文件系统中的某些种类的文件。这些标志可通过mount命令的选项进行设置。所有的标志定义如下:
---------------------------------------------------------------------
include/linux/mount.h
/* 在已安装的文件系统中禁止setuid和setgid标志*/
#define MNT_NOSUID 0x01
#define MNT_NODEV 0x02 /* 在已安装的文件系统中禁止访问设备文件*/
#define MNT_NOEXEC 0x04 /* 在已安装文件系统中禁止程序执行 */
#define MNT_NOATIME 0x08
#define MNT_NODIRATIME 0x10
#define MNT_RELATIME 0x20
#define MNT_READONLY 0x40 /* does the user want this to be r/o? */
#define MNT_STRICTATIME 0x80
#define MNT_SHRINKABLE 0x100
#define MNT_WRITE_HOLD 0x200
#define MNT_SHARED 0x1000 /* if the vfsmount is a shared mount */
#define MNT_UNBINDABLE 0x2000 /* if the vfsmount is a unbindable mount */
/*
* MNT_SHARED_MASK is the
set of flags that should be cleared when a
* mount becomes
shared. Currently, this is only the flag
that says a
* mount cannot be bind
mounted, since this is how we create a mount
* that shares events with
another mount. If you add a new MNT_*
* flag, consider how it
interacts with shared mounts.
*/
#define MNT_SHARED_MASK (MNT_UNBINDABLE)
#define MNT_PROPAGATION_MASK (MNT_SHARED
| MNT_UNBINDABLE)
#define MNT_INTERNAL 0x4000
---------------------------------------------------------------------
内核提供了几个用以处理vfsmount描述符的函数:
struct
vfsmount *alloc_vfsmnt(const char *name)
分配和初始化一个vfsmount描述符,其定义如下:
---------------------------------------------------------------------
fs/namespace.c
struct vfsmount *alloc_vfsmnt(const char *name)
{
struct vfsmount *mnt =
kmem_cache_zalloc(mnt_cache, GFP_KERNEL);
if (mnt) {
int err;
err =
mnt_alloc_id(mnt);
if (err)
goto
out_free_cache;
if (name) {
mnt->mnt_devname
= kstrdup(name, GFP_KERNEL);
if
(!mnt->mnt_devname)
goto
out_free_id;
}
atomic_set(&mnt->mnt_count,
1);
INIT_LIST_HEAD(&mnt->mnt_hash);
INIT_LIST_HEAD(&mnt->mnt_child);
INIT_LIST_HEAD(&mnt->mnt_mounts);
INIT_LIST_HEAD(&mnt->mnt_list);
INIT_LIST_HEAD(&mnt->mnt_expire);
INIT_LIST_HEAD(&mnt->mnt_share);
INIT_LIST_HEAD(&mnt->mnt_slave_list);
INIT_LIST_HEAD(&mnt->mnt_slave);
#ifdef CONFIG_SMP
mnt->mnt_writers
= alloc_percpu(int);
if
(!mnt->mnt_writers)
goto
out_free_devname;
#else
mnt->mnt_writers
= 0;
#endif
}
return mnt;
#ifdef CONFIG_SMP
out_free_devname:
kfree(mnt->mnt_devname);
#endif
out_free_id:
mnt_free_id(mnt);
out_free_cache:
kmem_cache_free(mnt_cache,
mnt);
return NULL;
}
---------------------------------------------------------------------
alloc_vfsmnt()函数执行如下操作:
a. 从vfsmount对象内存池中分配一个该对象,并初始化为0。
b. 调用mnt_alloc_id()为该vfsmount对象分配一个标识符,并设置vfsmount对象的标识符字段mnt_id。
c. 初始化vfsmount对象的mnt_devname字段。
d. 将vfsmount对象的引用计数设置为1。
e. 初始化连接各种链表的list_head。
f. 若配置了CONFIG_SMP,则初始化mnt_writers字段,否则该字段设置为0。
若成功则返回vfsmount对象,否则返回NULL。
void
free_vfsmnt(struct vfsmount *mnt)
释放由mnt指向的vfsmount描述符
struct
vfsmount *lookup_mnt(struct path *path)
在散列表中查找一个vfsmount并返回它的地址