none
SYSTEM_PROCESS_INFORMATIONの仕様について RRS feed

  • 質問

  • WindowsXP(SP3以降)及びWindows7(SP1以降)で、CPU使用率やI/O量を調べようと思っています。最初はWMIを使おうとしたのですが、ほしい情報が適切に取れない(XPで実行すると、タスクマネージャではCPUを数%消費しているのに、取得値が0だったりする)問題があったので、NtQuerySystemInformation()を利用しようとしています。

    NtQuerySystemInformation()のMSDNドキュメントを見ると、SYSTEM_PROCESS_INFORMATIONのメンバはほとんどreservedになっています。しかしWin32_Process classのMSDNドキュメントを見ると、「PeakVirtualSize」等、おそらくreserved部分にマップされるであろうメンバがあることが読み取れます。またProcessHackerのコードを見ると、SYSTEM_PROCESS_INFORMATIONを独自定義しており、それは確かに正しく動作しているようです。

    そこで各OSにおけるSYSTEM_PROCESS_INFORMATIONの仕様について適切に記述されているドキュメントを探しているのですが、見つかりません。もし情報源をご存知の方がおられましたら教えていただけないでしょうか。

    ※WDK 6001.18002やWindows Kits最新版(Win10対応版)などをインストールしてみたのですが、それらからは見つけられませんでした。

    ※各MSDNドキュメントやProcessHacker等へのリンクは、私のアカウントが確認されてないので?作成できませんでした。ProcessHackerはSourceForgeにプロジェクトがあります。

    2015年9月9日 1:55

