Unix使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装点(mount)。
Unix系统将文件的相关信息和文件本身这两个概念加以区分。文件的相关信息有时被称为元数据,被存储在一个单独的数据结构中,索引节点(inode)该结构成为,index node 的缩写。
文件系统的控制信息存储在超级块中,超级块是一种包含文件系统信息的数据结构。
VFS有四个主要的数据类型:
每个主要对象都包含一个操作对象,这些操作对象都包含了内核针对主要对象可以使用的方法:
VFS所包含的对象远远多于上面这几种对象。例如,file_system_type结构体表示每个注册的文件系统,描述了文件系统及其性能;vfsmount结构体表示每一个安装点,描述了位置和安装标志等。
所有文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,对应存放在磁盘特定扇区中的文件系统超级块或文件系统控制块。除了基于磁盘的文件系统外,还有基于内存的文件系统,比如sysfs,它们会在现场创建超级块并将其存放在内存中。
超级块对象由super_block结构体表示,定义在文件
struct super_block {
struct list_head s_list; /* Keep this first,指向所有超级块的链表*/
dev_t s_dev; /* search index; _not_ kdev_t ,设备标识符*/
unsigned long s_blocksize; /* 以字节为单位的块大小 */
unsigned char s_blocksize_bits; /* 以位为单位的块大小 */
unsigned char s_dirt; /* 修改(脏)标志 */
unsigned long long s_maxbytes; /* Max file size */
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; /* 安全模块 */
#endif
struct xattr_handler **s_xattr; /* 扩展的属性操作 */
struct list_head s_inodes; /* all inodes,inode链表 */
struct list_head s_dirty; /* dirty inodes */
struct list_head s_io; /* parked for writeback,回写链表 */
struct list_head s_more_io; /* parked for more writeback,更多回写链表 */
struct hlist_head s_anon; /* anonymous dentries for (nfs) exporting,匿名目录项 */
struct list_head s_files; /* 被分配文件链表 */
struct block_device *s_bdev; /* 相关块设备 */
struct mtd_info *s_mtd; /* 存储磁盘信息 */
struct list_head s_instances; /* 该类型文件系统 */
struct quota_info s_dquot; /* Diskquota specific options,磁盘配额选项 */
int s_frozen; /* 冻结标志位 */
wait_queue_head_t s_wait_unfrozen; /* 冻结的等待队列 */
char s_id[32]; /* Informational name,文本名字 */
void *s_fs_info; /* Filesystem private 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; /* Kludge,重命名信号量 */
/* Granularity of c/m/atime in ns.
Cannot be worse than a second */
u32 s_time_gran; /* 时间戳粒度 */
/*
* Filesystem subtype. If non-empty the filesystem type field
* in /proc/mounts will be "type.subtype"
*/
char *s_subtype; /* 子类型名称 */
};
创建、管理和撤销超级块对象的代码位于fs/super.c中。超级块对象通过alloc_super() 函数创建并初始化。在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并将其信息填充到内存中的超级块对象中。
超级块对象中s_op 域用于指向超级块的操作函数。操作函数游super_operations结构体表示,定义在文件
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb); //在给定超级块中创建和初始化一个新的索引节点对象
void (*destroy_inode)(struct inode *); //释放给定的索引节点
void (*dirty_inode) (struct inode *, int flags); //VFS在索引节点脏时调用此函数;日志文件系统(ext3和ext4)在日志更新时执行该函数
int (*write_inode) (struct inode *, struct writeback_control *wbc); //将给定索引节点写入磁盘
int (*drop_inode) (struct inode *); //最后一个指向索引节点的引用被释放后,VFS会调用该函数
void (*evict_inode) (struct inode *);
void (*put_super) (struct super_block *); //卸载文件系统时由VFS调用,用来释放超级块,调用者需要一直持有s_lock锁
int (*sync_fs)(struct super_block *sb, int wait); //使文件系统的数据元和磁盘上的文件系统同步。wait参数指定操作是否同步
int (*freeze_super) (struct super_block *);
int (*freeze_fs) (struct super_block *);
int (*thaw_super) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *); //VFS通过此函数获取文件系统状态。指定文件系统相关的统计信息将放置在statfs中
int (*remount_fs) (struct super_block *, int *, char *);//当指定新的安装选项重新安装文件系统时,VFS会调用此函数。调用者必须一直持有s_lock锁
void (*umount_begin) (struct super_block *); //VFS调用此函数中断安装操作。网络文件系统中使用(NFS)
int (*show_options)(struct seq_file *, struct dentry *);
int (*show_devname)(struct seq_file *, struct dentry *);
int (*show_path)(struct seq_file *, struct dentry *);
int (*show_stats)(struct seq_file *, struct dentry *);
#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);
struct dquot **(*get_dquots)(struct inode *);
#endif
int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
long (*nr_cached_objects)(struct super_block *,
struct shrink_control *);
long (*free_cached_objects)(struct super_block *,
struct shrink_control *);
};
除了dirty_inode() ,其他函数在必要时都可以阻塞。
不需要的函数可以设置为NULL,当VFS发现操作函数指针是NULL时,会调用通用函数执行操作,或者什么都不做。
索引节点对象包含了内核在操作文件或目录时需要的全部信息。对于Unix风格的文件系统,这些信息可以从磁盘索引节点直接读入。如果一个文件系统没有索引节点,那么通常将文件的描述信息作为文件的一部分来存放。
索引节点对象由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; /* 引用计数 */
unsigned int i_nlink; /* 硬链接数 */
uid_t i_uid; /* 使用者id */
gid_t i_gid; /* 使用组id */
dev_t i_rdev; /* 实际设备标识符 */
unsigned long i_version; /* 版本号 */
loff_t i_size; /* 以字节为单位的文件大小 */
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount; /* 对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, maybe i_size */
struct mutex i_mutex;
struct rw_semaphore i_alloc_sem;
const 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; /* 相关地址映射 */
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; /* Directory notify events */
struct dnotify_struct *i_dnotify; /* for directory notifications */
#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; /* jiffies of first dirtying,第一次脏数据的时间 */
unsigned int i_flags; /* 文件系统标志 */
atomic_t i_writecount; /* 写者计数 */
#ifdef CONFIG_SECURITY
void *i_security; /* 安全模块 */
#endif
void *i_private; /* fs or device private pointer,fs私有指针 */
};
索引节点对象中inode_operations域描述了VFS操作索引节点对象的方法。该结构体定义在
struct inode_operations {
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);//在特定目录中搜索节点
const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);
int (*permission) (struct inode *, int); //用来检查给定的inode所代表的文件是否允许特定的访问模式
struct posix_acl * (*get_acl)(struct inode *, int);
int (*readlink) (struct dentry *, char __user *,int); //由系统调用readlink()调用,拷贝数据到特定缓冲区中
int (*create) (struct inode *,struct dentry *, umode_t, bool); //VFS通过系统调用create()和open()来调用该函数,为denty对象创建一个新的索引节点,创建时使用 mode指定初始模式
int (*link) (struct dentry *,struct inode *,struct dentry *); //由系统调用link()调用,用来创建硬链接
int (*unlink) (struct inode *,struct dentry *); //由系统调用unlink()调用,从目录中删除指定的索引节点对象
int (*symlink) (struct inode *,struct dentry *,const char *); //由系统调用symlik()调用,创建符号链接
int (*mkdir) (struct inode *,struct dentry *,umode_t); //由系统调用mkdir()调用,创建一个新的目录
int (*rmdir) (struct inode *,struct dentry *); //由系统调用rmdir()调用,删除一个目录
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t); //由系统调用mknod()调用,创建特殊文件(设备、管道、套接字)
int (*rename) (struct inode *, struct dentry *, //VFS调用该函数来移动文件
struct inode *, struct dentry *, unsigned int);
int (*setattr) (struct dentry *, struct iattr *); //由系统调用notify_change()调用,在修改索引节点后,同时发生了“改变事件”
int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);//VFS会调用该函数,用于通知索引节点需要从磁盘中更新
ssize_t (*listxattr) (struct dentry *, char *, size_t); //将特定文件的所有属性列表拷贝到一个缓冲列表中
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
u64 len);
int (*update_time)(struct inode *, struct timespec *, int);
int (*atomic_open)(struct inode *, struct dentry *,
struct file *, unsigned open_flag,
umode_t create_mode, int *opened);
int (*tmpfile) (struct inode *, struct dentry *, umode_t);
int (*set_acl)(struct inode *, struct posix_acl *, int);
} ____cacheline_aligned;
VFS引入目录项概念,目录项对象由结构dentry表示,每个dentry代表路径中的一个部分,包括目录、文件和安装点。VFS在执行目录操作时,如果需要会现场创建目录项对象。
dentry结构定义在
struct dentry {
/* RCU lookup touched fields */
unsigned int d_flags; /* protected by d_lock,目录项标识 */
seqcount_t d_seq; /* per dentry seqlock */
struct hlist_bl_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* parent directory */
struct qstr d_name; /* 目录项名称 */
struct inode *d_inode; /* Where the name belongs to - NULL is negative,
相关联的索引节点 */
unsigned char d_iname[DNAME_INLINE_LEN];/* small names,短文件名 */
/* Ref lookup also touches following */
struct lockref d_lockref; /* per-dentry lock and refcount */
const struct dentry_operations *d_op; /* 目录项操作指针 */
struct super_block *d_sb; /* The root of the dentry tree,文件的超级块 */
unsigned long d_time; /* used by d_revalidate,重制时间 */
void *d_fsdata; /* fs-specific data,文件系统特有数据 */
union {
struct list_head d_lru; /* LRU list */
wait_queue_head_t *d_wait; /* in-lookup ones only */
};
struct list_head d_child; /* child of parent list */
struct list_head d_subdirs; /* our children,子目录索引 */
/*
* d_alias and d_rcu can share memory
*/
union {
struct hlist_node d_alias; /* inode alias list,索引节点别名链表 */
struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
struct rcu_head d_rcu; /* RCU锁 */
} d_u;
};
与索引节点和超级块不同,目录项对象没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它。目录项对象没有脏标识,因为它不会保存在磁盘上。
目录项对象有三种有效状态:被使用、未被使用和负状态。
如果VFS层遍历路径名中所有的元素并将它们猪哥地解析成目录项对象,还要到达最深层目录,这将是一件非常费力耗时的工作。所以内核将目录项对象缓存在目录项缓存(简称dcache,这只是个简称,4.9.1内核中使用dentry_cache高速缓存来实现,缓存中的对象即为dentry 结构)中:
目录项缓存包括三个主要部分:
为了避免每次访问某个路径名都要沿着目录依次解析全部路径,VFS会现在目录项缓存中搜索路径名,如果找到了就无须进行路径解析。相反,如果该目录项在目录项缓存中不存在,VFS就必须遍历文件系统为每个路径分量解析路径,解析完毕后,将目录项对象加入dcache中,以便以后可以快速查找到。
只要目录项被缓存,相应的索引节点也会被缓存。如果目录项对象在目录项缓存中,那么和目录项对象相关的索引节点对象不会被释放,因为目录项会让相关索引节点对象的引用计数为正,确保索引节点能留在内存中。因此,只要路径名在缓存中被找到了,那么相应的索引节点肯定也在内存中缓存着。
dentry_operation结构指明了VFS操作目录项的所有方法。结构定义在
struct dentry_operations {
/* 判断目录项对象是否有效。VFS准备从dcache中使用一个目录项时会调用该函数,
通常大部分文件系统认为目录项对象总是有效的,所以设置为NULL */
int (*d_revalidate)(struct dentry *, unsigned int);
int (*d_weak_revalidate)(struct dentry *, unsigned int);
/* 为目录项生成散列值,当目录项需要加入散列表时,VFS调用此函数 */
int (*d_hash)(const struct dentry *, struct qstr *);
/* 比较两个文件名是否相同。大部分文件系统区分大小写,因此设置为NULL使用默认操作。
如果使用此函数要加dcache_lock锁 */
int (*d_compare)(const struct dentry *,
unsigned int, const char *, const struct qstr *);
/* 当目录项对象的d_count计数值等于0时,VFS调用该函数。使用时需要加dcache_lock */
int (*d_delete)(const struct dentry *);
int (*d_init)(struct dentry *);
/* 当目录项对象要被释放时,VFS调用此函数。默认情况下,什么都不做 */
void (*d_release)(struct dentry *);
void (*d_prune)(struct dentry *);
/* 当一个目录项对象丢失其相关索引节点时(磁盘索引节点被删除了),VFS调用此函数。默认情况下VFS会调用
iput()函数。如果文件系统重载了此函数,那么除了执行此文件系统特殊的工作之外,还必须调用iput()函数 */
void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(struct dentry *, bool);
struct dentry *(*d_real)(struct dentry *, const struct inode *,
unsigned int);
} ____cacheline_aligned;
文件对象(不是物理文件)是以打开的文件在内存中的表示。文件对象由相应的open()系统调用创建,由close()系统调用撤销。因为多个进程可以同时打开和操作同一个文件,所以同一个文件可能存在多个对应的文件对象。文件对象仅仅代表进程观点上代表已打开文件,它反过来指向目录项对象(而目录项对象又指向索引节点)。只有目录项对象才表示已打开的实际文件。虽然一个文件对应的文件对象不是唯一的,但对应的索引节点和目录项对象是唯一的。
文件对象由file结构体表示,定义在
struct file {
union {
struct llist_node fu_llist; /* 文件对象链表 */
struct rcu_head fu_rcuhead; /* 释放之后的RCU链表 */
} f_u;
struct path f_path; /* 包含目录项 */
struct inode *f_inode; /* cached value */
const struct file_operations *f_op; /* 文件操作集 */
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock; /* 单个文件结构锁 */
atomic_long_t f_count; /* 文件对象的使用计数 */
unsigned int f_flags; /* 当打开文件时所指定的标志 */
fmode_t f_mode; /* 文件的访问模式 */
struct mutex f_pos_lock;
loff_t f_pos; /* 文件当前的位移量 */
struct fown_struct f_owner; /* 拥有者,可以通过信号进行异步IO数据传输 */
const struct cred *f_cred; /* 文件的信任状态 */
struct file_ra_state f_ra; /* 预读状态 */
u64 f_version; /* 版本号 */
#ifdef CONFIG_SECURITY
void *f_security; /* 安全模块 */
#endif
/* 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; /* 事件池链表 */
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping; /* 页缓存映射 */
} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
类似于目录项对象,文件对象没有实际的磁盘数据。所以在结构上没有“是否为脏”、“是否需要写回”的标志。文件对象可以通过f_dentry指针指向相关的目录项对象,目录项对象会指向相关的索引节点,索引节点会记录文件是否是脏的。
文件对象的操作由file_operations 结构体表示,定义在文件
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 (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
/* 当用户空间调用ioctl()时,VFS会调用unlocked_ioctl。
我觉得之所以叫unlock_ioctl是因为以前存在BKL(大内核锁),而这个函数不需要调用者持有BKL */
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
/* 在64位系统上使用32位应用程序时,使用此ioctl函数 */
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
/* 将给定的文件映射到指定的地址空间上,由系统调用mmap()调用 */
int (*mmap) (struct file *, struct vm_area_struct *);
/* 创建一个新的文件对象,并将它和相应的索引节点对象关联起来,由系统调用open()调用 */
int (*open) (struct inode *, struct file *);
/* 当以打开文件的引用计数减少时,该函数被VFS调用。具体作用看具体文件系统 */
int (*flush) (struct file *, fl_owner_t id);
/* 当文件的最后一个引用被注销时(最后一个共享文件描述符的进程条用了close()或退出时),
该函数会被VFS调用,具体作用看具体文件系统 */
int (*release) (struct inode *, struct file *);
/* 将给定文件的所有被缓存数据写回磁盘,由系统调用fsync()调用 */
int (*fsync) (struct file *, loff_t, loff_t, 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);
/* 当给出SETFL命令时,用来检查传递给fcntl()系统调用的flags的有效性,目前只有NFS文件系统上实现了。
在NFS文件系统中,不允许把O_APPEND和O_DIRECT相结合 */
int (*check_flags)(int);
/* 用来实现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);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
u64);
};
除了上面的四种VFS主要对象结构外,还有file_system_type用来描述各种特定文件系统类型,比如ext3、ext4等等;vfsmount 用来描述一个安装文件系统的实例。
file_system_type结构体定义在
struct file_system_type {
const char *name; /* 文件系统名字 */
int fs_flags; /* 文件系统类型标志 */
#define FS_REQUIRES_DEV 1
#define FS_BINARY_MOUNTDATA 2
#define FS_HAS_SUBTYPE 4
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
#define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *); /* 磁盘中读取目录项对象 */
void (*kill_sb) (struct super_block *); /* 终止访问超级块 */
struct module *owner; /* 文件系统模块 */
struct file_system_type * next; /* 链表中下一个文件系统类型 */
struct hlist_head fs_supers; /* 超级块对象链表 */
/* 以下字段在VFS运行时使锁生效 */
struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
struct lock_class_key s_vfs_rename_key;
struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];
struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key i_mutex_dir_key;
};
不管有几个文件系统被安装到系统中,都只有一个file_system_type结构。
当文件系统被实际安装时,将有一个vfsmount结构体在安装点被创建。vfsmount 结构定义在
较新的内核的结构变为如下所示:
struct vfsmount {
struct dentry *mnt_root; /* root of the mounted tree */
struct super_block *mnt_sb; /* pointer to superblock */
int mnt_flags;
};
vfsmount结构保存了在安装时指定的标志信息,这些信息存储在mnt_flags中:
标志 | 描述 |
---|---|
MNT_NOSUID | 禁止该文件系统的可执行文件设置setuid和setgid标志 |
MNT_MODEV | 禁止访问该文件系统上的设备文件 |
MNT_NOEXEC | 禁止执行该文件系统上的可执行文件 |
安装那些不充分信任的移动设备时,这些标志就很有用了。
系统中有三个数据结构将VFS层和系统的进程紧密联系在一起:files_struct 、 fs_struct 和namespace 结构。
files_struct 结构定义在文件
#define BITS_PER_LONG 32
#define NR_OPEN_DEFAULT BITS_PER_LONG
/*
* Open file table structure
*/
struct files_struct {
/*
* read mostly part
*/
atomic_t count; /* 引用计数 */
bool resize_in_progress;
wait_queue_head_t resize_wait;
struct fdtable __rcu *fdt; /* 指向其他fd表的指针 */
struct fdtable fdtab; /* 基fd表 */
/*
* written part on a separate cache line in SMP
*/
spinlock_t file_lock ____cacheline_aligned_in_smp; /* 单个文件的锁 */
unsigned int next_fd; /* 缓存下一个可用的fd */
unsigned long close_on_exec_init[1]; /* exec()时关闭的文件描述符链表 */
unsigned long open_fds_init[1]; /* 打开的文件描述符链表 */
unsigned long full_fds_bits_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT]; /* 缺省的文件对象数组 */
};
fd_array数组指针指向已打开的文件对象。NR_OPEN_DEFAULT = BITS_PER_LONG = 32,所以在32位机上这个数组最大容纳32个文件对象,如果一个进程打开超过32个文件对象,内核将分配一个新数据,并将fdt指针指向它。所以,如果打开的文件对象不超过32个,那么对这些文件对象的访问会执行很快。
fs_struct 结构包含文件系统和进程相关的信息,由进程描述符的fs域指向,定义在
struct fs_struct {
int users; /* 用户数目 */
spinlock_t lock; /* fs_struct保护锁 */
seqcount_t seq;
int umask; /* 不知道干什么的掩码 */
int in_exec; /* 当前正在执行的文件 */
struct path root, pwd; /* 根目录路径和当前工作目录路径 */
};
fs_struct包含了当前仅存的当前工作目录(pwd)和根目录。
在旧内核中namespace结构体定义在
namespace使每一个进程在系统中都看到唯一的安装文件系统:
struct namespace {
atomic_t count; /* 使用计数 */
struct vfsmount * root; /* 根目录的安装点对象 */
struct list_head list; /* 安装点链表 */
struct rw_semaphore sem;
};
list域是连接已安装文件系统的双向链表,它包含的元素组成了全体命名空间。
新内核中的nsproxy结构如下所示:
/*
* A structure to contain pointers to all per-process
* namespaces - fs (mount), uts, network, sysvipc, etc.
*
* The pid namespace is an exception -- it's accessed using
* task_active_pid_ns. The pid namespace here is the
* namespace that children will use.
*
* 'count' is the number of tasks holding a reference.
* The count for each namespace, then, will be the number
* of nsproxies pointing to it, not the number of tasks.
*
* The nsproxy is shared by tasks which share all namespaces.
* As soon as a single namespace is cloned or unshared, the
* nsproxy is copied.
*/
struct nsproxy {
atomic_t count;
struct uts_namespace *uts_ns;
struct ipc_namespace *ipc_ns;
struct mnt_namespace *mnt_ns;
struct pid_namespace *pid_ns_for_children;
struct net *net_ns;
struct cgroup_namespace *cgroup_ns;
};
上述的结构都是通过进程描述符连接起来的。对于多数进程来说,它们的描述符都指向唯一的files_struct和fs_struct结构体。但是对于那些使用克隆标志CLONE_FILES或CLONE_FS创建的进程,会共享这两个结构体。namespace结构体在默认情况下,所有进程共享同样的命名空间,所有进程都继承其父进程的命名空间。只有在进行clone() 操作时使用CLONE_NEWS标志,才会给进程一个唯一的命名空间结构体的拷贝。