linux虚拟文件系统概述

本文转载自:http://blog.chinaunix.net/uid-12567959-id-160983.html

原文是 Linux/Documentation/filesystems/vfs.txt

=========================================

Overview of the Linux Virtual File System

 

    Original author: Richard Gooch <rgooch@atnf.csiro.au>

 

         Last updated on June 24, 2007.

 

  Copyright (C) 1999 Richard Gooch

  Copyright (C) 2005 Pekka Enberg

 

  This file is released under the GPLv2.

 

简介

============

 

虚拟文件系统(或称为虚拟文件系统转换层)是一个内核中的软件层,它给用户空间程序提供文件系统接口。同时在内核中提供一种抽象以允许不同文件系统的实现的共存。

 

VFS系统调用open(2), stat(2), read(2), write(2), chmod(2)等从一个进程上下文调用。文件系统锁在文档Documentation/filesystems/Locking描述。

 

目录项缓存(dcache)

------------------

 

VFS实现open(2), stat(2), chmod(2)等一些系统调用。路径名参数被传递给它们,VFS使用路径名参数通过目录项缓存(也被称为dentry cache或dcache)来查找。这提供了一种非常快速的查询机制来将路径名(文件名)转换为一个特定的dentry。Dentries存在于内存中,并且从来不会被保存到磁盘:它们的存在仅仅是为了性能。

 

dentry缓存是作为一个通向你的整个文件空间的视图而存在的。由于大多数计算机都不能在同一时间将所有的dentries都装入到内存中,所以一些位的缓存是不命中的。为了将你的路径名解析为一个dentry,VFS可能不得不去创建dentries,并且加载inode。这通过查找inode来完成。

 

inode 对象

----------------

 

一个独立的dentry通常有一个指向一个inode的指针。Inodes是文件系统对象,比如常规文件,目录,FIFOs和其他的beasts。它们或者存在于磁盘上(对于块设备文件系统)或者存在于内存中(对于伪文件系统)。存在于磁盘上的Inodes将在请求的时候被拷贝进内存,而对inode的修改将被写回到磁盘。可以有多个dentries指向同一个inode(比如硬链接,hard links)。

 

为了查找一个请求的inode,VFS会调用父目录inode的lookup()方法。这个方法由inode所在的特定的文件系统实现安装。一旦VFS有了请求的dentry(并因此有了inode),那么我们就可以做所有的令人讨厌的如open(2) 文件, 或者stat(2) 它来查看inode数据等的事情。stat(2)操作相当的简单:VFS有了dentry,它查看inode数据并且将其中的一些传回给用户空间。

 

文件对象(The File Object)

--------------------------

 

打开一个文件需要另外一个操作:分配一个file结构(这是内核边(kernel-side,相对于进程的文件描述符)实现的文件描述符)。新分配的file结构总是用一个指向dentry的指针和一系列文件操作成员函数来初始化。这些都取自inode数据。然后,open()文件方法被调用,以使特定的文件系统实现可以做它的工作。你可以看到,这是由VFS执行另一个转换。file结构被放在进程的文件描述符表中。

 

读、写和关闭文件(还有其他的各种各样的VFS操作)通过使用文件描述符来获得合适的file结构,然后调用请求的文件结构方法来做请求的动作来完成。文件被打开后,它将保持dentry可用,这反过来意味着VFS inode是可用的。

 

注册和挂载一个文件系统

======================

 

注册或注销一个文件爱你系统,可以使用下面的API函数:

 

#include <linux/fs.h>

 

extern int register_filesystem(struct file_system_type *);

extern int unregister_filesystem(struct file_system_type *);

 

传递的struct file_system_type描述了你的文件系统。当有一个挂载一个设备到一个你文件空间的一个目录的请求产生时,VFS将会为特定的文件系统调用合适的get_sb()方法。挂载点的dentry将会被更新以指向新文件系统的根inode。

 

你可以在文件/proc/filesystems中查看所有的已注册的文件系统。

 

struct file_system_type

-----------------------

 

这个结构体用于描述一个文件系统。这个结构体有如下定义:

 

struct file_system_type {

    const char *name;

    int fs_flags;

    int (*get_sb) (struct file_system_type *, int,

              const char *, void *, struct vfsmount *);

    void (*kill_sb) (struct super_block *);

    struct module *owner;

    struct file_system_type * next;

    struct list_head fs_supers;

 

    struct lock_class_key s_lock_key;

    struct lock_class_key s_umount_key;

 

    struct lock_class_key i_lock_key;

    struct lock_class_key i_mutex_key;

    struct lock_class_key i_mutex_dir_key;

    struct lock_class_key i_alloc_sem_key;

};

 

  name: 文件系统类型的名字, 比如 "ext2", "iso9660",  "msdos" 等等

 

  fs_flags: 各种标志 (i.e. FS_REQUIRES_DEV, FS_NO_DCACHE, etc.)

 

  get_sb: 当有一个该文件系统的新的实例被挂载的时候调用的方法

 

  kill_sb: 当该文件系统的一个实例被卸载的时候调用的方法

 

  owner: 给VFS内部使用: 在大多数情况下你应该将它初始化为THIS_MODULE。

 

  next: 给VFS内部使用: 你应该将它初始化为NULL

 

  s_lock_key, s_umount_key: lockdep-specific

 

