文件读写原理(转)

mximo mximo     2022-09-02     777

关键词:

系统调用

操作系统的主要功能是为管理硬件资源和为应用程序开发人员提供良好的环境,但是计算机系统的各种硬件资源是有限的,因此为了保证每一个进程都能安全的执行。处理器设有两种模式:“用户模式”与“内核模式”。一些容易发生安全问题的操作都被限制在只有内核模式下才可以执行,例如I/O操作,修改基址寄存器内容等。而连接用户模式和内核模式的接口称之为系统调用。

应用程序代码运行在用户模式下,当应用程序需要实现内核模式下的指令时,先向操作系统发送调用请求。操作系统收到请求后,执行系统调用接口,使处理器进入内核模式。当处理器处理完系统调用操作后,操作系统会让处理器返回用户模式,继续执行用户代码。

进程的虚拟地址空间可分为两部分,内核空间和用户空间。内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中,都是对物理地址的映射。

应用程序中实现对文件的操作过程就是典型的系统调用过程。

 

虚拟文件系统

一个操作系统可以支持多种底层不同的文件系统(比如NTFS, FAT, ext3, ext4),为了给内核和用户进程提供统一的文件系统视图,Linux在用户进程和底层文件系统之间加入了一个抽象层,即虚拟文件系统(Virtual File System, VFS),进程所有的文件操作都通过VFS,由VFS来适配各种底层不同的文件系统,完成实际的文件操作。

通俗的说,VFS就是定义了一个通用文件系统的接口层和适配层,一方面为用户进程提供了一组统一的访问文件,目录和其他对象的统一方法,另一方面又要和不同的底层文件系统进行适配。如图所示:

         

 

虚拟文件系统主要模块

1、超级块(super_block),用于保存一个文件系统的所有元数据,相当于这个文件系统的信息库,为其他的模块提供信息。因此一个超级块可代表一个文件系统。文件系统的任意元数据修改都要修改超级块。超级块对象是常驻内存并被缓存的。

2、目录项模块,管理路径的目录项。比如一个路径 /home/foo/hello.txt,那么目录项有home, foo, hello.txt。目录项的块,存储的是这个目录下的所有的文件的inode号和文件名等信息。其内部是树形结构,操作系统检索一个文件,都是从根目录开始,按层次解析路径中的所有目录,直到定位到文件。

3、inode模块,管理一个具体的文件,是文件的唯一标识,一个文件对应一个inode。通过inode可以方便的找到文件在磁盘扇区的位置。同时inode模块可链接到address_space模块,方便查找自身文件数据是否已经缓存。

4、打开文件列表模块,包含所有内核已经打开的文件。已经打开的文件对象由open系统调用在内核中创建,也叫文件句柄。打开文件列表模块中包含一个列表,每个列表表项是一个结构体struct file,结构体中的信息用来表示打开的一个文件的各种状态参数。

5、file_operations模块。这个模块中维护一个数据结构,是一系列函数指针的集合,其中包含所有可以使用的系统调用函数,例如open、read、write、mmap等。每个打开文件(打开文件列表模块的一个表项)都可以连接到file_operations模块,从而对任何已打开的文件,通过系统调用函数,实现各种操作。

6、address_space模块,它表示一个文件在页缓存中已经缓存了的物理页。它是页缓存和外部设备中文件系统的桥梁。如果将文件系统可以理解成数据源,那么address_space可以说关联了内存系统和文件系统。我们会在文章后面继续讨论。

模块间的相互作用和逻辑关系如下图所示:

          

 

由图可以看出:

1、每个模块都维护了一个X_op指针指向它所对应的操作对象X_operations。

2、超级块维护了一个s_files指针指向了“已打开文件列表模块”,即内核所有的打开文件的链表,这个链表信息是所有进程共享的。

3、目录操作模块和inode模块都维护了一个X_sb指针指向超级块,从而可以获得整个文件系统的元数据信息。

4、 目录项对象和inode对象各自维护了指向对方的指针,可以找到对方的数据。

5、已打开文件列表上每一个file结构体实例维护了一个f_dentry指针,指向了它对应的目录项,从而可以根据目录项找到它对应的inode信息。

6、已打开文件列表上每一个file结构体实例维护了一个f_op指针,指向可以对这个文件进行操作的所有函数集合file_operations。

7、inode中不仅有和其他模块关联的指针,重要的是它可以指向address_space模块,从而获得自身文件在内存中的缓存信息。

