在介绍直接I/O之前,先介绍一下直接I/O的机制产生的原因。毕竟已经有了缓冲I/O(BufferedI/O),所以一定可以看出bufferedI/O有缺陷,所以按照这个思路。什么是缓冲I/O(BufferedI/O)缓冲I/O也称为标准I/O,大多数文件系统默认的I/O操作都是缓冲I/O。在Linux的缓存I/O机制中,操作系统会将I/O数据缓存在文件系统的页缓存(pagecache)中,即先将数据复制到操作系统内核的缓冲区中,然后从操作系统内核的缓冲区复制到应用程序的地址空间。写入过程是数据流的相反方向。缓存I/O具有以下优点:缓存I/O使用操作系统内核缓冲区,在某种程度上将应用程序空间与实际物理设备分开。缓存I/O通过减少磁盘读取次数来提高性能。对于读操作:当应用程序要读取某条数据时,如果该条数据已经在pagecache中,则返回。而不是通过硬盘读取操作。如果这段数据不在pagecache中,则需要从硬盘中读取数据到pagecache中。对于写操作:应用程序会先将数据写入pagecache,是否立即写入磁盘取决于所采用的写操作机制:同步机制,数据会立即写入磁盘,直到有数据写入完成后,写入接口返回;延时机制:写接口立即返回,操作系统会周期性的将pagecache中的数据刷新到硬盘中。所以这种机制会有丢失数据的风险。想象一下,当写接口返回时,pagecache中的数据还没有刷到硬盘,刚刚断电。对于应用程序来说,认为数据已经在硬盘上了。CachedI/OWriteOperationCachedI/ODadvantages在缓存I/O机制中,以写操作为例,数据先从用户态拷贝到内核态的pagecache中,再从page中拷贝cache写入磁盘时,这些复制操作造成的CPU和内存开销非常大。对于一些特殊的应用,能够绕过内核缓冲区可以获得更好的性能,这就是直接I/O的意义。DirectI/OWriteOperationDirectI/O简介当通过直接I/O传输数据时,数据直接从用户态地址空间写入磁盘,直接跳过内核缓冲区。对于某些应用程序,例如:数据库。他们更倾向于自己的缓存机制,可以提供更好的缓冲机制来提高数据库的读写性能。直接I/O写操作如上图所示。直接I/O的设计与实现要在块设备中进行直接I/O,进程在打开文件时必须将文件的访问模式设置为O_DIRECT,相当于告诉操作系统进程使用read()orwritenext()系统调用读写文件时,采用直接I/O方式,传输的数据不经过操作系统内核缓存空间。使用directI/O读写数据时,必须注意缓冲区对齐(bufferalignment)和缓冲区的大小,分别对应read()和write()系统调用的第二个和第三个参数.这里所说的对齐是指文件系统块大小的对齐,缓冲区的大小也必须是块大小的整数倍。下面主要介绍三个函数:open()、read()和write()。Linux中对文件的访问是多种多样的,所以这三个函数针对不同的文件访问方式定义了不同的处理方式。本文主要介绍直接I/O方式相关的函数和功能。首先,看一下open()系统调用。其函数原型如下:inopen(constchar*pathname,intoflag,.../*,mode_tmode*/);当应用程序需要不经过操作系统页面缓存而直接访问文件存储时,需要在打开文件时指定O_DIRECT标识符。操作系统内核中处理open()系统调用的内核函数是sys_open(),sys_open()会调用do_sys_open()来处理主要的打开操作。它主要做了三件事:调用getname()从进程地址空间中读取文件的路径名;do_sys_open()调用get_unused_fd()从进程的文件表中找到一个空闲的文件表指针,并将对应的新文件描述符存放在局部变量fd中;函数do_filp_open()会根据传入的参数执行相应的打开操作。下图是操作系统内核中处理open()系统调用的主函数示意图。sys_open()|-----do_sys_open()|--------getname()|--------get_unused_fd()|--------do_filp_open()|-----nameidata_to_filp()|------------__dentry_open()函数do_flip_open()在执行过程中会调用函数nameidata_to_filp(),nameidata_to_filp()最终会调用__dentry_open()函数,如果进程指定了O_DIRECT标识符,该函数将检查直接I./O操作是否可以作用于文件。下面列出了__dentry_open()函数中与直接I/O操作相关的代码。如果(f->f_flags&O_DIRECT){if(!f->f_mapping->a_ops||((!f->f_mapping->a_ops->direct_IO)&&(!f->f_mapping->a_ops->get_xip_page))){fput(f);f=ERR_PTR(-EINVAL);}}当文件打开时指定了O_DIRECT标识符,操作系统就会知道下一次对该文件的读或写操作必须使用直接I/O的方法。接下来,让我们看看当进程通过read()系统调用读取设置了O_DIRECT标识符的文件时,系统做了什么。函数read()的原型如下:ssize_tread(intfeledes,void*buff,size_tnbytes);read()函数在操作系统中的入口函数是sys_read(),其主要调用函数图如下:sys_read()|-----vfs_read()|----generic_file_read()|----generic_file_aio_read()|------------generic_file_direct_IO()函数sys_read()从中获取文件描述符该过程和文件的当前操作位置之后,会调用vfs_read()函数执行具体的操作过程,vfs_read()函数最终调用文件结构中的相关操作完成文件读取操作,即即,调用了generic_file_read()函数,其代码如下:;ssize_tret;init_sync_kiocb(&kireadocb,filp);ret=__generic_file_aio_(&kiocb,&local_iov,1,ppos);if(-EIOCBQUEUED==ret)ret=wait_on_sync_kiocb(&kiocb);returnret;}函数generic_file_read()初始化iovec和kiocb描述符。描述符iovec主要用来存放两个内容:用于接收读取数据的用户地址空间缓冲区的地址和缓冲区的大小;描述符kiocb用于跟踪I/O操作的完成状态。之后,函数generic_file_read()调用函数__generic_file_aio_read()。该函数检查iovec中描述的用户地址空间缓冲区是否可用,然后检查访问模式,如果访问模式描述符设置为O_DIRECT,则执行与直接I/O相关的代码。__generic_file_aio_read()函数中直接I/O相关的代码如下:mapping->host;retval=0;if(!count)gotoout;size=i_size_read(inode);if(pos