get_sb() 方法有下列参数:

 

  struct file_system_type *fs_type: 描述文件系统, 被特定文件系统码备份的初始化

 

  int flags: 挂载标志

 

  const char *dev_name: 我们挂载的设备的设备名

 

  void *data: 任意的挂载选项, 通常是一个ASCII字符串(参考 "挂载选项" 部分)

 

  struct vfsmount *mnt: 一个挂载点vfs内部的代表

get_sb()方法必须判断用dev_name和fs_type指定的特定设备是否包含一个该方法支持的文件系统类型。如果它成功的打开了用名字指定的块设备,则为块设备包含的文件系统初始化一个struct super_block描述符。出错时它返回错误。

 

get_sb()方法最多的是填充超级块结构的成员"s_op"。这是一个指向一个"struct super_operations"的指针,而"struct super_operations"则描述了文件系统实现的下一个层次。

 

通常一个文件系统使用某个通用的get_sb()实现并提供fill_super()方法来代替。通用的方法有:

 

  get_sb_bdev: 挂载一个位于一个块设备上的文件系

 

  get_sb_nodev: 挂载一个没有被一个设备支持的文件系统

 

  get_sb_single: 挂载一个mount a filesystem which shares the instance between all mounts

 

一个fill_super()方法实现具有如下的参数:

struct super_block *sb: 超级块结构。fill_super()方法必须适当的初始化它

 

  void *data: 任意的挂载选项,通常是一个ASCII字符串(参考 "挂载选项" 部分)

 

  int silent: whether or not to be silent on error

 

超级快对象(The Superblock Object)

===================================

 

一个超级快对象代表一个挂载的文件系统。

 

struct super_operations

-----------------------

 

他描述了VFS能如何管理你的文件系统的超级块。其定义如下:

 

struct super_operations {

   struct inode *(*alloc_inode)(struct super_block *sb);

    void (*destroy_inode)(struct inode *);

 

   void (*dirty_inode) (struct inode *);

    int (*write_inode) (struct inode *, int);

    void (*drop_inode) (struct inode *);

    void (*delete_inode) (struct inode *);

    void (*put_super) (struct super_block *);

    void (*write_super) (struct super_block *);

    int (*sync_fs)(struct super_block *sb, int wait);

    int (*freeze_fs) (struct super_block *);

    int (*unfreeze_fs) (struct super_block *);

    int (*statfs) (struct dentry *, struct kstatfs *);

    int (*remount_fs) (struct super_block *, int *, char *);

    void (*clear_inode) (struct inode *);

    void (*umount_begin) (struct super_block *);

 

    int (*show_options)(struct seq_file *, struct vfsmount *);

    int (*show_stats)(struct seq_file *, struct vfsmount *);

#ifdef CONFIG_QUOTA

    ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);

    ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);

#endif

    int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);

};

 

