当前位置: 首页 > news >正文

草稿!Linux网络系统总结!

网络系统

文件传输

  • 磁盘是计算机系统最慢的硬件设备之一,读写速度比内存慢百倍以上,
  • 所以优化磁盘的技术非常多
  • 零拷贝 直接IO 异步IO等
  • 都是为了提升系统吞吐量
  • 操作系统内核中还有磁盘高速缓冲区 目的是减少磁盘访问次数

以文件传输,分析io工作方式 以及如何优化传输文件的性能

DMA技术

传统IO过程

在这里插入图片描述

  • CPU发出对应的指令(IO 请求)给磁盘控制器,然后返回(是指继续手头工作么?)
  • 磁盘控制器收到指令/请求后,开始准备数据,会把数据放到一个 磁盘控制器的内部缓冲区,然后产生一个中断信号,发送给cpu
  • cpu收到中断信号后,停下手头工作,接着把磁盘控制器的缓冲区数据 一次一个字节地读取进cpu自己地寄存器,最后把寄存器里面地数据写入 内存!
  • 数据传输期间cpu无法执行其他任务!

整个数据传输过程,都需要cpu亲自参与,亲自逐个搬运数据,而这个过程,cpu完全不能做其他事情

简单搬运几个字节是ok 但是 如果我们用千兆网卡或者硬盘传输大量数据 都用cpu来搬运 是不行的 cpu资源非常重要!不能浪费在这个简单工作上面!

于是计算机科学家发明了直接内存访问DMA direct Memory access!技术

DMA
在进行io设备和内存的数据传输时 数据搬运的工作全部交给DMA控制器
cpu不再参与 任何与数据搬运有关的工作!只负责发起io请求(并且是对DMA控制器发起这个请求!) 接收数据搬运完毕的信号(从DMA接收!)
cpu不再直接与磁盘交互 数据是DMA搬运的 ! 交互也改为了CPU与DMA~

这样 cpu就可以在DMA处理io任务时去处理其他任务!

具体的过程
涉及到用户进程 操作系统cpu DMA 磁盘 这几个对象
用户进程在调用读取数据的时候 会进入阻塞状态

  • 用户进程希望从磁盘读取数据,会调用read方法,向操作系统发送io请求 请求读取数据到用户进程自身的内存缓冲区!发送read后 用户进程会进入阻塞状态,等待读取
  • 操作系统cpu收到请求后,进一步将io请求发送给DMA,然后就去执行其他任务
  • DMA收到操作系统发来的io请求后 进一步将请求发送给 磁盘
  • 磁盘收到DMA发来 的io请求之后
    • 先把用户进程请求的数据 放到磁盘控制器自己的缓冲区
    • 当磁盘控制器的缓冲区被读满
    • 向DMA发送中断信号 告知DMA,磁盘控制器的缓冲区已满
  • DMA收到磁盘信号后,
    • 将磁盘控制器缓冲区中的数据拷贝到操作系统内核缓冲区
    • 此时不占用cpu cpu可以执行其他
  • 当DMA读取到足够多的数据 就会发送中断信号给cpu
  • cpu收到dma的中断信号后,就知道了数据已经准备够了,于是将内核中的数据拷贝到用户空间的用户进程!最后返回 用户进程也结束阻塞

cpu不再 参与直接的搬运工作 也就是 将数据从磁盘控制器缓冲区 搬运到内核空间的工作 这部分搬运工的工作 全部由dma完成

但是cpu对于io工作依旧是必不可少的 因为用户进程需要什么数据 从哪里搬到哪里 都需要用户进程通过cpu来告诉dma来控制dma!!

传统文件传输极其糟糕的四次切换和四次拷贝!

比如 服务端可以给客户端提供文件传输功能 , 最简单的方式就是两步:

  • 从服务端磁盘读文件
  • 网络协议发送给客户端
    注意 我们只要传输一份数据 从服务端的磁盘 到 网卡 然后再通过网卡发送出去!
    但是这么简单的一个过程却发生了四次拷贝 以及 四次 用户态内核态之间的来回切换!!!

