文件系统注册了之后,当我们需要使用的时候,就可以挂载了。
在安装普通文件系统之前,必须先挂载根文件系统。根文件系统首先是一种文件系统,但是相对于普通的文件系统,它的特殊之处在于,它是内核启动时所mount的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。
一般文件系统的注册都是通过 module_init 宏以及 do_initcalls() 函数来完成(读者可通过阅读module_init 宏的声明及 arch\i386\vmlinux.lds 文件来理解这一过程),但是 rootfs 的注册却是通过 init_rootfs() 这一初始化函数来完成,这意味着 rootfs 的注册过程是 Linux 内核初始化阶段不可分割的一部分。
安装根文件系统是系统初始化的关键部分。这是一个相当复杂的过程,因为Linux内核允许根文件系统存放在很多不同的地方,比如硬盘分区、软盘、通过NFS共享的远程文件系统,甚至保存在ramdisk中(RAM中的虚拟块设备)。为了使叙述变得简单,让我们假定根文件系统存放在硬盘分区(毕竟这是最常见的情行)。当系统启动时,内核就要在变量ROOT_DEV中寻找包含根文件系统的磁盘主设备号:
//init/Do_mounts.c
dev_t ROOT_DEV;
当编译内核时,或者向最初的启动装入程序传递一个合适的“root”选项时,根文件系统可以被指定为/dev目录下的一个设备文件。类似地,根文件系统的安装标志存放在root_mountflags变量中:
//init/Do_mounts.c
int root_mountflags = MS_RDONLY | MS_SILENT;
用户可以指定这些标志,或者通过对已编译的内核映像使用rdev外部程序,或者向最初的启动装入程序传递一个合适的rootflags选项来达到。
安装根文件系统分两个阶段:
(1)内核安装特殊rootfs文件系统,该文件系统仅提供一个作为初始安装点的空目录。
(2)内核在空目录上安装实际根文件系统。
为什么内核不怕麻烦,要在安装实际根文件系统之前安装rootfs文件系统呢?这是因为,rootfs文件系统允许内核容易地改变实际根文件系统。实际上,在大多数情况下,系统初始化是内核会逐个地安装和卸载几个根文件系统。例如,一个发布版的初始启动光盘可能把具有一组最小驱动程序的内核装人RAM中,内核把存放在ramdisk中的一个最小的文件系统作为根安装。接下来,在这个初始根文件系统中的程序探测系统的硬件(例如,它们判断硬盘是否是EIDE、SCSI等等),装入所有必需的内核模块,并从物理块设备重新安装根文件系统.
第一阶段是由init_rootfs()和init_mount_tree()函数完成的,它们在系统初始化过程中执行。
先看下rootfs_fs_type的定义
static struct file_system_type rootfs_fs_type = { .name = "rootfs", .get_sb = rootfs_get_sb, .kill_sb = kill_litter_super, };
rootfs的注册在init_rootfs函数中
int __init init_rootfs(void) { int err; err = bdi_init(&ramfs_backing_dev_info); if (err) return err; err = register_filesystem(&rootfs_fs_type); if (err) bdi_destroy(&ramfs_backing_dev_info); return err; }
static void __init init_mount_tree(void) { struct vfsmount *mnt; struct mnt_namespace *ns; struct path root; mnt = do_kern_mount("rootfs", 0, "rootfs", NULL); if (IS_ERR(mnt)) panic("Can't create rootfs"); ns = create_mnt_ns(mnt); if (IS_ERR(ns)) panic("Can't allocate initial namespace"); init_task.nsproxy->mnt_ns = ns; get_mnt_ns(ns); root.mnt = ns->root; root.dentry = ns->root->mnt_root; set_fs_pwd(current->fs, &root); set_fs_root(current->fs, &root); }
init_mount_tree() 函数会调用 do_kern_mount("rootfs", 0, "rootfs", NULL) 来挂载前面已经注册了的 rootfs 文件系统。这看起来似乎有点奇怪,因为根据前面的说法,似乎是应该先有挂载目录,然后再在其上挂载相应的文件系统,然而此时 VFS 似乎并没有建立其根目录。没关系,这是因为这里我们调用的是 do_kern_mount(),这个函数内部自然会创建我们最关心也是最关键的根目录(在 Linux 中,目录对应的数据结构是 struct dentry)。
do_kern_mount可以看下这里:http://blog.csdn.net/new_abc/article/details/7711756
这里分析下里面的get_sb.
这里调用get_sb对vfsmnt进行了赋值,对于我们这里对应的是rootfs_get_sbstatic int rootfs_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, struct vfsmount *mnt) { return get_sb_nodev(fs_type, flags|MS_NOUSER, data, ramfs_fill_super, mnt); }rootfs_get_sb很简单,直接调用get_sb_nodev函数,注意这里的第四个参数为ramfs_fill_super
int get_sb_nodev(struct file_system_type *fs_type, int flags, void *data, int (*fill_super)(struct super_block *, void *, int), struct vfsmount *mnt) { int error; struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);//分配一个超级块结构 if (IS_ERR(s)) return PTR_ERR(s); s->s_flags = flags; error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);//ramfs_fill_super填充超级块 if (error) { deactivate_locked_super(s); return error; } s->s_flags |= MS_ACTIVE; simple_set_mnt(mnt, s);//对mnt赋值 return 0; }
3、调用ramfs_fill_super()函数分配索引节点对象和对应的目录项对象并填充超级块字段值。由于rootfs是一种特殊文件系统,没有磁盘超级块,因此只需执行两个超级块操作.
int ramfs_fill_super(struct super_block *sb, void *data, int silent) { struct ramfs_fs_info *fsi; struct inode *inode = NULL; struct dentry *root; int err; save_mount_options(sb, data); fsi = kzalloc(sizeof(struct ramfs_fs_info), GFP_KERNEL); sb->s_fs_info = fsi; if (!fsi) { err = -ENOMEM; goto fail; } err = ramfs_parse_options(data, &fsi->mount_opts); if (err) goto fail; sb->s_maxbytes = MAX_LFS_FILESIZE;//进行一些赋值操作 sb->s_blocksize = PAGE_CACHE_SIZE; sb->s_blocksize_bits = PAGE_CACHE_SHIFT; sb->s_magic = RAMFS_MAGIC; sb->s_op = &ramfs_ops;//操作指针 sb->s_time_gran = 1; //初始化inode节点 inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0); if (!inode) { err = -ENOMEM; goto fail; } //分配一个dentry做为文件系统的根目录 root = d_alloc_root(inode); sb->s_root = root; if (!root) { err = -ENOMEM; goto fail; } return 0; fail: kfree(fsi); sb->s_fs_info = NULL; iput(inode); return err; }
我们先看ramfs_get_inode
struct inode *ramfs_get_inode(struct super_block *sb, const struct inode *dir, int mode, dev_t dev) { struct inode * inode = new_inode(sb);//分配inode if (inode) {//初始化rootfs的inode inode_init_owner(inode, dir, mode); inode->i_mapping->a_ops = &ramfs_aops; inode->i_mapping->backing_dev_info = &ramfs_backing_dev_info; mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER); mapping_set_unevictable(inode->i_mapping); inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; switch (mode & S_IFMT) { default://字符、块、管道? init_special_inode(inode, mode, dev); break; case S_IFREG://一般文件 inode->i_op = &ramfs_file_inode_operations; inode->i_fop = &ramfs_file_operations; break; case S_IFDIR://目录 inode->i_op = &ramfs_dir_inode_operations; inode->i_fop = &simple_dir_operations; /* directory inodes start off with i_nlink == 2 (for "." entry) */ inc_nlink(inode); break; case S_IFLNK://符号链接 inode->i_op = &page_symlink_inode_operations; break; } } return inode; }这里主要对inode进行了赋值
inode->i_op = &ramfs_dir_inode_operations; inode->i_fop = &dcache_dir_ops; inode->i_sb = sb;
函数开始调用new_inode分配 inode
struct inode *new_inode(struct super_block *sb) { /* * On a 32bit, non LFS stat() call, glibc will generate an EOVERFLOW * error if st_ino won't fit in target struct field. Use 32bit counter * here to attempt to avoid that. */ static unsigned int last_ino; struct inode *inode; spin_lock_prefetch(&inode_lock); inode = alloc_inode(sb);//分配一个i节点并初始化 if (inode) { spin_lock(&inode_lock); __inode_add_to_lists(sb, NULL, inode);//添加到链表操作 inode->i_ino = ++last_ino; inode->i_state = 0; spin_unlock(&inode_lock); } return inode; }调用alloc_inode
static struct inode *alloc_inode(struct super_block *sb) { struct inode *inode; if (sb->s_op->alloc_inode) //如果超级块定义了i节点分配函数,则调用它的 inode = sb->s_op->alloc_inode(sb); else inode = kmem_cache_alloc(inode_cachep, GFP_KERNEL); if (!inode) return NULL; if (unlikely(inode_init_always(sb, inode))) {//默认初始化操作 if (inode->i_sb->s_op->destroy_inode) inode->i_sb->s_op->destroy_inode(inode); else kmem_cache_free(inode_cachep, inode); return NULL; } return inode; }
int inode_init_always(struct super_block *sb, struct inode *inode) { static const struct address_space_operations empty_aops; static const struct inode_operations empty_iops; static const struct file_operations empty_fops; struct address_space *const mapping = &inode->i_data; inode->i_sb = sb; inode->i_blkbits = sb->s_blocksize_bits; inode->i_flags = 0; atomic_set(&inode->i_count, 1); inode->i_op = &empty_iops; inode->i_fop = &empty_fops; inode->i_nlink = 1; inode->i_uid = 0; inode->i_gid = 0; atomic_set(&inode->i_writecount, 0); inode->i_size = 0; inode->i_blocks = 0; inode->i_bytes = 0; inode->i_generation = 0; #ifdef CONFIG_QUOTA memset(&inode->i_dquot, 0, sizeof(inode->i_dquot)); #endif inode->i_pipe = NULL; inode->i_bdev = NULL; inode->i_cdev = NULL; inode->i_rdev = 0; inode->dirtied_when = 0; if (security_inode_alloc(inode)) goto out; spin_lock_init(&inode->i_lock); lockdep_set_class(&inode->i_lock, &sb->s_type->i_lock_key); mutex_init(&inode->i_mutex); lockdep_set_class(&inode->i_mutex, &sb->s_type->i_mutex_key); init_rwsem(&inode->i_alloc_sem); lockdep_set_class(&inode->i_alloc_sem, &sb->s_type->i_alloc_sem_key); mapping->a_ops = &empty_aops; mapping->host = inode; mapping->flags = 0; mapping_set_gfp_mask(mapping, GFP_HIGHUSER_MOVABLE); mapping->assoc_mapping = NULL; mapping->backing_dev_info = &default_backing_dev_info; mapping->writeback_index = 0; /* * If the block_device provides a backing_dev_info for client * inodes then use that. Otherwise the inode share the bdev's * backing_dev_info. */ if (sb->s_bdev) { struct backing_dev_info *bdi; bdi = sb->s_bdev->bd_inode->i_mapping->backing_dev_info; mapping->backing_dev_info = bdi; } inode->i_private = NULL; inode->i_mapping = mapping; #ifdef CONFIG_FS_POSIX_ACL inode->i_acl = inode->i_default_acl = ACL_NOT_CACHED; #endif #ifdef CONFIG_FSNOTIFY inode->i_fsnotify_mask = 0; #endif return 0; out: return -ENOMEM; }
struct dentry * d_alloc_root(struct inode * root_inode) { struct dentry *res = NULL; if (root_inode) { static const struct qstr name = { .name = "/", .len = 1 }; res = d_alloc(NULL, &name);//分配dentry结构,并进行一些初始化 if (res) { res->d_sb = root_inode->i_sb;//赋值超级块指针 res->d_parent = res; d_instantiate(res, root_inode);//将inode和dentry结构关联起来 } } return res; }
struct dentry *d_alloc(struct dentry * parent, const struct qstr *name) { struct dentry *dentry; char *dname; dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);//分配dentry if (!dentry) return NULL; if (name->len > DNAME_INLINE_LEN-1) { dname = kmalloc(name->len + 1, GFP_KERNEL); if (!dname) { kmem_cache_free(dentry_cache, dentry); return NULL; } } else { dname = dentry->d_iname; } //下面就是对这个dentry结构进行赋值 dentry->d_name.name = dname; dentry->d_name.len = name->len; dentry->d_name.hash = name->hash; memcpy(dname, name->name, name->len); dname[name->len] = 0; atomic_set(&dentry->d_count, 1); dentry->d_flags = DCACHE_UNHASHED; spin_lock_init(&dentry->d_lock); dentry->d_inode = NULL; dentry->d_parent = NULL; dentry->d_sb = NULL; dentry->d_op = NULL; dentry->d_fsdata = NULL; dentry->d_mounted = 0; INIT_HLIST_NODE(&dentry->d_hash); INIT_LIST_HEAD(&dentry->d_lru); INIT_LIST_HEAD(&dentry->d_subdirs); INIT_LIST_HEAD(&dentry->d_alias); if (parent) { dentry->d_parent = dget(parent); dentry->d_sb = parent->d_sb; } else { INIT_LIST_HEAD(&dentry->d_u.d_child); } spin_lock(&dcache_lock); if (parent) list_add(&dentry->d_u.d_child, &parent->d_subdirs); dentry_stat.nr_dentry++;//目录数++ spin_unlock(&dcache_lock); return dentry; }
到 这里do_kern_mount函数就执行完了,总结一下这个函数主要做了:
1、分配了一个vfsmnt结构
2、分配了一个super_block结构,并将赋值给了前面 了vfsmnt结构
3、分配一个inode
4、分配一个dentry结构作为根目录并将前面的inode赋值给它,最后将它赋值给前面分配的super_block结构
最后回到init_mount_tree,do_kern_mount函数完后,我们调用create_mnt_ns来分本一个命令空间,这里介绍一个命名空间:
在传统的Unix系统中,只有一个已安装文件系统树:从系统的根文件系统开始,每个进程通过指定合适的路径名可以访问已安装文件系统中的任何文件。从这个方面考虑,Linux 2.6更加的精确:每个进程可拥有自己的已安装文件系统树——叫做进程的命名空间(namespace)。
一般来说,整个系统的命名空间只有一个,被大多数进程共享,即位于系统的根文件系统且被init进程使用的已安装文件系统树。不过,如果clone()系统调用以CLONE_NEWNS标志创建一个新进程,那么进程将获取一个新的命名空间。换句话说,如果父进程没有以CLONE_NEWNS标志创建这些子进程,命名空间将由随后的子进程继承。
当进程安装或卸载一个文件系统时,仅修改它的命名空间。因此,所做的修改对共享同一命名空间的所有进程都是可见的,并且也只对它们可见。进程甚至可通过使用Linux特有的pivot_root()系统调用来改变它的命名空间的根文件系统。
进程的命名空间由进程描述符的namespace字段指向的namespace结构描述:
struct namespace { atomic_t count; /* 引用计数器(共享命名空间的进程数) */ struct vfsmount * root; /* 命名空间根目录的已安装文件系统描述符 */ struct list_head list; /* 所有已安装文件系统描述符(vfsmount)链表的头 */ wait_queue_head_t poll; /* 命名空间等待队列 */ int event; /* 事件 */ };
我们看一下create_mnt_ns函数
struct mnt_namespace *create_mnt_ns(struct vfsmount *mnt) { struct mnt_namespace *new_ns; new_ns = alloc_mnt_ns(); if (!IS_ERR(new_ns)) { mnt->mnt_ns = new_ns;//赋值 new_ns->root = mnt; list_add(&new_ns->list, &new_ns->root->mnt_list); } return new_ns; }
static struct mnt_namespace *alloc_mnt_ns(void) { struct mnt_namespace *new_ns; new_ns = kmalloc(sizeof(struct mnt_namespace), GFP_KERNEL); if (!new_ns) return ERR_PTR(-ENOMEM); atomic_set(&new_ns->count, 1); new_ns->root = NULL; INIT_LIST_HEAD(&new_ns->list); init_waitqueue_head(&new_ns->poll); new_ns->event = 0; return new_ns; }
这样init_mount_tree函数就执行完了,这里要文件系统安装的第一步也就完成了。
将前面 几个结构体整理一下,大概形成了一个这样的关联结构