所有的方法都在不持有任何锁的情况下调用,除非另有说明。这意味着大多说方法可以安全的阻塞。所有的方法都只从进程上下文调用(比如,不会从一个中断处理程序或下半部)。

 

  alloc_inode: 这个方法由inode_alloc()调用来为struct inode 分配内存并初始化它。如果这个函数没有定义,则一个简单的'struct inode'被分配。通常alloc_inode 将被用来分配一个更大的内嵌有'struct inode'的结构体。

 

  destroy_inode: 这个方法由destroy_inode()调用来释放为struct inode 分配的资源。只有当->alloc_inode 有定义的时候才需要它,它只是简单的撤销->alloc_inode 所做的一切。

 

  dirty_inode: 这个方法被VFS调用来标记一个inode 为dirty。

 

  write_inode: 当VFS需要将一个inode 写回磁盘的时候调用这个方法。第二个参数用以说明写是否为同步的,并不是所有的文件系统都会检查这个标志。

 

  drop_inode: 当对inode 的最后的访问被丢弃的时候调用,调用时要持有inode_lock自旋锁。

 

  这个方法应该为NULL (普通的 UNIX 文件系统语义) 或者"generic_delete_inode" (对于那些不需要缓存inodes的文件系统 – 导致"delete_inode" 总是被调用而不管i_nlink的值是多少。

 

   generic_delete_inode()的行为和以前在put_inode()情形中使用的"force_delete"是一样的,只是它没有"force_delete()"方法中出现的竞争显现。

 

  delete_inode: 当VFS想要删除(delete)一个inode 时调用

 

  put_super: 当VFS想要释放superblock 是调用(比如unmount)。在持有superblock 锁时调用。

 

  write_super: 当VFS superblock 需要被写回磁盘时调用。这个方法是可选的。

 

  sync_fs: 当VFS写完所有的与superblock 相关的“脏”的数据之后调用。第二个参数用以说明这个方法是否需要等待知道写出操作完成。可选的。

 

  freeze_fs: 当VFS锁定一个文件系统并强制它进入一致性状态时调用。这个方法现在为Logical Volume Manager (LVM)所用。

 

  unfreeze_fs: 当VFS解除锁定一个文件系统并再次使它可写是调用。

 

  statfs: 当VFS需要获得文件系统统计量时调用。

 

  remount_fs: 当文件系统被重新挂载时调用。持有内核锁时调用。

 

  clear_inode: 当VFS清除(clear)inode 时调用。可选。

 

  umount_begin: 当VFS卸载一个文件系统时调用。

 

  show_options:被VFS调用来为/proc/<pid>/mounts显示挂载选项 。 (参考 "挂载选项" 部分)

 

  quota_read: VFS调用以从文件系统读取配额文件。

 

  quota_write: VFS调用以将配额文件写回文件系统。

 

设置inode者有责任填充"i_op"成员。这是一个指向一个"struct inode_operations"的指针,而struct inode_operations则描述可以操作单个inodes的方法。

 

inode 对象

================

 

一个inode代表一个文件系统内的对象。

 

struct inode_operations

-----------------------

 

它描述了VFS是如何管理你文件系统中的inode的。在kernel 2.6.32.7中它有如下的定义:

 

struct inode_operations {

    int (*create) (struct inode *,struct dentry *,int, struct nameidata *);

    struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);

    int (*link) (struct dentry *,struct inode *,struct dentry *);

    int (*unlink) (struct inode *,struct dentry *);

    int (*symlink) (struct inode *,struct dentry *,const char *);

    int (*mkdir) (struct inode *,struct dentry *,int);

    int (*rmdir) (struct inode *,struct dentry *);

    int (*mknod) (struct inode *,struct dentry *,int,dev_t);

    int (*rename) (struct inode *, struct dentry *,

           struct inode *, struct dentry *);

    int (*readlink) (struct dentry *, char __user *,int);

    void * (*follow_link) (struct dentry *, struct nameidata *);

    void (*put_link) (struct dentry *, struct nameidata *, void *);

    void (*truncate) (struct inode *);

    int (*permission) (struct inode *, int);

    int (*check_acl)(struct inode *, int);

    int (*setattr) (struct dentry *, struct iattr *);

    int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);

    int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);

    ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);

    ssize_t (*listxattr) (struct dentry *, char *, size_t);

    int (*removexattr) (struct dentry *, const char *);

    void (*truncate_range)(struct inode *, loff_t, loff_t);

    long (*fallocate)(struct inode *inode, int mode, loff_t offset,

             loff_t len);

    int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,

             u64 len);

};

 

再次,除非另有说明,所有的方法都在不持有锁的情况下调用。

 

  create: 被open(2) and creat(2)系统调用所调用。只要你想要支持普通(regular )文件时才需要。你获得的dentry应该还没有inode(比如,它应该是一个negative dentry)。这里你可能要以dentry和新建的inode为参数来调用d_instantiate()。

 

  lookup: 当VFS需要在一个父目录中查询一个inode时调用。需要查询的名字在dentry中。这个方法必须调用d_add()来将找到的inode插入到dentry中。inode 结构中的"i_count"成员应该被增加一。如果所要查找的特定名字的inode没要找到,NULL inode应该被插入到dentry中。(这就是所谓的 negative

dentry)。必须只有当一个实际的错误时这个例程才返回一个错误,否则使用如create(2), mknod(2), mkdir(2)等系统调用来创建inodes将会失败。如果你想要重载dentry方法,你应该初始化dentry的"d_dop"成员,这是一个指向一个"dentry_operations"结构体的指针。这个方法在持有目录inode信号量的的情况下调用。

 

  link: 被link(2)系统调用所调用。只有当你想要支持硬链接的时候才需要。你可能需要调用。正像在create()方法中一样,你将可能需要调用d_instantiate()。

 

  unlink: unlink(2)系统调用掉哟个。只要当你想要支持删除(deleting )inodes时才需要。

 

  symlink: symlink(2)系统调用调用。只要当你想要支持符号链接时才需要。正像在create()方法中一样,你将可能需要调用d_instantiate()。

 

  mkdir:系统调用 mkdir(2)调用。只有当你想要支持创建子目录时才需要。正像在create()方法中一样,你将可能需要调用d_instantiate()。

 

  rmdir: 系统调用rmdir(2)调用。只有当你想要支持删除(deleting)子目录时才需要。

 

  mknod: 系统调用mknod(2)调用来创建一个设备(字符设备,块设备)inode或一个命名管道 (FIFO)或socket。只有当你想要支持这些类型的inodes的创建时才需要。正像在create()方法中一样,你将可能需要调用d_instantiate()。

 

  rename: 系统调用rename(2)调用来重命名对象,以第二个inode和dentry为父目录和名字。

 

  readlink: 系统调用readlink(2)调用。只有当你想要支持读取符号链接时才需要。

 

  follow_link: 由VFS调用来使一个符号链接跟随它所指向的inode。只有当你想要支持符号链接时才需要它。此方法返回一个将会被传递给put_link()的无类型指针cookie 。

 

  put_link: 由VFS调用来释放由follow_link()分配的资源。由follow_link()返回的cookie 被传递给它作为最后一个参数。它被NFS等页缓存不稳定的文件系统使用。    (比如,page that was installed when the symbolic link walk started might not be in the page cache at the end of the walk).

 

  truncate: 由VFS调用来改变一个文件的大小。在调用这个方法之前,inode结构的i_size成员被VFS设置为需要的大小。这个方法由truncate(2)方法及相关的函数调用。

 

  permission: 在一个POSIX-like 文件系统上由VFS调用来检查访问权限。

 

  setattr: 由VFS调用来为一个文件设置属性。这个方法由chmod(2)及相关的系统调用来调用。

 

  getattr: 由VFS调用来获得一个文件的属性。这个方法由stat(2)及相关的系统调用来调用。

 

  setxattr: 由VFS调用来设置一个文件的扩展属性。扩展属性是一个与一个inode关联的name:value对。这个方法由系统调用setxattr(2)调用。

 

  getxattr: 由VFS调用来检索一个扩展属性名的值。这个方法由getxattr(2)函数掉哟个。

 

  listxattr: 由VFS调用来列出给定文件的所有的扩展属性。这个方法由系统调用listxattr(2)调用。

 

  removexattr: 由VFS调用来从一个文件移除(remove)。这个方法由系统调用removexattr(2)调用。

 

  truncate_range: 一个由底层文件系统提供来截断块的范围的方法,比如在一个文件的某些地方打洞。

 

