이 문서에서는 Linux의 주요 제로 복사 기술과 제로 복사 기술의 적용 가능한 시나리오에 대해 설명합니다. Zero Copy 개념을 빠르게 확립하기 위해 일반적으로 사용되는 시나리오를 소개합니다:
Citation ##
서버 프로그램(웹 서버 또는 파일 서버)을 작성할 때 파일 다운로드는 기본 기능입니다. 이때 서버의 작업은 다음과 같습니다. 수정 없이 연결된 소켓에서 서버 호스트 디스크의 파일을 보냅니다. 일반적으로 이를 완료하기 위해 다음 코드를 사용합니다.
while((n = read(diskfd, buf, BUF_SIZE)) > 0) write(sockfd, buf , n);
기본 작업은 소켓에서 파일을 읽는 것입니다. 루프 콘텐츠의 디스크를 버퍼로 보낸 다음 버퍼의 콘텐츠를 소켓
으로 보냅니다. 그러나 Linux의 I/O
작업은 기본적으로 버퍼링된 I/O
로 설정됩니다. 여기서 주로 사용되는 두 가지 시스템 호출은 read
와 write
입니다. 우리는 그 안에서 운영 체제가 무엇을 하는지 모릅니다. 실제로 위의 I/O
작업에서는 여러 개의 데이터 복사가 발생했습니다. 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替代read很明显减少了一次拷贝,当拷贝数据量很大时,无疑提升了效率。但是使用mmap
是有代价的。当你使用mmap
时,你可能会遇到一些隐藏的陷阱。例如,当你的程序map
了一个文件,但是当这个文件被另一个进程截断(truncate)时, write系统调用会因为访问非法地址而被SIGBUS
信号终止。SIGBUS
信号默认会杀死你的进程并产生一个coredump
buf
주소는 커널 버퍼의 내용을 buf
에 지정된 사용자 공간 버퍼에 복사합니다. 그렇지 않은 경우 운영 체제는 먼저 디스크의 데이터를 커널 버퍼에 복사합니다. 이 단계는 현재 주로 DMA
를 사용하여 전송한 다음 커널 버퍼의 내용을 사용자 버퍼에 복사합니다. write
시스템 호출은 사용자 버퍼의 내용을 네트워크 스택과 관련된 커널 버퍼에 복사하고, 마지막으로 socket
은 커널의 내용을 복사합니다. 버퍼 콘텐츠가 네트워크 카드로 전송됩니다. 🎜🎜 通常我们使用以下解决方案避免这种问题: 1、为SIGBUS信号建立信号处理程序 2、使用文件租借锁 我们应该在 使用sendfile##### 从2.1版内核开始,Linux引入了 系统调用 在我们调用 目前为止,我们已经减少了数据拷贝的次数了,但是仍然存在一次拷贝,就是页缓存到socket缓存的拷贝。那么能不能把这个拷贝也省略呢? 借助于硬件上的帮助,我们是可以办到的。之前我们是把页缓存的数据拷贝到socket缓存中,实际上,我们仅仅需要把缓冲区描述符传到 总结一下, 不过这一种收集拷贝功能是需要硬件以及驱动程序支持的。 使用splice##### sendfile只适用于将数据从文件拷贝到套接字上,限定了它的使用范围。Linux在 splice调用在两个文件描述符之间移动数据,而不需要数据在内核空间和用户空间来回拷贝。他从 splice调用利用了Linux提出的管道缓冲区机制, 所以至少一个描述符要为管道。 以上几种零拷贝技术都是减少数据在用户空间和内核空间拷贝技术实现的,但是有些时候,数据必须在用户空间和内核空间之间拷贝。这时候,我们只能针对数据在用户空间和内核空间拷贝的时机上下功夫了。Linux通常利用写时复制(copy on write)来减少系统开销,这个技术又时常称作 由于篇幅原因,本文不详细介绍写时复制。大概描述下就是:如果多个程序同时访问同一块数据,那么每个程序都拥有指向这块数据的指针,在每个程序看来,自己都是独立拥有这块数据的,只有当程序需要对数据内容进行修改时,才会把数据内容拷贝到程序自己的应用空间里去,这时候,数据才成为该程序的私有数据。如果程序不需要对数据进行修改,那么永远都不需要拷贝数据到自己的应用空间里。这样就减少了数据的拷贝。写时复制的内容可以再写一篇文章了。。。 除此之外,还有一些零拷贝技术,比如传统的Linux I/O中加上 스플라이스 호출은 Linux에서 제안한 파이프 버퍼 메커니즘을 활용하므로 적어도 하나의 설명자는 파이프여야 합니다. 위의 제로 복사 기술은 모두 사용자 공간과 커널 공간 사이의 데이터 복사를 줄여 구현됩니다. 그러나 때로는 사용자 공간과 커널 공간 사이에서 데이터를 복사해야 합니다. 현재로서는 사용자 공간과 커널 공간의 데이터 복사 타이밍에 대해서만 작업할 수 있습니다. Linux는 일반적으로 시스템 오버헤드를 줄이기 위해
위 그림에서 볼 수 있듯이 총 4개의 데이터 복사본이 생성됩니다. DMA
를 사용하여 하드웨어와의 통신을 처리하더라도 CPU는 여전히 필요합니다. 두 개의 데이터 복사본을 처리하기 위해 동시에 사용자 상태와 커널 상태 간에 여러 컨텍스트 전환이 발생하여 의심할 여지 없이 CPU에 대한 부담이 증가했습니다. 🎜🎜이 과정에서 우리는 파일 내용을 전혀 수정하지 않았기 때문에 커널 공간과 사용자 공간 사이를 오가며 데이터를 복사하는 것은 의심할 여지 없이 낭비이며, 제로 복사는 주로 이러한 비효율성을 해결하기 위한 것입니다. 🎜🎜🎜🎜제로카피 기술이란? ##🎜🎜🎜🎜제로 복사의 주요 작업은 CPU가 한 스토리지에서 다른 스토리지로 데이터를 복사하는 것을 🎜피하는 것입니다🎜 이는 주로 다양한 제로 복사 기술을 사용하여 CPU가 많은 수의 데이터 복사 작업을 수행하지 못하게 하고 데이터 복사 작업을 줄입니다. 불필요하게 복사하거나 다른 구성 요소가 이러한 유형의 간단한 데이터 전송 작업을 수행하도록 하여 CPU가 다른 작업에 집중할 수 있도록 합니다. 이를 통해 시스템 리소스를 보다 효율적으로 사용할 수 있습니다. 🎜🎜 인용문의 예로 돌아가서 데이터 복사 수를 어떻게 줄일 수 있나요? 분명한 초점은 커널 공간과 사용자 공간 사이를 오가는 데이터 복사를 줄이는 것입니다. 이는 또한 제로 복사 유형을 도입합니다: 🎜🎜🎜데이터 전송이 수행됩니다. 사용자 공간을 통과할 필요가 없습니다 🎜🎜🎜🎜🎜mmap 사용#####🎜🎜🎜🎜복사본 수를 줄일 수 있는 한 가지 방법은 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;
}
🎜응용 프로그램이 mmap()
을 호출하면 디스크의 데이터가 DMA
를 통해 커널 버퍼에 복사된 다음 운영 체제가 이 커널 버퍼를 애플리케이션 공유와 비교하므로 커널 버퍼의 내용을 사용자 공간에 복사할 필요가 없습니다. 그런 다음 애플리케이션은 write()
를 호출하고 운영 체제는 커널 버퍼의 내용을 소켓
버퍼에 직접 복사합니다. socket 버퍼는 데이터를 네트워크 카드로 보냅니다.
마찬가지로 사진을 보는 것도 매우 간단합니다. 🎜🎜🎜🎜읽기 대신 mmap을 사용하면 복사되는 데이터의 양이 많으면 확실히 효율성이 향상됩니다. 그러나 mmap
을 사용하면 비용이 발생합니다. mmap
을 사용할 때 몇 가지 숨겨진 함정에 직면할 수 있습니다. 예를 들어, 프로그램 map
이 파일을 매핑하지만 다른 프로세스에 의해 파일이 잘리면 불법 액세스로 인해 쓰기 시스템 호출이 SIGBUS
가 됩니다. 주소입니다. 신호가 종료되었습니다. SIGBUS
신호는 기본적으로 프로세스를 종료하고 coredump
를 생성합니다. 이러한 방식으로 서버가 중지되면 손실이 발생합니다. 🎜
当遇到SIGBUS
信号时,信号处理程序简单地返回,write
系统调用在被中断之前会返回已经写入的字节数,并且errno
会被设置成success,但是这是一种糟糕的处理办法,因为你并没有解决问题的实质核心。
通常我们使用这种方法,在文件描述符上使用租借锁,我们为文件向内核申请一个租借锁,当其它进程想要截断这个文件时,内核会向我们发送一个实时的RT_SIGNAL_LEASE
信号,告诉我们内核正在破坏你加持在文件上的读写锁。这样在程序访问非法内存并且被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
来简化操作:#include<sys>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);</sys>
sendfile()
在代表输入文件的描述符in_fd
和代表输出文件的描述符out_fd
之间传送文件内容(字节)。描述符out_fd
必须指向一个套接字,而in_fd
指向的文件必须是可以mmap
的。这些局限限制了sendfile
的使用,使sendfile
只能将数据从文件传递到套接字上,反之则不行。
使用sendfile
不仅减少了数据拷贝的次数,还减少了上下文切换,数据传送始终只发生在kernel space
。sendfile
时,如果有其它进程截断了文件会发生什么呢?假设我们没有设置任何信号处理程序,sendfile
调用仅仅返回它在被中断之前已经传输的字节数,errno
会被置为success。如果我们在调用sendfile之前给文件加了锁,sendfile
的行为仍然和之前相同,我们还会收到RT_SIGNAL_LEASE的信号。socket
缓冲区,再把数据长度传过去,这样DMA
控制器直接将页缓存中的数据打包发送到网络中就可以了。sendfile
系统调用利用DMA
引擎将文件内容拷贝到内核缓冲区去,然后将带有文件位置和长度信息的缓冲区描述符添加socket缓冲区去,这一步不会将内核中的数据拷贝到socket缓冲区中,DMA
引擎会将内核缓冲区的数据拷贝到协议引擎中去,避免了最后一次拷贝。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, unsigned int flags);</fcntl.h>
fd_in
拷贝len
长度的数据到fd_out
,但是有一方必须是管道设备,这也是目前splice
的一些局限性。flags
参数有以下几种取值:
pipe
에서 데이터를 이동할 수 없거나 pipe
의 캐시가 전체 페이지가 아닌 경우에도 여전히 복사해야 합니다. 데이터. Linux 초기 구현에는 몇 가지 문제가 있어서 2.6.21
부터는 이 옵션이 작동하지 않으며 이후 Linux 버전에서는 구현해야 합니다. pipe
移动数据或者pipe
的缓存不是一个整页面,仍然需要拷贝数据。Linux最初的实现有些问题,所以从2.6.21
开始这个选项不起作用,后面的Linux版本应该会实现。splice
操作不会被阻塞。然而,如果文件描述符没有被设置为不可被阻塞方式的 I/O ,那么调用 splice 有可能仍然被阻塞。splice
调用会有更多的数据。COW
。O_DIRECT
标记可以直接I/O
,避免了自动缓存,还有尚未成熟的fbufs
** SPLICE_F_NONBLOCK** : 접합
작업이 차단되지 않습니다. 그러나 파일 설명자가 비차단 I/O에 대해 설정되지 않은 경우 splice 호출은 여전히 차단될 수 있습니다. splice
호출에는 더 많은 데이터가 포함됩니다. COW
라고 합니다. 🎜🎜공간상의 이유로 이 글에서는 Copy-On-Write에 대해 자세히 소개하지 않습니다. 일반적인 설명은 다음과 같습니다. 여러 프로그램이 동시에 동일한 데이터 조각에 액세스하는 경우 각 프로그램은 이 데이터 조각에 대한 포인터를 갖습니다. 각 프로그램의 관점에서는 프로그램이 이 데이터 조각을 독립적으로 소유합니다. 데이터 내용이 수정되면 해당 데이터 내용은 프로그램 자체 응용 프로그램 공간에 복사되어야만 해당 데이터는 프로그램의 개인 데이터가 됩니다. 프로그램이 데이터를 수정할 필요가 없다면 데이터를 자체 애플리케이션 공간에 복사할 필요도 없습니다. 이렇게 하면 데이터 복사가 줄어듭니다. 작성 중 복사된 내용은 다른 글 작성에 활용될 수 있습니다. . . 🎜🎜또한 몇 가지 제로 복사 기술이 있습니다. 예를 들어 기존 Linux I/O에 O_DIRECT
태그를 추가하면 자동 캐싱을 직접 피할 수 있습니다. 그리고 미숙한 fbufs
기술도 있습니다. 이 기사에서는 모든 제로 복사 기술을 다루지 않았습니다. 관심이 있는 경우에는 일반적인 기술만 소개합니다. 사이드 프로젝트도 자체 커널을 수정하며 I/O 부분과 관련하여 자체 데이터 전송 속도를 향상시킵니다. 🎜🎜추천 튜토리얼: "🎜Linux 운영 및 유지 관리🎜"🎜
위 내용은 Linux에서 몇 가지 제로카피 기술과 적용 가능한 시나리오에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!