linux 激活文件系统,Linux VFS文件系统之rootfs注册与挂载

顺着上面的程序流程图,我们来具体跟一下源代码; 挂载rootfs首先会调用do_kern_mount()函数,具体代码段为:

struct vfsmount *

do_kern_mount(const char *fstype, int flags, const char *name, void *data)

{

struct file_system_type *type = get_fs_type(fstype);

struct vfsmount *mnt;

if (!type)

return ERR_PTR(-ENODEV);

mnt = vfs_kern_mount(type, flags, name, data);//挂载的核心函数

…………….

return mnt;

}

根据传入文件系统的名字fstype,调用get_fs_type()去匹配刚才注册的文件系统,如果未找到返回失败.接下来会调用挂载文件系统的核心函数vfs_kern_mount();

struct vfsmount *

vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)

{

…………..

mnt = alloc_vfsmnt(name);//在cache中分配一个vfsmount,并对其进行初始化

if (!mnt)

goto out;

………..

error = type->get_sb(type, flags, name, data, mnt);//分配文件系统超级块的回调函数

if (error < 0)

goto out_free_secdata;

BUG_ON(!mnt->mnt_sb);

error = security_sb_kern_mount(mnt->mnt_sb, secdata);

if (error)

goto out_sb;

mnt->mnt_mountpoint = mnt->mnt_root;

mnt->mnt_parent = mnt;

up_write(&mnt->mnt_sb->s_umount);

free_secdata(secdata);

return mnt;

……….

}

在alloc_vfsmnt()中,会在cache中动态分配并并初始化一个vfsmount, 由内核动态分配一个mnt_id,再初始化一些链表头和vfsmount名字.。接下来会调用一个重要的回调函数get_sb为文件系统建立一个超级块。

在rootfs中回调函数为rootfs_get_sb();

static 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);

}

一般的伪文件系统或叫内存文件系统分配超级块都是调用get_sb_nodev()或者get_sb_single(); 而实际文件系统如磁盘文件系统,在分配超级块的时候则调用get_sb_bdev()需要在具体块设备上建立超级块。因为rootfs是内存文件系统所以调用get_sb_nodev();下面跟一下这个代码的实现:

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);//填充超级块的回调函数

if (error) {

up_write(&s->s_umount);

deactivate_super(s);//kill超级块,会调用fs->kill_sb(s)

return error;

}

s->s_flags |= MS_ACTIVE;

return simple_set_mnt(mnt, s);//关联超级块和vfsmount

}

在这个函数中重点关注一下回调函数fill_super();这个函数会初始化超级块的部分成员,并建立VFS的根目录,也就是我们上面图中的 ”/” 。下面是rootfs fill_super的程序片段:

static int ramfs_fill_super(struct super_block * sb, void * data, int silent)

{

struct inode * inode;

struct dentry * root;

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;  //超级块的方法 ,在处理inode的时候会有用

sb->s_time_gran = 1;

inode = ramfs_get_inode(sb, S_IFDIR | 0755, 0); //建立根目录索引节点

if (!inode)

return -ENOMEM;

root = d_alloc_root(inode); //建立根目录目录对象

if (!root) {

iput(inode);

return -ENOMEM;

}

sb->s_root = root;  //超级块的s_root指向刚建立的根目录对象

return 0;

}

在linux文件系统的中目录也看成文件, 在建立一个文件的时候都会在目录高速缓存中建立一个目录项对象,并建立一个索引节点与之关联。注意一个目录项对象只能关联唯一一个索引节点,而一个索引节点却可以对应多个目录项对象.比如我们常见的在linux 下的硬链接就是一个索引节点对于多个目录项对象。

目录项对象的存在主要是为了我们进行路径的查找, 但是我们最终的目标是要找到目录项对象关联的索引节点,只有找到一个文件的索引节点才能找到指向该文件的操作方法。

下面来看看文件索引节点的创建和初始化,这是一个比较中要的函数,因为一个文件的属性和操作方法都是在这个函数里面定义的。

下面是rootfs的索引节点创建函数, 实际的磁盘文件系统(如EXT2)索引节点的创建更为复杂。程序片段为:

struct inode *ramfs_get_inode(struct super_block *sb, int mode, dev_t dev)

{

struct inode * inode = new_inode(sb);//在索引节点高速缓存中创建一个inode

if (inode) {

inode->i_mode = mode;  //文件的类型

inode->i_uid = current->fsuid;

inode->i_gid = current->fsgid;

inode->i_blocks = 0;  //文件的块数

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);

inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;

switch (mode & S_IFMT) {  //判断文件类型

default:

init_special_inode(inode, mode, dev); //特殊文件;如:字符~块设备文件,FIFO,SOCKET文件

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与对应的目录项对象关联

}

在new_inode函数中就是分配并且初始化一个inode,需要特别关注的是在alloc_inode()函数中的一段代码:

if (sb->s_op->alloc_inode)

inode = sb->s_op->alloc_inode(sb);

else

inode = (struct inode *) kmem_cache_alloc(inode_cachep, GFP_KERNEL);

如果在超级块方法中定义了alloc_inode函数,在inode时就需要用此函数来分配,在这种情况下struct inode可能被包含在一个更大的结构中被分配,在rootfs中并没有此定义,在以后分析其他文件系统中会看到它的用处。否则就会在cache中分配一个inode结构.

接下来我们来看看inode操作方法和文件操作方法的定义,init_special_inode()都做了些啥子,这些函数将会为我们揭开文件的操作的神秘的面纱 ;先来看看特殊文件的操作方法是咋个定义的:

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)