地址空间对象(The Address Space Object)

=======================================

 

地址空间对象用来分类并管理页缓存中的页。它可以被用来跟踪一个文件(或其他)的页,也可以被用来跟踪一个文件映射到进程地址空间的段。

 

地址空间(address-space)可以提供许多不同但相关的服务。这包括communicating memory pressure, 根据地址查询页,和追踪标记为脏(Dirty)或写回(Writeback)的页。

 

其他人第一个可以独立的使用(The first can be used independently to the others.)VM可以试着写回“脏”页以清理(clean)它们,或者释放清理的页以重新使用它们。为了做到这些可以在“脏”页上调用->writepage,和在设置了PagePrivate 的条件下调用 ->releasepage 。没有PagePrivate也没有外部引用的的清理过的页将会在不给address_space 通知的情况下释放。

 

为了完成这些功能,无论何时页被使用,则都需要使用lru_cache_add 和 mark_page_active 来将之放入一个LRU。

 

也通常被保存在一个由->index 索引的基数树里。这个数维护每一个页关于PG_Dirty和 PG_Writeback 的状态信息,以使具有这些标志的页可以被快速找到。

 

“脏”(Dirty)标记主要由mpage_writepages - 默认的->writepages 方法来使用。它使用标记来查找“脏”(dirty)页并在其上调用->writepage 。如果不使用mpage_writepages(比如地址(address)提供了它自己的->writepages),则PAGECACHE_TAG_DIRTY标记已经未使用。write_inode_now 和 sync_inode使用它(通过__sync_single_inode)来检查->writepages已经成功写出整个的address_space。

 

Writeback标记由filemap*wait* 和 sync_page*函数使用,通过filemap_fdatawait_range ,来等待所有的写回完成。等待的同时,将会在每一个请求写回的页上调用 ->sync_page (如果定义了)。

 

一个address_space handler 可能为page附加了额外信息,典型的是使用'struct page'的'private'成员。如果附加了这样的信息,则应该设置PG_Private 标记。这将导致各种VM例程做一些进入address_space  处理程序的额外的调用来处理那些数据。

 

一个地址空间就像存储和应用程序之间的一个中层。数据以页为单位在某一时刻被读入到地址空间,并通过对页的复制或者通过页的内存映射来提供给应用程序。应用程序将数据写回到地址空间,然后典型的以页为单位写回到存储器,然而address_space 可以对写大小进行更好的控制。

 

读进程需要仅仅请求'readpage'。写进程要更复杂一些,它要使用write_begin/write_end 或 set_page_dirty来将数据写回到address_space,并使用writepage,sync_page,和 writepages来将数据写回存储器。

 

向address_space 添加(add)和移除(removing)页有inode结构的i_mutex 来保护。

 

将数据写入页的时候,应该设置PG_Dirty标志。这个标志要一直处于设置状态,直到writepage请求将它写回。而此时则要清除(clear)PG_Dirty并设置(PG_Writeback)。它可能在PG_Dirty被清除(clear)之后的任何时间点上被实际写回。在安全的时候,则可以清除PG_Writeback。

 

写回充分利用了writeback_control 结构……

 

struct address_space_operations

-------------------------------

 

它描述了VFS是如何管理你的文件系统中的一个文件到页缓存中的映射映射的。在kernel 2.6.32.7中,这个结构体有如下定义:

 

