一个IO的传奇一生(2)

2025-08-07

较简单,在此进行简单说明。

Def_chr_fops方法集其实就定义了open方法,其它的方法都没有定义。其实字符设备的操作方法都需要字符设备驱动程序自己定义,每个设备驱动程序都需要定义自己的write、read、open和close方法,这些方法保存在字符设备对象中。当用户调用文件系统接口open函数打开指定字符设备文件时,VFS会通过上述讲述的sys_open函数找到设备文件inode中保存的def_chr_fops方法,并且执行该方法中的open函数(chrdev_open),chrdev_open函数完成的一个重要功能就是将文件对象file中采用的方法替换成驱动程序设定的设备操作方法。完成这个偷梁换柱的代码是:

一旦这个过程完成,后继用户程序通过文件系统的write方法都将会调用字符设备驱动程序设定的write方法。即对于字符设备文件而言,在VFS的sys_write函数将直接调用字符设备驱动程序的write方法。所以,对于字符设备驱动程序而言,整个过程很简单,用户态程序可以直接通过系统调用执行字符设备驱动程序的代码。而对于块设备和普通文件,这个过程将会复杂的多。

在用户程序发起写请求的时候,通常会考虑如下三个问题:第一个问题是用户态数据如何高效传递给内核?第二个问题是采用同步或者异步的方式执行IO请求。第三个问题是如果执行普通文件操作,需不需要文件Cache?

第一个问题是数据拷贝的问题。对于普通文件,如果采用了page cache机制,那么这种拷贝合并在很大程度上是避免不了的。但是对于网卡之类的设备,我们在读写数据的时候,需要避免这样的数据拷贝,否则数据传输效率将会变的很低。我第一次关注这个问题是在做本科毕业设计的时候,那时候我设计了一块PCI数据采集卡。在PCI采集卡上集成了4KB的FIFO,数据采集电路会将数据不断的压入FIFO,当FIFO半满的时候会对PCI主控芯片产生一个中断信号,通知PCI主控制器将FIFO中的2KB数据DMA至主机内存。CPU接收到这个中断信号之后,分配DMA内存,初始化DMA控制器,并且启动DMA操作,将2KB数据传输至内存。并且当DMA完成操作之后,会对CPU产生一个中断。板卡的设备驱动程序在接收到这个中断请求之后,面临一个重要的问题:如何将内核空间DMA过来的数据传输给用户空间?通常有两种方法:一种是直接将内核内存映射给用户程序;另一种是进行数据拷贝的方式。对于PCI数据采集卡而言,一个很重要的特性是实时数据采集,在板卡硬件FIFO很小的情况下,如果主机端的数据传输、处理耗费太多的时间,那么整条IO流水线将无法运转,导致FIFO溢出,数据采集出现漏点的情况。所以,为了避免这样的情况,在这些很严

格应用的场合只能采用内存映射的方法,从而实现数据在操作系统层面的零拷贝。在Linux中,我们可以采用memory map的方法将内核空间内存映射给用户程序,从而实现用户程序对内核内存的直接访问。在Windows操作系统中,这种内核空间和用户空间的数据交互方式定义成两种:Map IO和Direct IO。Map IO就是采用内存拷贝的方式,Direct IO就是采用MDL内存映射的方式。在编写WDM Windows设备驱动程序的时候经常会用到这两种数据传输模式。值得注意的是,Windows中的Direct IO和Linux中的Direct IO是完全不同的两个概念。在Linux中Direct IO是指写穿page cache的一种IO方法。

第二个问题是异步IO和同步IO的问题。对于普通文件而言,为了提高效率,通常会采用page cache对文件数据在内存进行缓存。Cache虽然提高了效率,但是有些应用一旦发出写请求,并且执行完毕之后,其期望是将数据写入磁盘,而不是内存。例如,有些应用会有一些元数据操作,在元数据操作的过程中,通常期望将数据直接刷新至磁盘,而不是Cache在内存。这就提出了同步IO的需求。为了达到这个效果,可以在打开文件的时候设置O_SYNC标记。当数据在page cache中聚合之后,如果发现O_SYNC标记被设置,那么就会将page cache中的数据强制的刷新到磁盘。对于EXT3文件系统,该过程在ext3_file_write函数中实现。

