如何使用cpuid在Intel处理器上获取L3缓存信息(大小,行长)?

时间:2016-04-21 20:27:17

标签: caching assembly x86 cpu-cache

在Intel处理器上获取L3缓存信息时遇到了问题。在AMD上获得L3线路长度很简单,如下所示:

mov eax, 0x80000006 
cpuid
shl edx, 24
shr edx, 24

对Intels的相同操作要复杂得多。 我知道这可以使用这个序列来完成:

mov eax, 2
cpuid

并通过本手册调整寄存器值:http://www.microbe.cz/docs/CPUID.pdf(第26页,"表2-7。描述符解码值")。

但我的程序没有找到任何枚举的描述符,并为缓存大小和行长度返回0。

是否有更简单和/或更充分的方法来获取英特尔的缓存大小和行长度?

这是完整的代码。将所有cpuid输出(eax,ebx,ecx,edx)压入堆栈,然后将每个值与硬编码描述符列表进行比较。比较是在低8位进行的,然后这些位缩小。

__declspec(dllexport) __declspec(naked) void GetMetricLevel2(int &length) {
    __asm {
        // check CPUID availability
        pushfd
        pop eax
        mov ebx, eax
        xor eax, 00200000h
        push eax
        popfd
        pushfd
        pop eax
        cmp eax, ebx
        jnz HAS_CPUID
        mov edx, -1 // return -1 by reference
        jmp RET_ARG
HAS_CPUID:
        mov eax, 2 // L3 Intel, incomplete
        mov ecx, 0
        cpuid
        push ecx
        or ecx, eax
        or ecx, ebx
        or ecx, edx
        cmp ecx, 0
        pop ecx // experimental
        je CPU_AMD // if all registers are 0, we try AMD scheme
CPU_INTEL:
        push ebp
        mov ebp, 0
        push 0
        push eax // store counter
        jmp CALL_BEGIN
CYCLE_BEGIN:
        pop ecx
        inc ecx
        push ecx
        push eax
        mov eax, 2
        cpuid
CALL_BEGIN:
        push eax
        push ebx
        push ecx
        push edx
        mov ch, 4
PARSE_REG:
        pop edx
        mov cl, 4
PARSE_DESCR:
DD0H://512,4w
        cmp dl, 0xD0
        jne DD1H
        add ebp, 512d
        jmp MISS_L3CACHE
DD1H://1024,4w
        cmp dl, 0xD1
        jne DD2H
        add ebp, 1024d
        jmp MISS_L3CACHE
DD2H://2048,4w
        cmp dl, 0xD2
        jne DD6H
        add ebp, 2048d
        jmp MISS_L3CACHE
DD6H://1024,8w
        cmp dl, 0xD6
        jne DD7H
        add ebp, 1024d
        jmp MISS_L3CACHE
DD7H://2048,8w
        cmp dl, 0xD7
        jne DD8H
        add ebp, 2048d
        jmp MISS_L3CACHE
DD8H://4096,8w
        cmp dl, 0xD8
        jne DDCH
        add ebp, 4096d
        jmp MISS_L3CACHE
DDCH://1536,12w
        cmp dl, 0xDC
        jne DDDH
        add ebp, 1536d
        jmp MISS_L3CACHE
DDDH://3072,12w
        cmp dl, 0xDD
        jne DDEH
        add ebp, 3072d
        jmp MISS_L3CACHE
DDEH://6144,12w
        cmp dl, 0xDE
        jne DE2H
        add ebp, 6144d
        jmp MISS_L3CACHE
DE2H://2048,16w
        cmp dl, 0xE2
        jne DE3H
        add ebp, 2048d
        jmp MISS_L3CACHE
DE3H://4096,16w
        cmp dl, 0xE3
        jne DE4H
        add ebp, 4096d
        jmp MISS_L3CACHE
DE4H://8192,16w
        cmp dl, 0xE4
        jne DEAH
        add ebp, 8192d
        jmp MISS_L3CACHE
DEAH://12mb,24w
        cmp dl, 0xEA
        jne DEBH
        add ebp, 12288d
        jmp MISS_L3CACHE
DEBH://18mb,24w
        cmp dl, 0xEB
        jne DECH
        add ebp, 18432d
        jmp MISS_L3CACHE
DECH://24mb,24w
        cmp dl, 0xEC
        jne MISS_L3CACHE
        add ebp, 24576d
MISS_L3CACHE:
        dec cl
        cmp cl, 0
        shr edx, 8 // it's 8-bit descriptor
        jne PARSE_DESCR
        dec ch
        cmp ch, 0
        jne PARSE_REG
CALL_FINISH:
        pop eax
        cmp al, 0
        je CYCLE_FINISH // replace to je then
        dec al
        jmp CYCLE_BEGIN
CYCLE_FINISH:
        mov edx, ebp
        shl edx, 8 // 8 bits for cache string length
        mov dl, 64d // Intel always has 64 byte L3 string
        add esp, 4
        pop ebp
        jmp RET_ARG
CPU_AMD:
        mov eax, 0x80000006 // L3 AMD
        cpuid
        shl edx, 24
        shr edx, 24
RET_ARG:
        mov eax, [esp+4] // first argument lies here
        mov [eax], edx // return by reference
        ret
    }
}