比如这个过程的代码是

read(file, tmp_buf, len);// 服务端磁盘读取数据
write(socket, tmp_buf, len);// 数据写入网卡

在这里插入图片描述

用到 两个系统调用!read()和write() 每次系统调用都得先从用户态切换内核态,内核完成任务后,再从内核态切换用户态
这样就发生了四次上下文切换!一次切换虽然只需要几十纳秒到几微秒 看上去很短 但是高并发场景下 这会被大大放大!从而拖垮系统性能!

还有四次数据拷贝!
其中两次是DMA的拷贝!另外两次是通过CPU拷贝!

  • 第一次拷贝:磁盘数据 经由 DMA搬运 到 操作系统内核的缓冲区
  • 第二次拷贝:操作系统内核缓冲区的数据 经由CPU 搬运到 用户缓冲区
  • 第三次拷贝:用户态缓冲区数据 经由CPU 拷贝到 内核socket的缓冲区
  • 第四次拷贝:内核的socket缓冲区数据 经由DMA 搬运到网卡缓冲区

只是从磁盘发一份数据 !光是从磁盘到网卡都经过了四次拷贝!
磁盘 (DMA) 内核态缓冲区 (CPU)用户态缓冲区 (CPU) socket 缓冲区 (DMA) 网卡缓冲区!
只是搬运一份数据,结果却搬运了 4 次,过多的数据拷贝无疑会消耗 CPU 资源大大降低了系统性能。存在冗余的上文切换和数据拷贝,在高并发系统里是非常糟糕的多了很多不必要的开销,会严重影响系统性能。

要提高文件传输性能 最关键的是 减少用户态与内核态的上下文切换 和 内存拷贝的次数!

如何优化文件传输的性能?

首先思考为什么前面传统传输数据方式这么繁琐!四次拷贝 内核态 用户态搬来搬去?
根本原因 在于 读取磁盘 或者操作网卡 时,用户空间没有权限操作网卡和磁盘!内核权限最高 所以这些操作设备的过程都得操作系统内核完成!

一般需要内核时,我们会使用操作系统提供的系统调用函数!
一次系统调用就必然发生两次上下文切换
先从用户态切换到内核态
待内核态执行完任务 再切换回用户态交由进程代码执行

所以要想减少数据拷贝次数 要减少上下文切换 即内核和用户态切换 那就要减少系统调用次数

传统我文件传输方式的四次数据拷贝中 首先 第一次和第四次
从内核读缓冲区拷贝到用户的缓冲区
用户缓冲区拷贝到socket缓冲区 没必要
因为用户空间中我们不会对数据再次加工!
所以将数据搬运进用户空间就是多此一举!
用户的缓冲区没有存在必要

如何实现零拷贝呢?

两种减少上下文切换和数据拷贝次数的方法!:

  • mmap+write
  • sendfile

原来传统的方法是:

read(file, tmp_buf, len);// 服务端磁盘读取数据
write(socket, tmp_buf, len);// 数据写入网卡

read()这个系统调用会把内核缓冲区数据拷贝到 用户缓冲区!
为了减少这一步开销!我们用mmap()替换掉 read() 系统调用函数

// read(file, tmp_buf, len); 替换为
buf = mmap(file, len);
write(sockfd, buf, len);

(豆包详细解释这里每个参数 调用 传入传出的东西!)

在这里插入图片描述
read()是将内核态缓冲区数据拷贝到用户态缓冲区!
mmap()不在用户态和内核态之间搬运数据 而是
创建了用户态缓冲区和内核态缓冲区 之间的 共享缓冲区

mmap() 系统调用函数 直接将内核缓冲区的数据映射到 用户空间 这样 操作系统内核与用户空间就不需要进行数据拷贝

使用mmap()代替read() 可以减少一次拷贝开销!本质上是减少了 将内核缓冲区数据经由cpu拷贝到用户缓冲区这一步!

