处理VFS对象
注册文件系统:在文件系统注册到内核时,文件系统是编译为模块,或者持久编译到内核中。
fs/super.c
中的register_filesystem
用来向内核注册文件系统。我们可以通过/proc/filesystems
查看系统所有的文件系统类型。
一个文件系统不能注册两次,否则,将描述新文件系统的对象置于链表末尾,这样就完成向内核的注册。
static struct file_system_type ext4_fs_type = {
.owner = THIS_MODULE,
.name = "ext4",
.mount = ext4_mount,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;
BUG_ON(strchr(fs->name, '.'));
if (fs->next)
return -EBUSY;
write_lock(&file_systems_lock);
//在已注册的全局文件系统链表file_systems中查找,看是否已经注册过
p = find_filesystem(fs->name, strlen(fs->name));
if (*p)
//如果查找到就返回错误,同一个文件系统不能注册两次
res = -EBUSY;
else
//没查找到,直接将该文件系统添加链表末尾
*p = fs;
write_unlock(&file_systems_lock);
return res;
}
static struct file_system_type **find_filesystem(const char *name, unsigned len)
{
struct file_system_type **p;
//在已注册的全局文件系统链表file_systems中查找
for (p = &file_systems; *p; p = &(*p)->next)
if (strncmp((*p)->name, name, len) == 0 &&
!(*p)->name[len])
break;
return p;
}
装载和卸载:目录树的装载和卸载比仅仅注册文件系统复杂得多,因为后者只需要向一个链表添加对象,而前者需要对内核的内部数据结构执行很多操作,所以要复杂得多。文件系统的装载由 mount
系统调用发起。我们需要阐明在现存目录树中装载新的文件系统必须执行的任务。还需要用于描述装载点的数据结构。
vfsmount
结构:采用一种单一的文件系统层次结构,新的文件系统可以集成到其中,使用mount
可查询目录树中各种文件系统的装载情况如下:
将文件系统装载到一个目录时,装载点的内容被替换为即将装载的文件系统的相对根目录的内容,前一个目录数据消失,直到新文件系统卸载才重新出现。
vfsmount
结构描述一个独立文件系统的挂载信息,每个不同挂载点对应一个独立的vfsmount
结构,属于同一文件系统的所有目录和文件隶属于同一个vfsmount
,该vfsmount
结构对应于该文件系统顶层目录,即挂载目录。
/*fs/mount.h*/
struct mount {
`struct` hlist_node mnt_hash;
struct mount *mnt_parent; //装载点所在的父文件系统
struct dentry *mnt_mountpoint; //装载点在父文件系统中的dentry(目录项)
struct vfsmount mnt;
union {
struct rcu_head mnt_rcu;
struct llist_node mnt_llist;
};
#ifdef CONFIG_SMP
struct mnt_pcp __percpu *mnt_pcp;
#else
int mnt_count;
int mnt_writers;
#endif
//子文件系统链表
struct list_head mnt_mounts; /* list of children, anchored here */
//链表元素,用于父文件系统中的mnt_mount链表
struct list_head mnt_child; /* and going through their mnt_child */
struct list_head mnt_instance; /* mount instance on sb->s_mounts */
//设备名称,例如/dev/dsk/hda1
const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
struct list_head mnt_list;
//链表元素,用于特定于文件系统的到期链表中
struct list_head mnt_expire; /* link in fs-specific expiry list */
//链表元素,用于共享装载的循环链表
struct list_head mnt_share; /* circular list of shared mounts */
//从属装载的链表
struct list_head mnt_slave_list;/* list of slave mounts */
//链表元素,用于从属装载的链表
struct list_head mnt_slave; /* slave list entry */
//指向主装载,从属装载位于master->mnt_slave_list链表上面
struct mount *mnt_master; /* slave is on master->mnt_slave_list */
//所属的命名空间
struct mnt_namespace *mnt_ns; /* containing namespace */
struct mountpoint *mnt_mp; /* where is it mounted */
struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */
struct list_head mnt_umounting; /* list entry for umount propagation */
#ifdef CONFIG_FSNOTIFY
struct hlist_head mnt_fsnotify_marks;
__u32 mnt_fsnotify_mask;
#endif
int mnt_id; /* mount identifier */
int mnt_group_id; /* peer group identifier */
int mnt_expiry_mark; /* true if marked for expiry */
struct hlist_head mnt_pins;
struct fs_pin mnt_umount;
struct dentry *mnt_ex_mountpoint;
};
文件系统之间的父子关系由上述两个成员实现链表表示,mnt_mounts
表头是子文件系统链表的起点,而mnt_child
字段则用作该链表的链表元素。
系统当中的每个vfsmount
实例,通过两种途径标识一个命名空间的所有装载的文件系统都保存在namespace->list
链表中。使用vfsmount
的 mnt_list
成员作为链表元素。
- 超级块管理:在装载新的文件系统时,
vfsmount
并不是唯一需要在内存中创建结构。装载操作开始于超级块的读取。
/*fs.h*/
struct super_block {
struct list_head s_list; //通过该变量链接到超级块全局链表super_blocks上
dev_t s_dev; //该文件系统对应的块设备标识符
unsigned char s_blocksize_bits;
unsigned long s_blocksize; //该文件系统的block size
loff_t s_maxbytes; //文件系统支持的最大文件
struct file_system_type *s_type; //文件系统类型,比如ext3、ext4
const struct super_operations *s_op; //超级块的操作函数
const struct dquot_operations *dq_op; //文件系统限额相关操作
const struct quotactl_ops *s_qcop; //磁盘限额
const struct export_operations *s_export_op;
unsigned long s_flags; //文件系统的mount标记
unsigned long s_iflags; /* internal SB_I_* flags */
unsigned long s_magic; //该文件系统类型的魔术字
struct dentry *s_root; //全局根目录的dentry项
...
struct block_device *s_bdev; //对应的块设备
struct backing_dev_info *s_bdi; //超级块对应的BDI设备
struct mtd_info *s_mtd;
//通过该变量,链接到file_system_type中的fs_supers链表
struct hlist_node s_instances;
...
char s_id[32]; /* Informational name */
uuid_t s_uuid; /* UUID tune2fs -l可以查看*/
void *s_fs_info; //指向具体文件系统超级块结构,如ext4_sb_info
...
const struct dentry_operations *s_d_op; //该超级块默认的目录项操作函数
...
struct shrinker s_shrink; //每个超级块注册的shrink函数,用于内存回收
...
/* AIO completions deferred from interrupt context */
struct workqueue_struct *s_dio_done_wq;
...
//该超级块对应的未在使用dentry列表
struct list_lru s_dentry_lru ____cacheline_aligned_in_smp;
//该超级块对应的未在使用inode列表
struct list_lru s_inode_lru ____cacheline_aligned_in_smp;
...
/* s_inode_list_lock protects s_inodes */
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes; //该超级块包含的所有inode
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; //该超级块正在回写的inode
};
s_op
指向一个包含了函数指针的结构,该结构按熟悉的VFS方式,提供了一个一般性的接口,用于处理超级块相关操作。操作的实现必须由底层文件系统的代码提供。该结构定义如下:
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
//将inode从内存和底层的存储介质删除
void (*destroy_inode)(struct inode *);
//将传递的inode结构标记为“脏的”,意思就是修改过
void (*dirty_inode) (struct inode *, int flags);
int (*write_inode) (struct inode *, struct writeback_control *wbc);
int (*drop_inode) (struct inode *);
void (*evict_inode) (struct inode *);
void (*put_super) (struct super_block *);
int (*sync_fs)(struct super_block *sb, int 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 *);
int (*remount_fs) (struct super_block *, int *, char *);
int (*remount_fs2) (struct vfsmount *, struct super_block *, int *, char *);
void *(*clone_mnt_data) (void *);
void (*copy_mnt_data) (void *, void *);
void (*umount_begin) (struct super_block *);
int (*show_options)(struct seq_file *, struct dentry *);
int (*show_options2)(struct vfsmount *,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 *);
};
-
mount
系统调用:
mount
系统调用的入口点是sys_mount
函数,由sys_mount
从用户空间复制到内核空间之后,内核将控制转移给do_mount
。
-
共享子树
共享子树最核心的特征是允许挂载和卸载事件以一种自动的,可控的方式在不同的namespaces
间传递(propagation)。这就意味着,在一个命名空间中挂载光盘的同时也会触发对于其他namespace
对同一张光盘的挂载。
在共享子树中,每个挂载点都存在一个名为传递类型(propagation type)的标记,该标记决定了一个namespace
中创建或者删除的挂载点是否会传递到其他的namespaces
。
共享子树有四种传递类型:
MS_SHARED
:该挂载点和它的共享挂载和卸载事件。
MS_PRIVATE
:和共享挂载相反,标记为private
的事件不会传递到任何的对等组,挂载操作默认使用次标志。
MS_SLAVE
:这个传递类型介于shared
和slave
之间,一个slave mount
拥有一个master
(一个共享的对等组),slave mount
不能将事件传递给master mount
;
MS_UNBINDABLE
:该挂载点是不可缩写的。
标准函数
VFS层提供的有用资源是用于读写数据的标准函数。这些操作对所有文件系统来说,在一定程度上都是相同的。
如果数据所在的块是已知的,则首先查询页缓存。如果数据并未保存在其中,则向对应的块设备发出读请求。
如果对每个文件系统都需要实现这些操作,则会导致代码大量复制,我们应该不惜代价防止这种情况发生。
常用 VFS与 read/write
系统调用,如vfs_read
和vfs_write
。
VFS(虚拟文件系统,Virtual File System) 是物理文件系统与服务之间的接口层,向下对文件系统提供标准接口,方便其他文件系统移植,向上对应用层提供标准文件操作接口,使open()、read()、write()
等系统调用可以跨越各种文件系统和不同介质执行。
超级块对象 super block:对应已装载的文件系统,用来描述整个文件系统的信息,每个具体的文件系统都有自己的超级块,所有超级块对象以双向链循环链表的形成连接,超级块对象在文件系统装载时创建,保存在内存中,在文件系统超载时它会自动删除。
索引节点对象 inode,对应介质上的一个文件,索引节点对象包含内核在操作文件或目录时需要的全部信息。
目录项对象 dentry:对应一个目录项,目录项对象没有对应的磁盘数据结构(三种状态:被使用、未使用、负状态)。
文件对象file:对应由进程所打开的文件。所有定义在linux/fs.h
.文件对象表示进程已打开的文件。由open()系统调用创建,由close()系统调用删除,多个进程同时打开和操作同一对象,存在多个对应的文件对象。
系统调用 _sys_read
会调用到 vfs 层的_vfs_read
接口,在 vfs 层接口会调用具体文件系统操作由内核来完成。
从内核文件系统看文件读写过程
sys_write()系统调用:
open/read 等系统调用
#include <unistd.h>
size_t write(int flides, const void *buf, size_t nbytes);
write 系统调用,是把缓存区buf
中的前nbytes
字节写入到与文件描述符flides
有关的文件中,返回
的是实际写入到文件中的字节数。
#include <unistd.h>
size_t read(int flides, void *buf, size_t nbytes);
read 系统调用,是从与文件描述符flides
相关联的文件中读取前nbytes
字节的内容,并且写入到数据区buf
中,返回
的是实际读入的字节数
open 有两种调用方法:
int open(const *path, int oflags);
将准备打开的文件或是设备的名字作为参数path
传给函数,oflags
用来指定文件访问模式,成功返回一个新的文件描述符
,失败返回 -1
。
必需部分:
O_RDONLY
:以只读方式打开
O_WRONLY
:以只写方式打开
O_RDWR
:以读写方式打开
可选部分:
O_CREAT
:按照参数mode
给出的访问模式创建文件
O_EXCL
:与O_CREAT
一起使用,确保创建出文件,避免两个程序同时创建同一个文件,如文件存在则open调用失败;
O_APPEND
:把写入数据追加在文件的末尾;
O_TRUNC
:把文件长度设置为0,丢弃原有内容;
int open(const *path, int oflags, mode_t mode);
在第一种调用方式上,加上了第三个参数 mode
,主要是搭配O_CREAT
使用,这个参数规定了属主、同组和其他人对文件的文件操作权限。
int close(int flides);
close系统调用,终止文件描述符 flides
与其对应的文件间的联系,文件描述符被释放,可重新使用。
https://www.cnblogs.com/jimbo17/p/10107318.html
https://blog.csdn.net/qq_39755395/article/details/78516383