linux访问文件过程容原理,Linux内核原理之虚拟文件系统(下)

文章目录

处理VFS对象

文件系统操作

注册文件系统

装载和卸载

mount系统调用

umount系统调用

文件操作过程

查找inode

打开文件

读取和写入

参考资料

处理VFS对象

文件系统操作

注册文件系统

文件系统注册到内核时,是编译为模块,或者持久编译到内核。fs/super.c中的register_filesystem函数用来向内核注册文件系统,该函数扫描文件系统结构组成的单链表,直至到达链表尾部然后添加新的元素或者找到所需的文件系统

装载和卸载

装载操作开始于超级块的读取,file_system_type中保存的read_super函数指针返回一个类型为super_block的对象,用来在内存中表示超级块

mount系统调用

入口点是sys_mount函数,在fs/namespace.c定义,代码流程如下图

8352996f9de1b0d34d160db8ec272240.png

首先将装载选项(类型、设备和选项)从用户空间复制到内核空间,内核将控制权转给do_mount函数,该函数调用path_lookup找到装载点所在的dentry项

do_mount充当一个多路分解器,将需要完成的工作委派给装载类型对应的各个函数,其中do_new_mount处理普通装载操作,它分为两部分:do_kern_mount和do_add_mount

ebca18d9e1adeab91c5b59b1694dd460.png

do_kern_mount首先使用get_fs_type找到对应的file_system_type实例,并扫描已注册文件系统的链表,返回正确的项,然后调用特定于文件系统类型的get_sb函数读取相关的超级块,返回struct super_block的实例

do_add_mount首先处理一些必须的锁定操作,确保同一个文件系统不会重复装载到同一位置,然后主要工作是在graft_tree函数,新装载的文件系统通过attach_recursive_mnt函数添加到父文件系统的命名空间

fe09a7eef4d0b876761126cc05a0ec3c.png

nameidata用于将vfsmount实例和denrty实例聚集起来,该结构保存了装载点的dentry实例和装载之前该目录对应的vfsmount实例

mnt_set_mount确保新的vfsmount实例中的mnt_parent成员指向父文件系统的vfsmount实例,以及mnt_mountpoint成员指向装载点在父文件系统中的dentry实例,旧的dentry实例d_mounted值加一 abf96aa2d5cc066ea44a6b64301323b4.png

函数commit_tree将新的vfsmount实例添加到全局散列表以及父文件系统vfsmount实例中的子文件系统链表 13f7a0a1e11a5f05bf031597b9ba5a4f.png

umount系统调用

umount系统调用的入口点是fs/namespace.c中的sys_umount,如下图

01b703e8853582b3ba7816666bed9287.png

首先,__user_walk找到装载点的dentry和vfsmount实例,主要的工作委派给do_umount函数

如果定义了特定于超级块的umount_begin函数,则调用它。例如,网络文件系统在卸载前,需要终止与远程文件系统的通信

如果装在的文件系统不再需要(通过使用计数判断),或者指定了MNT_DETACH来强制卸载文件系统,则调用umount_tree和release_mounts函数,前者将d_mounted减一,后者使用保存在mnt_mountpoints和mnt_parent中的数据,将环境恢复到文件系统装在之前的状态。同时被卸载的文件系统对应的数据结构,会从内核链表中移除

文件操作过程

查找inode

主要操作是根据给定的文件名查找inode,nameidata结构用于向查找函数传递参数,并保存查找结果,该结构定义如下

d59d11c848ff17c695eba94d3b5fc79d.png

主要成员:

查找完成后,dentry和mnt包含了找到的文件系统项的数据

last包含了查找的名称,包含字符串和散列值

flags保存了相关标志

内核使用path_lookup函数查找路径和文件名,该函数需要一个nameidata类型的指针,用作临时结果的“暂存器”

3e02f3435a43c62bc1a74d3fb3646c3f.png

内核使用nameidata实例规定查找的起点,如果名称以/开头,使用当前根目录的dentry实例和vfsmount实例;否则,从当前进程的task_struct获得当前工作目录的数据

主要处理在link_path_walk函数,它调用__link_path_walk函数,该函数代码流程图如下

20508cc38f27abe6c63a8fa46ceb2208.png

该函数由一个大的循环组成,逐分量处理文件或路径名(路径根据/被分解为多个分量),每个循环的主要逻辑为:

将nameidata实例的mnt和dentry成员设置为根目录或工作目录对应的数据项

对目录进行权限检查,判断进程是否允许进入该目录、

路径名称是逐字母扫描,根据/将路径分为多个路径分量,每个循环处理一个路径分量;路径分量的每个字符传给partial_name_hash函数,用于计算散列和,将他保存到qstr实例中

处理(.),直接跳过

处理(. .),委派给follow_dotdot函数,当前目录为进程的根目录时,没有效果,否则,分两种情况:

如果当前目录不是一个装载点的根目录时,将当前dentry对象的d_parent成员作为新的目录

如果是已装载文件系统的根目录,利用保存在mnt_mountpoint和mnt_parent中的信息定义下一个dentry和vfsmount对象。follow_mount和lookup_mnt用于取得所需的信息

如果路径分量是一个普通文件,需要区分两种情况进行处理,数据位于dentry缓存 或者 需要文件系统底层实现进行查找,函数do_lookup负责区分两种情况,返回所需的fentry实例

最后一步:判断该分量是否为符号链接(方法:只有勇于符号链接的inode,其inode_operations中才包含lookup函数,否则为NULL)

最后一个分量对应的dentry作为函数link_path_walk的返回结果

打开文件

标准库的open函数用于打开文件,该函数使用同名的open系统调用,调用了fs/open.c中的sys_open函数,代码流程图如下

38614e985b5e5fd07d9f05018546f900.png

force_o_largefile检查是否应该不考虑用户层传递的标志

接下来的主要处理是在do_sys_open函数

调用get_unused_fd_flags查找未使用的文件描述符

根据open参数中的文件路径名称,查找对应的inode,主要是在do_file_open函数

open_namei函数调用path_lookup函数查找inode并执行几个额外检查

nameidata_to_filp初始化预读结构,将新创建的file实例放置到超级块的sfiles链表中,并调用底层文件系统随影file_operations中的open函数

fd_install函数将file实例放置到进程task_struct结构的files->fd数组中

最后控制权转到用户进程,返回进程描述符

读取和写入

文件打开之后,使用read和write系统调用进行读写,入口函数是sys_read和sys_write(都是在fs/read_write.c实现)

read

函数需要三个参数:文件描述符、保存数据的缓冲区和指定读取字符数的长度参数,代码流程如下图

根据文件描述符,fget_light函数(fs/file_table.c中)从task_struct结构中找到相关的file实例

file_pos_read找到文件当前的读写位置(返回file->f_pos的值)

vfs_read函数调用特定于文件的读取函数file->f_op->read,如果该函数没有实现,则调用一般的辅助函数sys_sync_read

file_pos_write函数记录文件内部新的读写位置

注意:读取数据涉及到复杂的缓冲区和缓存系统,详见Linux内核原理之通用块设备层

8202eb4c29b0e02252a6a6e70ac813c1.png

参考资料

Linux内核设计与实现

深入Linux内核架构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值