Карта кэша логического процессора

Еще одним камнем в поле WMI может служить отсутсвие вменяемой информации о кэше процессора. По крайней мере так дело обстоит в Win7. Нет, есть конечно класс Win32_Processor, в котором среди прочего имеется пара свойств L2CacheSize и L3CacheSize, представленные беззнаковым целочисленным типами и указывающие размер кэша второго и третьего уровня соответсвенно, но этой информации явно маловато.
Если не ошибаюсь, то вроде бы начиная с ХР среди экспортируемых функций kernel32 можно найти такую, название которой отвечает само за себя — GetLogicalProcessorInformation. Использовать ее довольно просто: при первом вызове получаем истинный размер буфера, при повтороном — указатель на буфер массива структур SYSTEM_LOGICAL_PROCESSOR_INFORMATION. В переводе на С это будет выглядить примерно так:

#include 
#include 

#define RelationshipCache 2
#define CreateString(String) #String,
#define CacheType(Type) \
        Type(Unified) \
        Type(Instruction) \
        Type(Data) \
        Type(Trace)

int main(void) {
  PSYSTEM_LOGICAL_PROCESSOR_INFORMATION slpi, tmp;
  DWORD len, i, sz = sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
  LPCSTR CacheType[] = {CacheType(CreateString)};
  
  if (!GetLogicalProcessorInformation(NULL, &len) && len != 0) {
    slpi = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(len);
    if (!GetLogicalProcessorInformation(slpi, &len)) {
      LPVOID msg;
      FormatMessage(
          FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
          NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
          (LPTSTR) &msg, 0, NULL
      );
      printf("%s\n", msg);
      LocalFree(msg);
      
      free(slpi);
      return -1;
    }
  }
  
  tmp = slpi;
  for (i = 0; i < len; i += len / sz) {
    if (tmp->Relationship == RelationshipCache) {
      printf("%-11s L%d: %5lu KB, Assoc %d, LineSize %hu\n",
        CacheType[tmp->Cache.Type], tmp->Cache.Level, tmp->Cache.Size / 1024,
        tmp->Cache.Associativity, tmp->Cache.LineSize
      );
    }
    tmp = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)((PCHAR)tmp + sz);
  }
  free(slpi);
  
  return 0;
}


После компиляции результат в моем случае будет выглядить так:

Data        L1:    64 KB, Assoc 2, LineSize 64
Instruction L1:    64 KB, Assoc 2, LineSize 64
Unified     L2:   512 KB, Assoc 16, LineSize 64


А теперь вопрос на миллион: возможно ли то же сделать в PowerShell без объявления динамических сборок в домене приложений и без использования GetLogicalProcessorInformation? Ну, раз этот вопрос задается, то видимо да. Если проследить call’ы GetLogicalProcessorInformation, выясняется что в какой-то момент вызывается NtQuerySystemInformation. И правда, SystemInformationClass за номером 73 — SystemLogicalProcessorInformation. Иными словами, можно получить те же данные не прибегая к GetLogicalProcessorInformation, вся трудность в том, как читать структуру SYSTEM_LOGICAL_PROCESSOR_INFORMATION выглядящую как:

   +0x000 ProcessorMask    : Uint4B
   +0x004 Relationship     :
      RelationProcessorCore = 0n0
      RelationNumaNode = 0n1
      RelationCache = 0n2
      RelationProcessorPackage = 0n3
      RelationGroup = 0n4
      RelationAll = 0xffff
   +0x008 ProcessorCore    : _PROCESSOR_CORE
      +0x000 Flags            : UChar
   +0x008 NumaNode         : _NUMA_NODE
      +0x000 NodeNumber       : Uint4B
   +0x008 Cache            : _CACHE_DESCRIPTOR
      +0x000 Level            : UChar
      +0x001 Associativity    : UChar
      +0x002 LineSize         : Uint2B
      +0x004 Size             : Uint4B
      +0x008 Type             :
         CacheUnified = 0n0
         CacheInstruction = 0n1
         CacheData = 0n2
         CacheTrace = 0n3
   +0x008 Reserved         : [2] Uint8B