8、address_space内部维护了一个树结构来指向所有的物理页结构page,同时维护了一个host指针指向inode来获得文件的元数据。

进程和虚拟文件系统交互

1、内核使用task_struct来表示单个进程的描述符,其中包含维护一个进程的所有信息。task_struct结构体中维护了一个 files的指针(和“已打开文件列表”上的表项是不同的指针)来指向结构体files_struct,files_struct中包含文件描述符表和打开的文件对象信息。

2、file_struct中的文件描述符表实际是一个file类型的指针列表(和“已打开文件列表”上的表项是相同的指针),可以支持动态扩展,每一个指针指向虚拟文件系统中文件列表模块的某一个已打开的文件。

         

 

3、file结构一方面可从f_dentry链接到目录项模块以及inode模块,获取所有和文件相关的信息,另一方面链接file_operations子模块,其中包含所有可以使用的系统调用函数,从而最终完成对文件的操作。这样,从进程到进程的文件描述符表,再关联到已打开文件列表上对应的文件结构,从而调用其可执行的系统调用函数,实现对文件的各种操作。

进程 vs 文件列表 vs Inode

1、多个进程可以同时指向一个打开文件对象(文件列表表项),例如父进程和子进程间共享文件对象;

2、一个进程可以多次打开一个文件,生成不同的文件描述符,每个文件描述符指向不同的文件列表表项。但是由于是同一个文件,inode唯一,所以这些文件列表表项都指向同一个inode。通过这样的方法实现文件共享(共享同一个磁盘文件);

 

I/O 缓冲区

概念

如高速缓存(cache)产生的原理类似,在I/O过程中,读取磁盘的速度相对内存读取速度要慢的多。因此为了能够加快处理数据的速度,需要将读取过的数据缓存在内存里。而这些缓存在内存里的数据就是高速缓冲区(buffer cache),下面简称为“buffer”。

具体来说,buffer(缓冲区)是一个用于存储速度不同步的设备或优先级不同的设备之间传输数据的区域。一方面,通过缓冲区,可以使进程之间的相互等待变少,从而使从速度慢的设备读入数据时,速度快的设备的操作进程不发生间断。另一方面,可以保护硬盘或减少网络传输的次数。

Buffer和Cache

buffer和cache是两个不同的概念:cache是高速缓存,用于CPU和内存之间的缓冲;buffer是I/O缓存,用于内存和硬盘的缓冲;简单的说,cache是加速“读”,而buffer是缓冲“写”,前者解决读的问题,保存从磁盘上读出的数据,后者是解决写的问题,保存即将要写入到磁盘上的数据。

Buffer Cache和 Page Cache

buffer cache和page cache都是为了处理设备和内存交互时高速访问的问题。buffer cache可称为块缓冲器,page cache可称为页缓冲器。在linux不支持虚拟内存机制之前,还没有页的概念,因此缓冲区以块为单位对设备进行。在linux采用虚拟内存的机制来管理内存后,页是虚拟内存管理的最小单位,开始采用页缓冲的机制来缓冲内存。Linux2.6之后内核将这两个缓存整合,页和块可以相互映射,同时,页缓存page cache面向的是虚拟内存,块I/O缓存Buffer cache是面向块设备。需要强调的是,页缓存和块缓存对进程来说就是一个存储系统,进程不需要关注底层的设备的读写。

buffer cache和page cache两者最大的区别是缓存的粒度。buffer cache面向的是文件系统的块。而内核的内存管理组件采用了比文件系统的块更高级别的抽象:页page,其处理的性能更高。因此和内存管理交互的缓存组件,都使用页缓存。

 

Page Cache

页缓存是面向文件,面向内存的。通俗来说,它位于内存和文件之间缓冲区,文件IO操作实际上只和page cache交互,不直接和内存交互。page cache可以用在所有以文件为单元的场景下,比如网络文件系统等等。page cache通过一系列的数据结构,比如inode, address_space, struct page,实现将一个文件映射到页的级别:

1、struct page结构标志一个物理内存页,通过page + offset就可以将此页帧定位到一个文件中的具体位置。同时struct page还有以下重要参数:

(1)标志位flags来记录该页是否是脏页,是否正在被写回等等;

(2)mapping指向了地址空间address_space,表示这个页是一个页缓存中页,和一个文件的地址空间对应;

(3)index记录这个页在文件中的页偏移量;

2、文件系统的inode实际维护了这个文件所有的块block的块号,通过对文件偏移量offset取模可以很快定位到这个偏移量所在的文件系统的块号,磁盘的扇区号。同样,通过对文件偏移量offset进行取模可以计算出偏移量所在的页的偏移量。

