CPUID

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

CPUID는 CPU의 제조사, 모델, 기능 등을 식별하는 데 사용되는 명령어이다. x86 아키텍처에서 CPUID 명령어가 처음 도입되기 전에는, 프로그래머들이 CPU의 미세한 동작 차이를 이용하여 CPU를 식별하는 난해한 코드를 작성해야 했다. x86 외의 아키텍처, 예를 들어 ARM, MIPS, PowerPC 등에서는 CPUID와 유사한 정보를 얻기 위해 다른 방식의 레지스터를 사용한다. CPUID 명령은 어셈블리어에서 EAX 레지스터를 통해 매개변수를 전달받으며, 고급 언어에서도 래퍼 함수나 인라인 어셈블리를 통해 접근할 수 있다.

CPUID
CPUID
종류명령어 집합
구분x86 명령어
설계사인텔
세부 정보
CPUIDCPU 식별 확장 및 기능 발견
설명CPUID 명령어는 x86 아키텍처에서 프로세서의 식별 및 기능 정보를 얻기 위해 사용되는 명령어이다. 이 명령어는 프로세서의 벤더, 모델, 스테핑 정보뿐만 아니라 지원하는 기능 집합(예: SSE, AVX)과 캐시 정보 등을 제공한다.
작동 방식CPUID 명령어는 EAX 레지스터에 특정 값을 넣고 실행하면, 프로세서가 해당 값에 대응하는 정보를 EDX, ECX, EBX, EAX 레지스터에 반환하는 방식으로 작동한다. 예를 들어, EAX = 0을 넣고 CPUID를 실행하면 프로세서 벤더 문자열이 반환된다.
사용 예시운영체제나 특정 소프트웨어는 CPUID 명령어를 사용하여 프로세서의 기능을 확인하고, 이에 따라 최적화된 코드를 실행하거나 특정 기능을 사용할 수 있는지 판단한다. 예를 들어, AVX 명령어를 사용하기 전에 CPUID를 통해 프로세서가 AVX를 지원하는지 확인하는 과정을 거칠 수 있다.
역사1993년 인텔 펜티엄 프로세서에 처음 도입
이후 AMD 및 다른 x86 프로세서 제조업체에서도 구현
주요 정보
EAX = 0벤더 ID 문자열 (예: "GenuineIntel" 또는 "AuthenticAMD")
EAX = 1프로세서 모델, 스테핑, 기능 플래그
EAX = 4캐시 정보
EAX = 7확장 기능 플래그 (예: AVX2, BMI2)
EAX = 0Bh토폴로지 정보 (코어, 스레드 정보)
📚 더 읽어볼만한 페이지
  • 기계어 - 브랜치 (컴퓨터 과학)
    프로그램 실행 흐름을 제어하는 명령어인 브랜치는 점프, 호출, 반환 명령어로 나뉘며, CPU 플래그 레지스터와 주소 지정 방식을 활용하여 코드 분기 및 서브루틴 호출/반환을 수행하지만, 파이프라인 CPU에서 성능 저하를 유발하여 분기 예측 등의 기술로 해결합니다.
  • 기계어 - 주소 지정 방식
  • X86 아키텍처 - 물리 주소 확장
    물리 주소 확장(PAE)은 x86 아키텍처에서 32비트 주소를 36비트 이상으로 확장하여 CPU가 4GB 초과 물리 메모리에 접근하도록 하는 기술로, 페이지 테이블 확장 및 추가 페이지 테이블을 통해 최대 64GB 메모리를 지원하며 AMD64 아키텍처에서 가상 주소 공간 확장에 사용되고 운영 체제, 칩셋, 마더보드의 지원이 필요하며 CPUID 플래그로 지원 여부를 확인한다.
  • X86 아키텍처 - X86 가상화
    X86 가상화는 x86 아키텍처 기반 시스템에서 가상 머신을 구현하는 기술로, 소프트웨어 기반 가상화와 하드웨어 지원 가상화로 나뉘며, CPU 제조사의 가상화 확장 기술을 활용하여 가상 머신의 성능을 향상시킨다.
  • 빈 문단이 포함된 문서 - 광주고등법원
    광주고등법원은 1952년에 설치되어 광주광역시, 전라남도, 전북특별자치도, 제주특별자치도를 관할하며, 제주와 전주에 원외재판부를 두고 있다.
  • 빈 문단이 포함된 문서 - 1502년
    1502년은 율리우스력으로 수요일에 시작하는 평년으로, 이사벨 1세의 이슬람교 금지 칙령 발표, 콜럼버스의 중앙아메리카 해안 탐험, 바스쿠 다 가마의 인도 상관 설립, 크리미아 칸국의 킵차크 칸국 멸망, 비텐베르크 대학교 설립, 최초의 아프리카 노예들의 신대륙 도착 등의 주요 사건이 있었다.

