以简单的磁盘读写例子,从磁盘中读取A文件,写入到B文件。A文件数据是从磁盘开始,然后载入到“内核缓冲区”,然后再拷贝到“用户缓冲区”,我们才可以对数据进行处理。写入的时候,也同理,从“用户态缓冲区”载入到“内核缓冲区”,后写入到磁盘B文件。

  这样写文件很累吧,于是有人觉得这里可以跳过“用户缓冲区”的拷贝。其实,这是MMP(Memory-Mapping,内存映射)的实现,建立一个磁盘空间和内存的直接映射,数据不再复制到“用户态缓冲区”,而是返回一个指向内存空间的指针。于是,我们之前的读写文件例子,会变成,A文件数据从磁盘载入到“内核缓冲区”,然后从“内核缓冲区”复制到B文件的“内核缓冲区”,B文件再从”内核缓冲区“写回到磁盘中。这个过程,减少了一次内存拷贝,同时也少内存占用。

  好了,回到sendfile的话题上来,简单的说,sendfile的做法和MMP类似,是减少数据从”内核态缓冲区“到”用户态缓冲区“的内存拷贝。
  默认的磁盘文件读取,到传输给socket,流程(不使用sendfile)是:

  使用sendfile之后:

  这种方式,不仅节省了内存,而且还有CPU的开销。
  四、节约Web服务器的CPU
  对Web服务器而言,CPU是另一个非常核心的系统资源。虽然一般情况下,我们认为业务程序的执行消耗了我们主要CPU。但是,Web服务程序而言,多线程/多进程的上下文切换,也是比较消耗CPU资源的。一个进程/线程通常不能长期占有CPU,当发生阻塞或者时间片用完,无法继续占用CPU,这个时候,会发生上下文切换,CPU时间片从老进程/线程切换到新的。除此之外,在并发连接数目很高的场景下,对这些用户建立的连接(socket文件描述符)状态的轮询和检测,也是比较消耗CPU的。
  而Apache和Nginx的发展和演变,也在努力减少CPU开销。
  1.Select/Poll(Apache早期版本的I/O多路复用)
  通常,Web服务都要维护很多个和用户通信的socket文件描述符,I/O多路复用,其实是为了方便对这些文件描述符的管理和检测。Apache早期版本,是使用select的模式,简单的说,是将这些我们关注的socket文件描述符交给内核,让内核告诉我们,那些描述符可操作。Poll与select原理基本相同,因此放在一起,它们之间的区别,不赘叙了哈。
  select/poll返回的是一个我们之前提交的文件描述符集合(内核将其中可读、可写或者异常状态的socket文件描述符的标识位修改了),我们需要通过轮询检查才能获得我们可以操作的文件描述符。在这个过程中,不断重复执行。在实际应用场景中,大部分被我们监控的socket文件描述符,都是”空闲的“,也是说,不能操作。我们对整个集合轮询,是为了找了少部分我们可以操作的socket文件描述符。于是,当我们监控的socket文件描述符越多(用户并发连接数越来越多),这个轮询工作,也越来越沉重,进而导致增大了CPU的开销。

  如果我们监控的socket文件描述符,几乎都是”活跃的“,反而使用这种模式更合适一点。