struct address_space_operations {

    int (*writepage)(struct page *page, struct writeback_control *wbc);

    int (*readpage)(struct file *, struct page *);

    void (*sync_page)(struct page *);

 

    /* Write back some dirty pages from this mapping. */

    int (*writepages)(struct address_space *, struct writeback_control *);

 

    /* Set a page dirty.  Return true if this dirtied it */

    int (*set_page_dirty)(struct page *page);

 

    int (*readpages)(struct file *filp, struct address_space *mapping,

           struct list_head *pages, unsigned nr_pages);

 

    int (*write_begin)(struct file *, struct address_space *mapping,

              loff_t pos, unsigned len, unsigned flags,

              struct page **pagep, void **fsdata);

    int (*write_end)(struct file *, struct address_space *mapping,

              loff_t pos, unsigned len, unsigned copied,

              struct page *page, void *fsdata);

 

    /* Unfortunately this kludge is needed for FIBMAP. Don't use it */

    sector_t (*bmap)(struct address_space *, sector_t);

    void (*invalidatepage) (struct page *, unsigned long);

    int (*releasepage) (struct page *, gfp_t);

    ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,

           loff_t offset, unsigned long nr_segs);

    int (*get_xip_mem)(struct address_space *, pgoff_t, int,

                     void **, unsigned long *);

    /* migrate the contents of a page to the specified target */

    int (*migratepage) (struct address_space *,

           struct page *, struct page *);

    int (*launder_page) (struct page *);

    int (*is_partially_uptodate) (struct page *, read_descriptor_t *,

                  unsigned long);

    int (*error_remove_page)(struct address_space *, struct page *);

};

 

  writepage: 由VM调用来将一个“脏”页写回到辅存中。这可能是为了数据完整性(如'sync'),或者是为了释放内存(flush)。两者的区别可以由wbc->sync_mode 来判断。PG_Dirty被清除并且PageLocked返回true。同步或异步写操作完成时,writepage应该启动writeout,设置PG_Writeback,并且应该确保页处于解锁状态。

 

      如果wbc->sync_mode 是WB_SYNC_NONE ,则->writepage 在出错的时候不需要一直努力地尝试,如果写回其他页更容易的话(比如,由于内部依赖),它也可以从mapping中选择其他的页来写回。如果他选择不启动writeout,则它应该返回AOP_WRITEPAGE_ACTIVATE ,以使VM没有不停地在那个页上调用->writepage 。

 

      更多详情请参考"Locking"文件。

 

  readpage: 由VM调用来从辅存中读取一个页。当readpage调用时,页将被锁定,并且应该在读取结束时,立刻将其解除锁定并标记为uptodate。如果->readpage发现由于某些原因它需要解除锁定页,那么它可以这样做,并在之后返回AOP_TRUNCATED_PAGE。这种情况下,页将会被重新定位、重新锁定,如果都能正确完成,则将会再次调用->readpag。

 

  sync_page: 由VM调用来通知辅存,以为一个页执行所有排队的I/O 操作。与该address_space 对象相关其他的页的I/O 操作也可能被执行。

 

    这个函数是可选的,它仅仅在等待writeback 完成时对设置了PG_Writeback 标记的页调用。

 

  writepages: 由VM调用来将与address_space 对象相关的页写出。如果wbc->sync_mode 是 WBC_SYNC_ALL ,那么writeback_control将会指定一个必须写出的页的范围。而如果是WBC_SYNC_NONE ,则一个nr_to_write 将会被传递给它,并且,如果可能的话,应该有许多的页被写出。如果没有->writepages方法,那么将会用mpage_writepages来代替。这将会从地址空间中选择标记为DIRTY的页,并将它们传递给->writepage。

 

  set_page_dirty: 由VM调用来设置一个页为“脏”。这主要在一个address space向一个页加入了私有数据时使用,并且那些数据需要在页被标记为“脏”时更新。比如,它会在一个内存映射的页被改变时调用。

    如果定义,则它应该设置基数树结构中的PageDirty 标志(flag)和PAGECACHE_TAG_DIRTY 标记(tag)。

 

  readpages: 由VM调用来读取与address_space 对象相关的页。它本质上只是readpage 的一个向量版本。不同于只有一个页,它用于请求多个页的情况。Readpages仅仅被用于预读。因此忽略读错误。如果有任何错误,则可以随意放弃。

 

  write_begin: 由通用缓存写代码调用,以使文件系统 准备 在文件给定的偏 移处写入 len 字节。该address_space应检查写可以完成,通过分配空间,如果需要的话,并做其它的内部处理。如果写将更新存储器上的任何基本块(basic-blocks),那么那些块应给被预读(如果他们还没有被读入),以使那些更新的块可以被适当的写出。

 

    文件系统必须在*pagep 中给调用者返回锁定了指定偏移量的页缓存的页来写入。

 

    它必须能够应付short writes(即传递给write_begin的要写入的长度大于复制到页里的字节数的情况)。

 

    flags是一个AOP_FLAG_xxx型标志,在include/linux/fs.h描述。

 

   可能在fsdata 返回一个无类型指针,这个指针随后将会被传入write_end。

 

   成功则放回0,错误在返回负值(错误码),则将不调用write_end 。

 

  write_end: 成功地调用write_begin ,并完成了数据复制之后,则必须调用write_end 。len是最初传递给write_begin 的len,copied 参数是可以被复制的数量(如果write_begin在设置了AOP_FLAG_UNINTERRUPTIBLE标志的情况下被调用,则 copied == len总为真)。

 

    文件系统必须注意对页解除锁定,释放它的refcount并更新i_size。

 

    出错时返回负值,否则返回可以被复制进pagecache的字节数(<= 'copied')。

 

  bmap: 由VFS调用来将一个对象内的逻辑块偏移量映射为物理块号。这个方法由FIBMAP ioctl使用,并且和swap-files一起工作。为了能够交换文件,文件必须具有到一个块设备的稳定的影视。swap系统不是浏览整个文件系统,而是使用bmap 来查找文件所在的块并直接使用那些地址。

 

 

  invalidatepage: 如果一个页设置了PagePrivate ,则在部分或全部的页将被移除出(remove)地址空间时调用invalidatepage。这通常对应于一个截断或一个完全无效的地址空间。(后一种情况下则'offset'总为0)。任何与页关联的私有数据应该被更新以反映这种截断。如果offset为0,由于页必须能够被完全丢弃,则私有数据应该被释放。这可能通过调用->releasepage 函数来完成,但是在这种情况下释放必须成功。

 

  releasepage: releasepage 在PagePrivate页上调用以标示如果可能就应该释放的页。->releasepage 应该移除页的所有的数据(remove) 并清除PagePrivate 标志。它也可能会从address_space 中移除(remove)页。如果它由于某些原因而失败,则它可能用0返回值来标示失败。它被用于两种不同但相关的情况中。第一,是当VM发现一个没有活跃用户的页而想要使其成为一个空闲的页时。如果 ->releasepage 成功,则页将会被从address_space中移除(remove)并成为空闲的。

 

    第二种情形是, 当 要使address_space 里的一些或全部的页无效的请求发出时。这可以通过fadvice(POSIX_FADV_DONTNEED)系统调用或者文件系统作为NFS和9fs通过调用invalidate_inode_pages2()来明确的请求(当它们认为缓存与存储器相比可能过期了)。

 

    如果文件系统做了这个调用,则它的releasepage需要确保所有的页是无效的。如果它还不能释放私有数据,可能它可以清除PageUptodate 位。

 

  direct_IO: 由通用read/write 例程调用来执行直接IO – 即绕过页缓存而直接在存储器和应用程序的地址空间传递数据的IO请求。

 

  get_xip_page: 由VM调用来将一个块号转化为一个页。页将保持有效直到相应的文件系统被卸载。想要使用片内执行(execute-in-place, XIP)的文件系统需要实现它。在fs/ext2/xip.c中可以找到一个实现的实例。

 

  migrate_page:  它被用来压缩对物理内存的使用。如果VM想要重新定位一个页 (maybe off a memory card that is signalling imminent failure),则它将会传递一个新页和一个旧页给这个函数。migrate_page 应该为这个页传输所有的私有数据,并更新对这个页的饮用。

 

  launder_page: 在释放一个页之前调用- 它写回标记为“脏”的页。为了防止该页被重新标记为“脏”,则它将在整个操作期间持有锁。

 

  error_remove_page: 如果这个地址空间的truncation is ok ,那么它通常设置为generic_error_remove_page 。用于内存失败处理。除非你锁定它们或者增加它们的引用计数,则设置它意味着你将亲自处理页

    Setting this implies you deal with pages going away under you,

    unless you have them locked or reference counts increased.

 

 