2. 역사

`CPUID` 명령어가 널리 사용되기 전에는 프로그래머들이 프로세서 제조사와 모델을 파악하기 위해 CPU 동작의 미세한 차이를 이용하는 복잡한 기계어 코드를 작성해야 했다. 80386 프로세서부터는 EDX 레지스터를 통해 리비전을 확인할 수 있었지만, 이는 재부팅 후에만 가능했고 응용 프로그램에서 이 값을 읽을 수 있는 표준적인 방법은 없었다. x86 계열 외의 아키텍처에서는 개발자들이 CPU 설계 차이를 파악하기 위해 명령어 타이밍이나 CPU 오류 발생과 관련된 복잡한 과정을 사용해야 하는 경우가 많았다. `CPUID` 명령어는 x86 아키텍처에 특화되어 있지만, ARM과 같은 다른 아키텍처에서도 x86의 `CPUID`와 유사한 정보를 얻기 위해 규정된 방식으로 읽을 수 있는 온칩 레지스터를 제공하는 경우가 많다.

2.1. x86 아키텍처에서의 CPUID

`CPUID` 명령어가 일반에 사용되기 전에는 프로그래머들은 프로세서의 제조사와 모델을 파악하기 위해 CPU 동작의 미세한 차이를 이용하는 난해한 기계어를 작성하곤 했다. 80386 프로세서가 도입되면서, 리셋 시 EDX 레지스터는 리비전을 나타냈지만, 이는 리셋 후에만 읽을 수 있었고, 응용 프로그램이 해당 값을 읽을 수 있는 표준적인 방법은 없었다.

x86 계열 이외의 아키텍처에서는 개발자들이 CPU 설계의 차이를 파악하기 위해 (명령어 타이밍이나 CPU 오류 트리거와 관련된) 난해한 프로세스를 사용해야 하는 경우가 많았다.

`CPUID` 명령어는 x86 아키텍처에 특화되어 있지만, ARM과 같은 다른 아키텍처는 x86 `CPUID` 명령어와 동일한 종류의 정보를 얻기 위해 규정된 방식으로 읽을 수 있는 온칩 레지스터를 제공하는 경우가 많다. `CPUID` 명령은 일부 80486 및 그 이후 프로세서에서 사용할 수 있으므로, 경우에 따라 `CPUID` 명령이 존재하는 프로세서인지 여부를 사전에 판별할 필요가 생긴다. 80486 이상의 프로세서임을 확인할 수 있는 경우, `CPUID` 명령의 존재를 확인하기 위한 플래그로 32비트 플래그 레지스터(EFLAGS)의 제21비트가 새로 마련되었으므로, 이 비트가 "변경 가능"하면 `CPUID` 명령을 사용할 수 있다고 판단할 수 있다.

2.2. x86 외 아키텍처에서의 CPU 식별

x86 계열 이외의 아키텍처에서는 개발자들이 CPU 설계의 차이를 파악하기 위해 (명령어 타이밍이나 CPU 오류 트리거와 관련된) 난해한 프로세스를 사용해야 하는 경우가 많다.

예를 들어, CPUID 명령어가 전혀 없는 모토로라 680x0 계열에서는 특정 명령어에 특권이 필요했다. 이를 통해 다양한 CPU 제품군을 구별할 수 있었다. 모토로라 68010에서는 SR에서 MOVE 명령어가 특권을 가지게 되었다. 이 주목할 만한 명령어 (및 상태 머신) 변경을 통해 68010은 Popek과 Goldberg의 가상화 요구 사항을 충족할 수 있었다. 68000은 비특권 SR에서 MOVE를 제공했기 때문에, 두 개의 다른 CPU는 CPU 오류 조건이 트리거됨으로써 구별될 수 있었다.

