Help us understand the problem. What is going on with this article?

Windowsカーネルモード スレッドを一定時間停止させる方法

kmdf3.png

はじめに

前回の記事: Windowsカーネルモードドライバでワークアイテムを登録する

前回の記事の続きです。
カーネルモードでは、通常のユーザーモードと違い、Sleep()関数を使用することはできません。
したがって、スレッドのスリープにはカーネルのタイマーを使用するか、スレッドを眠らせるしかありません。
ゴリ押し手法 & 正確性皆無ではありますが、for(int x=0;x<n;x++){}でn回以上であれば処理を行う、という手法もあります。
しかしながら、システム上で動く以上無駄な処理は避けたいですし、速度も不確定なのでよっぽどの理由がない限り使用するべきではないです。
PCスペックに依存しますが、僕の環境だと優先レベル12で1秒に約100万回ループしていました。
PCの現在時刻を取得するのもまぁ手段の一つだと思います。

概要

今回は、KeDelayExecutionThread関数を使用します。

NTSTATUS KeDelayExecutionThread(
  KPROCESSOR_MODE WaitMode,
  BOOLEAN         Alertable,
  PLARGE_INTEGER  Interval
);

返り値は以下の通りです。

NTSTATUS 概要
STATUS_SUCCESS    遅延終了
STATUS_ALERTED    スレッドにアラートが発生して遅延終了した場合
STATUS_USER_APC    intervalが切れる前にユーザモードのAPCが配信された場合

第一引数KPROCESSOR_MODEは以下の通りです。
今回はカーネルモードなので、KernelModeを指定します。

enum KPROCESSOR_MODE {
    KernelMode,
    UserMode,
}

第二引数BOOLEAN Alertableは、
待機がアラート可能な場合はTRUEに、下位ドライバーではFALSEを指定するべきらしいです。

第三引数PLARGE_INTEGERは、止まる時間を指定します。
ネイティブ構造体は以下の通りです。

typedef union _LARGE_INTEGER {
  struct {
    DWORD LowPart;
    LONG  HighPart;
  };
  struct {
    DWORD LowPart;
    LONG  HighPart;
  } u;
  LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;

僕はここのPLARGE_INTEGER(LARGE_INTEGER)の扱いに少し戸惑いました。
これは、GetDiskFreeSpaceEx()などの大きな値を扱う場合に使用されるみたいです。
構造体を見て取れる通り、LARGE_INTEGERの実体はLONGLONGですね。
LONGLONGは4byteであるDWORD,LONG,ULONG,FLOATと違い、8byte(64bit)という大きなサイズを持ってます。
変数の基本データ型
大きすぎるから、LowPart(32bit)とHighPart(32bit)に分かれているということですね。

相対時間と絶対時間

PLARGE_INTEGERQuadPartには負の値である相対時間と正の値である絶対時間を指定できます。
今回の場合、相対時間で指定します。単位は100nsです。
したがって、1秒は以下の通りです。
1000ms(ミリ秒) = 1s(秒)
1000μs(マイクロ秒) = 1ms(ミリ秒)
1000ns(ナノ秒) = 1μs(マイクロ秒)
1s(秒) = 1/1,000,000,000秒
したがって、単位は1(00)nsなので
1000 * 1000 * 10(00)=1,000,000,000
ns.png

実際にやってみた

const auto sec = 1;
LARGE_INTEGER interval;
NTSTATUS status;
interval.QuadPart = -sec * 1000 * 1000 * 10;
status = KeDelayExecutionThread(KernelMode, FALSE, &interval);

これを基に関数化しました。

NTSTATUS SleepKeThread(int sec = 1)
{
    LARGE_INTEGER interval;
    NTSTATUS status;
    interval.QuadPart = -sec * 1000 * 1000 * 10;
    status = KeDelayExecutionThread(KernelMode, FALSE, &interval);
    return status;
}

フルソースコード

あくまでサンプルなので例外処理はしてません
本当はしたほうが良いです

#include <wdm.h>

DRIVER_INITIALIZE DriverEntry;

NTSTATUS SleepKeThread(int sec)
{
    LARGE_INTEGER interval;
    NTSTATUS status;
    interval.QuadPart = -sec * 1000 * 1000 * 10;
    status = KeDelayExecutionThread(KernelMode, FALSE, &interval);
    return status;
}

VOID NTAPI myRoutine()
{
    DbgPrint("ROUTINE STARTED!\n");

    //やりたい処理

    SleepKeThread(3);//スレッドを3秒止める ザ・ワールド

    //3秒後にここの処理

    DbgPrint("ROUTINE FINISHED!\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT dObject, PUNICODE_STRING regPath)
{
    UNREFERENCED_PARAMETER(dObject);
    UNREFERENCED_PARAMETER(regPath);

    NTSTATUS status = STATUS_SUCCESS;
    DbgPrint("HELLO WORLD!\n");

    PWORK_QUEUE_ITEM WorkItem = (PWORK_QUEUE_ITEM)ExAllocatePool(NonPagedPool, sizeof(WORK_QUEUE_ITEM));
    ExInitializeWorkItem(WorkItem, (PWORKER_THREAD_ROUTINE)myRoutine, WorkItem);
    ExQueueWorkItem(WorkItem, DelayedWorkQueue);

    //リソース解放
    IoUninitializeWorkItem(WorkItem);
    IoFreeWorkItem(WorkItem);

    return status;
}

最後に

今回はカーネルモードからシステムスレッドを眠らせてみました。
カーネルは何でも出来ますが、その分難易度も高いですね。。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした