In diesem Artikel werden die wichtigsten Zero-Copy-Technologien und die Szenarien beschrieben, in denen die Zero-Copy-Technologie unter Linux anwendbar ist. Um das Konzept der Nullkopie schnell zu etablieren, stellen wir ein häufig verwendetes Szenario vor:
Zitat ##
Schreiben eines Servers während der Ausführung Bei einem Programm (Webserver oder Dateiserver) ist das Herunterladen von Dateien eine Grundfunktion. Zu diesem Zeitpunkt besteht die Aufgabe des Servers darin: Die Dateien auf der Server-Host-Festplatte ohne Änderung zu senden Zum Abschließen verwenden wir normalerweise den folgenden Code:
while((n = read(diskfd, buf, BUF_SIZE)) > 0) write(sockfd, buf , n);
Grundlegende Operation Dabei wird der Dateiinhalt in einer Schleife von der Festplatte in den Puffer gelesen und dann der Pufferinhalt an socket
gesendet. Aber weil der I/O
-Vorgang von Linux standardmäßig die Pufferung I/O
verwendet. Die beiden hier verwendeten Hauptsystemaufrufe sind read
und write
. Wir wissen nicht, was das Betriebssystem darin macht. Tatsächlich wurden im obigen I/O
-Vorgang mehrere Datenkopien durchgeführt.
Wenn eine Anwendung auf ein bestimmtes Datenelement zugreift, prüft das Betriebssystem zunächst, ob kürzlich auf die Datei zugegriffen wurde und ob der Dateiinhalt im Kernel-Puffer zwischengespeichert ist. Wenn ja, prüft das Betriebssystem dies direkt zum read
-System. Rufen Sie die bereitgestellte buf
-Adresse auf, um den Inhalt des Kernel-Puffers in den durch buf
angegebenen User-Space-Puffer zu kopieren. Wenn nicht, kopiert das Betriebssystem zunächst die Daten auf der Festplatte in den Kernel-Puffer. Dieser Schritt basiert derzeit hauptsächlich auf DMA
für die Übertragung und kopiert dann den Inhalt des Kernel-Puffers in den Benutzerpuffer.
Als nächstes kopiert der write
-Systemaufruf den Inhalt des Benutzerpuffers in den Kernelpuffer, der sich auf den Netzwerkstapel bezieht, und socket
sendet schließlich den Inhalt des Kernelpuffers an die Netzwerkkarte.
Nachdem wir so viel gesagt haben, schauen wir uns das Bild an, um es klarzustellen:
Wie aus dem Bild oben ersichtlich ist, insgesamt Selbst wenn DMA
für die Kommunikation mit der Hardware verwendet wird, muss die CPU immer noch zwei Datenkopien verarbeiten. Gleichzeitig finden mehrere Kontextwechsel zwischen Benutzermodus und Kernelmodus statt erhöht die Belastung der CPU.
Während dieses Vorgangs haben wir keine Änderungen am Dateiinhalt vorgenommen, sodass das Hin- und Herkopieren von Daten zwischen Kernel- und Benutzerraum zweifellos eine Verschwendung ist und das Null-Kopieren hauptsächlich dazu dient, diese Ineffizienz zu beheben.
Was ist Zero-Copy-Technologie? ##
Die Hauptaufgabe von Zero Copy besteht darin, zu verhinderndass die CPU Daten von einem Speicher in einen anderen kopiert. Der Hauptzweck besteht darin, verschiedene Zero Copy zu verwenden Zu vermeidende Technologien Die CPU führt eine große Anzahl von Datenkopieraufgaben aus, um unnötige Kopien zu reduzieren, oder überlässt andere Komponenten diese Art einfacher Datenübertragungsaufgaben, sodass die CPU sich auf andere Aufgaben konzentrieren kann. Dies ermöglicht eine effizientere Nutzung der Systemressourcen.
Kehren wir zum Beispiel im Zitat zurück. Wie können wir die Anzahl der Datenkopien reduzieren? Ein offensichtlicher Schwerpunkt liegt darin, das Hin- und Herkopieren von Daten zwischen Kernel- und Benutzerbereich zu reduzieren. Dadurch wird auch eine Art Nullkopie eingeführt:
so dass die Datenübertragung nicht unterbrochen werden muss durch den Benutzerraum
Verwendung von mmap#####
Eine Möglichkeit, die Anzahl der Kopien zu reduzieren, ist Um stattdessen mmap() aufzurufen, lesen Sie den Aufruf:
buf = mmap(diskfd, len); write(sockfd, buf, len);
Die Anwendung ruft mmap()
auf, die Daten auf der Festplatte durchlaufen den Kernel-Puffer, der von DMA
kopiert wird, und dann gibt das Betriebssystem diese frei Kernel-Puffer mit der Anwendung, sodass der Inhalt des Kernel-Puffers nicht in den Benutzerbereich kopiert werden muss. Die Anwendung ruft erneut write()
auf und das Betriebssystem kopiert den Inhalt des Kernelpuffers direkt in den socket
-Puffer. Dies alles geschieht im Kernel-Status. Schließlich sendet der socket
-Puffer die Daten an die Netzwerkkarte.
In ähnlicher Weise ist das Betrachten des Bildes sehr einfach:
Die Verwendung von mmap anstelle von read reduziert offensichtlich eine Kopie, wenn die Menge der kopierten Daten groß ist, was zweifellos zu einer Verbesserung führt Effizienz. Die Verwendung von mmap
ist jedoch mit Kosten verbunden. Wenn Sie mmap
verwenden, stoßen Sie möglicherweise auf versteckte Fallen. Wenn Ihr Programm beispielsweise map
eine Datei liest, die Datei jedoch von einem anderen Prozess abgeschnitten (truncate) wird, wird der Schreibsystemaufruf durch das Signal SIGBUS
beendet, da auf eine ungültige Adresse zugegriffen wird. Das SIGBUS
-Signal bricht Ihren Prozess standardmäßig ab und generiert ein coredump
. Wenn Ihr Server auf diese Weise gestoppt wird, führt dies zu einem Verlust.
通常我们使用以下解决方案避免这种问题:
1、为SIGBUS信号建立信号处理程序
当遇到SIGBUS
信号时,信号处理程序简单地返回,write
系统调用在被中断之前会返回已经写入的字节数,并且errno
会被设置成success,但是这是一种糟糕的处理办法,因为你并没有解决问题的实质核心。
2、使用文件租借锁
通常我们使用这种方法,在文件描述符上使用租借锁,我们为文件向内核申请一个租借锁,当其它进程想要截断这个文件时,内核会向我们发送一个实时的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#####
从2.1版内核开始,Linux引入了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缓存的拷贝。那么能不能把这个拷贝也省略呢?
借助于硬件上的帮助,我们是可以办到的。之前我们是把页缓存的数据拷贝到socket缓存中,实际上,我们仅仅需要把缓冲区描述符传到socket
缓冲区,再把数据长度传过去,这样DMA
控制器直接将页缓存中的数据打包发送到网络中就可以了。
总结一下,sendfile
系统调用利用DMA
引擎将文件内容拷贝到内核缓冲区去,然后将带有文件位置和长度信息的缓冲区描述符添加socket缓冲区去,这一步不会将内核中的数据拷贝到socket缓冲区中,DMA
引擎会将内核缓冲区的数据拷贝到协议引擎中去,避免了最后一次拷贝。
不过这一种收集拷贝功能是需要硬件以及驱动程序支持的。
使用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, unsigned int flags);</fcntl.h>
splice调用在两个文件描述符之间移动数据,而不需要数据在内核空间和用户空间来回拷贝。他从fd_in
拷贝len
长度的数据到fd_out
,但是有一方必须是管道设备,这也是目前splice
的一些局限性。flags
参数有以下几种取值:
pipe
verschieben kann oder der Cache von pipe
keine vollständige Seite ist, muss er die Daten trotzdem kopieren. Bei der anfänglichen Implementierung von Linux gibt es einige Probleme, daher funktioniert diese Option ab 2.6.21
nicht mehr und sollte in späteren Linux-Versionen implementiert werden. splice
-Vorgang wird nicht blockiert. Wenn der Dateideskriptor jedoch nicht für nicht blockierende E/A eingerichtet ist, kann der Aufruf von splice dennoch blockieren. splice
Aufrufe verfügen über mehr Daten. Der Spleißaufruf nutzt den von Linux vorgeschlagenen Pipe-Puffer-Mechanismus, daher muss mindestens ein Deskriptor eine Pipe sein.
Die oben genannten Zero-Copy-Technologien werden alle implementiert, indem das Kopieren von Daten zwischen Benutzerraum und Kernelraum reduziert wird. Manchmal müssen jedoch Daten zwischen Benutzerraum und Kernelraum kopiert werden. Derzeit können wir nur am Timing des Datenkopierens im User-Space und Kernel-Space arbeiten. Linux verwendet normalerweise Copy on Write (Kopieren beim Schreiben) , um den Systemaufwand zu reduzieren. Diese Technologie wird oft als COW
bezeichnet.
Aus Platzgründen wird Copy-on-Write in diesem Artikel nicht im Detail vorgestellt. Eine grobe Beschreibung lautet: Wenn mehrere Programme gleichzeitig auf dasselbe Datenelement zugreifen, verfügt jedes Programm über einen Zeiger auf dieses Datenelement. Aus Sicht jedes Programms besitzt es dieses Datenelement nur dann, wenn das Programm Wenn der Dateninhalt geändert wird, wird er in den eigenen Anwendungsbereich des Programms kopiert. Zu diesem Zeitpunkt werden die Daten zu privaten Daten des Programms. Wenn das Programm die Daten nicht ändern muss, muss es die Daten auch nie in seinen eigenen Anwendungsbereich kopieren. Dadurch wird das Kopieren von Daten reduziert. Der beim Schreiben kopierte Inhalt kann zum Schreiben eines weiteren Artikels verwendet werden. . .
Darüber hinaus gibt es einige Zero-Copy-Technologien. Beispielsweise kann das Hinzufügen des O_DIRECT
-Tags zu herkömmlicher Linux-E/A direkt I/O
automatisches Caching vermeiden, und es gibt auch unausgereifte fbufs
Technologie In diesem Artikel werden noch nicht alle Zero-Copy-Technologien behandelt, sondern nur einige gängige Technologien vorgestellt. Wenn Sie interessiert sind, können Sie sie selbst studieren. Im Allgemeinen ändern ausgereifte Serverprojekte auch die E/A-bezogenen Teile des Kernels die eigene Datenübertragungsrate verbessern.
Empfohlenes Tutorial: „Linux-Betrieb und -Wartung“
Das obige ist der detaillierte Inhalt vonLassen Sie uns über verschiedene Zero-Copy-Technologien und anwendbare Szenarien unter Linux sprechen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!