前面讲了,为了使各种文件系统和谐相处,内核提供了一个通用的虚拟文件模型。本博,我们就重点讨论组成这个通用文件模型的那些对象的数据结构。
每个VFS对象都存放在一个恰当的数据结构中,其中包括对象的属性和指向对象方法表的指针。内核可以动态地修改对象的方法,因此可以为对象建立专用的行为。
所有的VFS对象都放在include/linux/Fs.h中。
1 超级块对象
超级块对象由super_block结构组成:
struct super_block {
struct list_head s_list; /* 指向超级块链表的指针 */
dev_t s_dev; /* 设备标识符 */
unsigned long s_blocksize; /* 以字节为单位的块大小 */
unsigned char s_blocksize_bits; /* 以位为单位的块大小 */
unsigned char s_dirt; /* 修改(脏)标志 */
unsigned long long s_maxbytes; /* 文件的最长长度 */
struct file_system_type *s_type; /* 文件系统类型 */
struct super_operations *s_op; /* 超级块方法 */
struct dquot_operations *dq_op; /* 磁盘限额处理方法 */
struct quotactl_ops *s_qcop; /* 磁盘限额管理方法 */
struct export_operations *s_export_op; /* 网络文件系统使用的输出操作 */
unsigned long s_flags; /* 安装标志 */
unsigned long s_magic; /* 文件系统的魔数 */
struct dentry *s_root; /* 文件系统根目录的目录项对象 */
struct rw_semaphore s_umount; /* 卸载所用的信号量 */
struct mutex s_lock; /* 超极块信号量 */
int s_count; /* 超级快引用计数器 */
int s_syncing; /* 表示对超级块的索引节点进行同步的标志 */
int s_need_sync_fs; /* 对超级块的已安装文件系统进行同步的标志 */
atomic_t s_active; /* 超级快次级引用计数器 */
void *s_security; /* 指向超级块安全数据结构的指针 */
struct xattr_handler **s_xattr; /* 指向超级块扩展属性结构的指针 */
struct list_head s_inodes; /* 所有索引节点的链表头 */
struct list_head s_dirty; /* 改进型索引节点的链表 */
struct list_head s_io; /* 等待被写入磁盘的索引节点的链表 */
struct hlist_head s_anon; /* 用于处理远程网络文件系统的匿名目录项的链表 */
struct list_head s_files; /* 文件对象的链表 */
struct block_device *s_bdev; /* 指向块设备驱动程序描述符的指针 */
struct list_head s_instances; /* 用于给定文件系统类型的超级块对象链表的指针 */
struct quota_info s_dquot; /* 磁盘限额的描述符 */
int s_frozen; /* 冻结文件系统时使用的标志(强制置于一致状态) */
wait_queue_head_t s_wait_unfrozen;/* 进程挂起的等待队列,直到文件系统被解冻 */
char s_id[32]; /* 包含超级块的块设备名称 */
void *s_fs_info; /* 指向特定文件系统的超级块信息的指针 */
/*
* The next field is for VFS *only*. No filesystems have any business
* even looking at it. You had been warned.
*/
struct mutex s_vfs_rename_mutex; /* 当VFS通过目录重命名文件时使用的信号量 */
/* Granularity of c/m/atime in ns. —— 时间戳的粒度(纳秒级)
Cannot be worse than a second */
u32 s_time_gran;
};
所有超级块对象都以双向循环链表的形式链接在一起。链表中第一个元素用super_blocks变量来表示,而超级块对象的s_list字段存放指向链表相邻元素的指针。sb_lock自旋锁保护链表免受多处理器系统上的同时访问。
s_fs_info字段指向属于具体文件系统的超级块信息;例如,假如超级块对象指的是Ext2文件系统,该字段就指向ext2_sb_info数据结构,该结构包括磁盘分配位掩码和其他与VFS的通用文件模型无关的数据。
通常,为了效率起见,由s_fs_info字段所指向的数据被复制到内存。任何基于磁盘的文件系统都需要访问和更改自己的磁盘分配位图,以便分配或释放磁盘块。VFS允许这些文件系统直接对内存超级块的s_fs_info字段进行操作,而无需访问磁盘。
但是,这种方法带来一个新问题:有可能VFS超级块最终不再与磁盘上相应的超级块同步,即读入内存的s_fs_info数据为脏。因此,有必要引入一个s_dirt标志来表示该超级块是否是脏的——那磁盘上的数据是否必须要更新。
缺乏同步还会导致产生我们熟悉的一个问题:当一台机器的电源突然断开而用户来不及正常关闭系统时,就会出现文件系统崩溃。我们将会在以后的博文中看到,Linux是通过周期性地将所有“脏”的超级块写回磁盘来减少该问题带来的危害。
与超级块关联的方法就是所谓的超级块操作。这些操作是由数据结构super_operations来描述的,该结构的起始地址存放在超级块的s_op字段中:
struct super_operations {
/* 为索引节点对象分配空间,包括具体文件系统的数据所需要的空间。 */
struct inode *(*alloc_inode)(struct super_block *sb);
/* 撤销索引节点对象,包括具体文件系统的数据。 */
void (*destroy_inode)(struct inode *);
/* 用磁盘上的数据填充以参数传递过来的索引节点对象的字段;
索引节点对象的i_ino字段标识从磁盘上要读取的具体文件系统的索引节点。*/
void (*read_inode) (struct inode *);
/* 当索引节点标记为修改(脏)时调用。
* 像ReiserFS和Ext3这样的文件系统用它来更新磁盘上的文件系统日志。*/
void (*dirty_inode) (struct inode *);
/* 用通过传递参数指定的索引节点对象的内容更新一个文件系统的索引节点。
* 索引节点对象的i_ino字段标识所涉及磁盘上文件系统的索引节点。
* flag参数表示I/O操作是否应当同步。*/
int (*write_inode) (struct inode *, int);
/* 释放索引节点时调用(减少该节点引用计数器值)以执行具体文件系统操作。*/
void (*put_inode) (struct inode *);
/* 在即将撤消索引节点时调用——也就是说,
* 当最后一个用户释放该索引节点时;
* 实现该方法的文件系统通常使用generic_drop_inode()函数。
* 该函数从VFS数据结构中移走对索引节点的每一个引用,
* 如果索引节点不再出现在任何目录中,
* 则调用超级块方法delete_inode将它从文件系统中删除。*/
void (*drop_inode) (struct inode *);
/* 在必须撤消索引节点时调用。删除内存中的VFS索引节点和磁盘上的文件数据及元数据。*/
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);
/* 阻塞对文件系统的修改并用指定对象的内容更新超级块。
* 当文件系统被冻结时调用该方法,例如,由逻辑卷管理器驱动程序(LVM)调用。*/
void (*write_super_lockfs) (struct super_block *);
/* 取消由write_super_lockfs()超级块方法实现的对文件系统更新的阻塞。*/
void (*unlockfs) (struct super_block *);
/* 将文件系统的统计信息返回,填写在buf缓冲区中。*/
int (*statfs) (struct dentry *, struct kstatfs *);
/* 用新的选项重新安装文件系统(当某个安装选项必须被修改时被调用)。*/
int (*remount_fs) (struct super_block *, int *, char *);
/* 当撤消磁盘索引节点执行具体文件系统操作时调用。*/
void (*clear_inode) (struct inode *);
/* 中断一个安装操作,因为相应的卸载操作已经开始(只在网络文件系统中使用)。*/
void (*umount_begin) (struct vfsmount *, int);
/* 用来显示特定文件系统的选项。*/
int (*show_options)(struct seq_file *, struct vfsmount *);
/* 用来显示特定文件系统的状态。*/
int (*show_stats)(struct seq_file *, struct vfsmount *);
/* 限额系统使用该方法从文件中读取数据,该文件详细说明了所在文件系统的限制。*/
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);
#ifndef __GENKSYMS__ //一般情况下用不到最后两个方法
int (*freeze_fs) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
#endif
};
每个具体的文件系统都可以定义自己的超级块操作。当VFS需要调用其中一个操作时,比如read_inode(),它执行下列操作:
sb->s_op->read_inode(inode);
这里sb存放所涉及超级块对象的地址。super_operations表的read_inode字段存放这函数的地址,因此,这一函数被直接调用。
super_operations中的方法对所有可能的文件系统类型均是可用的。但是,只有其中的一个子集应用到每个具体的文件系统;未实现的方法对应的字段置为NULL。
注意,系统没有定义get_super方法来读超级块,那么,内核如何能够调用一个对象的方法而从磁盘读出该对象?我们将在描述文件系统类型的另一个对象中找到等价的get_sb方法。
2 索引节点对象
文件系统处理文件所需要的所有信息都放在一个名为索引节点的数据结构中。文件名可以随时更改,但是索引节点对文件是唯一的,并且随文件的存在而存在。内存中的索引节点对象由一个inode数据结构组成:
struct inode {
struct hlist_node i_hash; /* 用于散列链表的指针 */
struct list_head i_list; /* 用于描述索引节点当前状态的链表的指针 */
struct list_head i_sb_list; /* 用于超级块的索引节点链表的指针 */
struct list_head i_dentry; /* 引用索引节点的目录项对象链表的头 */
unsigned long i_ino; /* 索引节点号 */
atomic_t i_count; /* 引用计数器 */
umode_t i_mode; /* 文件类型与访问权限 */
unsigned int i_nlink; /* 硬链接数目 */
uid_t i_uid; /* 所有者标识符 */
gid_t i_gid; /* 所有者组标识符 */
dev_t i_rdev; /* 实设备标识符 */
loff_t i_size; /* 文件的字节数 */
struct timespec i_atime; /* 上次访问文件的时间 */
struct timespec i_mtime; /* 上次写文件的时间 */
struct timespec i_ctime; /* 上次修改索引节点的时间 */
unsigned int i_blkbits; /* 块的位数 */
unsigned long i_version; /* 版本号(每次使用后自动递增) */
blkcnt_t i_blocks; /* 文件的块数 */
unsigned short i_bytes; /* 文件中最后一个块的字节数 */
spinlock_t i_lock; /* 保护索引节点一些字段的自旋锁:i_blocks, i_bytes, maybe i_size */
struct mutex i_mutex; /* 索引节点信号量 */
struct rw_semaphore i_alloc_sem; /* 在直接I/O文件操作中避免出现竞争条件的读/写信号量 */
struct inode_operations *i_op; /* 索引节点的操作 */
const struct file_operations *i_fop; /* 缺省文件操作:former->i_op->default_file_ops */
struct super_block *i_sb; /* 指向超级块对象的指针 */
struct file_lock *i_flock; /* 指向文件锁链表的指针 */
struct address_space *i_mapping; /* 指向缓存address_space对象的指针 */
struct address_space i_data; /* 嵌入在inode中的文件的address_space对象 */
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS]; /* 索引节点磁盘限额 */
#endif
struct list_head i_devices; /* 用于具体的字符或块设备索引节点链表的指针 */
union {
struct pipe_inode_info *i_pipe; /* 如果文件是一个管道则使用它 */
struct block_device *i_bdev; /* 指向块设备驱动程序的指针 */
struct cdev *i_cdev; /* 指向字符设备驱动程序的指针 */
};
int i_cindex; /* 拥有一组次设备号的设备文件的索引 */
__u32 i_generation; /* 索引节点版本号(由某些文件系统使用) */
#ifdef CONFIG_DNOTIFY
unsigned long i_dnotify_mask; /* 目录通知事件的位掩码 */
struct dnotify_struct *i_dnotify; /* 用于目录通知 */
#endif
#ifdef CONFIG_INOTIFY
struct list_head inotify_watches; /* watches on this inode */
struct mutex inotify_mutex; /* protects the watches list */
#endif
unsigned long i_state; /* 索引节点的状态标志 */
unsigned long dirtied_when; /* 索引节点的弄脏时间(以节拍为单位) */
unsigned int i_flags; /* 文件系统的安装标志 */
atomic_t i_writecount; /* 用于写进程的引用计数器 */
void *i_security; /* 指向索引节点安全结构的指针 */
void *i_private; /* 指向私有数据的指针 */
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;/* SMP系统为i_size字段获取一致值时使用的顺序计数器 */
#endif
};
每个索引节点对象都会复制磁盘索引节点包含的一些数据,比如分配给文件的磁盘块数。如果i_state字段的值等于I_DIRTY_SYNC、
I_DIRTY_DATASYNC或I_DIRTY_PAGES,那么该索引节点就是“脏”的,也就是说,对应的磁盘索引节点必须被更新。I_DIRTY宏可以用来立即检查这三个标志的值(详细内容参见后面)。
i_state字段的其他值有I_LOCK(涉及的索引节点对象处于I/O传送中)、I_FREEING(索引节点对象正在被释放)、I_CLEAR(索引节点对象的内容不再有意义)以及I_NEW(索引节点对象已经分配但还没有用从磁盘索引节点读取来的数据填充)。
每个索引节点对象总是出现在下列的其中一个双向循环链表的某个链表中(所有情况下,指向相邻元素的指针存放在i_list字段中):
(1)有效未使用的索引节点链表。典型的如那些镜像有效的磁盘索引节点,且当前未被任何进程使用。这些索引节点不为脏,且它们的
i_count字段置为0。链表中的首元素和尾元素是由变量inode_unused的next字段和prev字段分别指向的。这个链表用作磁盘高速缓存。
(2)正在使用的索引节点链表,也就是那些镜像有效的磁盘索引节点,且当前被某些进程使用。这些索引节点不为脏,但它们的i_count字段为正数。链表中的首元素和尾元素是由变量mode_in_use指向的。
(3)脏索引节点的链表。链表中的首元素和尾元素是由相应超级块对象的s_dirty字段引用的。
此外,每个索引节点对象也包含在每文件系统(per filesystem)的双向循环链表中,链表的头存放在超级块对象的s_inodes字段中;索引节点对象的i_sb_list字段存放了指向链表相邻元素的指针。
最后,索引节点对象也存放在一个称为inode_hashtable的散列表中。散列表加快了对索引节点对象的搜索,前提是系统内核要知道索引节点号及文件所在文件系统对应的超级块对象的地址。由于散列技术可能引发冲突,所以索引节点对象包含一个i_hash字段,该字段中包含向前和向后的两个指针,分别指向散列到同一地址的前一个索引节点和后一个索引节点;该字段因此创建了由这些索引节点组成的一个双向链表。
与索引节点对象关联的方法也叫索引节点操作。它们由inode_operations结构来描述,该结构的地址存放在i_op字段中:
struct inode_operations {
/* 在某一目录下,为与目录项对象相关的普通文件创建一个新的磁盘索引节点。 */
int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
/* 为包含在一个目录项对象中的文件名对应的索引节点查找目录。 */
struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
/* 创建一个新的名为new_dentry的硬链接,它指向dir目录下名为old_dentry的文件。 */
int (*link) (struct dentry *,struct inode *,struct dentry *new_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 *);
/* 在某个目录中,为与目录项对象相关的特定文件创建一个新的磁盘索引节点。
* 其中参数mode和rdev分别表示文件的类型和设备的主次设备号。 */
int (*mknod) (struct inode *,struct dentry *,int,dev_t);
/* 将old_dir目录下由old_entry标识的文件移到new_dir目录下。
* 新文件名包含在new_dentry指向的目录项对象中。 */
int (*rename) (struct inode *old_dir, struct dentry *old_entry,
struct inode *new_dir, struct dentry *new_dentry);
/* 将目录项所指定的符号链接中对应的文件路径名拷贝到buffer所指定的用户态内存区。 */
int (*readlink) (struct dentry *, char __user *,int);
/* 解析索引节点对象所指定的符号链接;如果该符号链接是一个相对路径名,
* 则从第二个参数所指定的目录开始进行查找。 */
void * (*follow_link) (struct dentry *, struct nameidata *);
/* 释放由follow_link方法分配的用于解析符号链接的所有临时数据结构。 */
void (*put_link) (struct dentry *, struct nameidata *, void *);
/* 修改与索引节点相关的文件长度。在调用该方法之前,
* 必须将inode对象的i_size字段设置为需要的新长度值。 */
void (*truncate) (struct inode *);
/* 检查是否允许对与索引节点所指的文件进行指定模式的访问。 */
int (*permission) (struct inode *, int, struct nameidata *);
/* 在触及索引节点属性后通知一个“修改事件”。 */
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);
#ifndef __GENKSYMS__
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);
#endif
};
上述列举的方法对所有可能的索引节点和文件系统类型都是可用的。不过,只有其中的一个子集应用到某一特定的索引节点和文件系统,未实现的方法对应的字段被置为NULL。
3 文件对象
文件对象描述进程怎样与一个打开的文件进行交互。文件对象是在文件被打开时创建的,由一个file结构组成。注意,文件对象在磁盘上没有
对应的映像,因此file结构中没有设置“脏”字段来表示文件对象是否已被修改:
struct file {
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u; /* 用于通用文件对象链表的指针 */
struct dentry *f_dentry; /* 与文件相关的目录项对象 */
struct vfsmount *f_vfsmnt; /* 含有该文件的已安装文件系统 */
const struct file_operations *f_op; /* 指向文件操作表的指针 */
atomic_t f_count; /* 文件对象的引用计数器 */
unsigned int f_flags; /* 当打开文件时所指定的标志 */
mode_t f_mode; /* 进程的访问模式 */
loff_t f_pos; /* 当前的文件位移量(文件指针) */
struct fown_struct f_owner; /* 通过信号进行I/O事件通知的数据 */
unsigned int f_uid, f_gid; /* 用户的UID、GID */
struct file_ra_state f_ra; /* 文件预读状态 */
unsigned long f_version; /* 版本号,每次使用后自动递增 */
void *f_security; /* 指向文件对象的安全结构的指针 */
/* needed for tty driver, and maybe others 指向特定文件系统或设备驱动程序所需的数据的指针*/
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links; /* 文件的事件轮询等待者链表的头 */
spinlock_t f_ep_lock; /* 保护f_ep_links链表的自旋锁 */
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping; /* 指向文件地址空间对象的指针 */
};
存放在文件对象中的主要信息是文件指针f_pos,即文件中当前的位置,下一个操作将在该位置发生。由于几个进程可能同时访问同一文件,因此文件指针必须存放在文件对象而不是索引节点对象中。
文件对象通过一个名为filp的slab高速缓存分配,filp描述符地址存放在file_cachep变量中。Linux对分配的文件对象数目是有限制的,因此files_stat变量在其max_files字段中指定了可分配文件对象的最大数目,也就是系统可同时访问的最大文件数。
在使用文件对象包含在由具体文件系统的超级块所确立的几个链表中。每个超级块对象把文件对象链表的头存放在s_files字段中;因此,属于不同文件系统的文件对象就包含在不同的链表中。链表中分别指向前一个元素和后一个元素的指针都存放在文件对象的f_list字段中。
files_lock自旋锁保护超级块的s_files链表免受多处理器系统上的同时访问。
文件对象的f_count字段是一个引用计数器:它记录使用文件对象的进程数(记住,以CLONE_FILES标志创建的轻量级进程共享打开文件表,因此它们可以使用相同的文件对象)。当内核本身使用该文件对象时也要增加计数器的值——例如,把对象插入链表中或发出dup()系统调用时。
当VFS代表进程必须打开一个文件时,它调用get_empty_filp()函数来分配一个新的文件对象。该函数调用kmem_cache_alloc()从filp高速缓存中获得一个空闲的文件对像,然后初始化这个对象的字段,如下所示:
f = kmem_cache_alloc(filp_cachep, GFP_KERNEL);
percpu_counter_inc(&nr_files);
memset(f, 0, sizeof(*f));
tsk = current;
INIT_LIST_HEAD(&f->f_u.fu_list);
atomic_set(&f->f_count, 1);
rwlock_init(&f->f_owner.lock);
f->f_uid = tsk->fsuid;
f->f_gid = tsk->fsgid;
正如在上一篇博文中讨论过的那样,每个文件系统都有其自己的文件操作集合,执行诸如读写文件这样的操作。当内核将一个索引节点从磁盘装入内存时,就会把指向这些文件操作的指针存放在file_operations结构中,而该结构的地址存放在该索引节点对象的i_fop字段中。当进程打开这个文件时,VFS就用存放在索引节点中的这个地址初始化新文件对象的fop字段,使得对文件操作的后续调用能够使用这些函数。如果需要,VFS随后也可以通过在f_op字段存放一个新值而修改文件操作的集合:
struct file_operations {
/* 指向一个模块的拥有者,该字段主要应用于那些有模块产生的文件系统 */
struct module *owner;
/* 更新文件指针。
loff_t (*llseek) (struct file *, loff_t, int);
/* 从文件的*offset处开始读出count个字节;然后增加*offset的值(一般与文件指针对应)。 */
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
/* 启动一个异步I/O操作,从文件的pos处开始读出len个字节的数据并将它们放入buf中
* (引入它是为了支持io_submit()系统调用)。 */
ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
/* 从文件的*offset处开始写入count个字节,然后增加*offset的值(一般与文件指针对应)。 */
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
/* 启动一个异步I/O操作,从buf中取len个字节写入文件pos处。 */
ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
/* 返回一个目录的下一个目录项,返回值存人参数dirent;
* 参数filldir存放一个辅助函数的地址,该函数可以提取目录项的各个字段。 */
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);
/* 与ioctl方法类似,但是它不用获得大内核锁。
* 我们认为所有的设备驱动程序和文件系统都将使用这个新方法而不是loctl方法。 */
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
/* 64位的内核使用该方法执行32位的系统调用ioctl()。 */
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);
/* 释放文件对象。当打开文件的最后一个引用被关闭时(即文件对象f_count字段的值变为0时)调用该方法。 */
int (*release) (struct inode *, struct file *);
/* 将文件所缓存的全部数据写入磁盘。 */
int (*fsync) (struct file *, struct dentry *, int datasync);
/* 启动一次异步I/O刷新操作。 */
int (*aio_fsync) (struct kiocb *, int datasync);
/* 通过信号来启用或禁止I/O事件通告。 */
int (*fasync) (int, struct file *, int);
/* 对file文件申请一个锁。 */
int (*lock) (struct file *, int, struct file_lock *);
/* 从文件中读字节,并把结果放入vector描述的缓冲区中;缓冲区的个数由count指定。 */
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
/* 把vector描述的缓冲区中的字节写人文件;缓冲区的个数由count指定。 */
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
/* 把数据从in_file传送到out_file(引人它是为了支持sendfile()系统调用)。 */
ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
/* 把数据从文件传送到页高速缓存的页;这个低层方法由sendfile()和用于套接字的网络代码使用。 */
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);
/* 当设置文件的状态标志(F_SETFL命令)时,
* fcntl()系统调用的服务例程调用该方法执行附加的检查。当前只适用于NFS网络文件系统。 */
int (*check_flags)(int);
/* 当建立一个目录更改通告(F_NOTIFY命令)时,
* 由fcntl()系统调用的服务例程调用该方法。当前只适用于CIFS(Common Internet File * system,公用互联网文件系统
)网络文件系统。 */
int (*dir_notify)(struct file *filp, unsigned long arg);
/* 用于定制flock()系统调用的行为。官方Linux文件系统不使用该方法。
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);
};
以上描述的方法对所有可能的文件类型都是可用的。不过,对于一个具体的文件类型,只使用其中的一个子集;那些未实现的方法对应的字段被置为NULL。
4 目录项对象
VFS把每个目录看作由若干子目录和文件组成的一个普通文件。然而目录项不同,一旦目录项被读人内存,VFS就把它转换成基于dentry结构的一个目录项对象。对于进程查找的路径名中的每个分量,内核都为其创建一个目录项对象;目录项对象将每个分量与其对应的索引节点相联系。例如,在查找路名/tmp/test时,内核为根目录“/“创建一个目录项对象,为根目录下的tmp项创建一个第二级目录项对象,为/tmp目录下的test项创建一个第三级目录项对象。
请注意,目录项对象在磁盘上并没有对应的映像,因此在dentry结构中不包含指出该对象已被修改的字段。目录项对象存放在名为
dentry_cache的slab分配器高速缓存中。因此,目录项对象的创建和删除是通过调用kmem_cache_alloc()和kmem_cache_free()实现的。
//include/linux/Dcache.h
struct dentry {
atomic_t d_count; /* 目录项对象引用计数器 */
unsigned int d_flags; /* 目录项高速缓存标志 */
spinlock_t d_lock; /* 保护目录项对象的自旋锁 */
struct inode *d_inode; /* 与文件名关联的索引节点*/
/*
* The next three fields are touched by __d_lookup. Place them here
* so they all fit in a cache line.
*/
struct hlist_node d_hash; /* 指向散列表表项链表的指针 */
struct dentry *d_parent; /* 父目录的目录项对象 */
struct qstr d_name; /* 文件名 */
struct list_head d_lru; /* 用于未使用目录项链表的指针 */
/*
* d_child and d_rcu can share memory
*/
union {
struct list_head d_child; /* 对目录而言,用于同一父目录中的目录项链表的指针 */
struct rcu_head d_rcu; /* 回收目录项对象时,由RCU描述符使用 */
} d_u;
struct list_head d_subdirs; /* 对目录而言,子目录项链表的头 */
struct list_head d_alias; /* 用于与同一索引节点(别名)相关的目录项链表的指针 */
unsigned long d_time; /* 由d_revalidate方法使用 */
struct dentry_operations *d_op; /* 目录项方法 */
struct super_block *d_sb; /* 文件的超级块对象 */
void *d_fsdata; /* 依赖于文件系统的数据 */
void *d_extra_attributes; /* TUX-specific data */
#ifdef CONFIG_PROFILING
struct dcookie_struct *d_cookie; /* cookie, if any 指向内核配置文件使用的数据结构的指针*/
#endif
int d_mounted; /* 对目录而言,用于记录安装该目录项的文件系统数的计数器 */
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* 存放短文件名的空间 */
};
每个目录项对象可以处于以下四种状态之一:
空闲状态(free):处于该状态的目录项对象不包括有效的信息,且还没有被VFS使用。对应的内存区由slab分配器进行处理。
未使用状态(unused):处于该状态的目录项对象当前还没有被内核使用。该对象的引用计数器d_count的值为0,但其d_inode字段仍然指向关联的索引节点。该目录项对象包含有效的信息,但为了在必要时回收内存,它的内容可能被丢弃。
正在使用状态(in use):处于该状态的目录项对象当前正在被内核使用。该对象的引用计数器d_count的值为正数,其d_inode字段指向关联的索引节点对象。该目录项对象包含有效的信息,并且不能被丢弃。
负状态(negative):与目录项关联的索引节点不复存在,那是因为相应的磁盘索引节点已被删除,或者因为目录项对象是通过解析一个不存在文件的路径名创建的。目录项对象的d_inode字段被置为NULL,但该对象仍然被保存在目录项高速缓存中,以便后续对同一文件目录名的查找操作能够快速完成。术语“负状态”容易使人误解,因为根本不涉及任何负值。
与目录项对象关联的方法称为目录项操作。这些方法由dentry_operations结构加以描述,该结构的地址存放在目录项对象的d_op字段中。尽管一些文件系统定义了它们自己的目录项方法,但是这些字段通常为NULL,而VFS使用缺省函数代替这些方法:
struct dentry_operations {
/* 在把目录项对象转换为一个文件路径名之前,判定该目录项对象是否仍然有效。
* 缺省的VFS函数什么也不做,而网络文件系统可以指定自己的函数。 */
int (*d_revalidate)(struct dentry *, struct nameidata *);
/* 生成一个散列值;这是用于目录项散列表的、特定干具体文件系统的散列函数。
* 参数dentry标识包含路径分量的目录。参数name指向一个结构,
* 该结构包含要查找的路径名分量以及由散列函数生成的散列值。 */
int (*d_hash) (struct dentry *, struct qstr *);
/* 比较两个文件名。name1应该属于dir所指的目录。
* 缺省的VFS函数是常用的字符串匹配函数。
* 不过,每个文件系统可用自己的方式实现这一方法。
* 例如,MS.DOS文件系统不区分大写和小写字母。 */
int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
/* 当对目录项对象的最后一个引用被删除(d_count变为“0”)时,
* 调用该方法。缺省的VFS函数什么也不做。 */
int (*d_delete)(struct dentry *);
/* 当要释放一个目录项对象时(放入slab分配器),调用该方法。
* 缺省的VFS函数什么也不做。 */
void (*d_release)(struct dentry *);
/* 当一个目录项对象变为“负”状态(即丢弃它的索引节点)时,调用该方法。
* 缺省的VFS函数调用iput()释放索引节点对象。 */
void (*d_iput)(struct dentry *, struct inode *);
};