{

inode->i_mode = mode;

if (S_ISCHR(mode)) {          //字符设备文件

inode->i_fop = &def_chr_fops;   //默认字符设操作方法

inode->i_rdev = rdev;

} else if (S_ISBLK(mode)) {          //块设备文件

inode->i_fop = &def_blk_fops;     //默认块设备操作方法

inode->i_rdev = rdev;

} else if (S_ISFIFO(mode))   //FIFO文件

inode->i_fop = &def_fifo_fops;    //默认FIFO文件操作方法

else if (S_ISSOCK(mode))  //SOCKET文件

inode->i_fop = &bad_sock_fops;  //默认SOCKET文件的操作方法

else

printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)\n",

mode);

}

看了这个函数对于我们理解linux设备文件可能会有很大的帮助,这个在以后分析设备驱动模型的时候再写吧。

对于像rootfs,ramfs,tmpfs等内存文件系统,文件其实就是放在内存中的,对于文件的操作其实就是对内存的读写操作,这个又是一个很大的话题了,设计到linux内存管理等复杂东东了;等研究透linux内存管理再来研究这部分吧;这些都是哥需要花时间的葛!路漫漫长兮~~~~~~~~~~~~

在建立好inode之后当然还需要创建rootfs根目录的目录项对象,不然VFS是找不到刚才建立的inode的,那还咋个操作文件呢,当然是空话了!!!

来看看VFS根目录是咋个创建的:

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);

if (res) {

res->d_sb = root_inode->i_sb; //指向该文件系统的超级块

res->d_parent = res;  //根目录的父亲当然是它自己了

d_instantiate(res, root_inode); //关联 dentry 和 inode

}

}

return res;

}

这里的name=”/” 就是我们根目录的名字了,它就是VFS树根的名字;然后在目录项高速缓存中建立一个dentry ,最后一件重要的事情就是关联dentry和inode,看一下代码片断:

void d_instantiate(struct dentry *entry, struct inode * inode)

{

BUG_ON(!list_empty(&entry->d_alias));

spin_lock(&dcache_lock);

if (inode)

list_add(&entry->d_alias, &inode->i_dentry);  //将dentry加到inde链表中

entry->d_inode = inode;  //关联inode

fsnotify_d_instantiate(entry, inode);

spin_unlock(&dcache_lock);

security_d_instantiate(entry, inode);

}

BUG_ON(!list_empty(&entry->d_alias));可以证明我们刚才说的 “一个目录项对象只能关联一个索引节点”

list_add(&entry->d_alias, &inode->i_dentry); 此可以证明刚才说的”一个索引节点可以关联多个目录项对象”

最后sb->s_root = root;将超级块的根目录指向刚才建立的根目录, 至此ramfs_fill_super函数就完成了它的使命。不过我们会发现直到这里我们都没有看到最初建立的vfsmount ,不要着急下面我们会看到它了。

在执行完fill_super()回调函数以后, vfsmount就登场了,看下面的代码:

int simple_set_mnt(struct vfsmount *mnt, struct super_block *sb)

{

mnt->mnt_sb = sb;  //对 mnt_sb超级块指针附值了

mnt->mnt_root = dget(sb->s_root); // 对mnt_root指向的根目录附值

return 0;

}

最后回到vfs_kern_mount()函数  还需要对vfsmount进行一些初始化:

mnt->mnt_mountpoint = mnt->mnt_root;   //文件系统挂载点目录,其实就是刚才建立的”/”目录

mnt->mnt_parent = mnt; //父对象是自己

到此rootfs挂载就算完成了,VFS的根就这样建立起来啦, 让我们看看内存中数据结构的关系图,“理理更清晰,洗洗更健康“。

100225230259.jpg

当然rootfs是一个特殊的文件系统,做了上面的工作是不够的,我们还需要建立一个命名空间,它主要是要将do_kern_mount()中建立起来的mnt和dentry记录在init进程中,这样在linux下fork出来的所有进程都会继承这样的属性。比如所有进程的根目录都是”/” .

{       ……….

ns->root = mnt;

mnt->mnt_ns = ns;

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);   //设置进程的根目录

}

四: 总结

本文介绍了rootfs的注册和挂载,可以说rootfs是VFS的基础, 有了rootfs文件系统VFS这颗树才可以发展壮大.下文会介绍VFS是如何发展的,既目录和文件的建立。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值