VFS基础学习笔记 - 4.打开文件过程

1. 前言

本专题我们开始学习虚拟文件系统VFS的相关内容。本专题主要参考了《存储技术原理分析》、ULA、ULK的相关内容。本文主要记录open文件的过程,让我们能够了解文件描述符的file operations到底来源于哪里。

kernel版本:5.10
FS: minix
平台:arm64

注:
为方便阅读,正文标题采用分级结构标识,每一级用一个"-“表示,如:两级为”|- -", 三级为”|- - -“

2. 打开文件过程对象关系

在这里插入图片描述

  • task_struct
    task_struct中的files指向了files_struct,指示当前进程打开了哪些文件

  • files_struct
    files_struct是fdttable的一个封装,它通过fdt指向fdttable,它管理了一个打开的文件描述符指针数组fd_array

  • fdtable
    fdtable的fd指向了files_struct的打开文件描述符指针数组fd_array,通过fdtable的fd可以快速访问files_struct的fd_array数组

  • inode
    inode用于维护文件的元数据,它在open文件时获取或创建

  • file
    文件描述符file维护了当前路径和根路径path,path是一个<vfsmount,dentry>二元组,指示文件是属于哪个文件系统下的哪个dentry。file的f_op指向file_operations结构体,它来源于file对应的inode->i_fop,inode->i_fop将在创建 inode时被初始化,file->f_op是在打开文件时被初始化。f_mapping指向了file拥有的page cache地址空间(address_space),对于普通文件来源于inode->address_space,对于块设备文件(裸设备)来源于bdev->address_space

  • address_space
    address_space作为inode的内嵌结构体而存在,在创建inode时一同创建,它维护了一个page cache tree

3. do_sys_open

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
	|--do_sys_open(AT_FDCWD, filename, flags, mode);
			|--do_sys_openat2(dfd, filename, &how);
					|  //将flags标志转换后放入op->flags
					|--build_open_flags(how, &op);
					|  //将文件路径名从用户空间复制到内核空间
					|--tmp = getname(filename);
					|  // 从进程打开的文件描述表current->files分配一个未使用的file*,并返回它的索引
					|--fd = get_unused_fd_flags(how->flags);
					|  //执行file->f_ops->open函数,返回file
					|--struct file *f = do_filp_open(dfd, tmp, &op)
					|  //file指针赋值给current->files->fd[fd]
					|--fd_install(fd, f);

open系统调用主要是通过解析路径名filename,并将路径名的最后分量生成最终的<vfsmount, dentry>二元组保存到路径上下文nd中(对于新建的文件则需要创建dentry),对于最后的路径分量通过其dentry获取到inode,如果inode为空还要为之创建inode,最后通过nd和inode来填充file,并返回file

  1. getname(filename):首先从用户空间将文件路径名拷贝到内核空间

  2. get_unused_fd_flags(flags):从进程打开的文件描述符表current->files->fd_array数组中分配一个未使用的file指针

  3. do_filp_open: 创建file描述符并填充分配的file描述符

  4. fd_install:将文件描述符和句柄fd联系起来,即将文件描述符保存在打开文件表current->files的句柄fd索引位置

|- -do_filp_open

struct file *f = do_filp_open(dfd, tmp, &op)
	|--struct nameidata nd;
	|--set_nameidata(&nd, dfd, pathname);
	|--filp = path_openat(&nd, op, flags | LOOKUP_RCU);
			|--file = alloc_empty_file(op->open_flag, current_cred());
			|  //初始化路径上下文开始解析文件路径名
			|--const char *s = path_init(nd, flags);
			|--while (!(error = link_path_walk(s, nd)) &&
			|			(s = open_last_lookups(nd, file, op)) != NULL);
			|--do_open(nd, file, op);
			|		|--error = may_open(&nd->path, acc_mode, open_flag);
			  		|--if (!error && !(file->f_mode & FMODE_OPENED))
			  				vfs_open(&nd->path, file);
			  					|--f->f_inode = inode;
			  					   f->f_mapping = inode->i_mapping;
			  					   f->f_op = fops_get(inode->i_fop);

.do_filp_open:填充分配的file描述符并返回

  1. alloc_empty_file:创建file描述符

  2. path_init:主要初始化最初的路径上下文nameidata(主要是nd->path),后续的查找过程将从此初始的nameidata进行。这里根据文件名name和dfd,可以将nd->path初始化为三种情况:
    (1)如果name的第一个字符为’/’,将nd->path初始化为当前进程的根目录;
    (2)如果dfd为AT_FDCWD,将 nd->path初始化为当前进程的当前路径;
    (3)其它情况则根据具体设置相对路径

  3. link_path_walk:解析文件路径名,由于使用了LOOKUP_PARENT,因此将得到最后一级目录的<vfsmount,dentry>二元组保存到路径上下文nd,否则保存的是最后一级路径名的<vfsmount,dentry>
    注意:此处设置了标志LOOKUP_PARENT,这样在执行link_path_walk之后路径上下文将保存的是最后一个目录的上下文。

  4. open_last_lookups:分析最后的路径分量,在open_last_lookups之前nd为最后一级目录的路径上下文,nd->last保存最后一级路径名分量的信息,open_last_lookups是将最后一级路径名单独拿出来进行处理,期间会为之获取dentry以及inode,如果为空则会创建,并用此inode和nd填充前述的file文件描述符

  5. do_open:主要对file描述符进行初始化,并最终调用具体文件系统的open回调,其中f->f_op = fops_get(inode->i_fop);用来初始化file的f_op回调

|- - -open_last_lookups

open_last_lookups(struct nameidata *nd,struct file *file, const struct open_flags *op)
	|  //nd->path.dentry保存了最后一级目录的dentry
	|--struct dentry *dir = nd->path.dentry;
	|  int open_flag = op->open_flag;
	|  struct dentry *dentry;
	|  //处理非创建文件的情况
	|--if (!(open_flag & O_CREAT))
	|		//获取到最后一级分量的dentry
	|		dentry = lookup_fast(nd, &inode, &seq);
	|		if (likely(dentry))
	|			goto finish_lookup;
	|  //处理新建文件的情况
	|--if (open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR))
	|  		mnt_want_write(nd->path.mnt);
	|--dentry = lookup_open(nd, file, op, got_write);
	|		|--struct dentry *dir = nd->path.dentry;
	|		|  //查找到最后一级分量的dentry
	|		|--dentry = d_lookup(dir, &nd->last);
	|		|  //如果dentry为空,则创建dentry
	|		|--for (;;)
	|		|		if (!dentry) dentry = d_alloc_parallel(dir, &nd->last, &wq);
	|		|  //创建inode
	|		|--if (!dentry->d_inode && (open_flag & O_CREAT))
	|				error = dir_inode->i_op->create(dir_inode, dentry, mode, open_flag & O_EXCL);
finish_lookup:
	|--step_into(nd, WALK_TRAILING, dentry, inode, seq);	

lookup_open:如果dentry->d_inode为空会调创建inode,其最终会调用到具体文件系统的create回调,对于minix文件系统就会调用minix_create->minix_new_inode创建minix_inode_info,而后者内嵌vfs inode;创建完inode后会通过minix_set_inode对inode进行初始化,minix_set_inode根据文件类型(目录、文件、链接)设置inode的i_op,i_fop(file->file_operation的来源),i_mapping->a_ops,对于字符设备、块设备则会调用init_special_inode进行初始化

参考文档

奔跑吧,Linux内核

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值