Windows NTでシステムの状態を取得する方法

戻る

システムで動作しているプロセスの情報を取得するには,Windows95とWindowsNTでは OS自体の構造の違いから同一ではありません.
Windows95ではWindows3.1以来,ツールヘルプAPIでこれらの情報を簡単にしかも高速に 取得することができます.WindowsNT3.51以前ではシステムに関する情報は DDK(Device Driver Kit)で公開されているAPIや,パフォーマンス情報から取得する しかありませんでした.しかし,WindowsNT3.51以降から PSAPI.DLL が追加され このAPIを利用することにより,Windows95のツールヘルプ並みの手軽さでシステムの 情報を取得することができるようになりました.

13-1 異常に遅いパフォーマンス情報

WindowsNTでは,プロセスやスレッドの状態を取得する方法をほとんど提供して いませんでした.というより,システムの内部に隠されており,ドライバなどの システム内部に関わる処理で利用されており,通常のアプリケーションを作成する プログラマーに対しては表立って公開されることはありませんでした.
しかし,Win32 SDK に含まれる開発ツールで利用されており,通常のアプリケーション でも自分自身が起動したスレッドやプロセス,関連するプロセスの制御や管理を行う 必要があるときには,このような機能は必要になります.
WindowsNT3.1から,公開され確実に実行できる保証(?)のある機能に,レジストリAPIで 取得するパフォーマンス情報があります.この情報は,レジストリデータベースに記録 されている訳ではありませんが,情報の取得を開始すると同時に各モジュールから 必要な情報の取得を行います.元々,パフォーマンス情報は,ネットワーク上の コンピュータで実行されているモジュールのパフォーマンス情報を取得するための機能 であるため,ローカルマシンのプロセスやスレッドの実行状態を取得するだけでよい 場合にはあまりにも処理が重過ぎます.

13-2 パフォーマンス情報以外からプロセス情報を取得する方法

Windows3.51 用のWin32 SDK のPFMON サンプルソースにパフォーマンス情報以外の 方法でプロセスやスレッドの情報を取得する方法があります.しかし,その方法は, このサンプルソース以外にドキュメントらしきものはなく,Win32 SDKやVisual C++ \INCLUDEや\LIBディレクトリにもヘッダやライブラリは存在しません.
しかし,PSAPI.DLLは再配布可能モジュールらしいので,Windows95のツールヘルプ APIのNT版のように利用することができます.
(* マイクロソフト社に直接問い合わせたわけではないので,実際に再配布を行う必要があるときには,マイクロソフト社に確認を取る必要がある.)
このAPIを利用するためのヘッダやライブラリは,サンプルソースとともに,提供され ており,現在入手可能な開発環境にも含まれています.
<PSAPI.DLLを利用するためのヘッダとライブラリの格納ディレクトリ> Visual C++ 5.0(Pro)
\devstudio\vc\samples\win32\sdktools\image\winnt\pfmon
Win32 SDK
\mssdk\samples\sdktools\image\winnt\pfmon

13-3 PSAPI.DLLとは?

PSAPIは Process Status Apprication Interface の略で,プロセスの状態に関する 情報を取得するAPIです.
このAPIは,WindowsNT 3.51 に対応する Win32 SDK から登場したようですが, あまりメジャーな存在ではありません.
 通常,自分が生成したプロセスは,Win32 API CreateProcess でプロセスを起動した ときに取得できるプロセスハンドルがあればほとんどの操作が可能でです. 他のプロセスの情報を取得するアプリケーションはどちらかというと特殊な部類に入る ため,他の公開されている多くのAPIと比較すると需要は少ないのではないかと思い ます.しかし,多くの部署で同時に開発が進行しているプロジェクトでは,開発の末期 に他のプロセスに関する方法や制御は必要になるといった状況がまれにあります.