第三个问题是普通文件的cache问题。对于普通文件,由于磁盘性能比较低,为了提高读写性能,通常会采用内存作为磁盘的cache。文件系统会采用预读等机制对文件读写性能进行优化,避免磁盘随机IO性能过低对文件读写性能造成影响。但是,page cache虽然提高了性能,但是也会对文件系统的可靠性造成一定影响。例如,当数据已经被写入内存之后,系统Crash,内存中的磁盘数据将会遭到破坏。为了避免这种情况,Linux文件系统提供了Direct IO的IO方式。该方式就是让一次IO过程绕过page cache机制,直接将文件内容刷新到磁盘。与上面的同步IO相比,Direct IO达到的效果似乎有点类似。其实,同步IO是一种write through的Cache机制,而Direct IO是完全把Cache抛弃了。同步IO的数据在内存还是有镜像的,而Direct IO是没有的,这是两者的区别。在Linux中的__generic_file_aio_write_nolock函数中,会判断O_DIRECT标记是否被设置,如果该标记被设置,那么调用generic_file_direct_write函数完成数据磁盘写入过程。如果该标记不存在,那么调用generic_file_buffered_write函数将数据写入page cache。

EXT3文件写操作

如果应用层发起的是一个Word文档的写操作请求,那么通过上述分析,IO会走到

sys_write的地方,然后执行file->f_op->write方法。对于EXT3 ,该方法注册的是do_sync_write。Do_sync_write的实现如下:

该方法会直接调用非阻塞写处理函数,然后等待IO完成。对于具体EXT3文件读写过程函数调用关系可以参考《Ext3文件系统读写过程分析》。对EXT3文件写操作主要考虑两种情况,一种情况是DIRECT IO方式;另一种情况是page cache的写方式。Direct IO方式会直接绕过缓存处理机制,page cache缓存方式是应用中经常采用的方式,性能会比Direct IO高出不少。对于每一个EXT3文件,都会在内存中维护一棵Radix tree,这棵radix tree就是用来管理来page cache页的。当IO想往磁盘上写入的时候,EXT3会查找其对应的radix tree,看是否已经存在与写入地址相匹配的page页,如果存在那么直接将数据合并到这个page 页中,并且结束一次IO过程,直接结束应用层请求。如果被访问的地址还没有对应的page页,那么需要为访问的地址空间分配page页,并且从磁盘上加载数据到page页内存,最后将这个page页加入到radix tree中。

对于一些大文件,如果不采用radix tree去管理page页,那么需要耗费大量的时间去查找一个文件内对应地址的page页。为了提高查找效率,Linux采用了radix tree的这种管理方式。Radix tree是通用的字典类型数据结构,radix tree又被称之为PAT位树(Patricia Trie or crit bit tree)。Radix tree是一种多叉搜索树,树的叶子节点是实际的数据条目。下图是一个radix tree的例子,该radix tree的分叉为4,树高为4,树中的每个叶子节点用来快速定位8位文件内偏移地址,可以定位256个page页。例如,图中虚线对应的两个叶子节点的地址值为0x00000010和0x11111010,通过这两个地址值,可以很容易的定位到相应的page缓存页。

通过radix tree可以解决大文件page页查询问题。在Linux的实现过程中,EXT3写操作处理函数会调用generic_file_buffered_write完成page页缓存写过程。在该函数中,其实现逻辑说明如下:

第一步通过radix tree找到内存中缓存的page页,如果page页不存在,重新分配一个。第二步通过ext3_prepare_write处理EXT3 日志,并且如果page是一个新页的话,那么需要从磁盘读入数据,装载进page页。第三步是将用户数据拷贝至指定的page页。最后一步将操作的Page页设置成dirty。便于writeback机制将dirty页同步到磁盘。