文件对象(The File Object)

==========================

 

一个文件对象代表进程打开的一个文件。

 

struct file_operations

----------------------

 

它描述了VFS是如何管理一个打开的文件的。在 kernel 2.6.32.7中,有如下定义:

 

/*

 * NOTE:

 * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl

 * can be called without the big kernel lock held in all filesystems.

 */

struct file_operations {

    struct module *owner;

    loff_t (*llseek) (struct file *, loff_t, int);

    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

    int (*readdir) (struct file *, void *, filldir_t);

    unsigned int (*poll) (struct file *, struct poll_table_struct *);

    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

    int (*mmap) (struct file *, struct vm_area_struct *);

    int (*open) (struct inode *, struct file *);

    int (*flush) (struct file *, fl_owner_t id);

    int (*release) (struct inode *, struct file *);

    int (*fsync) (struct file *, struct dentry *, int datasync);

    int (*aio_fsync) (struct kiocb *, int datasync);

    int (*fasync) (int, struct file *, int);

    int (*lock) (struct file *, int, struct file_lock *);

    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

    int (*check_flags)(int);

    int (*flock) (struct file *, int, struct file_lock *);

    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

    int (*setlease)(struct file *, long, struct file_lock **);

};

 

再次,除非另有说明,所有的方法均在不持有任何锁的情况下调用。

 

  llseek: 当VFS需要移动文件位置索引(文件指针)时调用。

 

  read: 由read(2)及相关的系统调用调用

 

  aio_read: 由io_submit(2)及其他的异步I/O 操作调用。

 

  write: 由write(2)及相关的系统调用调用

 

  aio_write: 由io_submit(2)及其他的异步I/O 操作调用。

 

  readdir: 当VFS需要读取目录内容时调用。

 

  poll: 当一个进程想要检查该文件是否活跃并(可选地)进入睡眠状态直到文件活跃 时由VFS调用。由select(2) 及poll(2)系统调用调用。

 

  ioctl: 由系统调用ioctl(2)调用

 

  unlocked_ioctl: 由系统调用ioctl(2)调用没有请求BKL 的文件系统应该使用这个方法来代替上面的ioctl()。

 

  compat_ioctl: 32位系统调用运行在64位内核上时由ioctl(2)系统调用调用。

 

  mmap: 由系统调用mmap(2)调用。

 

  open: 当需要打开一个inode时由VFS调用。 VFS打开一个文件时,它创建一个新的"struct file",然后为新分配的file 结构调用open方法。你可能认为open方法实际上应该放在"struct inode_operations"里,也许你是对的。我认为它之所以如现在这样工作,是为了使文件系统更容易实现。如果你想要指向一个设备结构体,那么open()方法是一个初始化file 结构体中"private_data"成员的好地方。

 

  flush: 由系统调用close(2)来刷新(flush)一个文件

 

  release: 当对一个打开文件的最后一个引用关闭时调用。

 

  fsync: 由系统调用fsync(2)调用

 

  fasync: 当一个文件的异步(非阻塞)模式使能时,由系统调用fcntl(2)调用。

 

  lock: 为系统调用fcntl(2)的F_GETLK, F_SETLK, 和 F_SETLKW 命令调用

 

  readv: called by the readv(2) system call

 

  writev: called by the writev(2) system call

 

  sendfile: called by the sendfile(2) system call

 

  get_unmapped_area: 有系统调用mmap(2)调用

 

  check_flags: 为系统调用fcntl(2)的F_SETFL命令调用。

 

  flock: 由系统调用flock(2)调用

 

  splice_write: VFS调用来从一个管道接合数据到一个文件,这个方法由系统调用splice(2)使用。

 

  splice_read: VFS调用来从一个文件中接合数据到一个管道。它由系统调用splice(2)使用。

 

