浅析Linux中的零拷贝技术

2023-05-16

浅析Linux中的零拷贝技术

目录

浅析Linux中的零拷贝技术

引文

什么是零拷贝技术(zero-copy)?

使用 mmap

使用sendfile

使用splice


本文探讨Linux中 主要的几种零拷贝技术 以及零拷贝技术 适用的场景 。为了迅速建立起零拷贝的概念,我们拿一个常用的场景进行引入:

引文

在写一个服务端程序时(Web Server或者文件服务器),文件下载是一个基本功能。这时候服务端的任务是:将服务端主机磁盘中的文件不做修改地从已连接的socket发出去,我们通常用下面的代码完成:

while((n = read(diskfd, buf, BUF_SIZE)) > 0)
{
    write(sockfd, buf , n);
}

基本操作就是循环的从磁盘读入文件内容到缓冲区,再将缓冲区的内容发送到socket。但是由于Linux的I/O操作默认是缓冲I/O。这里面主要使用的也就是read和write两个系统调用,我们并不知道操作系统在其中做了什么。实际上在以上I/O操作中,发生了多次的数据拷贝。

当应用程序访问某块数据时,操作系统首先会检查,是不是最近访问过此文件,文件内容是否缓存在内核缓冲区,如果是,

操作系统则直接根据read系统调用提供的buf地址,将内核缓冲区的内容拷贝到buf所指定的用户空间缓冲区中去。

如果不是,操作系统则首先将磁盘上的数据拷贝的内核缓冲区,这一步目前主要依靠DMA来传输,然后再把内核缓冲区上的内容拷贝到用户缓冲区中。

接下来,write系统调用再把用户缓冲区的内容拷贝到网络堆栈相关的内核缓冲区中,最后socket再把内核缓冲区的内容发送到网卡上。说了这么多,不如看图清楚:

数据拷贝

从上图中可以看出,共产生了四次数据拷贝,即使使用了DMA来处理了与硬件的通讯,CPU仍然需要处理两次数据拷贝,与此同时,在用户态与内核态也发生了多次上下文切换,无疑也加重了CPU负担。

在此过程中,我们没有对文件内容做任何修改,那么在内核空间和用户空间来回拷贝数据无疑就是一种浪费,而零拷贝主要就是为了解决这种低效性。

什么是零拷贝技术(zero-copy)?

零拷贝主要的任务就是避免CPU将数据从一块存储拷贝到另外一块存储,主要就是利用各种零拷贝技术,避免让CPU做大量的数据拷贝任务,减少不必要的拷贝,或者让别的组件来做这一类简单的数据传输任务,让CPU解脱出来专注于别的任务。这样就可以让系统资源的利用更加有效。

我们继续回到引文中的例子,我们如何减少数据拷贝的次数呢?一个很明显的着力点就是减少数据在内核空间和用户空间来回拷贝,这也引入了零拷贝的一个类型:

让数据传输不需要经过 user space。

使用 mmap

我们减少拷贝次数的一种方法是调用mmap()来代替read调用:

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

应用程序调用mmap(),磁盘上的数据会通过DMA被拷贝的内核缓冲区,接着操作系统会把这段内核缓冲区与应用程序共享,这样就不需要把内核缓冲区的内容往用户空间拷贝。应用程序再调用write(),操作系统直接将内核缓冲区的内容拷贝到socket缓冲区中,这一切都发生在内核态,最后,socket缓冲区再把数据发到网卡去。同样的,看图很简单:

mmap

使用mmap替代read很明显减少了一次拷贝,当拷贝数据量很大时,无疑提升了效率。但是使用mmap是有代价的。当你使用mmap时,你可能会遇到一些隐藏的陷阱。例如,当你的程序map了一个文件,但是当这个文件被另一个进程截断(truncate)时, write系统调用会因为访问非法地址而被SIGBUS信号终止。SIGBUS信号默认会杀死你的进程并产生一个coredump,如果你的服务器这样被中止了,那会产生一笔损失。

通常我们使用以下解决方案避免这种问题:

1. 为SIGBUS信号建立信号处理程序

当遇到SIGBUS信号时,信号处理程序简单地返回,write系统调用在被中断之前会返回已经写入的字节数,并且errno会被设置成success,但是这是一种糟糕的处理办法,因为你并没有解决问题的实质核心。

2. 使用文件租借锁

通常我们使用这种方法,在文件描述符上使用租借锁,我们为文件向内核申请一个租借锁,当其它进程想要截断这个文件时,内核会向我们发送一个实时的RTSIGNALLEASE信号,告诉我们内核正在破坏你加持在文件上的读写锁。这样在程序访问非法内存并且被SIGBUS杀死之前,你的write系统调用会被中断。write会返回已经写入的字节数,并且置errno为success。

我们应该在mmap文件之前加锁,并且在操作完文件后解锁:

if(fcntl(diskfd, F_SETSIG, RT_SIGNAL_LEASE) == -1) 
{
    perror("kernel lease set signal");
    return -1;
}

/* l_type can be F_RDLCK F_WRLCK 加锁*/
/* l_type can be F_UNLCK 解锁*/
if(fcntl(diskfd, F_SETLEASE, l_type))
{
    perror("kernel lease set type");
    return -1;
}

使用sendfile

从2.1版内核开始,Linux引入了sendfile来简化操作:

  1. #include<sys/sendfile.h>

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

系统调用sendfile()在代表输入文件的描述符infd和代表输出文件的描述符outfd之间传送文件内容(字节)。描述符outfd必须指向一个套接字,而infd指向的文件必须是可以mmap的。这些局限限制了sendfile的使用,使sendfile只能将数据从文件传递到套接字上,反之则不行。

使用sendfile不仅减少了数据拷贝的次数,还减少了上下文切换,数据传送始终只发生在kernel space。

sendfile系统调用过程

在我们调用sendfile时,如果有其它进程截断了文件会发生什么呢?假设我们没有设置任何信号处理程序,sendfile调用仅仅返回它在被中断之前已经传输的字节数,errno会被置为success。如果我们在调用sendfile之前给文件加了锁,sendfile的行为仍然和之前相同,我们还会收到RTSIGNALLEASE的信号。

目前为止,我们已经减少了数据拷贝的次数了,但是仍然存在一次拷贝,就是页缓存到socket缓存的拷贝。那么能不能把这个拷贝也省略呢?

借助于硬件上的帮助,我们是可以办到的。之前我们是把页缓存的数据拷贝到socket缓存中,实际上,我们仅仅需要把缓冲区描述符传到socket缓冲区,再把数据长度传过去,这样DMA控制器直接将页缓存中的数据打包发送到网络中就可以了。

总结一下,sendfile系统调用利用DMA引擎将文件内容拷贝到内核缓冲区去,然后将带有文件位置和长度信息的缓冲区描述符添加socket缓冲区去,这一步不会将内核中的数据拷贝到socket缓冲区中,DMA引擎会将内核缓冲区的数据拷贝到协议引擎中去,避免了最后一次拷贝。

带DMA的sendfile

不过这一种收集拷贝功能是需要硬件以及驱动程序支持的。

使用splice

sendfile只适用于将数据从文件拷贝到套接字上,限定了它的使用范围。Linux在2.6.17版本引入splice系统调用,用于在两个文件描述符中移动数据:

#define _GNU_SOURCE /* See feature_test_macros(7) */
#include<fcntl.h>

ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsignedint flags);

splice调用在两个文件描述符之间移动数据,而不需要数据在内核空间和用户空间来回拷贝。他从fdin拷贝len长度的数据到fdout,但是有一方必须是管道设备,这也是目前splice的一些局限性。flags参数有以下几种取值:

  • SPLICEFMOVE :尝试去移动数据而不是拷贝数据。这仅仅是对内核的一个小提示:如果内核不能从pipe移动数据或者pipe的缓存不是一个整页面,仍然需要拷贝数据。Linux最初的实现有些问题,所以从2.6.21开始这个选项不起作用,后面的Linux版本应该会实现。

  • SPLICEFNONBLOCK :splice 操作不会被阻塞。然而,如果文件描述符没有被设置为不可被阻塞方式的 I/O ,那么调用 splice 有可能仍然被阻塞。

  • SPLICEFMORE:后面的splice调用会有更多的数据。

splice调用利用了Linux提出的管道缓冲区机制, 所以至少一个描述符要为管道。

以上几种零拷贝技术都是减少数据在用户空间和内核空间拷贝技术实现的,但是有些时候,数据必须在用户空间和内核空间之间拷贝。这时候,我们只能针对数据在用户空间和内核空间拷贝的时机上下功夫了。Linux通常利用写时复制(copy on write)来减少系统开销,这个技术又时常称作COW。

