如何在字符设备中执行直接 I/O

在字符设备中执行直接 I/O 可能是有害的,只有在确定了设置缓冲 I/O 的开销非常巨大的时候才建议使用直接 I/O。在 Linux 2.6 的内核中,实现直接 I/O 的关键是函数 get_user_pages() 函数。其函数原型如下所示:

int get_user_pages(struct task_struct *tsk,struct mm_struct *mm,unsigned long start,int len,int write,int force,struct page **pages,struct vm_area_struct **vmas);

该函数的参数含义如下所示:

·tsk:指向执行映射的进程的指针;该参数的主要用途是用来告诉操作系统内核,映射页面所产生的页错误由谁来负责,该参数几乎总是 current。

·mm:指向被映射的用户地址空间的内存管理结构的指针,该参数通常是 current->mm 。

·start: 需要映射的用户地址空间的地址。

·len:页内缓冲区的长度。

·write:如果需要对所映射的页面有写权限,该参数的设置得是非零。

·force:该参数的设置通知 get_user_pages() 函数无需考虑对指定内存页的保护,直接提供所请求的读或者写访问。

·page:输出参数。调用成功后,该参数中包含一个描述用户空间页面的 page 结构的指针列表。

·vmas:输出参数。若该参数非空,则该参数包含一个指向 vm_area_struct 结构的指针,该 vm_area_struct 结构包含了每一个所映射的页面。

在使用 get_user_pages() 函数的时候,往往还需要配合使用以下这些函数:

  1. void down_read(struct rw_semaphore *sem);  
  2. void up_read(struct rw_semaphore *sem);  
  3. void SetPageDirty(struct page *page);  
  4. void page_cache_release(struct page *page); 

首先,在使用 get_user_pages() 函数之前,需要先调用 down_read() 函数将 mmap 为获得用户地址空间的读取者 / 写入者信号量设置为读模式;在调用完 get_user_pages() 函数之后,再调用配对函数 up_read() 释放信号量 sem。若 get_user_pages() 调用失败,则返回错误代码;若调用成功,则返回实际被映射的页面数,该数目有可能比请求的数量少。调用成功后所映射的用户页面被锁在内存中,调用者可以通过 page 结构的指针去访问这些用户页面。

直接 I/O 的调用者必须进行善后工作,一旦直接 I/O 操作完成,用户内存页面必须从页缓存中释放。在用户内存页被释放之前,如果这些页面中的内容改变了,那么调用者必须要通知操作系统内核,否则虚拟存储子系统会认为这些页面是干净的,从而导致这些数据被修改了的页面在被释放之前无法被写回到永久存储中去。因此,如果改变了页中的数据,那么就必须使用 SetPageDirty() 函数标记出每个被改变的页。对于 Linux 2.6.18.1,该宏定义在 /include/linux/page_flags.h 中。执行该操作的代码一般需要先检查页,以确保该页不在内存映射的保留区域内,因为这个区的页是不会被交换出去的,其代码如下所示:

if (!PageReserved(page)) 
SetPageDirty(page);

但是,由于用户空间所映射的页面通常不会被标记为保留,所以上述代码中的检查并不是严格要求的。

最终,在直接 I/O 操作完成之后,不管页面是否被改变,它们都必须从页缓存中释放,否则那些页面永远都会存在在那里。函数 page_cache_release() 就是用于释放这些页的。页面被释放之后,调用者就不能再次访问它们。

关于如何在字符设备驱动程序中加入对直接 I/O 的支持,Linux 2.6.18.1 源代码中 /drivers/scsi/st.c 给出了一个完整的例子。其中,函数 sgl_map_user_pages()和 sgl_map_user_pages()几乎涵盖了本节中介绍的所有内容。

直接 I/O 技术的特点

直接 I/O 的优点

直接 I/O 最主要的优点就是通过减少操作系统内核缓冲区和应用程序地址空间的数据拷贝次数,降低了对文件读取和写入时所带来的 CPU 的使用以及内存带宽的占用。这对于某些特殊的应用程序,比如自缓存应用程序来说,不失为一种好的选择。如果要传输的数据量很大,使用直接 I/O 的方式进行数据传输,而不需要操作系统内核地址空间拷贝数据操作的参与,这将会大大提高性能。

直接 I/O 潜在可能存在的问题

直接 I/O 并不一定总能提供令人满意的性能上的飞跃。设置直接 I/O 的开销非常大,而直接 I/O 又不能提供缓存 I/O 的优势。缓存 I/O 的读操作可以从高速缓冲存储器中获取数据,而直接 I/O 的读数据操作会造成磁盘的同步读,这会带来性能上的差异 , 并且导致进程需要较长的时间才能执行完;对于写数据操作来说,使用直接 I/O 需要 write() 系统调用同步执行,否则应用程序将会不知道什么时候才能够再次使用它的 I/O 缓冲区。与直接 I/O 读操作类似的是,直接 I/O 写操作也会导致应用程序关闭缓慢。所以,应用程序使用直接 I/O 进行数据传输的时候通常会和使用异步 I/O 结合使用。

总结

Linux 中的直接 I/O 访问文件方式可以减少 CPU 的使用率以及内存带宽的占用,但是直接 I/O 有时候也会对性能产生负面影响。所以在使用直接 I/O 之前一定要对应用程序有一个很清醒的认识,只有在确定了设置缓冲 I/O 的开销非常巨大的情况下,才考虑使用直接 I/O。直接 I/O 经常需要跟异步 I/O 结合起来使用,本文对异步 I/O 没有作详细介绍,有兴趣的读者可以参看 Linux 2.6 中相关的文档介绍。

原文:http://www.ibm.com/developerworks/cn/linux/l-cn-directio/index.html?ca=drs


相关内容