3、page cache缓存组件抽象了地址空间address_space这个概念来作为文件系统和页缓存的中间桥梁。地址空间address_space通过指针可以方便的获取文件inode和struct page的信息,所以可以很方便地定位到一个文件的offset在各个组件中的位置,即通过:文件字节偏移量 --> 页偏移量 --> 文件系统块号 block  -->  磁盘扇区号

4、页缓存实际上就是采用了一个基数树结构将一个文件的内容组织起来存放在物理内存struct page中。一个文件inode对应一个地址空间address_space。而一个address_space对应一个页缓存基数树。它们之间的关系如下:

         

 

Address Space

下面我们总结已经讨论过的address_space所有功能。address_space是Linux内核中的一个关键抽象,它被作为文件系统和页缓存的中间适配器,用来指示一个文件在页缓存中已经缓存了的物理页。因此,它是页缓存和外部设备中文件系统的桥梁。如果将文件系统可以理解成数据源,那么address_space可以说关联了内存系统和文件系统。

由图中可以看到,地址空间address_space链接到页缓存基数树和inode,因此address_space通过指针可以方便的获取文件inode和page的信息。那么页缓存是如何通过address_space实现缓冲区功能的?我们再来看完整的文件读写流程。

 

文件读写基本流程

读文件

1、进程调用库函数向内核发起读文件请求;

2、内核通过检查进程的文件描述符定位到虚拟文件系统的已打开文件列表表项;

3、调用该文件可用的系统调用函数read()

3、read()函数通过文件表项链接到目录项模块,根据传入的文件路径,在目录项模块中检索,找到该文件的inode;

4、在inode中,通过文件内容偏移量计算出要读取的页;

5、通过inode找到文件对应的address_space;

6、在address_space中访问该文件的页缓存树,查找对应的页缓存结点:

(1)如果页缓存命中,那么直接返回文件内容;

(2)如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页;重新进行第6步查找页缓存;

7、文件内容读取成功。

 

写文件

前5步和读文件一致,在address_space中查询对应页的页缓存是否存在:

6、如果页缓存命中,直接把文件内容修改更新在页缓存的页中。写文件就结束了。这时候文件修改位于页缓存,并没有写回到磁盘文件中去。

7、如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应的页填充该缓存页。此时缓存页命中,进行第6步。

8、一个页缓存中的页如果被修改,那么会被标记成脏页。脏页需要写回到磁盘中的文件块。有两种方式可以把脏页写回磁盘:

(1)手动调用sync()或者fsync()系统调用把脏页写回

(2)pdflush进程会定时把脏页写回到磁盘

同时注意,脏页不能被置换出内存,如果脏页正在被写回,那么会被设置写回标记,这时候该页就被上锁,其他写请求被阻塞直到锁释放

#littlefs原理分析#[五]文件读写(代码片段)

...了littlefs中的目录操作,这一篇文章则将介绍littlefs中的文件读写操作。本文会根据文件的存储类型进行介绍,即inline文件和outline文件,其读写过程也有差别。另外还会介绍inline文件到outline文件的转换,以及littlefs底层的读写API... 查看详情

mysql:31mysql数据库的日志顺序读写以及数据文件随机读写的原理

...种日志进行的磁盘顺序读写,一种是对表空间的磁盘文件里的数据页进行的磁盘随机读写。2.磁盘随机读写操作磁盘随机读操作MySQL在工作的时候,尤其是执行增删改操作的时候,肯定会先从表空间的磁盘文件里读取... 查看详情

用一个实际例子理解dockervolume工作原理

要了解DockerVolume,首先我们需要理解Docker文件系统的工作原理。Docker镜像是由多个文件系统的只读层叠加而成。当一个容器通过命令dockerrun启动时,Docker会加载只读镜像层并在镜像栈顶部添加一个读写层。如果运行中的容器修改... 查看详情

hadoop之hdfs读写原理

...HDFS全称是HadoopDistributedSystem。HDFS是为以流的方式存取大文件而设计的。适用于几百MB,GB以及TB,并写一次读多次的场合。而对于低延时数据访问、大量小文件、同时写和任意的文件修改,则并不是十分适合。目前HDFS支持的使用... 查看详情

漫画hdfs工作原理(转)

...:client、namenode、datanodes。①用户通过client对hdfs系统进行读写数据。②namenode在hdfs系统中是唯一的,主要负责协调整个系统。③datanode一般有多个,主要负责保存数据。 查看详情

hdfs读写原理

...同时也是一个分布式计算的解决方案。Hadoop=HDFS(分布式文件系统)+MapReduce(分布式计算)HDFSHDFS概念数据块NameNodeDataNode数据块:数据块是一个抽象的块,而不是整个文件。默认大小是64Mb,一般设置为128Mb,备份x3数据块的大小... 查看详情

cpp读写文件cstring转stringstring转cstring(代码片段)

场景C++读取文件技术点读取文件fstream提供了三个类,用来实现c++对文件的操作。(文件的创建、读、写)。ifstream--从已有的文件读入ofstream--向文件写内容fstream-打开文件供读写文件打开模式:ios::in只读ios::out只写ios::app从文件... 查看详情

windows系统下hosts文件工作原理(转)

Hosts文件原理解析hosts首先说明下,hosts文件没有后缀一.Hosts文件的位置很多用户都知道在Window系统中有个Hosts文件(没有后缀名),在Windows98系统下该文件在Windows文件夹。在Windows2000/XP系统中位于\%Systemroot%\System32\Drivers\Etc文件... 查看详情

营销mm让我讲mysql日志顺序读写及数据文件随机读写原理

...?本文分享自华为云社区《MySQL日志顺序读写及数据文件随机读写原理》,作者:JavaEdge。MySQL在实际工作时候的两种数据读写机制:对redolog、binlog这种日志进行的磁盘顺序读写对表空间的磁盘文件里的数据页进行... 查看详情

vmware快照的工作原理(转)

当我们为虚拟机创建的快照时,当前可写的VMDK文件变成为只读状态,并且创建一个新文件(称之为快照文件)来保存变化的内容(使用in-filedeltatechnology)。在初始状态下,快照文件的大小为16MB,并随着虚拟机对磁盘文件的写操... 查看详情

mycat分库读写分离原理

mycat的配置文件schema.xml<tablename="employee"primaryKey="ID"dataNode="dn1,dn2"rule="sharding-by-intfile"/><dataNodename="dn1"dataHost="localhost1"database="db1"/><dataNodename="dn2"dataHost 查看详情

b+树vs.lsm树(转)

...储结构和算法的选择影响甚大。为了改善数据访问特性,文件系统或数据库系统通常会对数据排序后存储,加快数据检索速度,这就需要保证数据在不断更新、插入、删除后依然有序,传统关系数据库的做法是使用B+树,如图4.20... 查看详情

mysql日志顺序读写及数据文件随机读写原理

...edolog、binlog这种日志进行的磁盘顺序读写对表空间的磁盘文件里的数据页进行的磁盘随机读写1磁盘随机读MySQL执行增删改操作时,先从表空间的磁盘文件里读数据页出来,这就是磁盘随机读。如下图有个磁盘文件,里... 查看详情

hdfs读写和冷备份原理

...考技术A当用户通过命令行或者JavaAPI向Hadoop集群发起写入文件操作时,将触发写文件流程,分为3个阶段:创建文件,建立数据流管道和写数据。数据传输过程中,如果datanode2突然挂掉了,HDFS会启动如下步骤进行容错。namenode负责... 查看详情

kafka入门:简介使用场景设计原理主要配置及集群搭建(转)

...将被分成多个partition(区),每个partition在存储层面是appendlog文件。任何发布到此partition的消息都会被直接追加到log文件的尾部,每条消息在文件中 查看详情

hdsf读写文件

HDFS读取文件HDFS的文件读取原理,主要包括以下几个步骤:1、首先调用FileSystem对象的open方法,其实获取的是一个DistributedFileSystem的  实例。2、DistributedFileSystem通过RPC(远程过程调用)获得文件的第一批block的locations,同一bl... 查看详情

kafka入门:简介使用场景设计原理主要配置及集群搭建(转)

...kafka的作用是什么?2.kafka中几乎不允许对消息进行“随机读写”的原因是什么?3.kafka集群consumer和producer状态信息是如何保存的?4.partitions设计的目的的根本原因是什么? 一、入门  1、简介  Kafkaisadistributed,part... 查看详情

kafka入门:简介使用场景设计原理主要配置及集群搭建(转)

...kafka的作用是什么?2.kafka中几乎不允许对消息进行“随机读写”的原因是什么?3.kafka集群consumer和producer状态信息是如何保存的?4.partitions设计的目的的根本原因是什么? 一、入门  1、简介  Kafkaisadistributed,part... 查看详情