Linux系统编程——文件IO——内核内幕

1. 引言

设计IO的内核实现,主要是三个子系统:虚拟文件系统,页面缓存,页面写回。

2. 虚拟文件系统

虚拟文件系统,也称为 虚拟文件切换系统(virtual file switch),让linux内核在调用文件系统函数时,不需要知道所使用的具体文件系统。

VFS的好处很多:单一系统调用可以读取任何存储媒介上的数据,单一实例可以从一个文件系统复制到任何其它系统。

VFS的实现原理是,公共文件模型,公共文件模型为linux内核的文件系统提供了一个必须遵循的框架。此框架以面向对象思想,提供了 回调函数 来支持 读写等功能,每个文件系统还可以注册其特有的函数。

此做法让各个文件系统有一定程度的共同性,如VFS会用到 inode, superblock, directory entry等对象。若一个非unix文件系统可能会欠缺unix-like概念(如inode),就必须克服,当然linux克服了,所以linux支持FAT NTFS之类的文件系统。

当应用程序调用read,C链接库会在编译时将其转换为trap语句,当用户空间进程陷入内核后,控制权会通过系统调用处理程序交给read系统调用,于是内核可以找出支持所指定的文件描述符的是何种对象,然后内核调用与背后对象相应的读取函数。对文件系统而言,此函数是文件系统程序代码的一部分,他会做该做的事(如,从文件读数据),并将数据返回给用户空间的read()调用。于是从系统调用处理程序返回后,系统调用处理程序会将数据复制回用户空间,接着从用户空间read()系统调用返回,然后进程继续执行

对于系统编程人员,VFS的细节很重要,VFS让编程人员通过统一的IO接口(read,write)操作不同的文件系统,且不用担心文件系统的类型或文件位于哪种存储媒介上。

3. 页面缓存

页面缓存是一块内存存储区,用来临时存放最近从磁盘文件系统上访问的数据。

页面缓存利用时间局部性,即当一个资源被访问后,将来被再次访问的概率很高,缓存第一次访问到的数据,以避免日后高昂的磁盘访问。

查找文件系统时,内核先从页面缓存开始。只有当他不存在于缓存时,内核才会调用内存子系统从磁盘读取数据。因此首次读取数据时,数据从磁盘传送到页面缓存,再从页面缓存返回到应用程序,以后再读该数据,数据直接从缓存返回。

页面缓存区的大小可以变动,当IO操作将越来越多的数据存入内存,页面缓存会越来越大 ,导致耗尽内存,所以页面缓存需要修剪,释放其最少被用到的页面。修剪动作无隙自动进行。由于页面缓存大小可以变动,所以缓存区可以使用linux系统中所有的内存,并缓存尽可能多的数据。

然后,相较于修剪页面缓存中常被用到的部分,把很少被用到的数据块(是进程数据不是文件缓存)交换到磁盘更有意义。交换功能让内核可以将数据存储到磁盘上,因此可以使用比系统的RAM还大的内存空间。

内核实现了试探性算法让数据交换和页面缓存达到平衡,试探性算法决定是否可以换出数据到磁盘以取代修剪页面缓存。

交换和缓存的平衡可用/proc/sys/vm/swappiness来调整。此虚拟文件的值的范围是0-100,默认是60,越高意味着越偏向于交换,越低越偏向于修剪。

顺序局部性指,数据往往是顺序地被引用,所以内核还实现了页面缓存的预读功能。预读指随着每个读请求,将额外的数据从磁盘读进页面缓存的行为。

和页面缓存一样,内核也是采用动态方式管理预读功能,如果内核注意到进程始终使用预读功能读取数据,则会扩大先读范围,因而预先读进越来越多的数据,如预读范围可为16KB,也可扩大到128KB,反过来,若内核注意到预读无法提供有用数据,内核可能完全停用预读功能

页缓存是透明的,对于系统程序设计者,无法控制页面缓存,但是有效率的程序需要善用页面缓存,利用预读功能是可能的,顺序IO总是优于随机访问。

4. 页面写回

如前面提到write行为,内核的延后写入功能是通过缓存区完成的。

当一个进程送出写入请求,数据会被复制到一个缓存区,该缓存区会被标记为已被改变(dirty),接着写入请求返回,如果另一个写入请求针对同一个文件的同一个数据块,则缓存区会被新数据锁改变,如果写入请求针对相同文件的其它地方,则会产生新的缓存区。

最后被改变的缓存区需要提交给磁盘,使内存数据和磁盘数据同步。这就是 写回 。

通常情况会这么做:

  • 若可用内存空间已经减少到可设置的阈值,则被改变的缓存区会被写回磁盘,并释放相关内存空间。
  • 若被改变缓存区保存时间已经超过可设置的阈值,则缓存区写回到磁盘。但不释放相关内存,只将缓存区状态去除dirty,以避免数据永远维持在被改变状态(dirty)

写回操作是通过一群名为pdflush的内核线程进行的。当上面两种情况有一个符号时,pdflush线程被唤醒,开始写回操作,直到两种情况都不符合。

缓存区在内核被表示成 buffer_head 数据结构,其中记录了与缓存区相关的各种元数据,如缓存区是否被改变,其中还包含一个指针,用于指向实际的数据,此数据放在页面缓存区,也是缓存区子系统与页面缓存可以被整合在一起。

linux 延后写入功能和缓存区提供了快速写入功能,代价是断电时有数据漏失的风险。为了避免此风险,可以使用同步IO

一般的写(包括用mmap)都是将数据写到内存中的文件(页缓存),当写回时,脏页会加入 BIO队列,pdflush会按某种算法(如电梯算法)将块写入磁盘中的文件

posted on 2021-08-18 09:18  开心种树  阅读(104)  评论(0编辑  收藏  举报