Карта кешу логічного процесора

Ще одним каменем у полі WMI може служити відсутність осудною інформації про кеші процесора. Принаймні так справа йде в Win7. Ні, є звичайно клас Win32_Processor, в якому серед іншого є пара властивостей L2CacheSize і L3CacheSize, представлені беззнаковим цілим числом типами і вказують розмір кеша другого і третього рівня відповідно, але цієї інформації явно недостатньо.

Якщо не помиляюся, то начебто починаючи з ХР серед експортованих функцій kernel32 можна знайти таку, назва якої відповідає сама за себе — GetLogicalProcessorInformation. Використовувати її досить просто: при першому виклику отримуємо справжній розмір буфера, при повтороном — покажчик на буфер масиву структур SYSTEM_LOGICAL_PROCESSOR_INFORMATION. У перекладі З це буде виглядати приблизно так:

#include <windows.h>
#include < stdio.h>

#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, Доц %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, Доц 2, LineSize 64
Instruction L1: 64 KB, Доц 2, LineSize 64
Unified L2: 512 КБ, Доц 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 data required.'
)
}
}

$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

Ніякого оголошення структур з динамічними масивами, ні тим більше якихось складнощів, вірно? — все гранично просто.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.