回答

  • 公式のドキュメントは、MSDNのNtQuerySystemInformationにあるのが多分すべてです。元々この辺の関数は一般のアプリケーションが使用することを想定していませんし(NtQuerySystemInformationのページにもいつ仕様が変わるか分からないよというのが強調されていますよね)。

    有志によってあれこれ解析された結果はそこらに転がっているようですけど。

    • 回答の候補に設定 星 睦美 2015年9月29日 2:01
    • 回答としてマーク 星 睦美 2015年10月8日 0:45
    2015年9月9日 2:33
  • WindowsXP(SP3以降)及びWindows7(SP1以降)で、CPU使用率やI/O量を調べようと思っています。

    この目的であればパフォーマンスカウンターを使いませんか?

    PDHインターフェースを用いてCPU使用率を取得するサンプルを書いてみました。ただし、xpにはPdhAddEnglishCounterがなかったりするのでローカライズされたカウンター名を設定する必要がありそうです。

    #define _USING_V110_SDK71_
    #define WIN32_LEAN_AND_MEAN
    #define NOMINMAX
    #include <memory>
    #include <vector>
    #include <crtdbg.h>
    #include <Windows.h>
    #include <Pdh.h>
    #include <PdhMsg.h>
    #pragma comment(lib, "Pdh.lib")
    
    template <class T, class D>
    std::unique_ptr<T, D> makeUniqueWithDeleter(T* t, D deleter) {
    	std::unique_ptr<T, D> ptr(t, deleter);
    	return ptr;
    }
    
    void wmain() {
    	auto query = makeUniqueWithDeleter([] {
    		PDH_HQUERY query;
    		auto status = PdhOpenQueryW(nullptr, 0, &query);
    		_ASSERTE(status == ERROR_SUCCESS);
    		return query;
    	}(), PdhCloseQuery);
    
    	PDH_HCOUNTER counter;
    	auto status = PdhAddEnglishCounterW(query.get(), L"\\Process(*)\\% Processor Time", 0, &counter);
    	_ASSERTE(status == ERROR_SUCCESS);
    
    	std::vector<unsigned char> buffer(1024);
    
    	for (auto i = 0; i < 10; i++) {
    		status = PdhCollectQueryData(query.get());
    		_ASSERTE(status == ERROR_SUCCESS);
    		DWORD bufferSize = buffer.size();
    		DWORD count;
    		auto items = reinterpret_cast<PDH_FMT_COUNTERVALUE_ITEM_W*>(buffer.data());
    		status = PdhGetFormattedCounterArrayW(counter, PDH_FMT_LONG, &bufferSize, &count, items);
    		if (status == PDH_MORE_DATA) {
    			buffer.resize(bufferSize);
    			items = reinterpret_cast<PDH_FMT_COUNTERVALUE_ITEM_W*>(buffer.data());
    			status = PdhGetFormattedCounterArrayW(counter, PDH_FMT_LONG, &bufferSize, &count, items);
    		}
    		_ASSERTE(status == ERROR_SUCCESS);
    		wprintf(L"%u\n", count);
    		for (auto i = 0; i < count; i++)
    			wprintf(L"  %s: %d\n", items[i].szName, items[i].FmtValue.longValue);
    		Sleep(1000);
    	}
    }
    

    • 回答の候補に設定 星 睦美 2015年9月29日 2:00
    • 回答としてマーク 星 睦美 2015年10月8日 0:45
    2015年9月10日 2:45
  • > パフォーマンスカウンタについても試してみて、
    > 最終的にやりたいことができて負荷が低く確度が高い手段を選びたいと思います。

    パフォーマンスカウンタ API の実体は、そのほとんどが NtQuerySystemInformation() API へのコールで実現されているようです。

    ---------------------------------------------------------------
    1: kd> kvn
     # ChildEBP RetAddr  Args to Child             
    00 807b1d1c 83a4aa66 00000015 09a3efc0 00000024 nt!NtQuerySystemInformation
    01 807b1d1c 773c70f4 00000015 09a3efc0 00000024 nt!KiSystemServicePostCall (FPO: [0,3] TrapFrame @ 807b1d34)
    02 09a3ef98 773c625c 6f8a37e2 00000015 09a3efc0 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
    03 09a3ef9c 6f8a37e2 00000015 09a3efc0 00000024 ntdll!NtQuerySystemInformation+0xc (FPO: [4,0,0])
    04 09a3f00c 6f8a12f5 09a3f18c 09a3f038 09a3f030 perfos!CollectMemoryObjectData+0x4a (FPO: [Non-Fpo])
    05 09a3f040 6f8a1492 00000004 09a3f18c 00002d90 perfos!ReadOSObjectData+0x105 (FPO: [Non-Fpo])
    06 09a3f060 75b6feb5 09130478 09a3f18c 09a3f1bc perfos!CollectOSObjectData+0x9a (FPO: [Non-Fpo])
    07 09a3f078 75b6fcdf 03a53bc0 09130478 09a3f18c ADVAPI32!CallExtObj+0x17 (FPO: [Non-Fpo])
    08 09a3f1d8 75b6efe7 09a3f224 3ba711a5 09a3f6e0 ADVAPI32!QueryExtensibleData+0x735 (FPO: [Non-Fpo])
    09 09a3f5bc 769305c0 80000004 09a3f6e0 00000000 ADVAPI32!PerfRegQueryValue+0x5da (FPO: [Non-Fpo])
    0a 09a3f6b0 7693d605 80000004 09a3f6e0 09a3f6f8 kernel32!LocalBaseRegQueryValue+0x366 (FPO: [Non-Fpo])
    0b 09a3f71c 72f67dc5 80000004 09130478 00000000 kernel32!RegQueryValueExW+0xb7 (FPO: [Non-Fpo])
    0c 09a3f798 72f70595 80000004 09130440 09130478 pdh!GetSystemPerfData+0x92 (FPO: [Non-Fpo])
    0d 09a3f804 72f6c753 0314a060 09a3f838 00000000 pdh!GetQueryPerfData+0xa4 (FPO: [Non-Fpo])
    0e 09a3f820 72f6c7b3 0314a060 09a3f838 000003e1 pdh!PdhiCollectQueryData+0x32 (FPO: [Non-Fpo])
    0f 09a3f840 6f3657d7 0314a060 01771ef0 00000007 pdh!PdhCollectQueryData+0x42 (FPO: [Non-Fpo])
    10 09a3f86c 6f366a7f 00000001 01771ef0 00000000 sysmon!CRealTimeDataSource::UpdateCounterValues+0x48 (FPO: [Non-Fpo])
    11 09a3f884 7693ee6c 01771ef0 09a3f8d0 773e3a03 sysmon!CollectProc+0x61 (FPO: [Non-Fpo])
    12 09a3f890 773e3a03 01771ef0 7efa7aa0 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
    13 09a3f8d0 773e39d6 6f366a1e 01771ef0 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
    14 09a3f8e8 00000000 6f366a1e 01771ef0 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
    ---------------------------------------------------------------

    個人的には佐祐理さんが提案されているように、パフォーマンスカウンタでの実装を検討された方が現実的かと思います。

    • 回答の候補に設定 星 睦美 2015年9月29日 2:00
    • 回答としてマーク 星 睦美 2015年10月8日 0:45
    2015年9月10日 10:11