制御を行うプロセスのコードを修正することができれば,よいのですが修正が不可能な ときには,どうにかして他のプロセスの情報を取得しなければならなくなります. このようなときに,OS内部の情報を取得できる方法をしっておくと,緊急回避を行う ことができるかもしれません.
 このAPIを利用したツールは,Resource Kit にもいくつか添付されているようです. ソースコードはありませんが,実行してみることでこのAPIで何ができるかを理解する にはよいかもしれません.

13-4 PSAPI.DLLが提供するAPI

PSAPIが提供する機能に関して確実な資料は,PSAPI.H です.
このファイルを見ることで,DLLで利用できるAPIのプロトタイプと構造体を知ることが きます.このAPIで取得することができる情報は,デバッガと同様に Win32 API ReadProcessMemory を利用して,イメージ内の情報を調べて取得する ことも可能ですが,非常に大変な作業になってしまいます.
PSPAI は,WindowsNT内部の非公開APIやIMAGEHLP.DLLのラッパー関数で,Windows95の ツールヘルプとは全く異なる構成になっています.
DUMPBIN.EXE で,PSAPI.DLL が利用しているAPIを出力させると,NTDLL.DLL内に Ntxxxxxxxx, Rtxxxxxxxx 関数が多く存在するのが分かります.
これらのAPIは,WindowsNT DDK のヘッダに定義がある程度で情報はありません. しかし,PSAPI.DLL を利用することで,これらの機能を利用することができるように なっていますので,あまり深追いはしないことにします.

●PSAPIの機能
PSAPI も,まだ正式にドキュメントに記述があるわけではないため,機能追加や 変更が加わる可能性がありますが,知っていて損をしない機能であることは確かです. PSAPIを利用すれば,実行中のデバイスドライバに関する情報を取得することもでき ますが,ほとんどの場合プロセスIDのリストを取得で事足りるのではないでしょうか? サンプルアプリケーションでは,プロセスIDとプロセスモジュールベース名のリスト を取得する機能を持っています.この機能を実現するために利用したAPIについては, 引数の使い方を実際に動作させて確認しています.その他のAPIについても似たような 構造になっていると思われますので,各自チャレンジしてみてはいかがでしょうか.
●引数lpcbNeededに注意
注意しなければならないのは,引数の lpcbNeeded は,必要なメモリサイズを返すの だと思いましたが,実際には設定された情報のサイズを返すようです.
このため,変数名は lpcbNeeded でなく lpcbReaded のほうがふさわしいような 気がします.Win32 スプーラAPI にも似たような引数のAPIがありますが,それらは バッファサイズに 0 を設定すると必要なバッファサイズを返してきますが,PSAPI では必ず 0 を返してきます.仮に必要なバッファサイズを返却したとしても, 取得直後に必要なバッファサイズが変化する恐れがあるため,サイズ取得後に呼び 出したAPIが必ず成功する保証はありません.必要なバッファサイズを取得できても, 目安にはなりますが確実な値であるとは言えません.
ということで,最初の呼び出しで十分な大きさのバッファ渡しておく必要が あります.

表13-1:PSAPI.DLL がインポートしているAPI(一部)


               77F67614    3B   NtAllocateVirtualMemory
               77F88566   1E2   RtlNtStatusToDosError
               77F88AFC   23A   RtlUnwind
               77F680C4    EF   NtStopProfile
               77F66990   1DC   RtlMultiByteToUnicodeN
               77F7B150   117   RtlAdjustPrivilege
               77F672BC   237   RtlUnicodeToOemN
               77F67774    52   NtCreateProfile
               77F67FC0    DD   NtSetIntervalProfile
               77F680B4    EE   NtStartProfile
               77F681C0   100   NtWriteFile
               77F67F90    DA   NtSetInformationProcess
               77F67C3C    A1   NtQueryInformationProcess
               77F67D7C    B6   NtQueryVirtualMemory
               77F67D2C    B1   NtQuerySystemInformation

                          (a) ntdll.dll

               76AC6FFC    31   SymLoadModule
               76AC6D3D    29   SymGetModuleInfo
               76AC6720    2C   SymGetSymFromAddr
               76AC6F3D    36   SymUnloadModule
               76AC6385    33   SymSetOptions
               76AC6166    30   SymInitialize
               76AC712B    2B   SymGetSearchPath

                         (b) IMAGEHLP.dll