ибо ProcessorCore, NumaNode, Cache и Reserved — это, если верить смещениям, union. Давайте подумаем: размер структуры — 24 байта, первые два значения представлены типами в четыре байта каждый, получается на union приходится шестнадцать байт; отбрасываем байты ProcessorCore и NumaNode и получаем, что диапазон интересующих нас значений находится от 0 до 11. Иными словами:

#function Get-CpuCache {
  begin {
    #акселератор типа Marshal
    if (($ta = [PSObject].Assembly.GetType(
      'System.Management.Automation.TypeAccelerators'
    ))::Get.Keys -notcontains 'Marshal') {
      $ta::Add('Marshal', [Runtime.InteropServices.Marshal])
    }
    #NtQuerySystemInformation - куда ж без нее?!
    Set-Variable ($$ = [Regex].Assembly.GetType(
      'Microsoft.Win32.NativeMethods'
    ).GetMethod('NtQuerySystemInformation')).Name $$
    
    $ret = 0
  }
  process {
    try {
      #так как ничего неизвестно о длине буфера, то выделяем
      #сначала память равной размеру одной структуры
      $ptr = [Marshal]::AllocHGlobal(24)
      
      if ($NtQuerySystemInformation.Invoke(
        $null, ($par = [Object[]]@(73, $ptr, 24, $ret)
      )) -eq 0xC0000004) {
        #перевыделяем память уже известной длиной буфера
        $ptr = [Marshal]::ReAllocHGlobal($ptr, [IntPtr]$par[3])
        
        if ($NtQuerySystemInformation.Invoke(
          $null, @(73, $ptr, $par[3], 0)
        ) -ne 0) {
          throw New-Object InvalidOperationException(
            'Could not retrieve required data.'
          )
        }
      }
      
      $len = $par[3]
      $tmp = $ptr
      $(for ($i = 0; $i -lt $len; $i += $len / 24) {
        if ([Marshal]::ReadInt32($tmp, 4) -eq 2) {
          #считываем интересующие нас байты каждой структуры
          [Byte[]]$bytes = 0..11 | ForEach-Object {
            $ofb = 8
            $CACHE_TYPE = @{
              0 = 'CacheUnified'
              1 = 'CacheInstruction'
              2 = 'CacheData'
              3 = 'CacheTrace'
            }
          }{
            [Marshal]::ReadByte($tmp, $ofb)
            $ofb++
          }
          #приводим байты к удобочитаемому виду
          New-Object PSObject -Property @{
            Level = $bytes[0]
            Associativity = $bytes[1]
            LineSize = [BitConverter]::ToInt16($bytes[2..3], 0)
            Size = [BitConverter]::ToInt32($bytes[4..7], 0)
            Type = [BitConverter]::ToInt32($bytes[8..15], 0)
          } | Select-Object @{
            N='Type';E={$CACHE_TYPE.Item($_.Type)}
          }, Level, @{
            N='Size(KB)';E={$_.Size / 1Kb}
          }, Associativity, LineSize
        }
        #указатель на следующую структуру
        $tmp = [IntPtr]($tmp.ToInt32() + 24)
      }) | Format-Table -AutoSize
    }
    catch { $_.Exception }
    finally {
      if ($ptr) { [Marshal]::FreeHGlobal($ptr) }
    }
  }
  end {
    #удаляем акселератор
    [void]$ta::Remove('Marshal')
  }
#}


Результат выполнеия сценария:

Type             Level Size(KB) Associativity LineSize
----             ----- -------- ------------- --------
CacheData            1       64             2       64
CacheInstruction     1       64             2       64
CacheUnified         2      512            16       64


Никакого объявления структур с динамическими сборками, ни тем более каких-то сложностей, верно? — все предельно просто.

© Habrahabr.ru