この記事では、主要なゼロコピー テクノロジと、Linux でゼロコピー テクノロジが適用されるシナリオについて説明します。ゼロコピーの概念を迅速に確立するために、一般的に使用されるシナリオを紹介します。
#引用
##実行時のサーバーの書き込みプログラム (Web サーバーまたはファイル サーバー) では、ファイルのダウンロードが基本的な機能です。この時点でのサーバーのタスクは次のとおりです。接続されたソケットからサーバーのホスト ディスク内のファイルを変更せずに送信します。 、通常は次のコードを使用して完了します。
while((n = read(diskfd, buf, BUF_SIZE)) > 0) write(sockfd, buf , n);
socket に送信します。ただし、Linux の
I/O 操作はデフォルトでバッファリングされた
I/O であるためです。ここで使用される 2 つの主なシステム コールは
read と
write ですが、オペレーティング システムがそれらの中で何を行うかはわかりません。実際、上記の
I/O 操作では、複数のデータ コピーが発生しました。
read によると、システム コールによって提供される
buf アドレスは、カーネル バッファの内容を
buf で指定されたユーザー空間バッファにコピーします。そうでない場合、オペレーティング システムはまずディスク上のデータをカーネル バッファにコピーします。このステップは現在主に
DMA を使用して送信し、その後カーネル バッファの内容をユーザー バッファにコピーします。
次に、
write システム コールはユーザー バッファの内容をネットワーク スタックに関連するカーネル バッファにコピーし、最後に
socket がカーネル バッファの内容を on に送信します。ネットワークカード。
ここまで言いましたが、より明確にするために写真を見たほうがよいでしょう:
上の写真からわかるように、合計 4 つのデータ コピーが生成されます。ハードウェアとの通信を処理するために
DMA が使用されている場合でも、CPU は依然として 2 つのデータ コピーを処理する必要があります。同時に、ユーザー モードで複数のコンテキスト スイッチが発生し、カーネル モードでは、間違いなく CPU の負荷が増大します。
ゼロコピーテクノロジーとは何ですか?
##ゼロ コピーの主なタスクは、CPU によるストレージ間でのデータのコピーを回避することです。主な目的は、さまざまなゼロ コピー テクノロジを使用して、回避 CPU は不要なコピーを減らすために大量のデータ コピー タスクを実行するか、この種の単純なデータ転送タスクを他のコンポーネントに実行させて、CPU を解放して他のタスクに集中させます。これにより、システム リソースをより効率的に使用できるようになります。
引用文の例に戻りましょう。データのコピー数を減らすにはどうすればよいでしょうか?明らかに焦点は、カーネル空間とユーザー空間の間で行われるデータのコピーを減らすことです。これにより、ゼロ コピーの一種 (#) も導入され、データ転送が不要になります。ユーザー スペースを介して
mmap を使用する
##コピー数を減らす 1 つの方法読み取り呼び出しの代わりに mmap() を呼び出すことです:
buf = mmap(diskfd, len); write(sockfd, buf, len);
アプリケーションは mmap()
を呼び出し、ディスク上のデータは DMA
を通じてカーネル バッファにコピーされます。カーネル バッファはアプリケーションと共有されるため、カーネル バッファの内容をユーザー空間にコピーする必要はありません。次に、アプリケーションは write()
を呼び出し、オペレーティング システムはカーネル バッファの内容を socket
バッファに直接コピーします。これはすべてカーネル状態で発生します。ソケット バッファはデータをネットワーク カードに送信します。
同様に、図を見ると非常に簡単です:
mmap の使用にはコストがかかります。
mmap を使用すると、いくつかの隠れた罠に遭遇する可能性があります。たとえば、プログラム
map がファイルをマップしているときに、そのファイルが別のプロセスによって切り捨てられる (truncate) 場合、書き込みシステム コールは
SIGBUS シグナルによって終了します。不正なアドレスです。
SIGBUS シグナルは、デフォルトでプロセスを強制終了し、
coredump を生成します。この方法でサーバーが停止すると、損失が発生します。
通常我们使用以下解决方案避免这种问题:
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
からデータを移動できない場合、または pipe
のキャッシュがフル ページでない場合でも、データをコピーする必要があります。 Linux の初期実装にはいくつかの問題があったため、このオプションは 2.6.21
からは機能しなくなり、後の Linux バージョンで実装する必要があります。 splice
操作はブロックされません。ただし、ファイル記述子がノンブロッキング I/O 用に設定されていない場合は、splice の呼び出しがブロックされる可能性があります。 splice
呼び出しにはさらに多くのデータが含まれます。 スプライス呼び出しは、Linux によって提案されたパイプ バッファー メカニズムを利用するため、少なくとも 1 つの記述子がパイプである必要があります。
上記のゼロコピー テクノロジはすべて、ユーザー空間とカーネル空間の間のデータのコピーを削減することによって実装されていますが、場合によっては、ユーザー空間とカーネル空間の間でデータをコピーする必要があります。現時点では、ユーザー空間とカーネル空間でのデータコピーのタイミングについてのみ作業できます。 Linux は通常、システム オーバーヘッドを削減するために copy on write(コピー オン ライト) を使用します。このテクノロジは、COW
と呼ばれることがよくあります。
紙面の都合上、この記事ではコピーオンライトについては詳しく紹介しません。一般的な説明は次のとおりです: 複数のプログラムが同時に同じデータにアクセスする場合、各プログラムはそのデータへのポインタを持ちます。各プログラムの観点から見ると、このデータは独立して所有されます。データの内容が変更されると、そのデータの内容はプログラム自身のアプリケーション空間にコピーされ、初めてそのデータはプログラムのプライベート データになります。プログラムがデータを変更する必要がない場合、データを独自のアプリケーション空間にコピーする必要はありません。これにより、データのコピーが削減されます。執筆中にコピーしたコンテンツは、別の記事を書くために使用できます。 。 。
さらに、ゼロコピー テクノロジがいくつかあります。たとえば、従来の Linux I/O に O_DIRECT
マークを追加すると、直接 I/O
が可能になり、自動キャッシュと、未熟な fbufs
テクノロジが必要です。この記事では、すべてのゼロコピー テクノロジをカバーしているわけではありません。一般的なテクノロジをいくつか紹介するだけです。興味がある場合は、自分で勉強してください。一般的に、成熟したサーバー プロジェクトも独自のカーネルを変更し、システムの I/O 部分のデータ転送速度が向上します。
推奨チュートリアル: 「Linux 運用と保守 」
以上がLinux でのいくつかのゼロコピー テクノロジと適用可能なシナリオについて話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。