注意,文件操作由inode所在的特定的文件系统实现。当打开一个设备文件(字符设备或块设备特殊文件)时,大多数文件系统将调用VFS的特殊支持例程,这些例程将定位请求设备的驱动程序信息。这些支持例程也将用设备驱动程序的file operations来代替文件系统的file operations,并为文件调用新的open()方法。这就是文件系统如何打开一个设备文件,它最终以调用设备驱动程序的open()方法结束。

 

 

目录项缓存(dcache)

==================

 

 

struct dentry_operations

------------------------

 

它描述了一个文件系统是如何重载一个标准的dentry operations 。Dentries and 和 dcache 是VFS和独立的文件系统实现的域。设备驱动与此无关。由于他们是可选的或者VFS使用默认的例程,这些方法可能被设为NULL。在kernel2.6.32.7中有如下定义:

 

struct dentry_operations {

    int (*d_revalidate)(struct dentry *, struct nameidata *);

    int (*d_hash) (struct dentry *, struct qstr *);

    int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);

    int (*d_delete)(struct dentry *);

    void (*d_release)(struct dentry *);

    void (*d_iput)(struct dentry *, struct inode *);

    char *(*d_dname)(struct dentry *, char *, int);

};

 

  d_revalidate: 当VFS需要使一个dentry重新生效时调用。用名字来在dcache 中查询一个dentry时调用。由于它们在dcache中的所有的dentries 是有效的,大多数文件系统将它设置为NULL。

 

  d_hash: 当VFS向哈希表中添加一个dentry是调用。

 

  d_compare: 当一个dentry要和另一个比较时调用

 

  d_delete: 对于一个dentry的最后的引用解除(delete)时调用。这意味着没有人正在使用这个dentry,但依然是有效的,并依然在dcache中。

 

  d_release: 当dentry被真正的解除分配时调用。

 

  d_iput: 当一个dentry丢失了他的inode(在它被释放之前)调用。如果它是NULL,则默认地VFS会调用iput()。如果你定义了这个方法,则你必须自己调用iput()。I

 

  d_dname: 当需要产生一个dentry的路径名的时候调用。对于某些想要延迟路径名的产生的伪文件系统(sockfs, pipefs, ...)很有用。(不是在dentry创建的时候,而是在需要路径名的时候才产生)。真实的文件系统可能不会使用它,因为它们的dentries 出现在全局的dcache哈希表中,它们的哈希应该是不变量。除非使用适当的SMP安全措施,否则由于没有持有锁,则d_dname()不应该试着自己去修改dentry 。注意:d_path()逻辑是相当复杂的。正确的返回 比如"Hello"的方法是将其放在缓冲区的结尾处,然后返回指向第一个字符的指针。dynamic_dname()辅助函数可被用来处理这一点。Real filesystems probably

 

