2015年(100)
分类: LINUX
2015-06-15 06:54:32
本章介绍了虚拟文件系统的结构以及虚拟文件系统如何实现文件的操作,还介绍了底层各种缓存的管理。
虚拟文件系统介绍概述Linux支持各种文件系统,Linux内核通过虚拟文件系统了对各种文件系统共性的进行抽象,并对外提供统一接口,从面向对象编程的角度来看,称为抽象文件系统更为合适。虚拟文件系统(VFS virtual file system)用来管理挂接(mount)各种具体文件系统(如:ext4文件系统)。具体的文件系统可设计成可加载模块,在系统需要时进行加载,例如VFAT就被实现成一个模块,当挂接VFAT文件系统时VFAT文件系统模块将被加载。 挂接具体文件系统时,VFS读取它的超级块,得到具体文件系统的拓扑结构并将这些信息映射到VFS超级块结构中。 当进程或shell命令(如:ls)访问目录和文件时,shell命令及应用程序分解成系统调用,系统调用进入内核空间,遍历虚拟文件系统的VFS节点(inode),而VFS节点指向了具体文件系统的节点,通过底层块I/O函数调用IDE接口,然后再通过块驱动程序访问块设备(如:硬盘),得到了文件数据。文件系统的运行流程示意图如图7-1。 虚拟文件系统对文件系统共有的内核上层及底层部分进行了处理,上层处理如:文件路径的查找、文件的读写操作从用户空间向下传递到具体文件系统的部分;底层进行各种缓存的处理,如:块缓存(buffer)。
VFS支持的文件系统分为磁盘文件系统、网络文件系统和特殊文件系统三类,分别说明如下:
磁盘文件系统管理本地的块设备,如:硬盘、闪存盘(闪存模拟的硬盘)、光驱等。常见的文件系统有ext3(Third Extend Filesystem)、ext4、ReiserFS、VFAT、NTFS、ISO9660 CD-ROM、通用磁盘格式(UDF)的DVD文件系统等。
网络文件系统用于访问其他网络计算机的文件系统所包含的文件。常见的网络文件系统有NFS、CIFS(微软的通用网络文件系统)等。
还有一种特殊的文件系统如/proc或devfs,它们操作内存缓冲区,在内存缓冲区中存有相关系统管理信息。常用的特殊文件系统功能说明如表1. 表1 常用的特殊文件系统功能说明
虚拟文件系统机制虚拟文件系统是文件系统共性的抽象,由超级块(superblock)、索引节点(inode)、文件(file)、目录 (dentry)对象组成,每个对象用数据结构进行描述。它通过系统调用向用户空间提供了API操作文件系统,通过对象操作函数API调用具体文件系统的函数进行读写文件操作。 虚拟文件系统的对象说明如下:
超级块存放已安装文件系统的信息,与具体文件系统的超级块相对应。超级块由超级块对象结构super_block和超级块操作函数集结构super_operation组成。
索引节点存放文件的通用信息,索引节点由索引节点对象结构inode和每个索引节点对象和索引节点操作函数集结构inode_operation组成。每个节点有一个节点号,用于惟一地标识文件系统中的文件。
文件对象描述了进程与打开文件之间交互过程的信息,目录文件存放目录中的文件列表信息。文件由文件对象结构file和文件操作函数集结构file operation组成。文件对象的文件指针描述了文件中当前操作的位置,几个进程可以并发访问同一个文件。
文件系统的每个目录用目录条目对象结构dentry描述。例如,在查找/tmp/test/目录时,VFS为根目录"/"创建一个目录条目对象,为tmp目录创建一个第二级目录条目对象,为test目录创建一个第三级目录条目对象。 目录条目对象在磁盘上并没有对应的映像,每个具体文件系统都有独特的方式将目录条目信存放在磁盘上。 文件系统系统调用说明文件系统的常见系统调用涉及文件操作和文件系统操作,分别说明如表1和表2. 表1 文件读写操作系统调用说明
VFS数据结构VFS的数据结构定义了VFS对象和对象的操作函数集,它们是一个接口标准,具体文件系统必须提供VFS接口标准对应的数据结构和操作函数。体文件系统在使用前,必须将自己的结构及操作函数映射到VFS中,这样被访问到。 超级块超级块对象VFS用超级块结构super_block描述整个文件系统信息,包括具体文件系统类型、起始级块操作函数、根目录、设备序号、等待队列、具体文件系统信息等数据。超级块是各种具体逻辑文件系统(如:ext4)在挂接时建立的,并在这些文件系统卸载时自动删除, 超级块确实只存在内存中,超级块是针对具体逻辑文件系统的。 每个具体文件系统挂接时,用具体文件系统的信息填充一个超级块结构实例,每个已挂接的文件系统有一个超级块对象,所有挂接文件系统的超级块对象链接成链表。 通常每个具体的文件系统有一个超级块信息结构,如:ext4_sb_info,超级块通过指针s_fs_info指向它。 超级块结构super_block列出如下(在include/linux/fs.h中):struct super_block { struct list_head s_list; /*超级块链表,必须在结构的最前面 */ /*包含该逻辑文件系统的块设备标识号。如:/dev/hda1设备标识符为0x301*/ dev_t s_dev; /*该逻辑文件系统中数据块的大小,以字节为单位*/ unsigned long s_blocksize; /*块大小的值占用的位数,如:1024字节时为10位*/ unsigned char s_blocksize_bits; /*脏位,若置该位,表明该超级块已被修改*/ unsigned char s_dirt; unsigned long long s_maxbytes; /*最大文件长度*/ struct file_system_type *s_type; /*文件系统类型*/ const struct super_operations *s_op; /*特定逻辑文件系统的超级块操作函数集*/ struct dquot_operations *dq_op; /*特定逻辑文件系统的限额操作函数集*/ struct quotactl_ops *s_qcop; /*磁盘限额控制方法操作函数集*/ const 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; /* #ifdef CONFIG_SECURITY void *s_security; /*用于SELinux的安全数据结构指针*/ #endif struct xattr_handler **s_xattr; /*超级块扩展属性处理例程结构指针*/ struct list_head s_inodes; /* 所有索引节点的链表 */ struct list_head s_dirty; /* 脏索引节点的链表 */ struct list_head s_io; /*等待写回到磁盘的索引节点的链表 */ struct list_head s_more_io; /* parked for more writeback */ struct hlist_head s_anon; /*用于NFS输出的匿名目录条目的链表*/ struct list_head s_files; /*文件对象的链表*/ struct block_device *s_bdev; /*指向块设备描述结构*/ struct mtd_info *s_mtd; /*指向MTD设备信息结构*/ 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; /* 特定具体文件系统的超级块信息结构*/ /*下面的域仅用于VFS */ struct mutex s_vfs_rename_mutex; /* 当VFS通过目录重命名文件时用的互斥锁 */ u32 s_time_gran; /* 时间戳粒度(纳秒)*/ /*如果在/proc/mounts下的文件类型域非空,则文件类型域格式为“type.subtype”*/ char *s_subtype; /*为使用generic_show_options()的懒惰文件系统存放挂接选项 */ char *s_options; }; 超级块操作函数集超级块对象的各种操作方法由超级块操作函数集结构super_operations定义,通过超级块对象的成员s_op可找到该操作函数集,如:sb->s_op->read_inode(inode)。 结构super_operations列出如下:struct super_operations { struct inode *(*alloc_inode)(struct super_block *sb); /*分配节点对象空间*/ void (*destroy_inode)(struct inode *); /*销毁节点*/ void (*dirty_inode) (struct inode *); /*处理脏节点,ext4用它更新文件系统日志*/ int (*write_inode) (struct inode *, int); /*将节点的内容写回到磁盘节点*/ /*通常为函数generic_drop_inode,当引用计数和硬连接计数i_nlink为0时,调用delete_inode删除节点*/ 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); void (*write_super_lockfs) (struct super_block *); /*锁住文件系统将超级块信息写回磁盘*/ void (*unlockfs) (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 }; 节点节点对象在磁盘上有对应的映像,Linux内核需要负责节点对象的数据与磁盘上映像数据的一致性,这通过节点的状态标识完成。 索引节点对象文件(包括目录文件)是管理文件系统的最基本单位,每个文件都有一个索引节点结构inode,存放了文件系统处理文件所需要的各种信息。 节点或索引节点(inode)是文件系统连接任何子目录、任何文件的桥梁。每个子目录或文件只能由惟一的inode描述。它包括结构操作函数、文件操作函数、等待队列、对应物理块的描述等。 结构inode包括描述文件信息域、索引节点操作域和其他域。文件信息域包括时间戳、块数、文件大小、操作权限、用户ID等文件的信息;索引节点操作域包括文件锁、文件操作函数集、节点操作函数集、节点脏标识和节点磁盘限额等操作文件所需要的操作函数或信息。 结构inode列出如下(在include/linux/fs.h中):struct inode { /*用Hash表加快索引节点的搜索,前提是系统需要知道索引节点号及所在文件系统的超级块地址*/ 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; /*引用计数器*/ unsigned int i_nlink; /*硬链接的数目*/ uid_t i_uid; /*文件拥有者标识号*/ gid_t i_gid; /*文件拥有者所在组的标识号*/ dev_t i_rdev; /*实设备标识符*/ u64 i_version; /*用于同步的版本号,每次使用后自动增加*/ loff_t i_size; /*文件的大小(以字节为单位)*/ #ifdef __NEED_I_SIZE_ORDERED seqcount_t i_size_seqcount; /*SMP系统为i_size字段获取一致值时使用的顺序计数器*/ #endif struct timespec i_atime; /*文件的最后访问时间*/ struct timespec i_mtime; /*文件的最后修改时间*/ struct timespec i_ctime; /*节点的最后修改时间*/ unsigned int i_blkbits; /*块大小所占的位数*/ blkcnt_t i_blocks; /*该文件所占块数*/ unsigned short i_bytes; /*文件最后一块所占字节数*/ umode_t i_mode; /*文件的访问权限*/ spinlock_t i_lock; /*保护i_blocks, i_bytes和i_size的自旋锁*/ struct mutex i_mutex; /*索引节点对象的互斥锁*/ struct rw_semaphore i_alloc_sem; /*直接I/O文件操作中用的读/写信号量*/ const struct inode_operations *i_op; /*索引节点的操作函数集*/ /* 缺省的文件操作函数集:former ->i_op->default_file_ops */ const struct file_operations *i_fop; struct super_block *i_sb; /*指向超级块对象*/ struct file_lock *i_flock; /*指向文件锁*/ struct address_space *i_mapping; /*指向地址映射空间的指针*/ struct address_space i_data; /*文件数据的地址映射空间*/ #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; /* 监视此节点 */ struct mutex inotify_mutex; /* 用于保护监视链表的互斥锁*/ #endif unsigned long i_state; /*索引节点的状态标识*/ unsigned long dirtied_when; /* 第1次设置脏位的时间(jiffies)*/ unsigned int i_flags; /*文件系统的挂接标志*/ atomic_t i_writecount; /*写操作的引用计数*/ #ifdef CONFIG_SECURITY void *i_security; /*用于SELinux的安全结构*/ #endif void *i_private; /* 指向文件系统或设备的私有数据*/ }; VFS的索引节点结构存有文件或目录的信息,具体文件系统的索引节点是存储在磁盘上的,是一种静态结构,又称为磁盘索引节点。每个磁盘索引节点有个节点号,节点号用于指向文件的索引节点所在磁盘地址。 Linux用磁盘索引节点的数据填写VFS的索引节点,也称VFS索引节点是动态节点,通常所说的索引节点指的是VFS索引节点,是结构inode的对象实例。 索引节点状态节点的状态由结构inode的成员i_state进行描述,状态标识由锁inode_lock进行保护。成员i_state用3个位表示节点的脏状态I_DIRTY_SYNC、I_DIRTY_DATASYNC和I_DIRTY_PAGES,用4个位表示节点的寿命I_NEW、I_WILL_FREE、I_FREEING和I_CLEAR,用2个位表示加锁I_LOCK和完成通知I_SYNC,这些状态标识说明如下(在include/linux/fs.h中):#define I_DIRTY_SYNC 1 #define I_DIRTY_DATASYNC 2 #define I_DIRTY_PAGES 4 #define I_NEW 8 #define I_WILL_FREE 16 #define I_FREEING 32 #define I_CLEAR 64 #define __I_LOCK 7 #define I_LOCK (1 << __I_LOCK) #define __I_SYNC 8 #define I_SYNC (1 << __I_SYNC) (1)I_NEW I_NEW表示节点为初始化时新节点, 直到该标识被清除。get_new_inode()设置到I_LOCK|I_NEW,iget()调用unlock_new_inode()清除它们。 (2)释放标识I_WILL_FREE,I_FREEING和I_CLEAR I_WILL_FREE,I_FREEING和I_CLEAR是删除节点各个阶段设置的标识,有这些标识的节点禁止用于多用途。iget()必须等待节点完全被释放,接着创建新的节点。其他函数将仅忽略这些节点。如果合适,函数使用I_LOCK进行等待。这些标识分别说明如下: I_WILL_FREE 如果i_count为0调用write_inode_now()时,必须设置此位,表示将被释放。当I_WILL_FREE被清除时,必须设置I_FREEING。 I_FREEING 当节点将要被释放但还有脏页或附有buffer或节点本身还是脏的时候,设置此位,表示节点正被释放。 I_CLEAR clear_inode()设置,表示节点被清除,并能被销毁,该节点的内容不再有意义。 (3)脏标识I_DIRTY_SYNC、I_DIRTY_DATASYNC和I_DIRTY_PAGES I_DIRTY_SYNC 表示节点是脏的,但还不需要用fdatasync()写此节点,通常由i_atime引起。 I_DIRTY_DATASYNC 表示数据相关的节点改变了挂起状态,从I_DIRTY_SYNC 分开跟踪这些变化,以便当仅mtime改变时不必用fdatasync()写节点。 I_DIRTY_PAGES 表示节点有脏页,节点本身可能是干净的。 (4)锁标识I_LOCK和I_SYNC I_LOCK 用作互斥锁和完成通知。新节点设置I_LOCK,如果两个进程创建同一节点,其中之一进程将释放它的节点并等待I_LOCK释放后返回。在I_WILL_FREE、I_FREEING或I_CLEAR状态的节点也会引起在I_LOCK上的等待,而实际上没有设置I_LOCK。find_inode()使用它阻止返回快要死亡的节点。 I_SYNC 类似于I_LOCK,但使用范围限制为节点脏数据的写回操作。为此方面设置一个独立锁的目的是减少延迟和阻止文件系统特定的死锁。 节点链表Linux内核根据节点的使用状态将节点链接成三种不同的双向循环链表,分别说明如下: (1)"in_use"链表 "in_use(正在使用)"链表 通过成员i_list链接正在使用的有效节点,特征是:i_count > 0或i_nlink > 0。"in_use"链表用全局变量inode_in_use表示。 (2)"dirty"链表 "dirty(脏)"链表 与"in_use"一样的有效节点,但还设置了脏标识。"dirty"链表表示。Linux内核为每个超级块维护"dirty"链表,允许用于低负载的节点sync()操作。 (3)"unused"链表 "unused(未使用)"链表 通过成员i_list链接未使用的节点,特征是:i_count = 0。该链表用作磁盘高速缓存。"unused"链表用全局变量inode_unused 每个节点同时位于两个独立的链表中:一个是通过成员i_hash链接于节点的Hash链表,用全局变量inode_hashtable表示,用于快速查找。另一个链表是上述类型的链表之一。 "in_use"链表和"unused"链表定义如下(在include/linux/writebach.h中):extern spinlock_t inode_lock; /*用于保护设置i_state*/ extern struct list_head inode_in_use; extern struct list_head inode_unused;
节点操作函数集每个节点都有一个对应的节点操作函数集结构inode_operations,定义节点的各种操作,通过节点的成员i_op可以找到节点操作函数集。 结构inode_operations定义了节点的操作函数集,其列出如下(在include/linux/fs.h中):struct inode_operations { /*创建节点,代表目录的节点才提供此函数*/ int (*create) (struct inode *,struct dentry *,int, struct nameidata *); /*当VFS在目录中寻找节点时调用*/ 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 *); /*释放follow_link解析符号链接所用的临时数据结构*/ void (*put_link) (struct dentry *, struct nameidata *, void *); 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); /*截取文件的指定范围*/ long (*fallocate)(struct inode *inode, int mode, loff_t offset, loff_t len); /*为一个文件预分配空间*/ }; 上述结构中,(*link)()是硬链接函数,链接分为硬链接(hard link)和符号链接(symbolic link)两种,符号链接有自己的节点,只是内容指到别的文件的路径而已,而硬链接是多个文件共享一个节点,并且硬链接的文件只能与指到的文件位于同一个文件系统。当指到的文件被删除时,只是你看不到那个文件,那个文件还是存在的。shell提供了ln命令来产生硬链接,标准库中提供了link()的系统调用来产生硬链接。 (*unlink) ()函数用来将参数dir指到的目录下的dentry文件删除。它检查dentry->d_inode->i_nlink是否为0,如为0,就真正删除文件。 文件
文件对象进程打开文件时创建结构file对象,描述操作文件的各种信息,如:文件位置、操作许可、文件操作函数集的指针等。与节点不同,结构file描述打开文件的操作信息,在磁盘上没有相应的映像,文件的数据是通过节点在磁盘上找到存储位置的。 结构file保存打开文件的信息,通过文件描述符查找文件描述符数组fd_arrayfile可得到file结构。结构file分析如下(在include/linux/fs.h中):struct file { /*在调用函数file_free且通过fu_rcuhead排队用于RCU释放后,fu_list变为无效*/ union { struct list_head fu_list; struct rcu_head fu_rcuhead; } f_u; struct path f_path; #define f_dentry f_path.dentry /*与文件对应的目录条目结构*/ #define f_vfsmnt f_path.mnt /*文件所在文件系统挂接点信息*/ 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; /*文件的所有者*/ unsigned int f_uid, f_gid; /*用户UID和GID*/ struct file_ra_state f_ra; /*文件预读状态*/ u64 f_version; /*使用版本号,每次使用后自动递增*/ #ifdef CONFIG_SECURITY void *f_security; /*用于SELinux的安全数据结构*/ #endif /* 用于指向tty驱动程序或其他设备的私有数据*/ void *private_data; #ifdef CONFIG_EPOLL /* fs/eventpoll.c用来链接此文件的所有hook */ struct list_head f_ep_links; /*文件的事件轮询链接到此文件的等待者队列*/ spinlock_t f_ep_lock; /*链表保护的自旋锁*/ #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; /*指向文件映射地址空间对象*/ #ifdef CONFIG_DEBUG_WRITECOUNT unsigned long f_mnt_write_state; /*用于调试时检查文件的写状态*/ #endif };
文件操作函数集文件操作函数集结构file_operations中存有对打开的文件进行操作的函数,该结构列出如下(在include/linux/fs.h中):/*在所有文件系统中,操作read, write, poll, fsync, readv, writev, unlocked_ioctl和compat_ioctl都不需要大内核锁*/ 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 *); /*异步I/O的读操作,是文件系统对磁盘的读操作*/ ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); /*异步I/O的写操作*/ ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); /*读目录内容*/ int (*readdir) (struct file *, void *, filldir_t); /*文件的poll操作,检查文件是否有操作发生,如果没有则睡眠,直到有文件操作发生为止*/ unsigned int (*poll) (struct file *, struct poll_table_struct *); /*只适用于设备的控制,通过设备文件,向设备发送控制命令*/ int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); /*与ioctl功能一致,只是不需要加大内核锁*/ 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 *); /* 打开文件,创建file结构,用dentry及inode结构填充file */ int (*open) (struct inode *, struct file *); /* 在关闭打开的文件时调用此函数,实际用途取决于具体的文件系统,一般用于将数据刷新回磁盘*/ int (*flush) (struct file *, fl_owner_t id); /*当引用计数为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); /*文件加锁*/ 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); /*当用F_SETFL命令设置文件状态标识时,系统调用fcntl()调用此函数执行附加检查。只适用于NFS网络文件系统*/ int (*check_flags)(int); /*系统调用fcntl()在命令F_NOTIFY下调用此函数建立一个目录更改通知,只适用于CIFS网络文件系统*/ int (*dir_notify)(struct file *filp, unsigned long arg); /*文件加锁,针对于系统调用flock的实现*/ 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); /*在打开的文件上建立租约,用于nfsv4和samba文件系统,通过fcntl(F_SETLEASE)调用此函数*/ int (*setlease)(struct file *, long, struct file_lock **); }; 文件锁文件加锁是为了阻止多个进程并发访问文件的竞争。Linux文件锁包括劝告锁(advisory lock)和强制锁(mandatory lock),强制锁还包括共享模式强制锁(share-mode mandatory lock)和租借锁(lease)两个变种。Linux支持系统调用fcntl()、flock()和lockf()进行加锁操作,最好是所有进程使用协作锁或"劝告"锁。 劝告锁和强制锁分别说明如下: (1)劝告锁劝告锁是基于系统调用fcntl()的POSIX标准文件加锁机制,可以对整个文件的部分或整个文件加锁。劝告锁类似于信号量,起到"劝告"的作用,只有所有访问进程使用劝告锁时,才能起到加锁的作用,如果其他进程不使用劝告锁而直接访问文件,内核不会加以阻拦。 (2)强制锁
强制锁介绍
强制锁是System V内核强制采用的文件锁,每当有系统调用 open()、read() 以及write() 发生时,内核都要检查并确保这些系统调用不会违反在所访问文件上加的强制锁约束。 强制锁包括共享模式强制锁和租借锁。共享模式强制锁用于网络文件系统,其他任何进程都不能打开与锁访问模式相冲突的文件。租借锁表示对锁"租借"一段时间,当一个进程试图打开由租借锁打开的文件时,该进程被锁阻塞,拥有锁的进程收到一个信号,在指定的时间内更新文件,以保持文件内容的一致,然后释放锁,如果拥有锁的进程不在指定的时间内更新文件,则内核自动删除租借锁,以允许阻塞的进程执行。 相对于更常用的保证多进程文件顺序访问的协作锁(或劝告负)来说,强制锁是内核强制的文件加锁。进程通过系统调用flock()(System V标准)和fcntl()(POSIX标准)加文件锁,分别称为FL_FLOCK和FL_POSIX锁。库函数lockf()封装了fcntl()。通常进程在再次应用它自己的锁、更新文件和解锁文件之前,负责检查它希望更新的文件上的锁。例如:sendmail访问用户的邮箱,邮箱用户代码和邮箱转发代理必须保证不能同时更新邮箱,并且在更新时,阻止读邮箱。 System V UNIX使用了"强制"锁,当其他进程持有"读"或"共享"锁,内核将阻塞进程写文件;当其他进程持有"写"或"互斥"锁时,进程将阻塞读写文件。 System V强制锁对已存在的用户代码有一些影响,它标识单个文件为强制锁的候选者,用已存在接口fcntl()/lockf()实现强制锁。System V锁基于fcntl(),允许对文件的部分或整个加锁。System V接口定义版本3(System V Interface Definition (SVID) Version 3)定义了强制锁。
强制锁使用
使用租借锁的方法是通过设置标识F_SETLEASE或F_GETLEASE调用系统调用fcntl()。强制锁使用较复杂,其步骤说明如下: 1)挂接时加强制锁选项 挂接文件系统时可以使用挂接选项"-o mand"或"nomand"基于每个文件系统打开或关闭强制锁,缺省值不允许强制锁,强制锁仅地特定需要时在本地文件系统上打开。 2)标记强制锁候选者 通过设置文件模式的组ID位和删除组执行位,将一个文件标识为强制锁候选者。System V选择该无含义的组合标识文件,是为了不破坏已存在的用户程序。 当文件设置组ID操作setgid完成时,内核自动清除组ID位,内核能识别强制锁候选者特例,并限制清除此位。内核不对有setgid特权的候选者运行强制锁。 3)调用fcntl()加锁或解锁。
不同操作系统下强制锁的限制
Linux遵循SVID定义,当其他进程已加强制锁时,进程调用open(),将不能设置标识O_TRUNC,因为仅通过设置O_TRUNC才能修改文件内容。 操作系统HP-UX遵循POSIX.1规范,它不仅对强制锁,甚至不允许对一个加"劝告"锁的文件用标识O_TRUNC调用open()。 所有操作系统禁止将强制锁应用于用mmap()映射的文件,HP-UX还不允许将"劝告"锁应用于该文件。SVID实际上定义了HP-UX的行为。 仅MAP_SHARED映射应该免于加强制锁。 SunOS甚至于不将标识O_NONBLOCK用于强制锁,因此,对加锁文件的读写操作总是阻塞的。
强制锁的使用语法
1)仅能通过System V/POSIX接口fcntl()/lockf()应用强制锁,BSD模式使用flock()将不会产生强制锁。 2)如果一个进程用强制锁锁住一个文件的一个区域,那么其他进程允许从该区域读。如果其他任一个进程尝试写该区域,将被阻塞直到锁被释放,除非进程用标识O_NONBLOCK打开文件,这种情况下,系统调用将立即返回错误EAGAIN。 3) 如果一个进程用强制写锁锁住一个文件的区域,所有尝试读或写该区域的进程将被阻塞直到锁被释放,除非非进程用标识O_NONBLOCK打开文件,这种情况下,系统调用将立即返回错误EAGAIN。 4)在被其他进程拥有强制锁的文件上,进程用标识O_TRUNC调用open()或调用creat(),将被拒绝并返回错误EAGAIN。 5)尝试对一个用标识MAP_SHARED通过mmap()映射的文件应用强制锁,将被拒绝并返回错误EAGAIN。 6)如果尝试用标识MAP_SHARED通过mmap()对一个有强制锁文件创建共享内存映射,则访问会被拒绝并返回错误EAGAIN。 锁相关数据结构包括锁对象结构file_lock、文件锁操作函数集结构file_lock_operations和锁管理操作函数集结构lock_manager_operations,分别列出如下:struct file_lock { struct file_lock *fl_next; /* 这个节点的单向链接链表*/ struct list_head fl_link; /* 所有锁的双向链接链表*/ struct list_head fl_block; /* 阻塞进程的循环链表*/ fl_owner_t fl_owner; unsigned int fl_pid; /*锁拥有者的pid*/ struct pid *fl_nspid; /*锁命名空间的结构pid*/ wait_queue_head_t fl_wait; /*阻塞进程的等待队列*/ struct file *fl_file; /*使用锁的文件对象*/ unsigned char fl_flags; /*锁标志*/ unsigned char fl_type; /*锁类型*/ loff_t fl_start; /*文件加锁区间的起始偏移量*/ loff_t fl_end; /*文件加锁区间的结束偏移量*/ struct fasync_struct * fl_fasync; /* 用于租借中断的通知*/ unsigned long fl_break_time; /* 文件锁中断前的时间,用于非阻塞租借中断*/ struct file_lock_operations *fl_ops; /*文件锁操作函数集,是用于文件系统的回调函数*/ struct lock_manager_operations *fl_lmops; /* 指向锁管理操作,是用于锁管理者回调用函数*/ union { struct nfs_lock_info nfs_fl; struct nfs4_lock_info nfs4_fl; struct { struct list_head link; /* 链接在文件系统AFS节点vnode的pending_locks链表中 */ int state; /*授权状态,如果为-ve,表示出错*/ } afs; } fl_u; /*具体文件系统的锁信息*/ }; /*在线程组中结构files_struct决定POSIX文件锁属主,如果为NULL,表示没有属主 */ typedef struct files_struct *fl_owner_t; struct file_lock_operations { void (*fl_copy_lock)(struct file_lock *, struct file_lock *); void (*fl_release_private)(struct file_lock *); }; struct lock_manager_operations { int (*fl_compare_owner)(struct file_lock *, struct file_lock *); void (*fl_notify)(struct file_lock *); /*解锁回调函数 */ int (*fl_grant)(struct file_lock *, struct file_lock *, int); void (*fl_copy_lock)(struct file_lock *, struct file_lock *); void (*fl_release_private)(struct file_lock *); void (*fl_break)(struct file_lock *); int (*fl_mylease)(struct file_lock *, struct file_lock *); int (*fl_change)(struct file_lock **, int); }; FL_POSIX锁还可以对文件的部分内容进行加锁,加锁区域用结构flock描述,加锁区域对象结构flock描述了加锁区域在文件中的位置及属主PID信息,其列出如下(在include/asm-generic/fcntl.h中): struct flock { 目录条目
目录条目对象目录是由多个子目录和文件组成的普通文件,是一个目录条目的列表,其中的每一个目录条目都有一个数据结构来描述。每个目录的头两项总是标准目录条目"."和"..",分别指向目录、父目录的inode。 为保持从目录访问inode的高效率,Linux维护了表达路径与inode对应的关系的VFS 目录缓存。被文件系统使用过的目录将会存入目录缓存中。这样,同一目录被再次访问时,可直接从目录缓存中得到,不必重复访问存储文件系统的设备。该目录缓存用目录条目结构dentry进行描述,每个结构dentry实例又称为一个目录条目对象,与用户用shell命令ls显示的目录项(目录或文件)相对应。 每个文件或目录都有一个结构dentry,它描述述文件或目录在文件系统目录树中的关系及状态,还有指向inode结构的成员。文件系统由文件或目录路径名可得到结构dentry及inode,反过来,由dentry也可得到文件或目录的路径名。dentry将路径名与节点联系起来了,它是动态生成,只存在缓存中。 目录条目对象的数据从分析路径过程获取,在磁盘上没有映像数据。结构dentry列出如下(在include/linux/dcache.h中):struct dentry { /*为负数时,表示与目录条目对应的索引节点不存在,可能是磁盘节点不存在或路径不存在*/ atomic_t d_count; /*引用计数器*/ unsigned int d_flags; /* 目录条目高速缓存标志,由锁d_lock保护 */ spinlock_t d_lock; /* 每目录条目对象的自旋锁*/ struct inode *d_inode; /*与文件名相对应的节点*/ /*下面三个域由函数__d_lookup修改*/ struct hlist_node d_hash; /* 查找hash链表*/ struct dentry *d_parent; /* 父目录*/ struct qstr d_name; /*目录条目的字符名*/ struct list_head d_lru; /* LRU(最近很少使用)链表 */ /* * d_child和d_rcu 能共享内存 */ 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; /* 文件系统特定的数据*/ #ifdef CONFIG_PROFILING struct dcookie_struct *d_cookie; /* cookie,指向内核配置文件使用的数据结构 */ #endif int d_mounted; /*已挂接到该目录的文件系统数量*/ unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* 存放短文件名,文件名前36字符*/ }; 目录条目使用hash表来管理,由文件名可以计算hash值,而具有同样的hash值的目录条目已不多了,再进行逐项比较,找到对应的目录条目。相同hash值的目录条目用双向链表连接。还可以通过父或子目录关系找到相应的目录条目。 对于正在使用的目录条目,保存在hash表可索引到的缓存里,它们放在d_hash链表里,而对于暂时不用的目录项,放在d_lru链表里,当系统内存紧张时,从这个链表中释放内存。通常要删除的节点被选择出来放在d_lru链表的尾部,然后由prune_dcache()负责剪除。 linux有这样的分配策略:目录条目缓存是节点缓存的控制者,无论何时,一个目录条目dentry存在,则节点inode存在,当一个目录条目dentry被删除,则iput()函数会被调用以删除节点。 在fs/dcache.c中有全局目录条目缓存链表的定义:static struct list_head *dentry_hashtable; static LIST_HEAD(dentry_unused);
目录条目操作函数集每个目录条目对象有一个操作函数集相对应,用于定义目录条目的操作方法。目录条目操作函数集结构dentry_operations列出如下:struct dentry_operations { /*在目录条目对象转换成文件路径名之前,再检查目录条目对象是否有效。缺省函数不做任何操作*/ int (*d_revalidate)(struct dentry *, struct nameidata *); /* hash值生成函数,产生的值用作hash表的hash地址*/ int (*d_hash) (struct dentry *, struct qstr *); /*文件名比较函数,用于hash链表比较文件名*/ int (*d_compare) (struct dentry *, struct qstr *, struct qstr *); /*当引用计数为0时,调用此函数进行删除操作,缺省函数不做任何操作*/ int (*d_delete)(struct dentry *); /*释放目录条目对象,缺省函数不做任何操作*/ void (*d_release)(struct dentry *); /*当引用计数为负值时,调用此函数释放索引节点对象*/ void (*d_iput)(struct dentry *, struct inode *); /*由目录条目对象获取文件路径名*/ char *(*d_dname)(struct dentry *, char *, int); }; 限额限额(Quota)子系统允许系统管理者对用户或组的磁盘使用空间和使用节点数进行限制。使用空间和使用的节点数不可能超过硬件的最大尺寸限制,但可以超过软件限制或限额一段时间,这段时间称为"宽限期"(grace period或grace time)。宽限期结束时,用户不能分配更多的空间或节点,直到他们释放空间直到低于限额。 每个文件系统的限额限制和宽限期大小独立地进行设置。当用户超出软件限制时,quota子系统通过netlink接口传递事件信息到用户空间,让用户空间进程打印提示信息。
限额设置方法用户设置磁盘限额的方法列出如下: 1)加入限额选项 在文件/etc/fstab中加入限额选项,例如:对目录home添加限额时,需要在该文件加入下面行挂接限额目录: LABEL=/home /home ext3 defaults,usrquota,grpquota 1 2
# cd /home # touch aquota.user //建立空文件,用于设置用户磁盘限额文件 # touch aquota.group //建立空文件,用于设置组的磁盘限额文件
限额配置信息结构mem_dqblk每个安装的文件系统都与一个限额文件相联系,限额文件通常驻留在文件系统的根目录里。它实际是一组以用户标识号来索引的限额记录,每个限额记录说明了一个用户或用户组的限额配置信息。结构mem_dqblk 列出如下(在include/linux/quota.h中):/*保存在内存中的一个用户/组的限额数据*/ struct mem_dqblk { __u32 dqb_bhardlimit; /*磁盘块分配上的硬限制*/ __u32 dqb_bsoftlimit; /* 磁盘块上的首选限制 */ qsize_t dqb_curspace; /* 当前的使用空间 */ __u32 dqb_ihardlimit; /* 在分配节点上的硬限制*/ __u32 dqb_isoftlimit; /*首选的节点限制 */ __u32 dqb_curinodes; /* 当前已分配的节点数 */ time_t dqb_btime; /* 过度使用磁盘的时间限制,即宽限期*/ time_t dqb_itime; /*过度使用节点的时间限制,即宽限期*/ };
限额描述结构dquot限额配置信息条目调入内存后,为方便使用哈希表,要用到另一个结构dquot。结构dquot在包括限额配置信息条目的基础上,还包括了将条目链接成链表、每个条目在限额文件中的偏移、限额类型等信息。每个结构dquot实例与限额文件中的一个限额配置信息记录相对应,还描述了与文件操作相关信息。 结构dquot列出如下:struct dquot { struct hlist_node dq_hash; /* hash链表 */ struct list_head dq_inuse; /* 所有dquot的链表 */ struct list_head dq_free; /* 空闲链表*/ struct list_head dq_dirty; /*脏dquots的链表 */ struct mutex dq_lock; /* dquot IO 锁 */ atomic_t dq_count; /* 引用计数*/ wait_queue_head_t dq_wait_unused; /* 等待dquot变成未使用的等待队列*/ struct super_block *dq_sb; /* 应用此限额的超级块*/ unsigned int dq_id; /* 应用此限额的ID*/ loff_t dq_off; /* dquot在磁盘上的偏移 */ unsigned long dq_flags; /*限额标识DQ_* */ short dq_type; /*限额类型*/ struct mem_dqblk dq_dqb; /*磁盘的限额配置信息*/ };
限额操作函数集结构dquot_operations如果文件系统使用了限额机制,则当有新的块分配请求,系统以文件拥有者的标识号为索引去查找限额文件中相应的限额条目,如果没有超过限额,则接受请求,并将该限额描述结构的引用计数加1。如果已达到或超过限额,则拒绝请求,并返回错误信息。 文件系统用限额操作函数集结构dquot_operations指定限额操作,其列出如下:struct dquot_operations { int (*initialize) (struct inode *, int); //用默认值初始化用户的限额 int (*drop) (struct inode *); //停止限额的作用 int (*alloc_space) (struct inode *, qsize_t, int); //为某个文件分配一个数据块 int (*alloc_inode) (const struct inode *, unsigned long); int (*free_space) (struct inode *, qsize_t); //为某个文件释放一个数据块 int (*free_inode) (const struct inode *, unsigned long); //释放一个节点 int (*transfer) (struct inode *, struct iattr *);//转移节点的信息 /*下面是dquot操作函数*/ int (*write_dquot) (struct dquot *); /* 通常的dquot 写操作*/ int (*acquire_dquot) (struct dquot *); /* 从磁盘上创建dquot */ int (*release_dquot) (struct dquot *); /* 从磁盘上删除dquot */ int (*mark_dirty) (struct dquot *); /* 将dquot标识为脏 */ int (*write_info) (struct super_block *, int); /* Write of quota "superblock" */ }; 块设备/* * On most architectures that alignment is already the case; but * must be enforced here for CRIS, to let the least signficant bit * of struct page's "mapping" pointer be used for PAGE_MAPPING_ANON. */ struct block_device { dev_t bd_dev; /* not a kdev_t - it's a search key */ struct inode * bd_inode; /* will die */ int bd_openers; struct mutex bd_mutex; /* open/close mutex */ struct semaphore bd_mount_sem; struct list_head bd_inodes; void * bd_holder; int bd_holders; #ifdef CONFIG_SYSFS struct list_head bd_holder_list; #endif struct block_device * bd_contains; unsigned bd_block_size; struct hd_struct * bd_part; /* number of times partitions within this device have been opened. */ unsigned bd_part_count; int bd_invalidated; struct gendisk * bd_disk; struct list_head bd_list; struct backing_dev_info *bd_inode_backing_dev_info; /* * Private data. You must have bd_claim'ed the block_device * to use this. NOTE: bd_claim allows an owner to claim * the same device multiple times, the owner must take special * care to not mess up bd_private for that case. */ unsigned long bd_private; }; /* * This is the "filldir" function type, used by readdir() to let * the kernel specify what kind of dirent layout it wants to have. * This allows the kernel to read directories into kernel space or * to have different dirent layouts depending on the binary type. */ typedef int (*filldir_t)(void *, const char *, int, loff_t, u64, unsigned); struct block_device_operations { int (*open) (struct inode *, struct file *); int (*release) (struct inode *, struct file *); int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned, unsigned long); long (*compat_ioctl) (struct file *, unsigned, unsigned long); int (*direct_access) (struct block_device *, sector_t, void **, unsigned long *); int (*media_changed) (struct gendisk *); int (*revalidate_disk) (struct gendisk *); int (*getgeo)(struct block_device *, struct hd_geometry *); struct module *owner; }; 地址空间struct backing_dev_info; struct address_space { struct inode *host; /* owner: inode, block_device */ struct radix_tree_root page_tree; /* radix tree of all pages */ rwlock_t tree_lock; /* and rwlock protecting it */ unsigned int i_mmap_writable;/* count VM_SHARED mappings */ struct prio_tree_root i_mmap; /* tree of private and shared mappings */ struct list_head i_mmap_nonlinear;/*list VM_NONLINEAR mappings */ spinlock_t i_mmap_lock; /* protect tree, count, list */ unsigned int truncate_count; /* Cover race condition with truncate */ unsigned long nrpages; /* number of total pages */ pgoff_t writeback_index;/* writeback starts here */ const struct address_space_operations *a_ops; /* methods */ unsigned long flags; /* error bits/gfp mask */ struct backing_dev_info *backing_dev_info; /* device readahead, etc */ spinlock_t private_lock; /* for use by the address_space */ struct list_head private_list; /* ditto */ struct address_space *assoc_mapping; /* ditto */ } __attribute__((aligned(sizeof(long)))); 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); /* * ext3 requires that a successful prepare_write() call be followed * by a commit_write() call - they must be balanced */ int (*prepare_write)(struct file *, struct page *, unsigned, unsigned); int (*commit_write)(struct file *, struct page *, unsigned, unsigned); 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 *); }; 文件系统管理文件系统对象之间的关系文件系统中各个对象是相互联系的,文件系统通过链表将文件系统的对象以不同的方式链接起来,通过全局变量的链表头可以找到各个对象。文件系统实现的功能主要包括用户对文件系统中的对象进行操作和进程对文件系统对象的操作。与功能相对应,对象之间的关系主要包括文件系统内对象之间的关系以及进程与文件系统对象的关系。 超级块、安装点和具体的文件系统的关系如图5所示。系统支持的文件系统类型挂接在文件系统类型链表file_systems中,已挂接的文件系统放在挂接点对象链表vfsmntlist中,已挂接的不同类型的文件系统的超级块放在超级块链表super_blocks中。 文件系统类型结构file_system_type的链表头为file_systems,超级块结构super_block的链表头为super_blocks,挂接点结构vfsmount的链表头为vfsmntlist。挂接头结构vfsmount通过mnt_sb指向超级块,超级块结构super_block通过成员s_type指向文件系统类型,同一类型的超级块实例通过其成员s_type链接在一起。 图5 超级块、安装点和具体的文件系统的关系进程可以操作文件,读写文件需要通过节点访问磁盘文件,进程与超级块、文件、索引结点、目录项的关系如图6所示。进程结构task_struct的成员files为打开文件描述结构files_struct,打开文件描述结构files_struct包括了所有打开文件的描述符数组,数组中每个描述符fd与文件对象file相对应,文件对象描述了具体打开文件的各种信息,它包括了目录条目对象指针f_dentry,目录条目结构dentry又包含了节点对象成员d_inode,通过节点结构inode可以查找磁盘上的文件了。 图6 进程与超级块、文件、索引结点、目录项的关系文件系统类型注册文件系统类型结构Linux内核支持多种文件系统,各个文件系统可以内核模块或者作为内核一部分进行编译,Linux内核使用文件系统类型结构file_system_type对各种文件系统进行跟踪。 文件系统类型结构file_system_type列出如下(在include/linux/fs.h中):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; } 文件系统类型链表文件系统注册后,不同类型的文件系统类型存放在全局变量的链表file_systems中,链表file_systems如图2所示。同-类型的多个文件系统将超级块链表到结构file_system_type的成员fs_supers上。通过全局变量file_systems,可以找到文件系统类型结构,从该结构中可以找到方法函数(*get_sb)()获取文件系统的超级块,也就可以访问该文件系统了。 图2 不同类型的文件系统类型链表示意图 链表file_systems和文件系统锁file_systems_lock定义如下(在fs/filesystem.c中):static struct file_system_type *file_systems; static DEFINE_RWLOCK(file_systems_lock); 文件系统类型API函数Linux内核提供了文件系统类型API函数对文件系统类型链表进行管理,这些函数的功能说明如表3. 表3 文件系统类型API函数说明
文件系统的挂接和卸载
挂接点结构vfsmount每个已挂装的文件系统用挂接点对象结构vfsmount描述,所有的结构vfsmount实例形成了一个链表,用全局变量vfsmntlist指向链表头,该链表可称为已挂接文件系统链表。 Linux支持一个文件系统挂接多次,但它们仅有一个超级块,每个挂接点用挂接点结构vfsmount描述。该结构列出如下:struct vfsmount { struct list_head mnt_hash; /*hash链表指针*/ struct vfsmount *mnt_parent; /* 父挂接点对象指针,本文件系统安装在其上 */ struct dentry *mnt_mountpoint; /*挂接点的目录条目指针 */ struct dentry *mnt_root; /* 已挂接的根目录条目 */ struct super_block *mnt_sb; /* 本文件系统的超级块 */ struct list_head mnt_mounts; /* 孩子链表头,*/ struct list_head mnt_child; /* 孩子条目*/ int mnt_flags; /* 在64位构架上的4字节空洞 */ char *mnt_devname; /* 设备名,如:/dev/dsk/hda1 */ struct list_head mnt_list; struct list_head mnt_expire; /*链接在文件系统指定的“过期”链表中*/ struct list_head mnt_share; /* 共享”挂接的循环链表*/ struct list_head mnt_slave_list;/* slave(从)挂接的链表头*/ struct list_head mnt_slave; /* slave链表条目 */ struct vfsmount *mnt_master; /* slave在master->mnt_slave_list上 */ struct mnt_namespace *mnt_ns; /* 含有命名空间的指针*/ int mnt_id; /* 挂接的ID*/ int mnt_group_id; /* 挂接组的ID*/ /*mnt_count和mnt_expiry_mark让那些经常修改的域放在独立的cache行,从而使读mnt_flags操作不会在多CPU上来回传递*/ atomic_t mnt_count; /*引用计数器*/ int mnt_expiry_mark; /* 如果标识为过期,其为true */ int mnt_pinned; /*“钉住”进程计数*/ int mnt_ghosts; /* “镜像”引用计数器*/ /*此值不稳定,除非持所有的mnt_writers[]自旋锁,并且在此挂接点的所有mnt_writer[]与->count一样为0*/ atomic_t __mnt_writers; }; 从上述挂接点结构可以看出,挂接点对象分类链接成不同的链表,如:属于同一命名空间的挂接点对象链接成链表、"共享"挂接的对象链接成链表等等。 命名空间结构mnt_namespace每个进程可以拥有属于自己的已挂接文件系统树,称为命名空间。通常大多数进程共享一个文件系统命名空间,即系统的根文件系统。命名空间被子进程继承,但如果系统调用clone()用标识CLONE_NEWNS创建一个新进程时,那么新进程将获得一个新命名空间。 进程挂接或卸载文件系统时,仅修改它的命名空间,在同一命名空间的进程才可见这些修改,修改对其他命名空间没有影响。文件系统命名空间用结构mnt_namespace描述,该结构列出如下(在include/linux/mnt_namespace.h中):struct mnt_namespace { atomic_t count; /*引用计数器,表示共享命名空间的进程数*/ struct vfsmount * root; /*挂接点对象指针*/ struct list_head list; /*属于本命名空间的已安装文件系统的挂接点对象链表头*/ wait_queue_head_t poll; int event; /*事件*/ }; 挂接/卸载API函数挂接/卸载API函数的功能说明如表2所示。 表2 挂接/卸载API函数功能说明
进程操作文件系统
与进程联系的文件系统相关结构进程是通过文件描述符(file descriptors, 简称fd)而不是文件名来访问文件的,文件描述符实际上是一个整数,规定每个进程最多能同时使用NR_OPEN个文件描述符,这个值在fs.h中定义为1024。每个进程用一个file_struct的结构来记录文件描述符的使用情况,它是进程的私有数据。 每个文件都有一个32位的整数来表示下一个读写的字节位置,即文件位置。每次打开一个文件,缺省时从文件的开始处操作。可以通过系统调用lseek对这个文件位置进行修改。 在进程的task_struct中有文件系统相关的数据成员,列出如下:struct task_struct { …… struct fs_struct *fs; //文件系统信息 struct files_struct *files;//打开文件信息 …… } 进程结构与文件系统相关结构的关系示意图如图7-2,下面对结构fs_struct及files_struct进行分析。 图 7 2进程结构与文件系统相关结构的关系示意图 结构fs_struct给出了文件系统信息,列出如下(在include/linux/fs_struct.h中): struct fs_struct { atomic_t count; rwlock_t lock; int umask; //系统默认的创建新文件时的掩码,可以通过系统调用来改变 //本进程所在的根目录,当前目录及替换根目录对应的路径结构path struct path root, pwd, altroot; }; 路径结构path列出如下(在include/linux/path.h中): struct path { struct vfsmount *mnt; struct dentry *dentry; }; struct files_struct { /*读操作最多的部分*/ atomic_t count; //已使用的文件描述符计数 struct fdtable *fdt; //文件描述符表 struct fdtable fdtab; /*在SMP中写部分在一个独立的cache行*/ spinlock_t file_lock ____cacheline_aligned_in_smp; int next_fd; struct embedded_fd_set close_on_exec_init; struct embedded_fd_set open_fds_init; /* #define NR_OPEN_DEFAULT BITS_PER_LONG ,值为64*/ struct file * fd_array[NR_OPEN_DEFAULT]; //文件描述符数组 }; struct fdtable { unsigned int max_fds; //最大文件描述符数 struct file ** fd; /* 当前的fd数组 */ fd_set *close_on_exec; fd_set *open_fds; //打开的文件描述符集 struct rcu_head rcu; struct fdtable *next; //用于链接成链表 }; 结构fd_set是文件描述符集,它将同一情况下的文件描述符放在一个数组中,它的定义如下: typedef __kernel_fd_set fd_set; 结构__kernel_fd_set 的定义如下(在include/linux/posix_types.h中): typedef struct { unsigned long fds_bits [__FDSET_LONGS]; //数组大小32 } __kernel_fd_set; 下面是__FDSET_LONGS值的计数: #define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS) 结构files_struct中成员fd_array是一个64项的结构file数组,数组的下标是文件描述符,而数组元素是打开文件描述结构file实例。在一个进程启动后,文件描述符0、1、2已经被分配并可供使用。0用作标准输入设备,1用作标准输出设备,2用作标准错误输出设备。所以你所能使用的第一个文件描述符是3。每当打开一个文件时,就会用fd_array数组的第1个空闲项来指向对应的结构file,此后对文件的访问通过结构file中定义的操作函数和VFS索引节点信息来完成。由于进程能同时使用的文件描述符数组是有限的,所以要适时地释放不再使用的文件描述符。 文件描述符操作API函数文件描述符操作API函数功能说明如表3所示。 表3 文件描述符操作API函数功能说明
dquot_operations结构dquot_operations是硬盘限量操作函数集,分析如下(在include/linux/quota.h中):struct dquot_operations { //用默认值初始化用户的限额。 void (*initialize) (struct inode *, short); void (*drop) (struct inode *); //停止限额的作用 //为某个文件分配一个数据块 int (*alloc_block) (const struct inode *, unsigned long, char); int (*alloc_inode) (const struct inode *, unsigned long); //为某个文件释放一个数据块 void (*free_block) (const struct inode *, unsigned long); //释放一个节点 void (*free_inode) (const struct inode *, unsigned long); //转移节点的信息 int (*transfer) (struct dentry *, struct iattr *); }; 文件锁操作文件锁类型和操作状态定义如下(在include/linux/fs.h中):#define FL_POSIX 1 #define FL_FLOCK 2 #define FL_ACCESS 8 /* 不进行加锁尝试,仅查看*/ #define FL_EXISTS 16 /* 当解锁时,测试存在性 */ #define FL_LEASE 32 /*文件上持有租借锁 */ #define FL_CLOSE 64 /* 关闭时解锁 */ #define FL_SLEEP 128 /* 一个正阻塞的锁 */ include/gsm-generic/fcntl.h中): FL_FLOCK锁实现BSD模式通过系统调用sys_flock实现文件加锁机制,Linux内核用标识FL_FLOCK表示BSD模式加锁机制。函数sys_flock根据命令cmd应用FL_FLOCK模式锁对一个打开的文件描述符进行加锁或解锁,函数sys_flock调用层次图如图1所示。
图1 函数sys_flock调用层次图 FL_FLOCK模式锁可实现劝告锁和强制锁,与一个文件对象相关联,系统调用sys_flock加锁或解锁,如果发生锁冲突,则将锁对象加到阻塞链表中,否则将锁对象加到锁链表中。当文件对象引用计数为0时,函数fput将释放文件对象并将清除文件对象上的所有锁对象。 函数sys_flock参数cmd为加锁命令选项,其值列出如下: LOCK_SH –共享锁 LOCK_EX – 排他锁 LOCK_UN – 开锁,删除一个正存在的锁 LOCK_MAND – 强制flock锁,用于模拟Windows操作系统共享模式。LOCK_MAND可与%LOCK_READ或%LOCK_WRITE联合在一起,允许其他进程分别读或写访问。 函数sys_flock列出如下(在fs/lock.c中):asmlinkage long sys_flock(unsigned int fd, unsigned int cmd) { struct file *filp; struct file_lock *lock; int can_sleep, unlock; int error; error = -EBADF; filp = fget(fd); if (!filp) goto out; can_sleep = !(cmd & LOCK_NB); cmd &= ~LOCK_NB; unlock = (cmd == LOCK_UN); if (!unlock && !(cmd & LOCK_MAND) && !(filp->f_mode & 3)) goto out_putf; error = flock_make_lock(filp, &lock, cmd); /*命令翻译,分配并创建锁对象*/ if (error) goto out_putf; if (can_sleep) lock->fl_flags |= FL_SLEEP; error = security_file_lock(filp, cmd); /*通过SELinux检查操作许可*、 if (error) /*如果不允许操作,直接释放对象空间返回*/ goto out_free; if (filp->f_op && filp->f_op->flock) /*调用具体文件系统的函数flock*/ error = filp->f_op->flock(filp, (can_sleep) ? F_SETLKW : F_SETLK, lock); else /*通用文件加锁操作*/ error = flock_lock_file_wait(filp, lock); /*对filp应用FL_FLOCK模式锁*/ out_free: locks_free_lock(lock); /*释放lock对象空间*/ out_putf: fput(filp); out: return error; } 函数flock_lock_file_wait对一个文件加FL_FLOCK锁,如果文件锁可延迟,则等待加锁完成。其列出如下: int flock_lock_file_wait(struct file *filp, struct file_lock *fl) 函数flock_lock_file 尝试在filp上创建FL_FLOCK锁,新的FL_FLOCK锁总是插入在租借锁之后FS_POSIX锁之前,如果请求的锁对象已存在或者正在开锁,则什么也不做返回,如果用参数FL_EXISTS调用此函数,调用者可以通过测试返回值-ENOENT判定是否成功释放了锁。发生冲突的锁对象存入链表blocked_list 中,非冲突的锁对象存入链表file_lock_list。 函数flock_lock_file列出如下(在fs/locks.c中): static LIST_HEAD(file_lock_list);/*非冲突的锁对象存入此链表中*/ static LIST_HEAD(blocked_list); /*发生冲突的读/写锁类型的锁对象存入此阻塞链表中 */ static int flock_lock_file(struct file *filp, struct file_lock *request) { struct file_lock *new_fl = NULL; struct file_lock **before; struct inode * inode = filp->f_path.dentry->d_inode; int error = 0; int found = 0; lock_kernel(); if (request->fl_flags & FL_ACCESS) goto find_conflict; if (request->fl_type != F_UNLCK) { error = -ENOMEM; new_fl = locks_alloc_lock(); if (new_fl == NULL) goto out; error = 0; } /*遍历文件对象的文件锁链表inode->i_flock*/ for_each_lock(inode, before) { struct file_lock *fl = *before; if (IS_POSIX(fl)) break; if (IS_LEASE(fl)) continue; if (filp != fl->fl_file) continue; /*到这里,说明已在该文件对象中找到了FL_FLOCK锁对象*/ /*如果请求锁的类型与已存在锁一样,则什么也不做,返回0*/ if (request->fl_type == fl->fl_type) goto out; found = 1; /*到这里,说明找到了FL_FLOCK锁,但类型与请求类型不一致,则删除锁对象*/ locks_delete_lock(before); break; } /*请求类型为F_UNLCK,表示正执行开锁,则什么也不做*/ if (request->fl_type == F_UNLCK) { /*如果请求标识为FL_EXISTS,表示开锁时测试锁是否存在,但却没有发现锁,则返回错误*/ if ((request->fl_flags & FL_EXISTS) && !found) error = -ENOENT; goto out; } /*如果锁已存在,说明可能有较高优先级进程被阻塞在旧文件锁上,给它机会锁住文件*/ if (found) cond_resched_bkl(); find_conflict: /*遍历文件对象的文件锁链表inode->i_flock,检查锁的冲突,即锁不可重叠,如:读/写锁*/ for_each_lock(inode, before) { struct file_lock *fl = *before; if (IS_POSIX(fl)) break; if (IS_LEASE(fl)) continue; /*如果f1或request的锁类型为F_WRLCK(读/写锁),则两者发生冲突*/ if (!flock_locks_conflict(request, fl)) continue; /*运行到这里,说明存在锁冲突*/ error = -EAGAIN; if (!(request->fl_flags & FL_SLEEP)) goto out; error = FILE_LOCK_DEFERRED; /*请求锁request含有标识FL_SLEEP,将请求锁插入到阻塞链表blocked_list中*/ locks_insert_block(fl, request); goto out; } /*存在访问标识FL_ACCESS,表示仅查看而不加锁,则什么也不做,返回*/ if (request->fl_flags & FL_ACCESS) goto out; locks_copy_lock(new_fl, request); /*拷贝锁对象*/ /*将拷贝得到的锁对象new_f1插入到锁对象链表file_lock_list中*/ locks_insert_lock(before, new_fl); new_fl = NULL; error = 0; out: unlock_kernel(); if (new_fl) locks_free_lock(new_fl); return error; } FL_POSIX锁实现POSIX标准规定了基于系统调用fcntl的文件加锁机制,可对文件部分或全部进行加锁。Linux内核用标识FL_POSIX表示POSIX标准文件加锁机制,两者加锁机制的使用同样的实现函数,这里不再分析。 租借锁也基于系统调用fcntl,租借锁管理操作函数集lease_manager_ops列出如下:static struct lock_manager_operations lease_manager_ops = { .fl_break = lease_break_callback, .fl_release_private = lease_release_private_callback, /*释放锁对象*/ .fl_mylease = lease_mylease_callback, /*使用自己的锁对象代替原锁对象*/ .fl_change = lease_modify, }; 在系统调用sys_fcntl的参数cmd中,命令标识F_GETLK、F_SETLK和F_SETLKW用于操作锁,命令F_GETLEASE和 F_SETLEASE用于得到和设置租借锁。系统调用sys_fcntl与锁操作相关的代码部分列出如下(在fs/fcntl.c中): asmlinkage long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg) 文件系统的建立过程在系统启动后进入函数start_kernelinit时,有文件系统的初始化函数,即start_kernel()调用了在fs/dcach.c中的vfs_caches_init()。函数vfs_caches_init()列出如下:void __init vfs_caches_init(unsigned long mempages) { unsigned long reserve; //基本可用内存的hash大小加上保留内存等于当前内核大小的150% reserve = min((mempages - nr_free_pages()) * 3/2, mempages - 1); mempages -= reserve; names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL); filp_cachep = kmem_cache_create("filp", sizeof(struct file), 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, filp_ctor, filp_dtor); dcache_init(mempages);//创建dentry结构对象的cache inode_init(mempages); //创建inode结构对象cache,初始化等待队列 files_init(mempages);//初始化struct files_stat_struct files_stat //创建vfsmount结构对象cache,并调用init_rootfs()和 //init_mount_tree()函数建立文件系统。 mnt_init(mempages); bdev_cache_init();//注册块设备文件系统 chrdev_init();//初始字符设备子系统 } 函数mnt_init初始化挂接点的全局链表,初始化rootfs及sysfs文件系统,初始化挂接点,函数分析如下(在fs/namespace.c中): void __init mnt_init(unsigned long mempages) 函数init_mount_tree挂接rootfs文件系统,给每个任务设置namespace,设置当前进程的根目录和当前目录,函数函数init_mount_tree分析如下(在fs/namespace.c中): static void __init init_mount_tree(void) 文件系统的注册、安装与卸载文件系统的注册如果文件系统是作为内核可装载的模块,则在实际安装时进行注册,并在模块卸载时注销。每个文件系统都有一个初始化例程,它的作用就是VFS中进行注册,即填写一个叫做file_system_type的数据结构。所有已注岫的文件系统的file_system_type结构形成一个链表,为区别后面将要说到的已安装的文件系统形成的加一个链表,我们把这个链表称为注册链表。 图 :已注册的文件系统形成的链表struct file_system_type { const char *name;文件系统的名称,例如:exts、vfat、iso9660等 int fs_flags; struct super_block *(*read_super) (struct super_block *, void *, int);当属于该文件系统类型的逻辑文件系统被安装时,VFS调用该例程读取超级块 struct module *owner; struct vfsmount *kern_mnt; /* For kernel mount, if it's FS_SINGLE fs */ struct file_system_type * next; }; 文件系统的安装每个已挂装的文件系统由vfsmount结构描述,所有的vfsmount结构形成了一个链表,用vfsmntlist来指向链表头,这个链表可称为已安装文件系统链表。 图 :已安装文件系统的结构示意图struct vfsmount { struct dentry *mnt_mountpoint; //挂接点的dentry结构 struct dentry *mnt_root; //挂接树的根目录 struct vfsmount *mnt_parent; /* fs we are mounted on */ struct list_head mnt_instances; /* other vfsmounts of the same fs */ struct list_head mnt_clash; /* those who are mounted on (other */ /* instances) of the same dentry */ struct super_block *mnt_sb; /* pointer to superblock */ struct list_head mnt_mounts; /* list of children, anchored here */ struct list_head mnt_child; /* and going through their mnt_child */ atomic_t mnt_count; int mnt_flags; char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */ struct list_head mnt_list; uid_t mnt_owner; };
Lookup_vfsmnt():在链表中寻找指定设备号的vfsmnt结构,成功则返回指向该结构的指针,否则返回0。 Add_vfsmnt():在链表加入一个vfsmnt结构,返回指向该结构的指针。 Remove_vfsmnt():从链表中移走指定设备号的vfsmnt结构,并释放其所占的内核内存空间。该函数无返回值。 |