Linux* 上で CPU と GPU バッファーを共有する

インテル® DPC++/C++ コンパイラーセキュリティー

この記事は、インテル® デベロッパー・ゾーンに公開されている「Sharing CPU and GPU buffers on Linux*」(https://software.intel.com/en-us/blogs/2016/05/13/sharing-cpu-and-gpu-buffers-on-linux) の日本語参考訳です。


概要

CPU と GPU が、インテル® アーキテクチャー (IA) 上の先進的かつスマートな階層ロジックを介して物理メモリーを共有するというのは、効率良くグラフィックス・テクスチャーを利用する重要な機能です。ここ数年、インテル・オープンソース・テクノロジー・センター (OTC) は、ゼロコピー・テクスチャー・アップロードと呼ばれる手法を使用して、Chrome™ 上でこのハードウェア機能を活用してきました。これによりパフォーマンスが改善され、Web コンテンツを表示する際にメモリー使用量を減らし電力を節約します。そしてこの機能は、IA ベースのすべての Chrome™ に統合される予定です。

この記事では、CPU と GPU でグラフィックス・バッファーを共有するために設計された、新しいアプリケーション・プログラミング・インターフェイス (API) を紹介します。ゼロコピー手法は、CPU と GPU の両方で効率良く動作し、他の Linux* バリアントにもスケールアップできます。これは、Linux* バージョン 4.6 で利用できます。

システム要件

新しいバッファー共有システムを設計する際、次のコア要件が考慮されました。

シンプルでベンダーに依存しない API: 異なるハードウェア・プラットフォームでは、異なる実装が行われます。開発者は、開発を容易にして攻撃対象領域を減らすため、必要なユーザー空間ライブラリーを最小限にすることを望みます。CPU と GPU に同時にメモリーバッファーをマッピングする場合、キャッシュの一貫性に特別な配慮が必要となります。私たちは、利用可能なハードウェアのインターフェイスにかかわりなく、オペレーティング・システム (OS) 上のアプリケーションを記述できるよう、単一のベンダーに依存しない API を提供することでこの要求に対処しました。

効率と堅牢性: 異なるデバイスのドメインおよびプロセスで物理メモリーを共有するという考えは、特定のメモリーチャンクをコピーして保存できるということです。これをスマートかつ効率良く行うことで、OS に関連する全体のリソースを大幅に軽減することができました。

セキュリティー: 私達は、悪意あるアプリケーションがこの API を使用してシステムをアクセスする可能性を減らすように、基本的なサンドボックスの概念によってバッファー共有 API を設計しました。バッファー割り当てタスクは、特権を持った安全なプロセスかもしれませんが、非特権プロセスはバッファーのマップ、読み込み、および書き込み権限のみを持ちます。特権プロセスは OS とともに、非特権 (ユーザー・アプリケーションなど) 実行がサンドボックス内で制限された領域にアクセスするバグに作用します。

API

前述の設計要件に対処し、これらの特性をシステム構築に反映するため、Linux* の dma-buf API を変更しました。カーネルモジュール内の最も重要な変更は以下です。

  • mmap(dma_buf_fd, ...): グラフィック・バッファーの dma-buf ファイル・ディスクリプターをユーザー空間にマップする際に、最も重要なことは、割り当てられたポインターに実際に書き込みを行うことです (以前は許されませんでした)。ダイレクト・レンダリング・マネージャー (DRM)ハードウェア・ドライバー実装が、マップされるグラフィック・ハンドルを安全にエクスポートすることが重要です。 
  • ioctl(dma_buf_fd, DMA_BUF_IOCTL_SYNC, &args): CPU と GPU デバイスにおけるキャッシュ一のコヒーレンシー管理は、dma-buf を介して同時にアクセスされることで行われます。既存の dma-buf デバイスドライバー vfunc フックに直接フォワードするコヒーレンシー・マーカーは、DMA_BUF_IOCTL_SYNC ioctl を介してユーザー空間に置かれ、マップされた領域がアクセスされる前後で使用します。これは、グラフィック・エンジンと CPU コアがキャッシュを共有しないハードウェア・アーキテクチャーでは原理的に重要ですが、メモリー階層が (ほとんど) コヒーレントであるそのほかのタイプのハードウェアでもまた重要です。詳細は、このパッチセットで見つけることができます。

上記の API の変更を示すため、intel-gpu-tools を使用します。完全なサンプルは、こちらでご覧いただけます。

バッファーの初期化とエクスポート

アイディアは、1 つのプロセス内でバッファー・オブジェクト (BO) を作成して (この場合フレームバッファー) エクスポートし、ほかのプロセスにその fd を渡すことです。順番にプロセスがマップして書き込みを行うため fd が使用されます。

この例で、gpu ポインターは特権プロセスです。これにより、DRM、ディスプレイ管理、そしてドライバーアクセスなど、システム・グラフィックス・ルーチンへアクセスします。run_test が実行される前に、gpu は適切にフレームバッファー情報に従って初期化されます。

static void run_test(gpu_process_t *gpu)
{
        struct drm_prime_handle args;
        ...
        args.handle = gpu->fb.gem_handle;
        args.flags = DRM_CLOEXEC | DRM_RDWR;
        ioctl(gpu->drm_fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &args);
        prime_fd = args.fd;
     
        if (fork() == 0) {
                init_renderer(prime_fd, gpu->fb.size, gpu->fb.width, gpu->fb.height);
        }
}

フレームバッファー・ハンドル (gpu->fb.gem_handle) を使用することで、後にほかのプロセス (レンダ―) と共有される prime_fd ファイル・ディスクリプターを取得できます。

レンダ―ポインターは、基本的にはユーザー空間の正規クライアントです。これは、限られたシステムリソースにアクセスする非特権プロセスです。dma-buf ファイル・ディスクリプターとフレームバッファー・ディメンジョンのみを共有して初期化を行ったのち、フレームバッファーへの書き込み準備が整います。

static void init_renderer(int prime_fd, int fb_size, int width, int height)
{
        render_process_t render;

        render.prime_fd = prime_fd;
        render.size = fb_size;
        render.width = width;
        render.height = height;
        paint(&render);
}

dma-buf fd のマッピング

dma-buf ファイル・ディスクリプターで mmap 関数を呼び出すことで、簡単にエクスポートされたバッファーをマップできます。

static void paint(render_process_t *render)
{
        void *frame;
        frame = mmap(NULL, render->size, PROT_READ | PROT_WRITE, MAP_SHARED,
                     render->prime_fd, 0);
        igt_assert(frame != MAP_FAILED);
        ...
}  // end of paint()

キャッシュ制御

ここから、ペイントを行うことができます。処理デバイス間で矛盾する可能性のあるマッピングされたポインターにアクセスする場合、特に注意が必要です。dma-buf API のドキュメント (http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/plain/Documentation/dma-buf-sharing.txt (英語)) に記載されるように、実行中のハードウェアがコヒーレントであるかどうかにかかわらず、常に ioctrl 同期呼び出しでポインターをラップすべきです。そうしないと、システムはスクリーンにアーティファクトを描画します。

static void paint(render_process_t *render)
{
        ...
        rect_t rect;
        int stride, bpp, color = 0xFF;
        int x, y, line_begin;
        struct dma_buf_sync sync_args;

        ...
        sync_args = DMA_BUF_SYNC_START;
        ioctl(render->prime_fd, DMA_BUF_IOCTL_SYNC, sync_args);

        for (y = rect.y; y < rect.y + rect.h; y++) {
                line_begin = y * stride / (bpp / 8);
                for (x = rect.x; x < rect.x + rect.w; x++)
                        set_pixel(frame, line_begin + x, color, bpp);
        }

        sync_args = DMA_BUF_SYNC_END;
        ioctl(render->prime_fd, DMA_BUF_IOCTL_SYNC, sync_args);
        munmap(frame, render->size);
}  // end of paint()

未解決の問題とまとめ

どのようにクライアントは、BO が X タイルまたは Y タイルで作成されたか知ることができるでしょう? また、どのようなメモリーレイアウトになるでしょう? 現在の dma-buf API では、このようなバッファーのセマンティクスを扱う方法がありません。これは、将来間違いなく解決すべきことです。

この記事では、IA 上で CPU によるテクスチャー処理を簡単にプログラムする方法を実証しました。Chrome™ では、私達は、新しい dma-buf API について DRI とカーネル・コミュニティーで議論を重ねた結果、VGEM バッファー共有システムを置き換えることに成功しました。dma-buf サブモジュールに導入されたこの新しい API の実装は、Linux カーネルの 4.6 で利用できるようになります。

著者紹介

Tiago Vignatti は、インテル・オープンソース・テクノロジー・センター (OTC) のプログラマーであり、10 年ほどオープンソース・グラフィックス業界と作業してきました。Tiago は、Linux* と Mesa* グラフィックスで、 X Window と Wayland* システムそして Chrome™ に影響を与えました。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

Chrome は Google Inc. の登録商標または商標です。

タイトルとURLをコピーしました