例如 :

 

static char *pipefs_dname(struct dentry *dent, char *buffer, int buflen)

{

    return dynamic_dname(dentry, buffer, buflen, "pipe:[%lu]",

              dentry->d_inode->i_ino);

}

 

每一个dentry有一个指向它的父dentry的指针,并有一个它的子dentries的列表。子dentries 基本上就像一个目录中的文件。

 

 

目录项缓存API(Directory Entry Cache API)

------------------------------------------

 

内核定义了许多函数可以用来管理dentries:

 

  dget: 为一个现有的dentry打开一个新的句柄(它仅仅增加使用计数)。

 

  dput: 为一个dentry 关闭一个句柄(将使用计数减一)。如果使用计数减到0,则调用"d_delete"方法,并且,如果dentry仍然在它的父哈希列表中时,将dentry放入未使用列表。将dentry放入未使用列表仅仅意味着如果系统需要RAM,它浏览dentries的未使用列表并对dentries解除分配。如果dentry已经unhashed ,并且使用计数减为了0,则在调用了"d_delete"方法之后要解除分配dentry。

 

  d_drop: 该函数从dentry的父哈希表中unhashes 一个dentry。如果它的使用计数减为了0,则随后调用的dput()解除分配该dentry。

 

  d_delete: 删除(delete)一个dentry。如果没有其他的对于该dentry的打开引用,则该dentry将被转换为一个negative  dentry(调用d_iput()方法)。如果还有其他的引用,则调用d_drop()来代替。

 

  d_add: 将一个dentry添加进它的父哈希表中,并调用d_instantiate()。

 

  d_instantiate: 为inode将一个dentry添加进别名哈希表,并更新"d_inode"成员。Inode结构体的"i_count"成员应该被设置/增加计数。如果inode指针为NULL,则dentry被称为"negative    dentry"。这个函数通常在 为一个现有的negative dentry 创建一个inode时调用。

 

  d_lookup: 根据父dentry和路径名来查找一个dentry。它从dcache哈希表中查找与给定名字相同的子dentry。如果找到了,则增加引用计数,并返回dentry。调用者必须在结束使用dentry时使用dput()释放它。

 

更多关于dentry锁的信息,请参考文档Documentation/ filesystems/ dentry-locking.txt.

 

挂载选项(Mount Options)

========================

 

解析参数(Parsing options)

---------------------------

 

在挂载和重挂载(remount)文件系统时,会传递一个含有由逗号分隔的挂载选项的字符串。选项可以具有下列各式之一:

 

  option

  option=value

 

<linux/parser.h>头文件定义一个API以帮助解析这些选项。有许多关于如何在一个现有的文件系统使用它的例子。

 

Showing options

---------------

 

如果一个文件系统接受挂载选项,那么它必须定义show_options()来显示当前可用的所有的选项。规则是:

 

  - 非默认的及与默认值不相同的选项必须显示。

 

  - 默认代开或使用默认值的选项可以显示。

 

选项仅仅在内部使用,在一个挂载辅助者和内核(比如文件描述符)之间使用,或者只在挂载时(比如控制日志的创建)服从上面的规则。

 

上述规则的根本原因是为了确保,根据/proc/mounts的信息,可以对一个挂载进行精确的复制(比如,卸载然后重新挂载)。

 

一个在挂载/重挂载时保存选项并显示他们的方法(method)是提供save_mount_options() 和generic_show_options()辅助函数。请注意,使用这些可能有不足之处。更多信息,可以参考这些函数在文件fs/namespace.c 中的注释信息。

 

资源(Resources)

================

 

(注意,有些资源没有随着最新版本的内核而更新。)

 

Creating Linux virtual filesystems. 2002

    <http://lwn.net/Articles/13325/>

 

The Linux Virtual File-system Layer by Neil Brown. 1999

    <http://www.cse.unsw.edu.au/~neilb/oss/linux-commentary/vfs.html>

 

A tour of the Linux VFS by Michael K. Johnson. 1996

    <http://www.tldp.org/LDP/khg/HyperNews/get/fs/vfstour.html>

 

A small trail through the Linux kernel by Andries Brouwer. 2001

    <http://www.win.tue.nl/~aeb/linux/vfs/trail.html>



后记:

这是linux内核文档中的 Documentation/filesystems/vfs.txt文件的翻译,很好的一个specification,对linux虚拟文件系统中的许多对象都有比较清晰的描述。这个文件最后一次更新是在2007年了,有点老了,自那之后linux虚拟文件系统有变动。说明仍然根据内核文档,但各结构体的定义取的都是较新linux 2.6.32.7版。实在是有好多看不太懂的地方,还请各位多多指教了。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值