【计算机原理】网络系统


前言

本文为 《图解系统》系列文章的个人学习笔记,对具体知识点与示例进行了归纳整理,详细内容参考小林coding



一、 什么是零拷贝?

1.1 传统 I/O 过程 & DMA 技术引入

在没有 DMA 技术前,I/O 的过程是这样的:

在这里插入图片描述
整个数据的传输过程,都要需要 CPU 亲自参与搬运数据的过程,而且这个过程,CPU 是不能做其他事情的。

直接内存访问(Direct Memory Access,DMA) 技术引入后,在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务。

在这里插入图片描述

1.2 传统文件传输的性能问题

如果服务端要提供文件传输的功能,能想到的最简单的方式是:将磁盘上的文件读取出来,然后通过网络协议发送给客户端。上述操作一般需要使用两个系统调用:

read(file, tmp_buf, len);
write(socket, tmp_buf, len);

数据读取(read())和写入(wirte())是从用户空间到内核空间来回复制,而内核空间的数据是通过操作系统层面的 I/O 接口从磁盘读取或写入。

如图所示,传统文件传输方式将涉及 4 次 用户态与内核态的上下文切换,以及 4 次数据拷贝。显然,传统文件传输过程中,存在这多次不必要的上下文切换和内存拷贝,这极大的限制了文件传输性能。在这里插入图片描述

1.3 文件传输性能优化——零拷贝的引入

A. 优化思路

要想提高文件传输的性能,就需要减少「用户态与内核态的上下文切换」和「内存拷贝」的次数。

  • 针对用户态与内核态的上下文切换,由于一次系统调用必然会发生 2 次上下文切换(用户态 - 内核态 - 用户态),因此需要减少减少系统调用的次数。
  • 针对内存拷贝,若在用户空间我们并不会对数据「再加工」,数据实际上可以不用搬运到用户空间。

B. 零拷贝的实现

零拷贝技术实现的方式通常有 2 种:
(1)mmap + write:减少内存拷贝次数(数据不再搬运至用户空间)。
(2)senfile:减少系统调用次数 + 减少内存拷贝次数(一次系统调用完成读/写两个操作 + 数据不再搬运至用户空间)。
此外,若网卡支持 SG-DMA技术,还可以进一步减少内核空间的内存拷贝次数(将内核缓冲区里的数据直接拷贝到 网卡,跳过 socket 缓冲区)。

优化方案1. mmap + write

read() 系统调用的过程中会把内核缓冲区的数据拷贝到用户的缓冲区里,于是为了减少这一步开销,我们可以用 mmap() 替换 read() 系统调用函数。

mmap() 系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,因此操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。

buf = mmap(file, len);
write(sockfd, buf, len);

但这还不是最理想的零拷贝,因为仍然需要通过 CPU 把内核缓冲区的数据拷贝到 socket 缓冲区里,而且仍然需要 4 次上下文切换和 3 次数据拷贝次数。
在这里插入图片描述

方案2. sendfile

在 Linux 内核版本 2.1 中,提供了一个专门发送文件的系统调用函数sendfile(),函数形式如下:

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

前两个参数分别是目的端和源端的文件描述符,后面两个参数是源端的偏移量和复制数据的长度,返回值是实际复制数据的长度。该系统调用有一下两个特点:

  • senfile() 替代了前面的 read()write() 这两个系统调用,这样就可以减少一次系统调用,也就减少了 2 次上下文切换的开销。
  • senfile()可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态。

在该方案下,文件传输过程需要 2 次上下文切换和 3 次数据拷贝次数。
在这里插入图片描述

方案3. sendfile + SG-DMA

如果网卡支持 SG-DMA 技术,可以进一步减少通过 CPU 把内核缓冲区里的数据拷贝到 socket 缓冲区的过程。
在该方案下,文件传输过程只需要 2 次上下文切换和 2 次数据拷贝次数。

在这里插入图片描述

Kafka 和 Nginx 都有实现零拷贝技术,这将大大提高文件传输的性能。

C. PageCache 的作用?

零拷贝技术是基于 PageCache 的,PageCache 会缓存最近访问的「热点」数据,提升了访问缓存数据的性能,同时,为了解决机械硬盘寻址慢的问题。这些优势,进一步提升了零拷贝的性能。

需要注意的是,零拷贝技术是不允许进程对文件内容作进一步的加工的,比如压缩数据再发送。

另外,当传输大文件时,不能使用零拷贝,因为可能由于 PageCache 被大文件占据,而导致「热点」小文件无法利用到 PageCache,并且大文件的缓存命中率不高,这时就需要使用「异步 IO + 直接 IO 」的方式。

TIP:在 Nginx 里,可以通过配置,设定一个文件大小阈值,针对大文件使用异步 IO 和直接 IO,而对小文件使用零拷贝。


二、I/O 多路复用:select/poll/epoll

2.1 基本Socket模型

最基础的 TCP 的 Socket 编程,它是阻塞 I/O 模型,基本上只能一对一通信,那为了服务更多的客户端,我们需要改进网络 I/O 模型。

比较传统的方式是使用多进程/线程模型,每来一个客户端连接,就分配一个进程/线程,然后后续的读写都在对应的进程/线程,这种方式处理 100 个客户端没问题,但是当客户端增大到 10000 个时,10000 个进程/线程的调度、上下文切换以及它们占用的内存,都会成为瓶颈。

2.2 I/O 的多路复用

为了解决上面这个问题,就出现了 I/O 的多路复用,可以只在一个进程里处理多个文件的 I/O,Linux 下有三种提供 I/O 多路复用的 API,分别是:select、poll、epoll

A. select & poll

select 和 poll 并没有本质区别,它们内部都是使用「线性结构」来存储进程关注的 Socket 集合。

在使用的时候,首先需要把关注的 Socket 集合通过 select/pol 系统调用从用户态拷贝到内核态,然后由内核检测事件,当有网络事件产生时,内核需要遍历进程关注 Socket 集合,找到对应的 Socket,并设置其状态为可读/可写,然后把整个 Socket 集合从内核态拷贝到用户态,用户态还要继续遍历整个 Socket 集合找到可读/可写的Socket,然后对其处理。

很明显发现,select和 poll 的缺陷在于,当客户端越多,也就是 Socket 集合越大,Socket 集合的遍历和拷贝会带来很大的开销,因此也很难应对 C10K。

B. epoll

epoll 是解决 C10K 问题的利器,通过两个方面解决了 select/poll 的问题。

  • epoll 在内核里使用「红黑树」来关注进程所有待检测的 Socket,红黑树是个高效的数据结构,增删改一般时间复杂度是 O(logn),通过对这棵黑红树的管理,不需要像 select/poll 在每次操作时都传入整个 Socket 集合减少了内核和用户空间大量的数据拷贝和内存分配。
  • epoll 使用事件驱动的机制,内核里维护了一个「链表」来记录就绪事件,只将有事件发生的 Socket 集合传递给应用程序,不需要像select/poll 那样轮询扫描整个集合(包含有和无事件的 Socket ),大大提高了检测的效率。

而且,epoll 支持边缘触发和水平触发的方式,而 select/poll 只支持水平触发,一般而言,边缘触发的方式会比水平触发的效率高。


相关问题

  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值