ドライバーとアプリケーション間の共有メモリの実装


すべての人に挨拶!
この短い記事では、カーネルモードとユーザーモードの両方からアクセスできる共有メモリを作成する1つの方法に焦点を当てます。 メモリの割り当てと解放の機能の例を示します。また、ソースへのリンクもあり、誰でも試すことができます。



ドライバーは32ビットWindows XP用に構築されました(その動作もチェックし、
32ビットWindows 7)。

WDK(DDK)のインストール、開発ツールの選択、標準ドライバー関数の作成など、ドライバーの開発については完全には説明しません。 この記事が肥大化しすぎないように、共有メモリを実装する方法のみを説明します。

理論のビット

ドライバは、コードを実行するための特別なプログラムスレッドを作成しませんが、現在アクティブなスレッドのコンテキストで実行します。 したがって、ドライバーは任意のスレッドのコンテキストで実行されると考えられていますコンテキストスイッチング )。 割り当てられたメモリをユーザーアドレス空間にマッピングするとき、ドライバーを管理するアプリケーションフローのコンテキストにいることが非常に重要です。 この場合、このルールは尊重されます。 ドライバーは単一レベルであり、 IRP_MJ_DEVICE_CONTROLリクエストを使用してアクセスします。したがって、フローコンテキストは切り替わりません。アプリケーションのアドレススペースにアクセスできます。

メモリ割り当て

#pragma LOCKEDCODE NTSTATUS AllocateSharedMemory(PDEVICE_EXTENSION pdx, PIRP Irp) { // AllocateSharedMemory KdPrint(("SharedMemory: Entering AllocateSharedMemory\n")); int memory_size = 262144000; // 250 Mb PHYSICAL_ADDRESS pstart = { 0x0, 0x00000000 }; PHYSICAL_ADDRESS pstop = { 0x3, 0xffffffff }; PHYSICAL_ADDRESS pskip = { 0x0, 0x00000000 }; // pointer to the output memory => pdx->vaReturned pdx->vaReturned = (unsigned short **) GenericGetSystemAddressForMdl(Irp->MdlAddress); // create MDL structure (pointer on MDL => pdx->mdl) pdx->mdl = MmAllocatePagesForMdl(pstart, pstop, pskip, memory_size); if (pdx->mdl != NULL) { KdPrint(("SharedMemory: MDL allocated at address %08X\n", pdx->mdl)); // get kernel space virtual address pdx->kernel_va = (unsigned short*) MmGetSystemAddressForMdlSafe(pdx->mdl, NormalPagePriority); if (pdx->kernel_va != NULL) { KdPrint(("SharedMemory: pdx->kernel_va allocated at address %08X\n", pdx->kernel_va)); for (int i = 0; i < 10; ++i) { pdx->kernel_va[i] = 10 - i; // write in memory: 10-9-8-7-6-5-4-3-2-1 } } else { KdPrint(("SharedMemory: Not mapped memory into kernel space\n")); return STATUS_NONE_MAPPED; } // get user space virtual address pdx->user_va = (unsigned short*) MmMapLockedPagesSpecifyCache(pdx->mdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority); if (pdx->user_va != NULL) { KdPrint(("SharedMemory: pdx->user_va allocated at address %08X\n", pdx->user_va)); // return pointer on sharing memory into user space *pdx->vaReturned = pdx->user_va; } else { KdPrint(("SharedMemory: Don't mapped memory into user space\n")); return STATUS_NONE_MAPPED; } } else { KdPrint(("SharedMemory: Don't allocate memory for MDL\n")); return STATUS_MEMORY_NOT_ALLOCATED; } return STATUS_SUCCESS; } // AllocateSharedMemory 

部品の機能分析:
ポインターを保存し、割り当てられたメモリーへのポインターをアプリケーションに渡します。

 pdx->vaReturned = (unsigned short **) GenericGetSystemAddressForMdl(Irp->MdlAddress); 

次のステップは、サイズがmemory_sizeの再配置不可能な物理メモリの割り当てと、それに基づくMDL( メモリ記述子リスト )構造の構築です。ポインタは変数pdx-> mdlに格納されます。

 pdx->mdl = MmAllocatePagesForMdl(pstart, pstop, pskip, memory_size); 


画像からわかるように、固定された物理ページを記述するMDL構造が必要です。

次に、システムアドレス空間でMDLの仮想アドレスの範囲を取得し、これらのアドレスへのポインターを変数pdx-> kernel_vaに保存します。

 pdx->kernel_va = (unsigned short*) MmGetSystemAddressForMdlSafe(pdx->mdl, NormalPagePriority); 

この関数は、ドライバーに割り当てられたメモリにアクセスするためのポインターを返します(さらに、アドレスはシステムアドレス空間から受信されるため、ストリームの現在のコンテキストに関係なく)。