CPUID 명령어는 x86 아키텍처에 특화되어 있지만, ARM과 같은 다른 아키텍처는 x86 CPUID 명령어와 동일한 종류의 정보를 얻기 위해 규정된 방식으로 읽을 수 있는 온칩 레지스터를 제공하는 경우가 많다.

3. CPUID 호출

어셈블리어에서 CPUID 명령어는 EAX 레지스터를 사용하므로 별도의 매개변수가 필요 없다. EAX 레지스터에는 어떤 정보를 반환할지를 지정하는 값을 설정해야 한다. CPUID는 EAX = 0으로 먼저 호출되어야 하는데, 이는 CPU가 지원하는 가장 높은 호출 변수를 반환하기 때문이다. 확장 정보를 가져오려면 EAX의 비트 31을 설정하여 CPUID를 호출해야 한다. 가장 높은 확장 명령 호출 변수를 확인하려면 EAX = 80000000h로 CPUID를 호출하면 된다.

CPUID 연산 코드는 0F A2이다.

최근 추가된 일부 리프(leaf)에는 CPUID를 호출하기 전에 ECX 레지스터를 통해 선택되는 서브 리프도 존재한다.

3.1. CPUID 명령 사용 가능 여부 판별 (일본어 문서 내용)

CPUID 명령은 일부 80486 및 그 이후 프로세서에서 사용할 수 있으므로, CPUID 명령이 존재하는 프로세서인지 여부를 사전에 판별해야 할 필요가 있는 경우가 있다. 80486 이전의 프로세서를 식별해야 하는 경우에는, 각 프로세서의 미묘한 동작 차이를 이용하는 난해한 기술을 구사해야 했다.

80486 이상의 프로세서임을 확인할 수 있는 경우, CPUID 명령의 존재를 확인하기 위한 플래그로 32비트 플래그 레지스터(EFLAGS)의 제21비트가 새로 마련되었으므로, 이 비트가 "변경 가능"하면 CPUID 명령을 사용할 수 있다고 판단할 수 있다. EFLAGS는 80386 이상의 프로세서라면 액세스할 수 있으므로, 80286 이하의 프로세서를 판별·제외할 필요는 있지만, CPUID 명령의 유무만을 조사하고 싶다면 실질적으로 80386 이상임을 확인할 수 있다면 이 플래그를 조사할 수 있다.

4. 고급 언어에서의 CPUID 사용

CPUID 명령어는 여러 고급 프로그래밍 언어에서 쉽게 사용할 수 있다.

C/C++에서는 GCC, MSVC, Borland/Embarcadero C 컴파일러 등에서 CPUID를 활용할 수 있다. GCC는 `` 헤더를 통해 `__cpuid` 매크로나 `__get_cpuid` 함수를 제공하고, MSVC는 내장 함수 `__cpuid()`를 제공한다.

.NET 5 이상에서는 `System.Runtime.Intrinsics.X86.X86base.CpuId` 메서드를 통해 CPUID 기능을 사용할 수 있다. 또한, 많은 인터프리터 언어나 스크립팅 언어는 FFI 라이브러리를 통해 CPUID를 사용한다.

4.1. 인라인 어셈블리

CPUID 명령어는 다양한 언어에서 접근할 수 있다. 다음은 C++ (gcc)를 사용하여 cpuid가 반환하는 처음 다섯 개의 값을 출력하는 코드이다.


#include

int main(int argc, char argv) {
int b;
for (int a = 0; a < 5; a++) {
asm ( "mov %1, %%eax; " // a → eax
"cpuid;"
"mov %%eax, %0;" // eeax → b
:"=r"(b) /* 출력 */
:"r"(a) /* 입력 */
:"%eax" /* 레지스터 표시 */
);
std::cout << "코드 " << a << "은(는) 다음을 제공한다: " << b << std::endl;
}
return 0;
}


마이크로소프트 비주얼 C 컴파일러는 `__cpuid()` 함수를 내장하고 있어, 인라인 어셈블리를 사용하지 않고도 cpuid 명령을 추가할 수 있다. x64 버전의 MSVC는 인라인 어셈블리를 허용하지 않으므로 이 방법이 더 편리하다. MSVC에서는 다음과 같이 사용한다.


#include
#include

