代码改变世界

linux直接I/O简单介绍

2011-08-28 11:51  后端技术  阅读(470)  评论(0编辑  收藏  举报

1.     缓冲I/O与直接I/O

 

什么是缓存 I/O (Buffered I/O)

缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。缓存 I/O 有以下这些优点:

  • 缓存 I/O 使用了操作系统内核缓冲区,在一定程度上分离了应用程序空间和实际的物理设备。
  • 缓存 I/O 可以减少读盘的次数,从而提高性能。

当应用程序尝试读取某块数据的时候,如果这块数据已经存放在了页缓存中,那么这块数据就可以立即返回给应用程序,而不需要经过实际的物理读盘操作。当然,如果数据在应用程序读取之前并未被存放在页缓存中,那么就需要先将数据从磁盘读到页缓存中去。对于写操作来说,应用程序也会将数据先写到页缓存中去,数据是否被立即写到磁盘上去取决于应用程序所采用的写操作机制:如果用户采用的是同步写机制( synchronous writes , 那么数据会立即被写回到磁盘上,应用程序会一直等到数据被写完为止;如果用户采用的是延迟写机制( deferred writes ),那么应用程序就完全不需要等到数据全部被写回到磁盘,数据只要被写到页缓存中去就可以了。在延迟写机制的情况下,操作系统会定期地将放在页缓存中的数据刷到磁盘上。与异步写机制( asynchronous writes )不同的是,延迟写机制在数据完全写到磁盘上的时候不会通知应用程序,而异步写机制在数据完全写到磁盘上的时候是会返回给应用程序的。所以延迟写机制本身是存在数据丢失的风险的,而异步写机制则不会有这方面的担心。

 

缓存 I/O 的缺点

在缓存 I/O 机制中,DMA 方式可以将数据直接从磁盘读到页缓存中,或者将数据从页缓存直接写回到磁盘上,而不能直接在应用程序地址空间和磁盘之间进行数据传输,这样的话,数据在传输过程中需要在应用程序地址空间和页缓存之间进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。

缓存I/O机制中,系统会将数据缓存在page cache中,当内存不太宽裕时势必会挤占其他文件的page cache空间。当新打开文件仅仅是为了读取一次或是后续不会频繁读取时,挤占原有的page cache会导致page cache的命中率下降,从而导致程序性能下降。

对于某些特殊的应用程序来说,避开操作系统内核缓冲区而直接在应用程序地址空间和磁盘之间传输数据会比使用操作系统内核缓冲区获取更好的性能,下面会介绍一下直接I/O

直接 I/O 技术的特点

当采用直接I/O方式时,磁盘文件中的内容会直接拷贝到用户指定的用户空间,并不会像缓冲I/O那样先拷贝到pagecache,然后再从page cache拷贝到用户空间。直接 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 结合使用。

2.     使用直接I/O进行编程

采用直接I/O方式,打开文件时需需要设置O_DIRECT,通过设置O_DIRECT来实现直接I/O,在使用O_DIRECT的注意bufferaddress必须是block alignment, 可以用posix_memalign()函数分配内存以得到这样的buffer

 1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <fcntl.h>
6 #include <errno.h>
7
8 const int BUFSIZE = 1204 * 1024 * 4;
9
10 int main(int argc,char* argv[])
11 {
12 if( argc != 2)
13 {
14 fprintf(stderr,"usage : ./dio_cat filename\n");
15 return -1;
16 }
17
18 char *read_buf = NULL;
19 if(posix_memalign((void**)&read_buf, 512, BUFSIZE) != 0) //512
20 {
21 fprintf(stderr,"posix_memalign memory failed\n");
22 return -1;
23 }
24
25 int fd = open(argv[1], O_RDONLY | O_DIRECT, 0777);
26 if(fd < 0)
27 {
28 fprintf(stderr,"can't open file %s: %s\n",argv[1],strerror(errno));
29 return -1;
30 }
31
32 int bytes = 0;
33 while((bytes = read(fd, read_buf, BUFSIZE)) > 0)
34 {
35 fprintf(stderr,"%s",read_buf);
36 }
37
38 close(fd);
39 return 0;
40
41 }

3.     小结

直接I/O的适用场景非常特定,使用者最好在确定应用场景非常适合时再采用。

 

4.     参考资料

1)      http://www.ibm.com/developerworks/cn/linux/l-cn-directio/index.html?ca=drs-  linux直接I/O介绍