Карта кэша логического процессора
Еще одним камнем в поле 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
Никакого объявления структур с динамическими сборками, ни тем более каких-то сложностей, верно? — все предельно просто.