由于篇幅原因,本文不详细介绍写时复制。大概描述下就是:如果多个程序同时访问同一块数据,那么每个程序都拥有指向这块数据的指针,在每个程序看来,自己都是独立拥有这块数据的,只有当程序需要对数据内容进行修改时,才会把数据内容拷贝到程序自己的应用空间里去,这时候,数据才成为该程序的私有数据。如果程序不需要对数据进行修改,那么永远都不需要拷贝数据到自己的应用空间里。这样就减少了数据的拷贝。写时复制的内容可以再写一篇文章了。

除此之外,还有一些零拷贝技术,比如传统的Linux I/O中加上O_DIRECT标记可以直接I/O,避免了自动缓存,还有尚未成熟的fbufs技术,本文尚未覆盖所有零拷贝技术,只是介绍常见的一些,如有兴趣,可以自行研究,一般成熟的服务端项目也会自己改造内核中有关I/O的部分,提高自己的数据传输速率。


 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

浅析Linux中的零拷贝技术 的相关文章

  • Linux C++ 错误:未定义对“dlopen”的引用

    我在 Linux 上使用 C Eclipse 工作 并且想要使用一个库 Eclipse 向我显示一个错误 undefined reference to dlopen 你知道解决办法吗 这是我的代码 include
  • 无法在 Linux 的 NetBeans 中编译 C++ 和 OpenGL (GLFW) 的简单源代码

    我开始学习 OpenGL glfw 我从教程中复制源代码并尝试编译它 但出现了错误 我想我已经正确安装了所有头文件 glm glfw 等 这是我的来源 我没有在头文件中使用这些字符 include iostream include stdi
  • linux新手关于嵌入式linux设备驱动的问题

    最近在研究linux驱动 正如我读过的那些文章所说 设备驱动程序模块很可能会根据内核的需要自动加载 因此我想知道内核如何确定为特定设备 声卡 I2C spi 设备 等 我也无法彻底想象内核如何在启动时检测每个硬件设备 与嵌入式linux相关
  • 确定 TCP Listen() 队列中当前积压的连接数

    有没有办法找出currentLinux 上 TCP 套接字上等待 Accept 的连接尝试次数 我想我可以在每个事件循环上点击 EWOULDBLOCK 之前计算成功的 Accept 数量 但我使用的是隐藏这些细节的高级库 Python Tw
  • 我们如何在使用循环时调用 ansible playbook 中的变量

    我有两个文件 其中这些文件包含server names and server IP s 我想更改 替换一些特定的server names and IP addressees根据要求在两个文件中 这与这篇文章 因为它被要求开设一个新职位 ht
  • Pthreads - 高内存使用率

    我正在用 C 编写一些东西 在 256Mb 系统上的 Linux 中创建大量 Pthread 我通常有 200Mb 的免费空间 当我使用少量线程运行该程序时 它可以工作 但是一旦我让它创建大约 100 个线程 它就会出现错误 因为系统内存不
  • 应用程序中两个不同版本的库

    考虑一个场景 其中有两个不同版本的共享库 考虑 A 1 so 链接到 B so A 2 so 链接到 C so 现在 B so 和 C so 都链接到 d exe 当 B so 想要调用 A 1 so 中的函数时 它最终会调用 A 2 so
  • Linux中如何避免sleep调用因信号而中断?

    我在 Linux 中使用实时信号来通知串行端口中新数据的到达 不幸的是 这会导致睡眠呼叫在有信号时被中断 有人知道避免这种行为的方法吗 我尝试使用常规信号 SIGUSR1 但我不断得到相同的行为 来自 nanosleep 联机帮助页 nan
  • 任何退出 bash 脚本但不退出终端的方法

    当我使用exitshell 脚本中的命令 该脚本将终止终端 提示符 有什么方法可以终止脚本然后停留在终端中吗 我的剧本run sh预计通过直接获取或从另一个脚本获取来执行 编辑 更具体地说 有两个脚本run2 sh as run sh ec
  • 在本地主机上使用相同的 IP 和端口创建套接字

    我在 Linux 上看到奇怪的行为 我看到远程端和本地端都显示相同的 IP 和端口组合 以下是 netstat 输出 netstat anp 网络统计grep 6102 tcp 0 0 139 185 44 123 61020 0 0 0
  • 如何在C(Linux utf8终端)中打印“盒子抽屉”Unicode字符?

    我正在尝试显示 方框图范围 2500 257F 中的 Unicode 字符 它应该是标准 utf8 Unicode 标准 版本 6 2 我根本做不到 我首先尝试使用旧的 ASCII 字符 但 Linux 终端以 utf8 显示 并且没有显示
  • 在 scapy 中通过物理环回发送数据包

    我最近发现了 Scapy 它看起来很棒 我正在尝试查看 NIC 上物理环回模块 存根上的简单流量 但是 Scapy sniff 没有给出任何结果 我正在做的发送数据包是 payload data 10 snf sniff filter ic
  • bash 将输出重定向到文件,但结果不完整

    重定向命令输出的问题已经被问过很多次了 但是我有一个奇怪的行为 我使用的是 bash shell debian 版本 4 3 30 1 release 并尝试将输出重定向到文件 但并非所有内容都记录在文件中 我尝试运行的 bin 文件是 l
  • Linux shell 从用户输入中获取设备 ID

    我正在为一个程序编写安装脚本 该程序需要在其配置中使用 lsusb 的设备 ID 因此我正在考虑执行以下操作 usblist lsusb put the list into a array for each line use the arr
  • 正则表达式删除块注释也删除 * 选择器

    我正在尝试使用 bash 从 css 文件中删除所有块注释 我有以下 sed 命令的正则表达式 sed r s w s w d 这可以很好地去除块注释 例如 This is a comment this is another comment
  • 为 Linux 编译 Objective-C 应用程序(API 覆盖范围)

    我可能在这里问一些奇怪的问题 但我不确定从哪里开始 问题是我正在考虑使用 Obj C 和 Foundation 类在 Mac 上编写一个命令行工具 但存在一个非常大的风险 那就是我希望能够为不同的 Linux 发行版编译它 以便将来作为服务
  • Linux无法删除文件

    当我找到文件时 我在删除它们时遇到问题 任务 必须找到带有空格的文件并将其删除 我的尝试 rm find L root grep i 但我有错误 rm cannot remove root test No such file or dire
  • Linux 使用 boost asio 拒绝套接字绑定权限

    我在绑定套接字时遇到问题 并且以用户身份运行程序时权限被拒绝 这行代码会产生错误 acceptor new boost asio ip tcp acceptor io boost asio ip tcp endpoint boost asi
  • ioctl 命令的用户权限检查

    我正在实现 char 驱动程序 Linux 并且我的驱动程序中有某些 IOCTL 命令仅需要由 ADMIN 执行 我的问题是如何在 ioctl 命令实现下检查用户权限并限制非特权用户访问 IOCTL 您可以使用bool capable in
  • 在内核代码中查找函数的最佳方法[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我开始浏览内核代码 遇到的一件事是如何跟踪函数调用 结构定义等 有没有一种好的方法可以快速跳转到函数定义并退出 我尝试过 Source N

随机推荐

  • c++ 后端linux开发常见问题

    计算机操作系统 xff08 Linux xff09 命令 xff1a netstat tcpdump ipcs ipcrm 这四个命令的熟练掌握程度基本上能体现实际开发和调试程序的经验 cpu 内存 硬盘 等等与系统性能调试相关的命令必须熟
  • STL c++ 使用小结

    各位看官 xff0c 欢迎来到趁热搬砖小码农的博客 在写C 43 43 程序的时候会发现STL是一个不错的东西 xff0c 减少了代码量 xff0c 使代码的复用率大大提高 xff0c 减轻了程序猿的负担 还有一个就是容器 xff0c 你会
  • Oracle面试题,带答案!

    1 你要对操纵Oracle数据库中的数据 下列哪个选项表示Oracle中select语句的功能 xff0c 并且不需要使用子查询 xff08 C xff09 A xff0e 可以用select语句改变Oracle中的数据 B xff0e 可
  • 什么是进程?什么是线程?总结

    1 什么是进程 xff1f 什么是线程 xff1f 进程是表示资源分配的基本单位 xff0c 又是调度运行的基本单位 例如 xff0c 用户运行自己的程序 xff0c 系统就创建一个进程 xff0c 并为它分配资源 xff0c 包括各种表格
  • C++知识点小结(趁热搬砖三年半的小码农) 2020年07月2日整理

    c 43 43 最好用易用的新特性 xff1a auto decltype https blog csdn net zyc2018 article details 93591189nullptr range forusing c 43 43
  • 如何定位内存泄漏问题

    如何定位内存泄漏问题 Things You 39 ll Need Proficiency in C 43 43 C 43 43 compilerDebugger and other investigative software tools
  • C++之future和promise

    C 43 43 之future和promise future和promise的作用是在不同线程之间传递数据 使用指针也可以完成数据的传递 xff0c 但是指针非常危险 xff0c 因为互斥量不能阻止指针的访问 xff1b 而且指针的方式传递
  • linux常用小知识点

    答案linux考试题 1 在登录Linux时 xff0c 一个具有唯一进程ID号的shell将被调用 xff0c 这个ID是什么 b A NID B PID C UID C CID 答 xff1a w命令查看用户tty终端信息 ps ef
  • 无锁编程基础

    背景 我们处在技术快速发展的时代 xff0c 竞争变得前所未有的激烈 xff0c 不仅要十八般武艺俱全 xff0c 还得选对正确的技术 xff0c 跟上发展的脚步 xff0c 并贴上精研某个专业方向的标签 我们不仅要面对多线程和并发 xff
  • Linux网络相关概念和修改IP地址的方法

    网卡的命名规则 ifconfig xff1a 用于显示或设置网络设备 ens32 span class token punctuation span flags span class token operator 61 span span
  • 二维坦克大战游戏代码开发

    这是我实际面试中 xff0c 遇到的一个题目 xff0c 编写一个坦克大战游戏 一开始感觉懵 xff0c 后来代码写写就好了 include lt iostream gt include lt stdlib h gt include lt
  • 软件系统性能常识

    不管是系统设计人员 开发人员还是测试人员 xff0c 要构建高性能的系统 xff0c 对于系统性能的一些常用术语都不了解 xff0c 那是无从做起的 xff0c 这里主要介绍几个软件性能指标的术语及计算方法 xff0c 便以在性能优化及性能
  • Socket的三种轮询方式select、poll、epoll之间的区别

    select poll epoll之间的区别 搜狗面试 1 select 61 61 gt 时间复杂度O n 它仅仅知道了 xff0c 有I O事件发生了 xff0c 却并不知道是哪那几个流 xff08 可能有一个 xff0c 多个 xff
  • linux后端c++开发人员需要学习的技术栈

    数据结构和算法 学完之后要刷leetcode xff08 剑指offer xff09 计算机网络 tcp ip 协议栈 xff08 tcp ip详解 xff09 操作系统 进程和线程 并发 和锁 内存分布调度等等 xff08 深入理解操作系
  • 内核态和用户态的区别

    内核态和用户态的区别 当一个任务 进程 执行系统调用而陷入内核代码中执行时 xff0c 我们就称进程处于内核状态 此时处理器处于特权级 最高的 0级 内核代码 当进程处于内核态时 xff0c 执行的内核代码会使用当前的内核栈 每个进程都有自
  • Linux查找命令四剑客awk、sed、find(locate)、grep讲解

    目录 find命令 xff1a 一旦执行了chmod 000 那么如何恢复权限呢 xff1f 2 grep xff08 找文件内容 行操作 xff09 3 awk 4 sed 找文件内容 行操作 find命令 xff1a 1 find xf
  • go语言学习笔记,特点

    1 并发编程 Go语言在并发编程方面比绝大多数语言要简洁不少 xff0c 这一点是其最大亮点之一 xff0c 也是其未来进入高并发高性能场景的重要筹码 golang的并发执行单元是一种称为goroutine的协程 协程又称为微线程 xff0
  • 进程调度,一个调度器的自白

    进程调度 xff0c 一个调度器的自白 我是一个进程调度器 我的职责是调度计算机内所有的进程 xff0c 为他们分配 CPU 资源 1 批处理时代 想当初 xff0c 操作系统创造我时 xff0c 只是打算让我用 FCFS 调度算法 xff
  • 厉害了!除了find命令,还有这么多文件查找命令,高手必备!

    目录 1 locate 2 whereis 3 which 4 type 1 locate locate命令其实是 find name 的另一种写法 xff0c 但是要比后者快得多 xff0c 原因在于它不搜索具体目录 xff0c 而是搜索
  • 浅析Linux中的零拷贝技术

    浅析Linux中的零拷贝技术 目录 浅析Linux中的零拷贝技术 引文 什么是零拷贝技术 xff08 zero copy xff09 xff1f 使用 mmap 使用sendfile 使用splice 本文探讨Linux中 主要的几种零拷贝