8

HPC ワークロードに DMA バッファを割り当てようとしています。64GB のバッファ領域が必要です。計算の合間に、一部のデータが PCIe カードにオフロードされます。pci_alloc_consistent で指定された大量の 4MB バッファにデータをコピーするのではなく、1GB の HugePages でバックアップされた 64 個の 1GB バッファを作成したいと考えています。

背景情報: カーネル バージョン: CentOS 6.4 / 2.6.32-358.el6.x86_64 カーネル ブート オプション: hugepagesz=1g hugepages=64 default_hugepagesz=1g

/proc/meminfo の関連部分: AnonHugePages: 0 kB HugePages_Total: 64 HugePages_Free: 64 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 1048576 kB DirectMap4k: 848 kB DirectMap2M: 2062336 kB DirectMap1G: 132120576 kB

-t hugetlbfs nodev /mnt/hugepages をマウントできます。CONFIG_HUGETLB_PAGE は true です。MAP_HUGETLB が定義されています。

libhugetlbfs を使用してユーザー空間で get_huge_pages() を呼び出すことに関する情報を読みましたが、理想的には、このバッファーはカーネル空間に割り当てられます。MAP_HUGETLB で do_mmap() を呼び出してみましたが、空き hugepage の数は変わらないようでした。

だから私は何を得ていると思いますか、カーネル空間でバッファを1GBのHugePageにマップできる方法はありますか、それともユーザー空間で行う必要がありますか? または、誰かが他の方法を知っていれば、カーネルバッファーとして利用可能な連続した物理メモリの膨大な量 (1-64GB) を取得できますか?

4

3 に答える 3

4

問題

  1. 通常、DMA バッファを割り当てたい場合、または物理アドレスを取得したい場合、これはカーネル空間で行われます。これは、ユーザー コードが物理アドレスをいじる必要がないためです。
  2. Hugetlbfs は、1GB の huge ページを割り当て、ユーザー空間の仮想アドレスを取得するためのユーザー空間マッピングのみを提供します。
  3. ユーザーのヒュージページの仮想アドレスを物理アドレスにマップする機能はありません

ユーレカ

しかし、機能は存在します!2.6 カーネル ソース コードの奥深くに埋もれているのは、仮想アドレスから構造体ページを取得するこの関数で、「テスト用」としてマークされ、#if 0 でブロックされます。

#if 0   /* This is just for testing */
struct page *
follow_huge_addr(struct mm_struct *mm, unsigned long address, int write)
{
    unsigned long start = address;
    int length = 1;
    int nr;
    struct page *page;
    struct vm_area_struct *vma;

    vma = find_vma(mm, addr);
    if (!vma || !is_vm_hugetlb_page(vma))
        return ERR_PTR(-EINVAL);

    pte = huge_pte_offset(mm, address);

    /* hugetlb should be locked, and hence, prefaulted */
    WARN_ON(!pte || pte_none(*pte));

    page = &pte_page(*pte)[vpfn % (HPAGE_SIZE/PAGE_SIZE)];

    WARN_ON(!PageHead(page));

    return page;
}

解決策: 上記の関数は実際にはカーネルにコンパイルされていないため、ドライバー ソースに追加する必要があります。

ユーザー側のワークフロー

  1. カーネル ブート オプションを使用して、ブート時に 1 GB の hugepage を割り当てる
  2. get_huge_pages() を hugetlbfs で呼び出して、ユーザー空間ポインター (仮想アドレス) を取得します。
  3. ユーザーの仮想アドレス (unsigned long にキャストされた通常のポインター) をドライバー ioctl に渡します。

カーネル ドライバーのワークフロー

  1. ioctl 経由でユーザーの仮想アドレスを受け入れる
  2. follow_huge_addr を呼び出して構造体ページを取得します*
  3. struct page* で page_to_phys を呼び出して、物理アドレスを取得します。
  4. DMA 用にデバイスに物理アドレスを提供する
  5. カーネル仮想ポインターも必要な場合は、構造体ページで kmap* を呼び出します。

免責事項

  • 上記の手順は、数年後に回想されています。元のソース コードにアクセスできなくなりました。あなたのデューデリジェンスを行い、私がステップを忘れていないことを確認してください.
  • これが機能する唯一の理由は、ブート時に 1 GB のヒュージ ページが割り当てられ、その物理アドレスが永久にロックされるためです。非 1GBhugepage に裏打ちされたユーザー仮想アドレスを DMA 物理アドレスにマップしようとしないでください! あなたは悪い時間を過ごすつもりです!
  • システムで慎重にテストして、1 GB のヒュージ ページが実際に物理メモリにロックされていること、およびすべてが正確に機能していることを確認します。このコードは私のセットアップでは問題なく動作しましたが、何か問題が発生した場合、大きな危険が伴います。
  • このコードは、x86/x64 アーキテクチャ (物理アドレス == バス アドレス) およびカーネル バージョン 2.6.XX でのみ動作することが保証されています。後のカーネル バージョンではこれを行う簡単な方法があるかもしれませんが、現在は完全に不可能かもしれません。
于 2017-06-26T09:35:53.480 に答える
2

これはカーネル空間では一般的に行われないため、あまり多くの例はありません。

他のページと同様に、巨大なページは alloc_pages で割り当てられます。

struct page *p = alloc_pages(GFP_TRANSHUGE, HPAGE_PMD_ORDER);

HPAGE_PMD_ORDER はマクロであり、通常のページに関して単一のヒュージ ページの順序を定義します。上記は、透過的なヒュージ ページがカーネルで有効になっていることを意味します。

次に、kmap() を使用して、通常の方法で取得したページ ポインターのマッピングを続行できます。

免責事項:私は自分で試したことがないので、実験する必要があるかもしれません. 確認すべきことの 1 つは、HPAGE_PMD_SHIFT が小さい「巨大な」ページの順序を表していることです。これらの巨大な 1GB ページを使用する場合は、おそらく PUD_SHIFT - PAGE_SHIFT など、別の順序を試す必要があります。

于 2013-10-31T02:04:44.263 に答える