具体:

  1. 应用进程调用mmap() DMA 会把磁盘的数据拷贝到 内核的缓冲区中 。接着应用进程跟操作系统内核会共享这个缓冲区!!
  2. 应用进程再调用write()时 操作系统直接将内核缓冲区(视为与应用进程共享 )的数据拷贝到socket缓冲区!这一切都发生在内核态 !这一步由CPU搬运
  3. 最后, 还是把socket中数据搬运到拷贝到网卡缓冲区 这个过程是DMA搬运!

通过mmap() 来代替read() 可以减少一次数据拷贝!

磁盘缓冲区 (DMA)内核缓冲区(与用户共享) (cpu搬运) socket缓冲区 (DMA) 网卡

mmap()这种方式只是减少了一次数据拷贝过程!仍然是cpu拷贝一次占用cpu
仍然是两次系统调用 所以仍需要4次上下文切换!

sendfile()

这是Linux内核版本2.1中 提供的专门发送文件的系统调用函数sendfile()!|

#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

out_fd in_fd 分别是目的端 源端的文件描述符!
后面的offset count分别是源端的偏移量和复制数据的长度
返回值是实际复制数据长度!

sendfile一个代替了两个系统调用read() 和write()

减少了一次系统调用 减少了两次系统调用!

这样做彻底不再考虑用户态,跳过搬运到用户态缓冲区这一步!直接将内核缓冲区的数据拷贝到socket缓冲区!这样就只有两次上下文切换(因为一次系统调用!)和三次数据拷贝!
在这里插入图片描述
但这样仍然开销过大!仍然占用cpu进行第二次拷贝 也就是从内核缓冲区搬运到socket缓冲区!
还有优化空间!如果网卡支持SG-DMA!那么就可以把这一步也节省掉!
SG-DMA(The Scatter-Gather Direct Memory Access)技术(和普通的 DMA 有所不同),我们可以进一步减少通过 CPU 把内核缓冲区里的数据拷贝到 socket 缓冲区的过程。

我们可以在Linux系统通过这个命令查看网卡是否支持这个SG-DMA(The Scatter-Gather Direct Memory Access)技术!

$ ethtool -k eth0 | grep scatter-gather
scatter-gather: on

如果支持SG-DMA!?

  • 第一步依旧是DMA将磁盘数据拷贝搬运到内核缓冲区!
  • 第二步 不再是将全部数据拷贝搬运到 socket缓冲区! 而是 只将缓冲区描述符和数据长度传到socket缓冲区!然后由SG-DMA控制器直接将内核缓冲区的数据拷贝到网卡缓冲区!
  • 这个技术不再需要将数据从内核缓冲区拷贝到socket缓冲区!减少了一次CPU的数据拷贝!
    所以这个过程只执行了两次数据拷贝 且都由DMA操作 不占用CPU!

在这里插入图片描述
这个技术全程没有用cpu搬运数据,因为没有在内存层面拷贝数据 没用cpu所以叫做零拷贝技术!

所有数据都是DMA搬运!
SG-DMA 零拷贝相比传统方式 两次系统调用四次上下文开销 四次拷贝! 只有一次系统调用 两次上下文开销 两次拷贝 而且还是dma拷贝 没占用cpu!所以零拷贝技术至少将文件传输性能提高一倍不止!

使用零拷贝的项目有哪些?(可以深入拓展一点其他的项目!)

一个是kafka 这个开源项目!利用了零拷贝技术!!
大幅度提高了i/o吞吐量!零拷贝利用就是kafka处理海量数据这么快的原因!
看源码它调用了 Java NIO 库里的 transferTo 方法:
如果Linux支持sendfile() 系统调用实际上 transferTo 最后就是使用的sendfile()!

经过实测零拷贝能够缩短65%y以上的文件传输时间!
在这里插入图片描述
另外 就是Nginx 也支持零拷贝技术,一般默认是开启零拷贝技术,这样有利于提高文件传输的效率
(简单介绍这两个项目!)
http {

sendfile on

}
即Nginx可以配置
sendfile
设置为on表示 用零拷贝技术传输文件 : sendfile!这样只需要两次上下文开销(一次sendfile系统开销) 2次数据拷贝(DMA)
设置为off表示 使用传统文件传输技术! read + write!两次系统调用 四次上下文切换 四次拷贝(2次cpu 2次dma)

