GNU/Linux >> Linux の 問題 >  >> Linux

Linux カーネルモジュールで 1GB の HugePages に裏打ちされた DMA バッファを割り当てるにはどうすればよいですか?

問題

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

    しかし、機能は存在します! 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 GB の hugepage を割り当てる
  • get_huge_pages() を hugetlbfs で呼び出して、ユーザー空間ポインタ (仮想アドレス) を取得します
  • ユーザーの仮想アドレス (unsigned long にキャストされた通常のポインター) をドライバー ioctl に渡します
  • カーネル ドライバーのワークフロー

    <オール>
  • ioctl 経由でユーザーの仮想アドレスを受け入れる
  • follow_huge_addr を呼び出して構造体ページを取得します*
  • 構造体ページ*で page_to_phys を呼び出して、物理アドレスを取得します
  • DMA 用にデバイスに物理アドレスを提供する
  • カーネル仮想ポインターも必要な場合は、構造体ページで kmap* を呼び出します
  • 免責事項

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

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

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

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

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

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

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


    Linux
    1. Linux –どのモジュールがカーネルを汚染しているかを判断する方法は?

    2. Linux – Linuxの汚染されたカーネル?

    3. Linux –カーネルモジュールを適切にリロードする方法は?

    1. Linux LKM ローダブル カーネル モジュールを作成、コンパイル、ロードする方法

    2. Linuxカーネルバッファをユーザー空間にmmapする方法は?

    3. Linux カーネルモジュールのコーディング方法は?

    1. Linuxデスクトップでカーネルをアップグレードする方法

    2. 簡単な例で独自の Linux カーネル モジュールを作成する方法

    3. ブート時に組み込み Linux カーネルモジュールに値を渡す方法は?