表13-2:PSAPI.DLLがエクスポートしているAPI
BOOL EnumProcesses(DWORD* lpidProcess, DWORD cb, DWORD* cbNeeded)
実行中のプロセスのプロセスIDを列挙する

引数
DWORD* lpidProcess ... 列挙したプロセスIDを格納する領域
DWORD cb ... lpidProcessに設定した領域のサイズ(bytes)
DWORD* cbNeeded ... 取得した情報のサイズ(bytes)

戻り値
正常終了 TRUE
異常終了 FALSE
BOOL EnumProcessModules(
 HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded)
プロセス内のモジュールを列挙する

引数
HANDLE hProcess ... プロセスハンドル
HMODULE* lphModule ... モジュールハンドルを格納する領域
DWORD cb ... lphModuleに設定した領域のサイズ(bytes)
LPDWORD lpcbNeeded ... 取得した情報のサイズ(bytes)

戻り値
正常終了 TRUE
異常終了 FALSE
DWORD GetModuleBaseName(
 HANDLE hProcess, HMODULE hModule, LPSTR lpBaseName, DWORD nSize)
モジュールベース名を取得する

引数
HANDLE hProcess ... プロセスハンドル
HMODULE hModule ... モジュールハンドル
LPSTR lpBaseName ... ベース名を格納する領域
DWORD nSize ... lpBaseNameに設定した領域のサイズ(bytes)

戻り値
取得したモジュール名のバイト数
DWORD GetModuleFileNameEx(HANDLE, HMODULE, LPSTR, DWORD)
BOOL GetModuleInformation(HANDLE, HMODULE, LPMODULEINFO, DWORD)
BOOL EmptyWorkingSet(HANDLE)
BOOL QueryWorkingSet(HANDLE, PVOID, DWORD)
BOOL InitializeProcessForWsWatch(HANDLE)
BOOL GetWsChanges(HANDLE, PPSAPI_WS_WATCH_INFORMATION, DWORD)
DWORD GetMappedFileName(HANDLE, LPVOID, LPSTR, DWORD)

BOOL EnumDeviceDrivers(LPVOID*, DWORD, LPDWORD)
DWORD GetDeviceDriverBaseName(LPVOID, LPSTR, DWORD)
DWORD GetDeviceDriverFileName(LPVOID, LPSTR, DWORD)
BOOL GetProcessMemoryInfo(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD)

13-5 プログラムについて

PSAPI.DLLを利用したプロセスを強制終了させるWindowsNT専用アプリケーション です.このアプリケーションは,基本的には拙著「Win32 サブルーチンズ2」で 紹介した,KILLER.EXE をPSAPI.DLLでプロセスの情報を取得するように修正したもの です.KILLER.EXE と KILLERNT.EXE を実行して,PSAPI.DLLとパフォーマンス情報 の速度を比較すると,PSAPIがどのくらい速いかを確かめることができます. また,今回のバージョンは,サービスのデバッグを行うことができるアクセス権を持つ ユーザ(通常 Administratorsグループのユーザ)であれば,サービスを強制終了させる ことができるようにしてあります.
プロセスのベース名を取得するために Win32 API OpenProcess を利用していますが, 一部のシステムモジュールにオープンできないものがあります. 表示できないモジュールはいずれもシステムモジュールであるため,実用上問題 ありませんが,表示するプロセスが KILLER.EXE より少なくなってしまいます.
なお,PSAPI.DLLを利用するためのヘッダ・ライブラリファイルは, Visual C++ ver5.0 や Win32 API にありますので,そちらを利用してください.

●ソースファイル
KillerNT.exe
 KillerNT.c (ソースファイル)
 KillerNT.h (ヘッダファイル)
 KillerNT.rc (リソースファイル)
 KillerNT.ico (アイコン)
 MAKEFILE (メイクファイル)