Linux におけるゼロコピー技術の使用に関する簡単な分析

Linux におけるゼロコピー技術の使用に関する簡単な分析

この記事では、Linux におけるいくつかの主要なゼロコピー テクノロジと、ゼロコピー テクノロジを適用できるシナリオについて説明します。ゼロコピーの概念をすぐに理解するために、一般的なシナリオを紹介しましょう。

引用

サーバープログラム(Webサーバーやファイルサーバー)を作成する場合、ファイルのダウンロードは基本的な機能です。このとき、サーバーのタスクは、接続されたソケットからサーバー ホスト ディスク上のファイルを変更せずに送信することです。通常、これを完了するには次のコードを使用します。

while((n = read(diskfd, buf, BUF_SIZE)) > 0)
  書き込み(sockfd, buf, n);

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

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

次に、書き込みシステム コールは、ユーザー バッファーの内容をネットワーク スタックに関連付けられたカーネル バッファーにコピーし、最後にソケットはカーネル バッファーの内容をネットワーク カードに送信します。

ここまで述べてきましたが、写真をよく見た方が良いでしょう。

データコピー

上図からわかるように、合計 4 つのデータコピーが生成されます。ハードウェアとの通信に DMA を使用した場合でも、CPU は 2 つのデータコピーを処理する必要があります。同時に、ユーザー モードとカーネル モードの間で複数のコンテキスト スイッチが発生するため、CPU の負荷が確実に増加します。
このプロセスでは、ファイルの内容に変更を加えなかったため、カーネル空間とユーザー空間の間でデータをコピーすることは間違いなく無駄であり、ゼロコピーは主にこの非効率性を解決するためのものです。

ゼロコピーテクノロジーとは何ですか? ##

ゼロ コピーの主なタスクは、CPU が 1 つのストレージ ユニットから別のストレージ ユニットにデータをコピーするのを防ぐことです。主にさまざまなゼロ コピー テクノロジを使用して、CPU が大量のデータ コピー タスクを実行するのを回避し、不要なコピーを減らしたり、他のコンポーネントにこの種の単純なデータ転送タスクを実行させたりすることで、CPU が他のタスクに集中できるようにします。これにより、システム リソースをより効率的に使用できるようになります。

前回の記事の例に戻りましょう。データのコピー回数を減らすにはどうすればよいでしょうか?明らかな焦点は、カーネル空間とユーザー空間の間でのデータのやり取りを減らすことであり、これにより、ゼロコピーのタイプも導入されます。

ユーザー空間を通過せずにデータ転送を許可する

mmap の使用#####

コピー数を減らす 1 つの方法は、読み取りではなく mmap() を呼び出すことです。

buf = mmap(diskfd, len);
書き込み(sockfd、buf、len);

アプリケーションが mmap() を呼び出すと、ディスク上のデータが DMA 経由でカーネル バッファにコピーされます。その後、オペレーティング システムはこのカーネル バッファをアプリケーションと共有するため、カーネル バッファの内容をユーザー スペースにコピーする必要はありません。アプリケーションは再度 write() を呼び出し、オペレーティング システムはカーネル バッファーの内容をソケット バッファーに直接コピーします。この処理はすべてカーネル状態で行われます。最後に、ソケット バッファーはデータをネットワーク カードに送信します。
同様に、画像も簡単に確認できます。

mmap

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

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

SIGBUS シグナル用のシグナル ハンドラーを作成する SIGBUS シグナルが発生すると、シグナル ハンドラーは単に戻り、write システム コールは中断される前に書き込まれたバイト数を返し、errno は success に設定されますが、これは問題の核心を解決していないため、対処方法としては適切ではありません。

ファイル リース ロックの使用 通常、この方法はファイル記述子のリース ロックを使用するために使用します。カーネルからファイルのリース ロックを申請します。他のプロセスがファイルを切り捨てる場合、カーネルはリアルタイムの RT_SIGNAL_LEASE シグナルを送信し、カーネルがファイルに対する読み取り/書き込みロックを破棄していることを通知します。この方法では、プログラムが不正なメモリにアクセスして SIGBUS によって強制終了される前に、書き込みシステム コールが中断されます。 write は書き込まれたバイト数を返し、errno を success に設定します。
mmap の前にファイルをロックし、操作後にロックを解除する必要があります。

