Linux의 제로카피 기술에 대한 간략한 분석

풀어 주다: 2023-08-04 16:23:14
앞으로
706명이 탐색했습니다.

이 문서에서는 주요 제로 복사 기술과 Linux의 제로 복사 기술에 적용 가능한 시나리오에 대해 설명합니다. 제로 카피 개념을 빠르게 확립하기 위해 일반적으로 사용되는 시나리오를 소개합니다:

Citation

서버 프로그램(웹 서버 또는 파일 서버)을 작성할 때 파일 다운로드는 기본 기능입니다. 이때 서버의 임무는 연결된 소켓에서 서버 호스트 디스크의 파일을 수정 없이 전송하는 것입니다. 이를 완료하기 위해 일반적으로 다음 코드를 사용합니다.

while((n = read(diskfd, buf, BUF_SIZE)) > 0)
    write(sockfd, buf , n);
로그인 후 복사

기본 작업은 소켓에서 파일을 읽는 것입니다. 루프 내용의 디스크를 버퍼로 보낸 다음 버퍼의 내용을 소켓으로 보냅니다. 그러나 Linux I/O 작업은 기본적으로 버퍼링된 I/O로 수행됩니다. 여기서 사용되는 두 가지 주요 시스템 호출은 읽기와 쓰기입니다. 우리는 그 호출에서 운영 체제가 무엇을 하는지 모릅니다. 실제로 위의 I/O 작업 중에 여러 데이터 복사본이 발생했습니다.

응용 프로그램이 특정 데이터에 액세스하면 운영 체제는 먼저 해당 파일에 최근에 액세스했는지 여부와 파일 내용이 커널 버퍼에 캐시되어 있는지 확인합니다. 그렇다면 운영 체제는 buf를 직접 사용합니다. 읽기 시스템 호출 Address에서 제공하는 커널 버퍼의 내용을 buf에 지정된 사용자 공간 버퍼에 복사합니다. 그렇지 않은 경우 운영 체제는 먼저 디스크의 데이터를 커널 버퍼에 복사합니다. 이 단계는 현재 주로 DMA를 사용하여 전송한 다음 커널 버퍼의 내용을 사용자 버퍼에 복사합니다.

다음으로 write 시스템 호출은 사용자 버퍼의 내용을 네트워크 스택과 관련된 커널 버퍼에 복사하고, 마지막으로 소켓은 커널 버퍼의 내용을 네트워크 카드로 보냅니다. 너무 많이 말했으니 이해하기 쉽도록 사진을 살펴보겠습니다.

Linux의 제로카피 기술에 대한 간략한 분석

데이터 복사

从上图中可以看出,共产生了四次数据拷贝,即使使用了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缓冲区再把数据发到网卡去。同样的,看图很简单:

Linux의 제로카피 기술에 대한 간략한 분석

mmap

읽는 대신 mmap을 사용하면 복사되는 데이터의 양이 많을 때 확실히 효율성이 향상됩니다. 그러나 mmap을 사용하면 비용이 발생합니다. mmap을 사용하면 몇 가지 숨겨진 함정에 직면할 수 있습니다. 예를 들어, 프로그램이 파일을 매핑했지만 다른 프로세스에 의해 파일이 잘리면 쓰기 시스템 호출이 잘못된 주소에 액세스하기 때문에 SIGBUS 신호에 의해 종료됩니다. SIGBUS 신호는 기본적으로 프로세스를 종료하고 코어 덤프를 생성합니다. 이러한 방식으로 서버가 중지되면 손실이 발생합니다.

일반적으로 우리는 이 문제를 피하기 위해 다음 솔루션을 사용합니다.

1. SIGBUS 신호에 대한 신호 처리기를 구축합니다.

SIGBUS 신호가 발견되면 신호 처리기는 단순히 반환되고 쓰기 시스템 호출이 발생합니다. 중단되기 전에 기록된 바이트 수가 반환되고 errno는 성공으로 설정되지만 이는 문제의 실제 핵심을 해결하지 못하기 때문에 좋지 않은 접근 방식입니다.

2. 파일 임대 잠금 사용

일반적으로 우리는 파일 설명자에 임대 잠금을 사용합니다. 다른 프로세스가 파일을 자르려고 할 때 커널에서 임대 잠금을 적용합니다. , 커널은 실시간 RTSIGNALLEASE 신호를 보내 사용자가 파일에 설정한 읽기-쓰기 잠금을 커널이 파괴하고 있음을 알려줍니다. 이러한 방식으로 프로그램이 불법 메모리에 액세스하고 SIGBUS에 의해 종료되기 전에 쓰기 시스템 호출이 중단됩니다. write는 쓴 바이트 수를 반환하고 errno를 성공으로 설정합니다.

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来简化操作:

#include<sys/sendfile.h>
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。

Linux의 제로카피 기술에 대한 간략한 분석

sendfile系统调用过程

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

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

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

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

Linux의 제로카피 기술에 대한 간략한 분석

带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: 후속 접속 호출에는 더 많은 데이터가 있을 것입니다.

스플라이스 호출은 Linux에서 제안한 파이프 버퍼 메커니즘을 활용하므로 적어도 하나의 설명자는 파이프여야 합니다.

위의 제로 복사 기술은 모두 사용자 공간과 커널 공간 사이의 데이터 복사를 줄여 구현되지만, 때로는 사용자 공간과 커널 공간 사이에서 데이터를 복사해야 하는 경우도 있습니다. 현재로서는 사용자 공간과 커널 공간의 데이터 복사 타이밍에 대해서만 작업할 수 있습니다. Linux는 일반적으로 시스템 오버헤드를 줄이기 위해 쓰기 시 복사를 사용합니다. 이 기술을 흔히 COW라고 합니다.

이 글에서는 지면 관계로 Copy-On-Write에 대해 자세히 소개하지 않습니다. 일반적인 설명은 다음과 같습니다. 여러 프로그램이 동시에 동일한 데이터 조각에 액세스하는 경우 각 프로그램은 이 데이터 조각에 대한 포인터를 갖습니다. 각 프로그램의 관점에서는 프로그램이 이 데이터 조각을 독립적으로 소유합니다. 데이터 내용이 수정되면 해당 데이터 내용은 프로그램 자체 응용 프로그램 공간에 복사되어야만 해당 데이터는 프로그램의 개인 데이터가 됩니다. 프로그램이 데이터를 수정할 필요가 없다면 데이터를 자체 애플리케이션 공간에 복사할 필요도 없습니다. 이렇게 하면 데이터 복사가 줄어듭니다. 작성 중 복사된 내용은 다른 글 작성에 활용될 수 있습니다. . .

또한 기존 Linux I/O에 O_DIRECT 마크를 추가하여 직접 I/O를 활성화하고 자동 캐싱을 방지하는 등 몇 가지 제로 복사 기술과 미성숙한 fbufs 기술이 있지만 이 기사에서는 아직 다루지 않았습니다. 모든 제로 복사 기술은 일반적인 기술 중 일부일 뿐입니다. 관심이 있다면 스스로 연구할 수도 있습니다. 일반적으로 성숙한 서버 측 프로젝트에서는 개선을 위해 커널의 I/O 관련 부분도 수정합니다. 데이터 전송 속도.

위 내용은 Linux의 제로카피 기술에 대한 간략한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:Linux中文社区
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