Chinaunix首页 | 论坛 | 博客
  • 博客访问: 145097
  • 博文数量: 19
  • 博客积分: 1746
  • 博客等级: 上尉
  • 技术积分: 443
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-22 22:54
文章分类
文章存档

2011年(3)

2010年(16)

分类: LINUX

2010-02-25 23:12:00

------------------------------------------------
#纯属个人理解,如有问题敬请谅解!
#kernel version: 2.6.26
#Author: andy wang
-------------------------------------------------
一: 序言
       文件系统在linux这个“阿房宫”中扮演着重要的角色,它与许多其他子系统紧密联系;关系错中复杂,但是要想对linux有一个深入的理解,文件系统这关是必须要过的。
在linux中用虚拟文件系统(VFS)来为linux文件系统提供一个统一的模型,VFS是一个纯软件机制;它为文件系统的操作提供了一个统一的接口)(文件read,write……);是linux实现’一切皆文件’的口号实现的基础。
 
 
   在linux系统中VFS就是一颗庞大的目录树,下面是基本结构图:
 
二:rootfs注册
   既然是一棵树那么根就是它存在的基础, 所以要研究VFS我们必须从文件系统的根入手;那么VFS的根什么咋个建立起来的呢? 以及VFS这颗大树是如何发展起来的呢?要弄清楚这么都必须先从rootfs入手,接下来我们就具体看一下rootfs文件系统是咋个建立起来的。
在linux kernel初始化阶段会调用int __init init_rootfs(void)向内核注册rootfs文件系统,下面看看rootfs怎样注册到内核中的:
 
 
这个函数比较简单,函数被宏__init修饰,说明该函数被linux链接脚本链接到.init段中,在系统初始化的时候调用,而在调用完之后会将其占用的内存释放掉,kernel中定义的一些修饰宏还是挺有意思的,以后有时间得专门写个文档..好了我们来看看代码, 其中核心的函数为register_filesystem();这个函数将结构file_system_type 注册在内核一个单链表中,每个文件系统必须调用此函数来注册到内核.
 
   
三: rootfs挂载
     此时该文件系统已经成功注册到内核中,在后面的挂载文件系统中需要再次遍历链表取出对于的file_system_type结构进行挂载。接下来该分析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的根就这样建立起来啦, 让我们看看内存中数据结构的关系图,“理理更清晰,洗洗更健康“。
 
当然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是如何发展的,既目录和文件的建立。
阅读(5727) | 评论(3) | 转发(4) |
0

上一篇:没有了

下一篇:Linux VFS文件系统之创建文件

给主人留下些什么吧!~~

6248014742015-04-09 10:42:34

写的比较易懂。不错

zd零2010-08-26 09:08:01

写得非常好!学习了! 只是有一句不太明白:“一个索引节点可以关联多个目录项对象” 若是在一个真正的文件系统中EXT2,不就是相当于给目录创建硬链接?

chinaunix网友2010-04-01 15:28:50

写的不错,赞:)