Linux のゼロコピー テクノロジの簡単な分析

リリース: 2023-08-04 16:23:14
転載
630 人が閲覧しました

この記事では、Linux の 主要なゼロコピー テクノロジ と、ゼロコピー テクノロジの 適用可能なシナリオ について説明します。 。ゼロコピーの概念を迅速に確立するために、一般的に使用されるシナリオを紹介します。サーバーサイドプログラム (Web サーバーまたはファイルサーバー) を作成する場合、ファイルのダウンロードは基本的な機能です。このとき、サーバーのタスクは、接続されたソケットからサーバーのホスト ディスクにあるファイルを変更せずに送信することであり、通常は次のコードを使用して完了します:

while((n = read(diskfd, buf, BUF_SIZE)) > 0)
    write(sockfd, buf , n);
ログイン後にコピー

基本操作ファイルの内容をディスクからバッファにループで読み取り、バッファの内容をソケットに送信します。ただし、Linux I/O 操作はデフォルトでバッファリングされた I/O になります。ここで使用される 2 つの主なシステム コールは読み取りと書き込みですが、オペレーティング システムがそれらの中で何を行うかはわかりません。実際、上記の I/O 操作中に複数のデータ コピーが発生しました。

アプリケーションが特定のデータにアクセスすると、オペレーティング システムはまず、そのファイルが最近アクセスされたかどうか、またファイルの内容がカーネル バッファにキャッシュされているかどうかを確認します。オペレーティング システム read システム コールによって提供される buf アドレスに直接基づいて、カーネル バッファの内容を buf で指定されたユーザー空間バッファにコピーします。そうでない場合、オペレーティング システムはまずディスク上のデータをカーネル バッファにコピーします。このステップは現在主に DMA に依存して送信され、その後カーネル バッファの内容をユーザー バッファにコピーします。

次に、write システム コールはユーザー バッファーの内容をネットワーク スタックに関連するカーネル バッファーにコピーし、最後にソケットがカーネル バッファーの内容をネットワーク カードに送信します。 。ここまで述べたので、より明確にするために図を見てみましょう:

データ コピー

从上图中可以看出,共产生了四次数据拷贝,即使使用了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

read の代わりに mmap を使用すると、コピーが 1 つ減ります。コピーされるデータの量が多い場合、間違いなく効率が向上します。ただし、mmap の使用にはコストがかかります。 mmap を使用すると、いくつかの隠れた落とし穴に遭遇する可能性があります。たとえば、プログラムがファイルをマップしているときに、そのファイルが別のプロセスによって切り捨てられると、書き込みシステム コールは不正なアドレスにアクセスするため、SIGBUS シグナルによって終了します。 SIGBUS シグナルはデフォルトでプロセスを強制終了し、コアダンプを生成します。この方法でサーバーが停止すると、損失が発生します。

#通常、この問題を回避するには次の解決策を使用します:

1. SIGBUS シグナルのシグナル ハンドラーを作成します

SIGBUS シグナルが発生すると、シグナル ハンドラーは単純に戻り、write システム コールは中断される前に書き込まれたバイト数を返し、errno は success に設定されますが、これは悪いアプローチです。問題の本当の核心に取り組んでいません。

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 によって提案されたパイプ バッファー メカニズムが使用されるため、少なくとも 1 つの記述子がパイプである必要があります。

上記のゼロコピー テクノロジはすべて、ユーザー空間とカーネル空間の間のデータのコピーを削減することによって実装されていますが、場合によっては、ユーザー空間とカーネル空間の間でデータをコピーする必要があります。現時点では、ユーザー空間とカーネル空間でのデータコピーのタイミングについてのみ作業できます。 Linux では通常、システム オーバーヘッドを削減するためにコピー オン ライトが使用され、このテクノロジは COW と呼ばれることがよくあります。

紙面の都合上、この記事ではコピーオンライトについては詳しく紹介しません。一般的な説明は次のとおりです: 複数のプログラムが同時に同じデータにアクセスする場合、各プログラムはそのデータへのポインタを持ちます。各プログラムの観点からは、このデータを独立して所有します。プログラムが実行されたときのみ、データの内容が変更されると、そのデータの内容はプログラム自身のアプリケーション空間にコピーされ、初めてプログラムのプライベート データとなります。プログラムがデータを変更する必要がない場合、データを独自のアプリケーション空間にコピーする必要はありません。これにより、データのコピーが削減されます。執筆中にコピーしたコンテンツは、別の記事を書くために使用できます。 。 。

さらに、ゼロコピー テクノロジもいくつかあります。たとえば、従来の Linux I/O に O_DIRECT マークを追加すると、自動キャッシュを回避して直接 I/O が可能になります。成熟した fbufs テクノロジ。この記事ではすべてのゼロコピー テクノロジをカバーしているわけではありません。いくつかの一般的なテクノロジのみを紹介します。興味がある場合は、自分で勉強してください。一般に、成熟したサーバー プロジェクトでは、I/O 関連部分も変換されます。カーネル自体のデータを改善するための通信速度。

以上がLinux のゼロコピー テクノロジの簡単な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:Linux中文社区
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!