if(fcntl(diskfd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
  perror("カーネルリースセットシグナル");
  -1 を返します。
}
/* l_type は F_RDLCK F_WRLCK ロックにすることができます*/
/* l_type は F_UNLCK ロック解除にすることができます*/
if(fcntl(diskfd, F_SETLEASE, l_type)){
  perror("カーネルリースセットタイプ");
  -1 を返します。
}

sendfile の使用#####

カーネル バージョン 2.1 以降、Linux では操作を簡素化するために sendfile が導入されました。

#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *オフセット, size_t カウント);

sendfile() システムコールは、入力ファイル記述子 in_fd と出力ファイル記述子 out_fd の間でファイルの内容 (バイト) を転送します。記述子 out_fd はソケットを参照する必要があり、in_fd が指すファイルは mmap 可能である必要があります。これらの制限により、sendfile の使用は、ファイルからソケットへのデータ転送のみ可能となり、その逆は不可能となります。
sendfile を使用すると、データのコピー数が減るだけでなく、コンテキストの切り替えも減り、データ転送は常にカーネル空間でのみ行われるようになります。

sendfile システムコールプロセス

sendfile を呼び出したときに別のプロセスがファイルを切り捨てるとどうなりますか?シグナル ハンドラを設定しないと仮定すると、sendfile 呼び出しは単に中断される前に転送されたバイト数を返すだけであり、errno は成功に設定されます。 sendfile を呼び出す前にファイルをロックすると、sendfile は以前と同じように動作し、RT_SIGNAL_LEASE シグナルを受信します。

これまで、データのコピー数は削減されましたが、ページ キャッシュからソケット キャッシュへのコピーがまだ 1 つ残っています。では、このコピーも省略できますか?

ハードウェアの助けがあれば、それが実現できます。これまでは、ページ キャッシュ内のデータをソケット キャッシュにコピーしていました。実際には、バッファー記述子をソケット バッファーに渡し、データ長を渡すだけで済みます。このようにして、DMA コントローラーはページ キャッシュ内のデータを直接パッケージ化し、ネットワークに送信できます。

要約すると、sendfile システム コールは DMA エンジンを使用してファイルの内容をカーネル バッファーにコピーし、ファイルの場所と長さの情報を含むバッファー記述子をソケット バッファーに追加します。この手順では、カーネル内のデータはソケット バッファーにコピーされません。DMA エンジンは、カーネル バッファー内のデータをプロトコル エンジンにコピーし、最後のコピーを回避します。

DMA によるファイル送信

ただし、この収集およびコピー機能には、ハードウェアとドライバーのサポートが必要です。

スプライスの使用#####

sendfile はファイルからソケットへのデータのコピーにのみ適用可能であり、使用範囲が制限されます。 Linux では、バージョン 2.6.17 で 2 つのファイル記述子間でデータを移動するための splice システム コールが導入されました。

#define _GNU_SOURCE /* 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);

スプライス呼び出しは、カーネル空間とユーザー空間間でデータをコピーせずに、2 つのファイル記述子間でデータを移動します。これは、len の長さのデータを fd_in から fd_out にコピーしますが、2 つのうちの 1 つはパイプ デバイスである必要があり、これは現時点での splice の制限の一部でもあります。 flags パラメータには次の値があります。

  • SPLICE_F_MOVE : データをコピーするのではなく移動しようとします。これはカーネルへの小さなヒントです。カーネルがパイプからデータを移動できない場合、またはパイプのバッファが完全なページでない場合は、データをコピーする必要があります。初期の Linux 実装にはいくつか問題があるため、このオプションは 2.6.21 以降では機能しませんが、それ以降の Linux バージョンでは実装される予定です。
  • ** SPLICE_F_NONBLOCK** : スプライス操作はブロックされません。ただし、ファイル記述子が非ブロッキング I/O 用に設定されていない場合は、splice の呼び出しがブロックされる可能性があります。
  • ** SPLICE_F_MORE**: 後続のスプライス呼び出しでは、より多くのデータが含まれます。

スプライス呼び出しは Linux によって提案されたパイプ バッファ メカニズムを利用するため、少なくとも 1 つの記述子はパイプである必要があります。

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

スペースの制約により、この記事ではコピーオンライトについて詳しく説明しません。大まかに言うと、複数のプログラムが同時に同じデータにアクセスする場合、各プログラムにはこのデータへのポインタがあります。各プログラムの観点から見ると、このデータは独立して所有されています。プログラムがデータの内容を変更する必要がある場合にのみ、データの内容がプログラム独自のアプリケーション空間にコピーされます。この時点で、データはプログラムのプライベート データになります。プログラムがデータを変更する必要がない場合は、データを独自のアプリケーション スペースにコピーする必要はありません。これにより、データのコピーが削減されます。コピーオンライトの内容については、別の記事で説明できるほどです。 。 。

さらに、ゼロコピー技術もいくつかあります。たとえば、従来の Linux I/O に O_DIRECT マークを追加すると、直接 I/O を実行し、自動キャッシュを回避できます。また、未熟な fbufs 技術もあります。この記事では、すべてのゼロコピー技術を網羅しているわけではなく、一般的な技術をいくつか紹介するだけです。興味があれば、自分で勉強してください。一般的に、成熟したサーバー プロジェクトでは、データ転送速度を向上させるために、カーネルの I/O 関連部分も独自に変更します。

Linux でのゼロコピー技術の使用に関するこの記事はこれで終わりです。Linux のゼロコピーに関する関連コンテンツをさらにご覧になりたい場合は、123WORDPRESS.COM で過去の記事を検索するか、以下の関連記事を引き続きご覧ください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Linuxで大きなファイルを素早くコピーする方法
  • Linuxはscpコマンドを使用してファイルをローカルコンピュータにコピーし、ローカルファイルをリモートサーバーにコピーします。
  • Linux で scp コマンドを使用してファイルをリモートでコピーする方法の詳細な説明
  • Linux で R の新しいバージョンを直接コピーする方法
  • Linux のコピーオンライトに関する必読記事
  • Linuxのcpコマンドで「copy all」と書く方法の詳しい説明
  • Virtualbox ホストと仮想マシン間のフォルダ共有と双方向コピー (Windows<->Windows、Windows<->Linux)
  • Linux の高度なコピー scp コマンドの詳細な説明
  • Linuxはscpコマンドを使用してファイルをバックアップします。scpはファイルをコピーします。
  • scpを使用してリモートLinuxサーバー上のファイルを取得するLinuxリモートコピーファイル

<<:  JSは5つ星の賞賛効果を達成

>>:  MySQL 8.0.21 のインストールと設定方法のグラフィックチュートリアル

推薦する

Linux7で仮想ホストを実装する3つの方法

1. 同じIPアドレス、異なるポート番号仮想ホスト 1: ホスト IP アドレスは 172.16.3...

Docker 用ビジュアル UI 管理ツール Portainer のインストールと使用方法の分析

Portainer は、ステータス表示パネル、アプリケーション テンプレートの迅速な展開、コンテナ ...

MySQL 8.0.18 ハッシュ結合は左/右結合をサポートしていません 左と右の結合の問題

MySQL 8.0.18 では、インデックスが作成されていないフィールドに適用でき、等価値の関連付け...

DockerプライベートイメージライブラリとAlibaba CloudオブジェクトストレージOSSの簡単な分析

Docker プライベートイメージライブラリDockerプライベートイメージライブラリとAlibab...

Kylin V10 サーバーで Storm をコンパイルしてインストールする詳細なプロセス

1 はじめにApache Storm は、Hadoop と同様に、大量のデータを処理するために使用で...

React forwardRefの使い方と注意点

これまで react.forwardRef は react の高階コンポーネントには適用できませんで...

自動同期テーブル構造のMySql開発

開発の問題点開発プロセスでは、データベース フィールドが頻繁に変更されるため、RD 環境と QA 環...

MySQL インデックス最適化の説明

日常業務では、実行に時間のかかる SQL ステートメントを記録するために、スロー クエリを実行するこ...

VUE ユニアプリの基本コンポーネントの簡単な紹介

1. スクロールビュー垂直スクロールを使用する場合は、固定の高さを指定して CSS で高さを設定する...

MySQL ジョイントテーブルクエリの簡単な例

MySql は結合テーブルクエリを使用しますが、初心者には理解しにくい場合があります。以下の記事では...

bashの初期化メカニズムの詳細な説明

Bash 初期化ファイル対話型ログインシェル次の場合にはログイン シェルを取得できます。ローカル端末...

HTML リンク アンカー タグと SEO におけるその役割の概要

<a> タグは主に、ハイパーリンクまたはアンカー リンクとも呼ばれるリンクとブックマーク...

CentOS 6.2 に MySQL 5.7.28 をインストールするチュートリアル (mysql ノート)

1. 環境整備1.MySQLインストールパス: /usr/local 2. CentOS 6.2 ...

フロントエンドに必要なNginx設定の詳細な説明

Nginx (エンジン x) は、軽量で高性能な HTTP およびリバース プロキシ サーバーであり...