read/write数据读写传输方式(转)
前言
笔者本打算撰写一篇讲解标准I/O(缓存I/O)的博文,但是发现已经有网友做过同样的工作,并且工作质量上乘,特转载于此。
原文地址http://lenky.info/archives/2012/08/1856
正文
利用系统调用函数read()/write()是我们平常用得最多的一种数据读写方式,大多数情况下我们并没有考虑这种数据读写方式的执行效率,因为在很多并不以数据频繁读写为性能瓶颈的应用程序中函数read()/write()消耗的执行时间可以忽略,但是它们内在具体实现和执行效率到底如何呢?下面我们就来进行详细的分析。
函数read()/write()定义在头文件unistd.h内,原型如下:
#include <unistd.h> ssize_t read(int fildes, void *buf, size_t nbyte); ssize_t write(int fildes, const void *buf, size_t nbyte);
在这里我并不打算讲解函数read()/write()的源码,简略的描述其执行过程,涉及到的主要调用关系如下图所示:
如果我们的服务器程序,比如nginx采用read()/write()数据读写传输方式,当某客户端发送“GET /index.htm HTTP/1.1”请求时,nginx则需将存放在站点根目录的index.htm文本文件当作响应数据发送给客户端。当没有启用mmap()的情况下,nginx完成这个响应数据的发送工作需要两步,首先利用函数read()将index.htm文本文件数据读入内存,接着利用函数write()将第一步读入内存的数据写到连接套接口描述符来完成响应数据的发送:
如上图所示,nginx应用程序利用read()/write()数据读写传输方式完成响应数据的发送工作一共需要4次上下文切换和4次数据拷贝(即假定为一次read()/write()就将index.htm文本文件数据发送完毕的情况,如果不只一次则切换和拷贝次数将会更多),这些切换和拷贝过程是不是必须的呢?答案是否定的。比如当在启用mmap()的情况下就可以减少一次数据拷贝,此时利用系统调用mmap()将文本文件index.htm数据拷贝到内核缓存区,并将拷贝映射目标地址的起始值返回给nginx应用程序,正是因为nginx应用程序有了这块内核缓存区的映射起始地址并且可以共享这块内核缓存区(系统调用mmap()实现的结果),因此在第一幅图中,从内核Buffer到用户Buffer再到Socket Buffer的拷贝就可以变成一次从内核Buffer到Socket Buffer的直接拷贝: