文件系统之file

注:本文分析基于linux-4.18.0-193.14.2.el8_2内核版本,即CentOS 8.2

1 file

file结构体和进程相关,内核中用struct file结构体来描述一个被进程打开的文件,而且一个文件可以被多个进程打开,此时内存中就会有多个对应的file结构体,但其对应的inode是唯一的。

2 file主要成员变量

struct file {
	union {
		struct llist_node	fu_llist; //用于释放时链接到delayed_fput_list链表
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path; //文件路径
	struct inode		*f_inode;	//文件对应inode
	const struct file_operations	*f_op; //文件的操作函数集

	spinlock_t		f_lock;  //文件锁
	enum rw_hint		f_write_hint;
	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;
	const struct cred	*f_cred;
	struct file_ra_state	f_ra; //预读状态
	...
	//文件系统和驱动程序使用的数据,比如对于网络文件,该变量就指向对应的socket结构体
	void			*private_data;
	/* Used by fs/eventpoll.c to link all the hooks to this file */
	struct list_head	f_ep_links;  //epoll事件池
	struct list_head	f_tfile_llink;

	struct address_space	*f_mapping; //文件关联的pagecache
	errseq_t		f_wb_err;
};

3 创建file结构体

file结构体的创建主要是在open文件时,用来描述一个被进程打开的文件,我们以打开“/home/test.c”为例,大概分析下file创建和填充过程。我们假设home目录和test.c都是普通文件,同时都在缓存中。

SYSCALL_DEFINE3(open ->							//open系统调用入口
	do_sys_open ->
		get_unused_fd_flags						//获取未使用的文件描述符
		do_filp_open ->							//开始查找文件,并打开
			path_openat ->
				alloc_empty_file				//从slab缓存分配空闲file对象
				path_init						//初始化中间变量nd
				link_path_walk ->				//遍历每一级目录,除最后一级路径外
					walk_component ->			//查找当前目录
						lookup_fast				//在dentry hash表中查找缓存
				do_last ->						//解析最后一级路径,也就是test.c
					lookup_fast					//在dentry hash表中查找缓存
					vfs_open ->					//找到后打开该dentry对应的inode
						do_dentry_open ->
							f->f_op->open ->
								ext4_file_open	//调用对应文件系统的open方法打开文件,最终返回file结构
		fd_install								//安装file结构到fd table中

我们主要关注最后do_last的操作,也就是对于test.c的查找,

  • 缓存里查找到test.c对应的dentry结构,同时也能获取到其对应的inode
  • 将找到的dentry和inode赋值给nd
  • 最终通过nd的信息填充file结构,同时调用对应文件系统的open方法,打开文件
static int do_last(struct nameidata *nd,
		   struct file *file, const struct open_flags *op,
		   int *opened)
{
	...
	if (!(open_flag & O_CREAT)) {
		if (nd->last.name[nd->last.len])
			nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
		//在dentry哈希表中查找最后一级路径,找到的dentry保存在path->dentry中
		//inode指向dentry->d_inode
		error = lookup_fast(nd, &path, &inode, &seq);
	} 
	//将上面找到的dentry和inode赋值给nd
	error = step_into(nd, &path, 0, inode, seq);
	//用nd填充最终返回的file结构
	error = vfs_open(&nd->path, file);
	*opened |= FILE_OPENED;
	return error;
}

在最终填充file结构中,主要是,

  • 文件路径信息
  • 文件对应的inode
  • 文件对应的pagecache
  • 文件对应的操作函数
const struct file_operations ext4_file_operations = {
	.llseek		= ext4_llseek,
	.read_iter	= ext4_file_read_iter,
	.write_iter	= ext4_file_write_iter,
	.mmap		= ext4_file_mmap,
	.open		= ext4_file_open,
	.release	= ext4_release_file,
	...
};

int vfs_open(const struct path *path, struct file *file)
{
	file->f_path = *path; //填充文件路径信息
	//打开上一级找到的dentry,并填充file结构
	return do_dentry_open(file, d_backing_inode(path->dentry), NULL);
}

static int do_dentry_open(struct file *f,
			  struct inode *inode,
			  int (*open)(struct inode *, struct file *))
{
	...
	f->f_inode = inode; //文件对应的inode
	f->f_mapping = inode->i_mapping; //文件对应的pagecache
	...
	f->f_op = fops_get(inode->i_fop); //文件对应的操作函数
	...
	if (!open)
		open = f->f_op->open;
	if (open) {
		//调用对应文件系统的open方法打开文件,对于ext4是调用ext4_file_open
		error = open(inode, f); 
	}
	...
	return 0;
}

4 删除file结构体

文件删除,我们通常使用rm命令,而rm命令实际调用的是unlinkat系统调用,我们还是以删除/home/test.c为例,大概看下整个过程。

SYSCALL_DEFINE3(unlinkat, int, dfd, const char __user *, pathname, int, flag)
{
	if ((flag & ~AT_REMOVEDIR) != 0)
		return -EINVAL;
	//如果删除的是目录
	if (flag & AT_REMOVEDIR)
		return do_rmdir(dfd, pathname);
	//删除文件走的是这个路径
	return do_unlinkat(dfd, getname(pathname));
}

具体操作主要是释放相关资源,

  • 与dentry关联的inode对象
  • file关联的pagecache数据
  • dentry自身
long do_unlinkat(int dfd, struct filename *name)
{
	...
	//查找要删除文件的父目录
	name = filename_parentat(dfd, name, lookup_flags, &path, &last, &type);
	...
	//查找文件最后一级路径,也就是test.c
	dentry = __lookup_hash(&last, path.dentry, lookup_flags);
	error = PTR_ERR(dentry);
	if (!IS_ERR(dentry)) {
		...
		//获取dentry对应的inode对象
		inode = dentry->d_inode;
		...
		//调用对应文件系统的unlink方法删除文件,delegated_inode指向dentry对应的inode对象
		error = vfs_unlink(path.dentry->d_inode, dentry, &delegated_inode);
exit2:
		//释放该dentry
		dput(dentry);
	}
	inode_unlock(path.dentry->d_inode);
	//如果该dentry关联的inode还没有释放,则对其释放
	if (inode)
		iput(inode);	/* truncate the inode here */
	...
}

int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegated_inode)
{
	struct inode *target = dentry->d_inode;
	...
	if (is_local_mountpoint(dentry))
		error = -EBUSY;
	else {
		error = security_inode_unlink(dir, dentry);
		if (!error) {
			error = try_break_deleg(target, delegated_inode);
			if (error)
				goto out;
			//调用对应文件系统的unlink方法删除文件和回写脏数据等操作,对于ext4是调用ext4_unlink
			error = dir->i_op->unlink(dir, dentry);
			if (!error) {
				dont_mount(dentry);
				detach_mounts(dentry);
			}
		}
	}
out:
	inode_unlock(target);

	//删除dentry,除非是NFS文件系统
	if (!error && !(dentry->d_flags & DCACHE_NFSFS_RENAMED)) {
		fsnotify_link_count(target);
		//如果dentry引用计数为1,则解除并释放与其关联的inode对象
		//否则只将dentry从dentry全局哈希表中删除
		d_delete(dentry);
	}

	return error;
}

5 超级块、索引结点、目录项、文件以及进程的关系

我们仍旧以打开/home/test.c为例,看下进程是如何与file结构关联,进而与文件系统关联,

  • 假设sda1为根分区是ext4文件系统,home目录是普通目录,test.c也是普通文件
  • 并且进程打开/home/test.c文件,系统返回的文件描述符是2

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值