ゼロコピー mmap、sendfile
意味
ゼロコピー テクノロジは主に、従来のネットワーク I/O 操作におけるファイル送信のパフォーマンスの問題を解決することを目的としています。次の図は、読み取りおよび書き込み中の従来の I/O に関係する CPU 操作を示しています。
- これには 4 つのユーザー モード ↔ カーネル モード コンテキスト スイッチが含まれ、そのうち読み取りスイッチが 2 回、書き込みスイッチが 2 回行われます。
- データのコピーが 4 つ含まれます。そのうち、DMA は 2 回コピーし、CPU は 2 回コピーします。
複数のコンテキストの切り替えと上記の操作のコピーは、パフォーマンスに影響します。
ゼロコピーテクノロジーを使用してmmap+write
最適化できます。sendfile
splice
DMA
DMA (Direct Memory Access)、つまりダイレクト メモリ アクセスは、データを高速に転送するためのメカニズムです。データ転送に使用する場合、CPU の参加は必要ありません。
DMA を使用してデータをコピーすると、CPU パラメータなしでデータ転送用のシステム データ バス リソースの一部が取得されます。IO 読み取りでも割り込みは発生しません。CPU は IO 操作を読み取り、システム コールによって割り込みが発生します。
mmap
mmap (メモリ マップ) は、仮想メモリとアドレス マッピングを使用して 1 つのコピーを削減します。カーネル モードからユーザー モードにデータをコピーする際のパフォーマンスの消費を削減できます。
上図に示すように、スレーブデータはカーネルステートからユーザーステートにコピーされるのではなく、メモリマッピングを通じて送信するファイルの仮想メモリアドレスを直接取得して送信することができます。共有仮想メモリ アドレスを通じて転送され、情報はソケット バッファにコピーされて送信されます。
#include <sys/mman.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
int main() {
// 打开文件并将其映射到内存中
int fd = open("file.txt", O_RDONLY);
size_t size = lseek(fd, 0, SEEK_END);
char* data = (char*) mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
// 创建套接字并连接到目标地址
int sock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(1234);
connect(sock, (sockaddr*) &addr, sizeof(addr));
// 将内存中的数据直接写入套接字
write(sock, data, size);//
// 关闭套接字和文件,并解除内存映射
close(sock);
munmap(data, size);
close(fd);
return 0;
}
ここでの mmap はコピーを減らすだけですが、それでも 4 つのコンテキスト スイッチが必要です。それで、コンテキストスイッチを減らす方法はありますか。そうすれば、sendfileが登場します。
sendfile
sendfile を使用すると、ファイル送信時のコンテキストの切り替えを減らすことができます。
Linux バージョン 2.1 以降、sendfile
操作を簡素化するために Linux が導入されました。sendfile
さらに最適化するには、この方法を上記の方法に置き換えることができますmmap/write
。
sendfile
以下をせよ:
mmap();
write();
替换为:
sendfile();
write
これにより、操作を開始するアプリケーションが 1 つ減り、sendfile
操作を直接開始できるため、コンテキストの切り替えが減少します。
ユーザーモードを介さず、カーネルモードでディスクデータをDMA経由でバッファ領域に直接コピーし、ソケットバッファ領域にバッファデータをコピーします。
#include <sys/socket.h>
#include <fcntl.h>
#include <cstring>
int main() {
// 打开文件并获取文件描述符
int fd = open("file.txt", O_RDONLY);
// 创建套接字并连接到目标地址
int sock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(1234);
connect(sock, (sockaddr*) &addr, sizeof(addr));
// 使用sendfile函数将文件信息发送到套接字
off_t offset = 0;
struct stat stat_buf;
fstat(fd, &stat_buf);
sendfile(sock, fd, &offset, stat_buf.st_size);//直接使用sendfile发送文件,避免上下文切换
// 关闭套接字和文件
close(sock);
close(fd);
return 0;
}
sendfile では 3 回のコピー アクションが実行されており、ユーザー モードとカーネル モードの間で頻繁に状態が切り替えられていないことがわかります。
sendfile は完璧ですが、CPU のコピーも保存できますか?
スキャッター/ギャザーを使用した sendfile
Linux 2.4 カーネルは最適化されており、最後のものを削除scatter/gather
できる sendfile 操作を提供します。CPU COPY
原則として、読み取りバッファとソケット バッファはカーネル空間内のデータをコピーせず、読み取りバッファのメモリ アドレスとオフセットを対応するソケット バッファに記録するため、コピーは必要ありません。その本質は、メモリアドレスの記録である仮想メモリのソリューションと一致しています。
次の図は、scatter/gather の sendfile の原理を示しています。
スプライス
違いは、ファイルとデータの転送だけでなく、任意の 2 つのファイルを相互に接続できることsendfile
です。splice
socket
ファイル記述子との間でデータを送受信するsocket
特殊な場合には、sendfile
常にシステム コールが使用されてきました。
そしてsplice
それは常に単なるメカニズムであり、sendfile
機能に限定されません。つまり、sendfile は splice のサブセットです。
sendfile とは異なり、splice はハードウェアのサポートを必要としません。
詳細は
https://juejin.cn/post/6995519558475841550
https://juejin.cn/post/7126195733471952910をご参照ください。