日期:2014-05-16  浏览次数:20623 次

Linux的IO系统常用系统调用及分析

    Linux的IO从广义上来说包括很多类,从狭义上来说只是讲磁盘的IO。在本文中我也就只是主要介绍磁盘的IO。在这里我对Linux的磁盘IO的常用系统调用进行深入一些的分析,希望在大家在磁盘IO产生瓶颈的时候,能够帮助做优化,同时我也是对之前的一篇博文作总结。转载此文请标明出处:http://blog.csdn.net/jiang1st2010/article/details/8373063


一、读磁盘:

ssize_t read(int fd,void * buf ,size_t count);

        读磁盘时,最常用的系统调用就是read(或者fread)。大家都很熟悉它了,首先fopen打开一个文件,同时malloc一段内存,最后调用read函数将fp指向的文件读到这段内存当中。执行完毕后,文件读写位置会随读取到的字节移动。

        虽然很简单,也最通用,但是read函数的执行过程有些同学可能不大了解。这个过程可以总结为下面这个图:

     

        图中从上到下的三个位置依次表示:

  • 文件在磁盘中的存储地址;
  • 内核维护的文件的cache(也叫做page cache,4k为一页,每一页是一个基本的cache单位);
  • 用户态的buffer(read函数中分配的那段内存)。

         发起一次读请求后,内核会先看一下,要读的文件是否已经缓存在内核的页面里面了。如果是,则直接从内核的buffer中复制到用户态的buffer里面。如果不是,内核会发起一次对文件的IO,读到内核的cache中,然后才会拷贝到buffer中。

         这个行为有三个特点:

  • read的行为是一种阻塞的系统调用(堵在这,直到拿到数据为止);
  • 以内核为缓冲,从内核到用户内存进行了一次内存拷贝(而内存拷贝是很占用CPU的)
  • 没有显示地通知使用者从文件的哪个位置开始去读。使用者需要利用文件指针,通过lseek之类的系统调用来指定位置。

         这三个特点其实都是有很多缺点的(相信在我的描述下大家也体会到了)。对于第二个特点,可以采用direct IO消除这个内核buffer的过程(方法是fopen的时候在标志位上加一个direct标志),不过带来的问题则是无法利用cache,这显然不是一种很好的解决办法。所以在很多场景下,直接用read不是很高效的。接下来,我就要依次为大家介绍几种更高效的系统调用。


ssize_t pread(intfd, void *buf, size_tcount, off_toffset);

        pread与read在功能上完全一样,只是多一个参数:要读的文件的起始地址。在多线程的情况下,多个线程要同时读同一个文件的不同地址时,要对文件指针加锁,影响了性能,而用pread后就不需要加锁了,使程序更加高效。解决了第三个问题。


ssize_t readahead(int fd, off64_t offset, size_t count);

       readahead是一种非阻塞的系统调用,它只要求内核把这段数据预读到内核的buffer中,并不等待它执行完就返回了。执行readahead后再执行read的话,由于之前已经并行地让内核读取了数据了,这时更多地是直接从内核的buffer中直接copy到用户的buffer里,效率就有所提升了。这样就解决了read的第一个问题。我们可以看下面这个例子:

//original function
while(time < Ncnts)
{
   read(fd[i], buf[i], lens);
   process(buf[i]);   //相对较长的处理过程
}