int main(int argc, char
argv) {
int b[4];
for (int a = 0; a < 5; a++) {
__cpuid(b,a);
std::cout << "코드 " << a << "은(는) 다음을 제공한다: " << b[0] << std::endl;
}
return 0;
}


다음은 gcc용 C 코드로, cpuid가 반환하는 처음 5개의 값을 출력한다.

```c
#include
#include

int main()
{
unsigned int i, eax, ebx, ecx, edx;

for (i = 0; i < 5; i++) {
__cpuid(i, eax, ebx, ecx, edx);
printf ("InfoType %x\nEAX: %x\nEBX: %x\nECX: %x\nEDX: %x\n", i, eax, ebx, ecx, edx);
}

return 0;
}
```

MSVC 및 Borland/Embarcadero C 컴파일러(bcc32) 스타일의 인라인 어셈블리에서는 파괴 정보가 명령어에 암시적으로 포함된다.

```c
#include

int main()
{
unsigned int a, b, c, d, i = 0;

__asm {
/* Do the call. */
mov EAX, i;
cpuid;
/* Save results. */
mov a, EAX;
mov b, EBX;
mov c, ECX;
mov d, EDX;
}

printf ("InfoType %x\nEAX: %x\nEBX: %x\nECX: %x\nEDX: %x\n", i, a, b, c, d);
return 0;
}
```

어느 버전이든 일반 어셈블리어로 작성하는 경우, 프로그래머는 EAX, EBX, ECX, EDX 레지스터의 값을 계속 사용하려면 결과를 다른 곳에 수동으로 저장해야 한다.

4.2. 래퍼 함수

CPUID는 다양한 프로그래밍 언어에서 래퍼 함수를 통해 쉽게 접근할 수 있다.

C++ (GCC)에서는 다음과 같은 코드로 `cpuid`가 반환하는 처음 다섯 개의 값을 출력할 수 있다.

```cpp
#include

int main(int argc, char argv) {
int b;
for (int a = 0; a < 5; a++) {
asm ( "mov %1, %%eax; " // a → eax
"cpuid;"
"mov %%eax, %0;" // eeax → b
:"=r"(b) /* 출력 */
:"r"(a) /* 입력 */
:"%eax" /* 레지스터 표시 */
);
std::cout << "코드 " << a << "은(는) 다음을 제공한다: " << b << std::endl;
}
return 0;
}
```

MSVC는 `__cpuid()` 함수를 내장하고 있어, 인라인 어셈블리 없이 `cpuid` 명령을 사용할 수 있다. 특히 x64 버전의 MSVC는 인라인 어셈블리를 허용하지 않으므로 더욱 편리하다.

```cpp
#include
#include

int main(int argc, char
argv) {
int b[4];
for (int a = 0; a < 5; a++) {
__cpuid(b,a);
std::cout << "코드 " << a << "은(는) 다음을 제공한다: " << b[0] << std::endl;
}
return 0;
}
```

GCC는 `cpuid.h` 헤더 파일을 통해 `__cpuid` 매크로를 제공한다. 이 매크로는 인라인 어셈블리로 확장된다.

```c
#include
#include

int main()
{
unsigned int eax, ebx, ecx, edx;

__cpuid(0 /* 공급업체 문자열 */, eax, ebx, ecx, edx);
printf("EAX: %x\nEBX: %x\nECX: %x\nEDX: %x\n", eax, ebx, ecx, edx);

return 0;
}
```

CPU에 없는 확장 기능을 요청할 경우를 대비하여, `cpuid.h`는 `__get_cpuid` 함수를 제공한다. 이 함수는 확장 기능을 확인하고 안전 검사를 수행하며, 포인터를 사용하여 출력 값을 전달한다.

```c
#include
#include

int main()
{
unsigned int eax, ebx, ecx, edx;

/* 0x81234567은 존재하지 않지만 존재한다고 가정합니다. */
if (!__get_cpuid (0x81234567, &eax, &ebx, &ecx, &edx)) {
printf("경고: CPUID 요청 0x81234567이 유효하지 않습니다!\n");
return 1;
}

printf("EAX: %x\nEBX: %x\nECX: %x\nEDX: %x\n", eax, ebx, ecx, edx);

return 0;
}
```

`__get_cpuid` 함수는 올바른 요청에 대해 0이 아닌 값을, 실패 시 0을 반환한다.

MSVC에서 인라인 어셈블리 없이 CPUID를 사용하는 또 다른 예시는 다음과 같다.