ループ内で、ユーザーモードから割り当てられたメモリの可用性を確認できるように、最初の10個のメモリセルに10〜1の数字を書き込みます。

 for (int i = 0; i < 10; ++i) { pdx->kernel_va[i] = 10 - i; // write in memory: 10-9-8-7-6-5-4-3-2-1 } 

次に、割り当てられたメモリを、ドライバーにアクセスしたアプリケーションのアドレス空間にマップする必要があります。

 pdx->user_va = (unsigned short*) MmMapLockedPagesSpecifyCache(pdx->mdl, UserMode, MmCached, NULL, FALSE, NormalPagePriority); 

変数pdx-> vaReturnedは、ポインターへのポインターであり、pdx構造体で宣言されています(source_driverフォルダーのdriver.hを参照)。 それを使用して、pdx-> user_vaポインターをアプリケーションに渡します。

 *pdx->vaReturned = pdx->user_va; 


空きメモリ

 #pragma LOCKEDCODE NTSTATUS ReleaseSharedMemory(PDEVICE_EXTENSION pdx, PIRP Irp) { // ReleaseSharedMemory KdPrint(("SharedMemory: Entering ReleaseSharedMemory\n")); if (pdx->mdl != NULL) { MmUnmapLockedPages(pdx->user_va, pdx->mdl); MmUnmapLockedPages(pdx->kernel_va, pdx->mdl); MmFreePagesFromMdl(pdx->mdl); IoFreeMdl(pdx->mdl); KdPrint(("SharedMemory: MDL at address %08X freed\n", pdx->mdl)); } return STATUS_SUCCESS; } // ReleaseSharedMemory 


ここで、アプリケーションのアドレススペースが解放されます。

 MmUnmapLockedPages(pdx->user_va, pdx->mdl); 

システムアドレス空間:

 MmUnmapLockedPages(pdx->kernel_va, pdx->mdl); 

その後、物理ページが解放されます。

 MmFreePagesFromMdl(pdx->mdl); 

および「殺す」MDL:

 IoFreeMdl(pdx->mdl); 


ユーザーモードからドライバーにアピールします

(添付資料のアプリケーションコード全体を参照してください)

最初に行うことは、 CreateFile ()関数を使用してデバイスマニピュレーター(ハンドル)を取得することです。

 hDevice = CreateFile(L"\\\\.\\SharedMemory", GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); 


次に、 DeviceIoControl ()関数を使用してドライバーにI / O要求を送信する必要があります。

 unsigned short** vaReturned = new unsigned short*(); ioCtlCode = IOCTL_ALLOCATE_SHARED_MEMORY; checker = DeviceIoControl(hDevice, ioCtlCode, NULL, 0, vaReturned, sizeof(int), &bytesReturned, NULL); 

関数呼び出しはIRPパケットに変換され、ドライバーのディスパッチ関数で処理されます(ドライバーのcontrol.cppファイルのDispatchControl()を参照)。 つまり DeviceIoControl()を呼び出すと、制御は上記のコードで説明したドライバー関数に転送されます。 また、DebugViewプログラムでDeviceIoControl()関数を呼び出すと(カーネルモードイベントをキャッチするようにボックスをオンにする必要があります)、次のように表示されます。



制御がアプリケーションに返されると、vaReturned変数は共有メモリを指します(より正確には、既にメモリを指しているポインタを指します)。 メモリへの通常のポインタを取得するために、少し単純化してみましょう。

 unsigned short* data = *vaReturned; 

これで、 データポインターを使用して、アプリケーションから共有メモリにアクセスできます。

[メモリの割り当て]ボタンをクリックすると、アプリケーションはドライバーに制御を転送し、ドライバーは上記のすべての手順を実行し、割り当てられたメモリーへのポインターを返します。 「Fill TextEdit」ボタンを使用して、QTextEditでドライバーに入力された最初の10個の要素の内容を表示し、共有メモリーへの呼び出しが成功したことを確認します。

[メモリの解放]ボタンをクリックすると、メモリが解放され、作成されたMDL構造が削除されます。


ソースコード

1. source_driver.rar
2. source_app.rar
3. source_generic_oney

ドライバー(source_driver)の基礎として、私はWalter Oniからサンプルの1つを取りました(サンプルは彼の本「Using the Microsoft Windows Driver Model」に添付されています)。 また、次のようにGenericコアライブラリをダウンロードする必要があります。 このライブラリは、アセンブリ中とドライバー操作中の両方で必要です。

自分で試してみたい人

ディレクトリ(C:\ Driversなど)を作成し、そこにソース(source_driver、source_generic_oney、source_app)を展開します。 ドライバーを再構築しない場合は、コントロールパネルから(infファイル:sharedmemory.infを指定して)新しい機器を手動でインストールするだけで十分です-新しい機器をインストールします(Windows XPの場合)。 次に、habr_app.exe(source_app / release)を実行する必要があります。

再構築することに決めた場合:
1. WDKをインストールする必要があります。
2.最初に、Genericライブラリを再構築する必要があります。 OSのバージョンに応じて、出力ファイルのあるフォルダーを異なる方法で呼び出すことができます(たとえば、XPの場合-objchk_wxp_x86、Win7の場合-objchk_win7_x86)。
3.ポイント1および2の後、WDKに含まれるx86 Checked Build Environmentを使用して、「build」コマンドでドライバーのビルドを試みることができます。

ソース


UPD: jon habrayuzerは、 共有メモリがQtを意味するリンクをもたらしました。

Source: https://habr.com/ru/post/J144507/


All Articles