注意!要用sendfile需要Linux2.1以上版本!

PageCache?

刚刚我们所有的文件传输方法 第一步都是将磁盘缓冲区数据 拷贝到 内核缓冲区!
这个内核缓冲区 实际上就是 磁盘高速缓存PageCache

pagecache磁盘高速缓存(内核缓冲区)技术使得零拷贝性能进一步提升

我们都知道 磁盘读写速度太慢 我们应该 尽量把读写磁盘 替换成 读写内存!

于是我们用DMA把磁盘数据搬运到内存 这样就通过读内存代替读磁盘

但内存空间很小 内存注定只能拷贝磁盘很小一部分数据

那么问题是 选择那些磁盘数据拷贝到内存?更加有利于传输整个文件?

程序运行时具有{局部性}(豆包简要解析这个局部性!)所以通常刚访问的数据短时间内再次被访概率比较高

所以pagecache的主要功能就是缓存最近被访问的数据!
当空间不足时淘汰最久没访问的缓存!

即读取磁盘数据时,优先在pagecache里面找,如果数据存在直接返回,如果没有 从磁盘里面找!找到后还要缓存进pagecache!

pacache还有个预读功能!因为机械磁盘读取数据时候 需要先找到数据所在位置 具体是 通过磁头旋转到数据所在扇区 再开始顺序的读取数据 但是旋转磁头的操作非常耗时
所以pagecache 有预读 这个功能来提升效率!

比如read 每次只会 读取 32KB字节 刚开始read只读取前面0—32KB字节!但是内核会将后面的32—64kb 一同读取到pageCache 这样 后面如果读取32-64KB就可以直接从缓冲区pagecache中得到! 如果在32-64Kb淘汰出pagecache之前 被进程读取到 那收益极大!

所以总的来说pagecache两个大优点

  • 缓存最近被访问的数据
  • 预读

虽然这两个做法可以大大提高磁盘读写性能!
但是 对于GB级别大文件 pagecache很可能不起作用!

http://www.dtcms.com/a/292645.html

相关文章:

  • 碰一碰发视频源码搭建:支持OEM
  • 10.Java中的反射
  • 深度学习-全连接神经网络2
  • 使用EasyExcel导出明细数据
  • gpt面试题
  • 【学习路线】Python全栈开发攻略:从编程入门到AI应用实战
  • 深度学习篇---车道线循迹
  • 快速了解pandas库
  • opencv简介(附电子书资料)
  • VS Code 美化插件
  • Java (Spring AI) 实现MCP server实现数据库的智能问答
  • SpringAOP的实现原理和场景
  • 《汇编语言:基于X86处理器》第9章 字符串和数组(2)
  • 服务器租用:网络钓鱼具体是指什么?
  • Linux 内核与底层开发
  • Linux 下分卷压缩与解压缩全指南:ZIP 与 TAR.GZ 实战
  • Python趣味算法:实现任意进制转换算法原理+源码
  • Spring Boot环境搭建与核心原理深度解析
  • 【Dij】P1807 最长路
  • Linux文件——文件系统Ext2(1)_理解硬件
  • js的基本内容:引用、变量、打印、交互、定时器、demo操作
  • 【LeetCode 热题 100】46. 全排列——回溯
  • Windows 编程辅助技能:转到文档
  • 【方案】网页由微应用拼图,微前端
  • 『 C++ 入门到放弃 』- 红黑树
  • 一文详解Java类中的构造器是什么及主要特性
  • 70.爬楼梯
  • ABP VNext 报表:EPPlus DinkToPdf 多格式导出
  • redis秒杀之lua脚本
  • 20250722解决在Ubuntu 24.04.2下编译RD-RK3588开发板的Android13出现找不到python2的问题