图解系统 - 网络系统

本文介绍了零拷贝技术的基本概念,包括使用DMA在数据传输中的作用,以及mmap和sendfile两种常见实现方式。同时详细讨论了I/O多路复用,特别是select、poll和epoll的区别,以及边缘触发和水平触发模式在处理I/O事件时的不同策略。
摘要由CSDN通过智能技术生成

9.1 什么是零拷贝?

说白了就是使用DMA技术

在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务。

如何实现零拷贝?

零拷贝技术实现的方式通常有 2 种:

  • mmap + write
  • sendfile

下面就谈一谈,它们是如何减少「上下文切换」和「数据拷贝」的次数。

mmap + write

使用mmap代替read()

sendfile

Linux内核2.1版本

Linux内核2.4以上

支持网卡支持 SG-DMA 技术

PageCache的作用

将磁盘数据放入缓存中,提升零拷贝的性能

大文件拷贝

在传输大文件(GB 级别的文件)的时候,PageCache 会不起作用,那就白白浪费 DMA 多做的一次数据拷贝,造成性能的降低,即使使用了 PageCache 的零拷贝也会损失性能

大文件传输用什么方式实现?

1. 发起异步IO请求,直接返回

2. 内核对磁盘发起IO请求

3. 发起IO中断信号

4. 将数据从磁盘控制器缓冲区拷贝到用户缓冲区

5. 通知用户读取成功

6. 用户处理IO

9.2 I/O 多路复用:select/poll/epoll

TCP通信

TCP连接四元组:本机IP、本机端口、对端IP、对端端口

客户端与服务端的通信方式使用TCP,详情可以看网络的TCP协议

但是,TCP连接只能一对一且阻塞的,那么服务端如何服务多个客户端呢?

如何服务多个客户端呢?

多进程模型

缺点很明显,占用资源过多,如果是100个客户端还能应对,一旦高达1w时,肯定扛不住,因为每个进程必定会占用一定的系统资源,且进程上下文切换对性能的消耗也是比较大

多线程模型

通过一个进程进行建立连接,使用线程池来创建线程,这样不会频繁创建和销毁线程

操作【已连接的socket的队列】时需要加锁,因为这个队列是全局的

C10K问题,以上两种模式都扛不住

I/O多路复用

select

实现多路复用:

  • 将已连接的Socket都放在一个文件描述符集合中,使用的是BitsMap数据结构,
  • 把文件描述符集合拷贝到内核中,内核会遍历它检查是否有网络事件产生,如果有对socket标记为可读或可写,再把整个文件描述符集合拷贝回用户态里
  • 用户态遍历找到可读或可写的Socket,再对其进行处理

BitsMap受内核中的FD_SETSIZE限制,默认值1024

poll
  • 实现的方式跟select没有太大区别,区别在于使用的数据结构是动态链表,突破了数量的限制
epoll(重点)

epoll通过两个方面解决了select/poll的问题

第一点,epoll内核使用红黑树来跟踪进程所有待检测的文件描述符

第二点,epoll使用事件驱动机制,内核维护了一个链表来记录就绪事件

边缘触发和水平触发

epoll 支持两种事件触发模式,分别是边缘触发(edge-triggered,ET水平触发(level-triggered,LT

  • 使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完;
  • 使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取;

举个例子,你的快递被放到了一个快递箱里,如果快递箱只会通过短信通知你一次,即使你一直没有去取,它也不会再发送第二条短信提醒你,这个方式就是边缘触发;如果快递箱发现你的快递没有被取出,它就会不停地发短信通知你,直到你取出了快递,它才消停,这个就是水平触发的方式。

这就是两者的区别,水平触发的意思是只要满足事件的条件,比如内核中有数据需要读,就一直不断地把这个事件传递给用户;而边缘触发的意思是只有第一次满足条件的时候才触发,之后就不会再传递同样的事件了。

如果使用水平触发模式,当内核通知文件描述符可读写时,接下来还可以继续去检测它的状态,看它是否依然可读或可写。所以在收到通知后,没必要一次执行尽可能多的读写操作。

如果使用边缘触发模式,I/O 事件发生时只会通知一次,而且我们不知道到底能读写多少数据,所以在收到通知后应尽可能地读写数据,以免错失读写的机会。因此,我们会循环从文件描述符读写数据,那么如果文件描述符是阻塞的,没有数据可读写时,进程会阻塞在读写函数那里,程序就没办法继续往下执行。所以,边缘触发模式一般和非阻塞 I/O 搭配使用,程序会一直执行 I/O 操作,直到系统调用(如 read 和 write)返回错误,错误类型为 EAGAIN 或 EWOULDBLOCK

一般来说,边缘触发的效率比水平触发的效率要高,因为边缘触发可以减少 epoll_wait 的系统调用次数,系统调用也是有一定的开销的的,毕竟也存在上下文的切换。

select/poll 只有水平触发模式,epoll 默认的触发模式是水平触发,但是可以根据应用场景设置为边缘触发模式。

另外,使用 I/O 多路复用时,最好搭配非阻塞 I/O 一起使用

待更新。。。

  • 10
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值