Page cache会占用Linux的大量内存,当内存紧张的时候,需要从page cache中回收一些内存页,另外,dirty page在内存中聚合一段时间之后,需要被同步到磁盘。应该在3.0内核之前,Linux采用pdflush机制将dirty page同步到磁盘,在之后的版本中,Linux采用了writeback机制进行dirty page的刷新工作。有关于writeback机制的一些源码分析可以参考《writeback机制源码分析》。总的来说,如果用户需要写EXT3文件时,默认采用的是writeback的cache算法,数据会首先被缓存到page页,这些page页会采用radix tree的方式管理起来,便于查找。一旦数据被写入page之后,会将该页标识成dirty。Writeback内核线程或者pdflush线程会周期性的将dirty page刷新到磁盘。如果,系统出现内存不足的情况,那么page回收线程会采用cache算法考虑回收文件系统的这些page缓存页。

EXT3文件系统设计要点

EXT3文件系统是Linux中使用最为广泛的一个文件系统,其在EXT2的基础上发展起来,在EXT2的基础上加入了日志技术,从而使得文件系统更加健壮。考虑一下,设计一个文件系统需要考虑哪些因素呢?根据我的想法,我认为设计一个文件系统主要需要考虑如下几个方面的因素:

1)文件系统使用者的特征是什么?大文件居多还是小文件居多?如果基本都是大文件应用,那么数据块可以做的大一点,使得元数据信息少点,减少这方面的开销。

2)文件系统是读应用为主还是写应用为主?这点也是很重要的,如果是写为主的应用,那么可以采用log structured的方式优化IO pattern。例如在备份系统中,基本都是以写请求,那么对于这样的系统,可以采用log structured的方式使得底层IO更加顺序化。

3)文件系统的可扩展性,其中包括随着磁盘容量的增长,文件系统是否可以无缝扩展?例如以前的FAT文件系统由于元数据的限制,对支持的容量有着很强的限制。

4)数据在磁盘上如何布局?数据在磁盘上的不同布局会对文件系统的性能产生很大的影响。如果元数据信息离数据很远,那么一次写操作将会导致剧烈的磁盘抖动。

5)数据安全性如何保证?如果文件系统的元数据遭到了破坏,如何恢复文件数据?如果用户误删了文件,如何恢复用户的数据?这些都需要文件系统设计者进行仔细设计。

6)如何保证操作事务的一致性?对于一次写操作设计到元数据更新和文件数据的更新,这两者之间的操作次序如何设计?既能保证很好的性能,又能在系统crash的时候保证文件系统的一致性?在很多设计中采用数据先于元数据的方式,并且通过日志机制保证事务一致性。

7)文件系统元数据占用多少系统内存?如果一个文件系统占用太多的系统内存,那么会影响整个系统的性能。

在设计一个文件系统的时候,需要考虑很多方面的问题,上述仅仅是列出了一些基本点,针对不同的文件系统,会有很多的具体问题。例如对于淘宝的TFS集群文件系统,由于系统前端采用了大量的缓存,从而使得TFS的输入全是大文件,因此可以采用大文件设计思想对其进行优化。比如减少目录层次,这样在检索一个文件的时候,不需要花费太多的时间,这种处理可以提升系统性能。另一个例子是中科蓝鲸的BWFS在非线编领域的应用。BWFS是一个带外的集群存储系统,是一种SAN集群文件系统。这种文件系统架构的最大好处是可扩展性极高,由于元数据和数据IO流分离,所以,在很多应用中可以得到很高的集群性能。


一个IO的传奇一生(2).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:16春华师《幼儿科学教育》在线作业

相关阅读
本类排行
× 游客快捷下载通道(下载后可以自由复制和排版)

下载本文档需要支付 7

支付方式:

开通VIP包月会员 特价:29元/月

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信:xuecool-com QQ:370150219