すべての返信

  • フォーラム オペレーターの星 睦美です。
    Takahiro Toyama さん、投稿ありがとうございます。

    >※各MSDNドキュメントやProcessHacker等へのリンクは、私のアカウントが確認されてないので?作成できませんでした。
    こちらのメッセージに関する解決方法を以下に掲載しています。よろしければご覧いただければと思います。

    ・フォーラムについて>マイクロソフトからのお知らせ>本文に画像やリンクを含むことが出来ませんというエラーについて

    今回の質問にあたってTakahiro Toyama さんが参照した情報のURLをテキストでお知らせいただければ回答者の参考になると思います。


    フォーラム オペレーター 星 睦美 - MSDN Community Support

    2015年9月9日 2:14
  • 星様

    お返事ありがとうございます。参照先の各URLは以下のとおりです。

    ■NtQuerySystemInformation()
    https://msdn.microsoft.com/en-us/library/windows/desktop/ms724509(v=vs.85).aspx

    ■Win32_Process class
    https://msdn.microsoft.com/en-us/library/aa394372(v=vs.85).aspx

    ■ProcessHacker
    http://sourceforge.net/projects/processhacker/
    http://processhacker.sourceforge.net/

    2015年9月9日 2:28
  • 公式のドキュメントは、MSDNのNtQuerySystemInformationにあるのが多分すべてです。元々この辺の関数は一般のアプリケーションが使用することを想定していませんし(NtQuerySystemInformationのページにもいつ仕様が変わるか分からないよというのが強調されていますよね)。

    有志によってあれこれ解析された結果はそこらに転がっているようですけど。

    • 回答の候補に設定 星 睦美 2015年9月29日 2:01
    • 回答としてマーク 星 睦美 2015年10月8日 0:45
    2015年9月9日 2:33
  • 既にごご↓を覧になっているとは思いますが、大昔に私が調べた限りでは、下記情報は「それなりに」正しかったと記憶しています。(多分。。。)

    ----------------------------------------------------------
    SYSTEM_PROCESS_INFORMATION
    http://undocumented.ntinternals.net/source/usermode/undocumented%20functions/system%20information/structures/system_process_information.html

    typedef struct _SYSTEM_PROCESS_INFORMATION {

      ULONG                   NextEntryOffset;
      ULONG                   NumberOfThreads;
      LARGE_INTEGER           Reserved[3];
      LARGE_INTEGER           CreateTime;
      LARGE_INTEGER           UserTime;
      LARGE_INTEGER           KernelTime;
      UNICODE_STRING          ImageName;
      KPRIORITY               BasePriority;
      HANDLE                  ProcessId;
      HANDLE                  InheritedFromProcessId;
      ULONG                   HandleCount;
      ULONG                   Reserved2[2];
      ULONG                   PrivatePageCount;
      VM_COUNTERS             VirtualMemoryCounters;
      IO_COUNTERS             IoCounters;
      SYSTEM_THREAD           Threads[0];

    } SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;
    ----------------------------------------------------------

    ただし、上記に示されているメンバのうち、"Reserved2" メンバに関しては実際には何かに使われている様でしたし、"Threads[0]" 辺りからはちょっと違っているように見えた記憶があります。
    更にいえば、「本当」の定義は "Threads[0]" メンバ以降も存在している感じだったような。。。
    また、NtQuerySystemInformation() API 1st パラメータ SYSTEM_INFORMATION_CLASS で定義されているクラス値は、MSDN ドキュメントに記載されているもの以外にも、多数存在しているようでした。

    そもそも Native API は Win32 API の様に詳細な情報が公開されている訳ではないので、Native API を使う場合は、WinDBG 等のデバッガを使って自分で調べるぐらいの根性がないと、その詳細を把握することは不可能だと思います。
    その手間を面倒だと感じるのであれば、別の手段を検討されることをお勧めします。
    (下手に使うと、後々痛い目に合うことになるかと。。。)

    ちなみに、ntdll.dll 内に実装されている NtQuerySystemInformation() は ZwQuerySystemInformation() と同義ですので、こちらの関数名でも検索されてみることをお勧めします。
    (もっとも、あまり有用な情報はヒットしないかもしれませんが。。。)

    2015年9月9日 7:50
  • WindowsXP(SP3以降)及びWindows7(SP1以降)で、CPU使用率やI/O量を調べようと思っています。

    この目的であればパフォーマンスカウンターを使いませんか?

    PDHインターフェースを用いてCPU使用率を取得するサンプルを書いてみました。ただし、xpにはPdhAddEnglishCounterがなかったりするのでローカライズされたカウンター名を設定する必要がありそうです。

    #define _USING_V110_SDK71_
    #define WIN32_LEAN_AND_MEAN
    #define NOMINMAX
    #include <memory>
    #include <vector>
    #include <crtdbg.h>
    #include <Windows.h>
    #include <Pdh.h>
    #include <PdhMsg.h>
    #pragma comment(lib, "Pdh.lib")
    
    template <class T, class D>
    std::unique_ptr<T, D> makeUniqueWithDeleter(T* t, D deleter) {
    	std::unique_ptr<T, D> ptr(t, deleter);
    	return ptr;
    }
    
    void wmain() {
    	auto query = makeUniqueWithDeleter([] {
    		PDH_HQUERY query;
    		auto status = PdhOpenQueryW(nullptr, 0, &query);
    		_ASSERTE(status == ERROR_SUCCESS);
    		return query;
    	}(), PdhCloseQuery);
    
    	PDH_HCOUNTER counter;
    	auto status = PdhAddEnglishCounterW(query.get(), L"\\Process(*)\\% Processor Time", 0, &counter);
    	_ASSERTE(status == ERROR_SUCCESS);
    
    	std::vector<unsigned char> buffer(1024);
    
    	for (auto i = 0; i < 10; i++) {
    		status = PdhCollectQueryData(query.get());
    		_ASSERTE(status == ERROR_SUCCESS);
    		DWORD bufferSize = buffer.size();
    		DWORD count;
    		auto items = reinterpret_cast<PDH_FMT_COUNTERVALUE_ITEM_W*>(buffer.data());
    		status = PdhGetFormattedCounterArrayW(counter, PDH_FMT_LONG, &bufferSize, &count, items);
    		if (status == PDH_MORE_DATA) {
    			buffer.resize(bufferSize);
    			items = reinterpret_cast<PDH_FMT_COUNTERVALUE_ITEM_W*>(buffer.data());
    			status = PdhGetFormattedCounterArrayW(counter, PDH_FMT_LONG, &bufferSize, &count, items);
    		}
    		_ASSERTE(status == ERROR_SUCCESS);
    		wprintf(L"%u\n", count);
    		for (auto i = 0; i < count; i++)
    			wprintf(L"  %s: %d\n", items[i].szName, items[i].FmtValue.longValue);
    		Sleep(1000);
    	}
    }
    

    • 回答の候補に設定 星 睦美 2015年9月29日 2:00
    • 回答としてマーク 星 睦美 2015年10月8日 0:45
    2015年9月10日 2:45
  • 皆様、情報ありがとうございます。

    SYSTEM_PROCESS_INFORMATIONの各reservedメンバについては、色々と解析して確認しています。こんな情報も見つけました:
    https://msdn.microsoft.com/en-us/library/cc248684.aspx
    SpareLi1~2の箇所はイマイチわからないですが、それ以外はだいぶわかってきました。あと末尾側にあるスレッド情報関連(SYSTEM_THREADの中身)です。

    パフォーマンスカウンタについても試してみて、最終的にやりたいことができて負荷が低く確度が高い手段を選びたいと思います。あるプロセス内にある全スレッドのCPU使用率をスレッドIDと結びつけて記録したいので、それができるかどうか・・・。

    結果はまた別途ご報告します。

    2015年9月10日 7:53
  • > パフォーマンスカウンタについても試してみて、
    > 最終的にやりたいことができて負荷が低く確度が高い手段を選びたいと思います。

    パフォーマンスカウンタ API の実体は、そのほとんどが NtQuerySystemInformation() API へのコールで実現されているようです。

    ---------------------------------------------------------------
    1: kd> kvn
     # ChildEBP RetAddr  Args to Child             
    00 807b1d1c 83a4aa66 00000015 09a3efc0 00000024 nt!NtQuerySystemInformation
    01 807b1d1c 773c70f4 00000015 09a3efc0 00000024 nt!KiSystemServicePostCall (FPO: [0,3] TrapFrame @ 807b1d34)
    02 09a3ef98 773c625c 6f8a37e2 00000015 09a3efc0 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
    03 09a3ef9c 6f8a37e2 00000015 09a3efc0 00000024 ntdll!NtQuerySystemInformation+0xc (FPO: [4,0,0])
    04 09a3f00c 6f8a12f5 09a3f18c 09a3f038 09a3f030 perfos!CollectMemoryObjectData+0x4a (FPO: [Non-Fpo])
    05 09a3f040 6f8a1492 00000004 09a3f18c 00002d90 perfos!ReadOSObjectData+0x105 (FPO: [Non-Fpo])
    06 09a3f060 75b6feb5 09130478 09a3f18c 09a3f1bc perfos!CollectOSObjectData+0x9a (FPO: [Non-Fpo])
    07 09a3f078 75b6fcdf 03a53bc0 09130478 09a3f18c ADVAPI32!CallExtObj+0x17 (FPO: [Non-Fpo])
    08 09a3f1d8 75b6efe7 09a3f224 3ba711a5 09a3f6e0 ADVAPI32!QueryExtensibleData+0x735 (FPO: [Non-Fpo])
    09 09a3f5bc 769305c0 80000004 09a3f6e0 00000000 ADVAPI32!PerfRegQueryValue+0x5da (FPO: [Non-Fpo])
    0a 09a3f6b0 7693d605 80000004 09a3f6e0 09a3f6f8 kernel32!LocalBaseRegQueryValue+0x366 (FPO: [Non-Fpo])
    0b 09a3f71c 72f67dc5 80000004 09130478 00000000 kernel32!RegQueryValueExW+0xb7 (FPO: [Non-Fpo])
    0c 09a3f798 72f70595 80000004 09130440 09130478 pdh!GetSystemPerfData+0x92 (FPO: [Non-Fpo])
    0d 09a3f804 72f6c753 0314a060 09a3f838 00000000 pdh!GetQueryPerfData+0xa4 (FPO: [Non-Fpo])
    0e 09a3f820 72f6c7b3 0314a060 09a3f838 000003e1 pdh!PdhiCollectQueryData+0x32 (FPO: [Non-Fpo])
    0f 09a3f840 6f3657d7 0314a060 01771ef0 00000007 pdh!PdhCollectQueryData+0x42 (FPO: [Non-Fpo])
    10 09a3f86c 6f366a7f 00000001 01771ef0 00000000 sysmon!CRealTimeDataSource::UpdateCounterValues+0x48 (FPO: [Non-Fpo])
    11 09a3f884 7693ee6c 01771ef0 09a3f8d0 773e3a03 sysmon!CollectProc+0x61 (FPO: [Non-Fpo])
    12 09a3f890 773e3a03 01771ef0 7efa7aa0 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
    13 09a3f8d0 773e39d6 6f366a1e 01771ef0 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
    14 09a3f8e8 00000000 6f366a1e 01771ef0 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
    ---------------------------------------------------------------

    個人的には佐祐理さんが提案されているように、パフォーマンスカウンタでの実装を検討された方が現実的かと思います。

    • 回答の候補に設定 星 睦美 2015年9月29日 2:00
    • 回答としてマーク 星 睦美 2015年10月8日 0:45
    2015年9月10日 10:11
  • 皆様情報ありがとうございました。結果としては、以下の目的に対してパフォーマンスカウンタAPIでの実現手段がどうしてもわからなかったので、当初の方針通り隠しAPIと解析で目的を達成しました。

    「あるプロセスについて、スレッド毎のCPU使用率を測定し、それをアプリケーションの動作と紐付ける」

    パフォーマンスカウンタAPIで取る手段がわからなかったのは、スレッド毎CPU使用率の取得方法と、それをスレッドIDに紐付ける方法の2点です。

    以下に簡単に解析結果の概要をまとめておきます:

    • プロセス、スレッドの情報は NtQuerySystemInformation()にSystemProcessInformationを指定することで取得する。
    • 取得した情報はSYSTEM_PROCESS_INFORMATION構造体である。ただしこの構造体は末尾にスレッドの情報を格納するので、プロセス毎のデータサイズは可変となる。(BITMAPINFOのbmiColorsみたいな感じ)
    • ↑のスレッド情報の構造体を仮にSYSTEM_THREAD_INFORMATIONとすると、この構造体のサイズは64bytesである。
    • SYSTEM_THREAD_INFORMATIONは先頭8bytesにカーネル時間、次の8bytesにユーザー時間がそれぞれ整数で格納されている。またスレッドIDは36-39byte目に格納されている。
    • Windows7では、NtQueryInformationThread()のTHREADINFOCLASSに23を指定してスレッド毎のサイクルタイムが取得できるので、より正確にCPU時間を演算できる。

    2015年12月15日 2:40