```cpp
#include
#ifdef __MSVC__
#include
#endif

int main()
{
unsigned int regs[4];
int i;

for (i = 0; i < 4; i++) {
__cpuid(regs, i);
printf("코드 %d는 %d, %d, %d, %d를 제공합니다", regs[0], regs[1], regs[2], regs[3]);
}

return 0;
}
```

FFI 라이브러리를 통해 CPUID를 사용하는 인터프리터 언어나 스크립팅 언어도 많다. [https://web.archive.org/web/20150429190703/http://www.cstrahan.com/posts/pure-ruby-cpuid-via-ffi.html Ruby FFI 모듈을 사용한 구현 예시]에서는 어셈블리 언어를 실행하여 CPUID 연산 코드를 사용하는 방법을 보여준다.

.NET 5 이상 버전은 `System.Runtime.Intrinsics.X86.X86base.CpuId` 메서드를 제공한다. 다음 C# 코드는 CPUID 명령을 지원하는 경우 프로세서 브랜드를 출력한다.

```c#
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Text;

namespace X86CPUID {
class CPUBrandString {
public static void Main(string[] args) {
if (!X86Base.IsSupported) {
Console.WriteLine("CPU가 CPUID 명령을 지원하지 않습니다.");
} else {
Span raw = stackalloc int[12];
(raw[0], raw[1], raw[2], raw[3]) = X86Base.CpuId(unchecked((int)0x80000002), 0);
(raw[4], raw[5], raw[6], raw[7]) = X86Base.CpuId(unchecked((int)0x80000003), 0);
(raw[8], raw[9], raw[10], raw[11]) = X86Base.CpuId(unchecked((int)0x80000004), 0);

Span bytes = MemoryMarshal.AsBytes(raw);
string brand = Encoding.UTF8.GetString(bytes).Trim();
Console.WriteLine(brand);
}
}
}
}

4.3. 기타 언어

CPUID가 반환하는 처음 다섯 개의 값을 출력하는 C++ (gcc) 코드는 다음과 같다.


#include

int main(int argc, char argv) {
int b;
for (int a = 0; a < 5; a++) {
asm ( "mov %1, %%eax; " // a → eax
"cpuid;"
"mov %%eax, %0;" // eax → b
:"=r"(b) /* 출력 */
:"r"(a) /* 입력 */
:"%eax" /* 레지스터 표시 */
);
std::cout << "코드 " << a << "은(는) 다음을 제공한다: " << b << std::endl;
}
return 0;
}


마이크로소프트 비주얼 C 컴파일러는 `__cpuid()` 함수를 내장하고 있으므로, 인라인 어셈블리를 사용하지 않고도 CPUID 명령을 추가할 수 있다. x64 버전의 MSVC가 인라인 어셈블리를 허용하지 않기 때문에 유용하다. MSVC의 경우 다음과 같이 사용한다.


#include
#include

int main(int argc, char
argv) {
int b[4];
for (int a = 0; a < 5; a++) {
__cpuid(b,a);
std::cout << "코드 " << a << "은(는) 다음을 제공한다: " << b[0] << std::endl;
}
return 0;
}

5. x86 외 CPU 아키텍처에서의 CPU 정보

ARM 아키텍처예외 레벨 EL1 이상이 필요한 `CPUID` 보조 프로세서 레지스터를 가지고 있다.

IBM System z 메인프레임 프로세서는 1983년 IBM 4381부터 프로세서 ID를 쿼리하기 위한 CPU ID 저장(STIDP) 명령어를 가지고 있으며, 설치된 하드웨어 기능을 나열하는 확장 기능 목록 저장(STFLE) 명령어도 가지고 있다.

MIPS32/64 아키텍처는 필수 프로세서 식별(PrId) 및 일련의 데이지 체인 구성 레지스터를 정의한다.

PowerPC 프로세서는 사용 중인 프로세서 모델을 식별하는 32비트 읽기 전용 프로세서 버전 레지스터(PVR)를 가지고 있다. 이 명령어는 슈퍼바이저 액세스 레벨이 필요하다.

DSP 및 트랜스퓨터와 유사한 칩 제품군은 설계에 많은 변형이 있음에도 불구하고 눈에 띄는 방식으로 이 명령어를 채택하지 않았다.