1 个答案:

答案 0 :(得分:4)

您的代码存在许多问题。您应该使用__cpuid编译器内在函数并在C ++中完全编写它。它使代码更容易编写和维护。

您的代码存在两个主要问题。首先,您没有正确使用CPUID功能2。使用此功能时,将忽略ECX中的值。第二个是当函数2返回0FFh描述符时,你没有使用CPUID函数4来确定缓存大小。

您的代码的其他问题包括:

  • 不忽略功能2返回的高位设置返回的无效寄存器值。
  • 不处理描述L3缓存的大量缓存描述符。
  • 由于shr edx, 8设置了标志,因此实际使用了内部循环字节计数器。循环仍然有效,因为当EDX变为0时,它不包含任何可能的L3描述符。

您的部分问题是您使用的是过时的手册。您应该使用最新的Intel Software Developers Manual

它没有经过很好的测试,它可能在缓存描述符switch语句中有一些转录错误,但这里是一个使用CPUID函数2和4来确定大小,关联性的C实现并缓存L3缓存的行大小:

#include <intrin.h>

int
get_intel_l3_info(unsigned *size, unsigned *assoc, unsigned *linesize) {
    int regs[4];
    int i;

    __cpuid(regs, 0); /* Maximum Input Value */
    int max_leaf = regs[0];
    if (max_leaf < 2) {
        return -1; /* no way to find L3 cache info */
    }

    __cpuid(regs, 1); /* Additional Information */
    int family = (regs[0] >> 8) & 0xF;
    int model = (regs[0] >> 4) & 0xF;

    __cpuid(regs, 2); /* Cache and TLB Information */

    regs[0] &= 0xFFFFFF00; /* least significant byte of EAX is invalid */
    for (i = 0; i < 4; i++) {
        if (regs[i] < 0) { /* invalid if most significant bit set */
            regs[i] = 0;
        }
    }

    unsigned char *descriptors = (unsigned char *) regs;

    const int kb = 1024;
    const int mb = 1024 * kb;

#define RETINFO(s, a, l) *size = (s); *assoc = (a); *linesize = (l); return 0

    int use_leaf_4 = 0;
    for (i = 0; i < 32; i++) {
        switch(descriptors[i]) {
        case 0x22: RETINFO(512 * kb, 4, 64);
        case 0x23: RETINFO(1 * mb, 8, 64);
        case 0x25: RETINFO(2 * mb, 8, 64);
        case 0x29: RETINFO(4 * mb, 8, 64);
        case 0x40: RETINFO(0, 0, 0); /* no L3 cache */
        case 0x46: RETINFO(4 * mb, 4, 64);
        case 0x47: RETINFO(8 * mb, 8, 64);
        case 0x49:
            if (family == 0x0F && model == 0x06) {
                RETINFO(4 * mb, 16, 64);
            }
            break;
        case 0x4A: RETINFO(6 * mb, 12, 64);
        case 0x4B: RETINFO(8 * mb, 16, 64);
        case 0x4C: RETINFO(12 * mb, 12, 64);
        case 0x4D: RETINFO(16  * mb, 16, 64);
        case 0xD0: RETINFO(512 * kb, 4, 64);
        case 0xD1: RETINFO(1 * mb, 4, 64);
        case 0xD6: RETINFO(1 * mb, 8, 64);
        case 0xD7: RETINFO(2 * mb, 8, 64);
        case 0xD8: RETINFO(4 * mb, 8, 64);
        case 0xDC: RETINFO(1 * mb + 512 * kb, 12, 64);
        case 0xDD: RETINFO(3 * mb, 12, 64);
        case 0xDE: RETINFO(6 * mb, 12, 64);
        case 0xE2: RETINFO(2 * mb, 16, 64);
        case 0xE3: RETINFO(4 * mb, 16, 64);
        case 0xE4: RETINFO(8 * mb, 16, 64);
        case 0xEA: RETINFO(12 * mb, 24, 64);
        case 0xEB: RETINFO(18 * mb, 24, 64);
        case 0xEC: RETINFO(24 * mb, 24, 64);

        case 0xFF:
            use_leaf_4 = 1;
            break;
        }
    }

    if (!use_leaf_4 || max_leaf < 4) {
        return -1; /* failed, no L3 info found */
    }

    i = 0;
    while(1) {
        __cpuidex(regs, 4, i); /* Deterministic Cache Parameters */
        if ((regs[0] & 0x1F) == 0) {
            return RETINFO(0, 0, 0); /* no L3 cache */
        }
        if (((regs[0] >> 5) & 0x7) == 3) {
            int lsize = (regs[1] & 0xFFF) + 1;
            int partitions = ((regs[1] >> 12) & 0x3FF) + 1;
            int ways = ((regs[1] >> 22) & 0x3FF) + 1;
            int sets = regs[2] + 1;
            RETINFO(ways * partitions * lsize * sets,
                ways, lsize);
        }
        i++;
    }
}