부트로더/커널을 개발할 때 기본 아키텍처를 이해하는 것은 소프트웨어와 하드웨어 간의 성능 및 호환성을 최적화하는 데 중요합니다.
엔지니어가 시스템 정보를 쿼리하고 검색하기 위해 사용할 수 있는 중요하지만 때로는 간과되는 도구 중 하나는 CPUID 명령입니다.
CPUID 명령어는 무엇인가요?
CPUID 명령어는 모든 최신 x86 및 x86-64 프로세서의 핵심 내부에 있는 하위 수준 명령어로, 소프트웨어가 CPU에 프로세서 및 지원되는 기능에 대한 정보를 쿼리할 수 있도록 해줍니다.
이 명령어를 호출하면 프로세서 모델, 제품군, 내부 캐시 크기 및 SIMD나 하드웨어 가상화와 같은 지원 기능과 같은 정보를 수집할 수 있습니다. 이는 성능을 최적화하고 지원되는 기능을 동적으로 활성화 또는 비활성화하는 데 도움이 될 수 있습니다.
부트로더 또는 커널 개발자의 경우 하드웨어 가상화, 캐시 크기, SIMD 명령 등 프로세서가 지원하는 기능을 이해하면 시스템이 효율적으로 실행되고 작성한 코드가 다양한 CPU에서 호환되는지 확인할 수 있습니다. CPUID 명령을 활용하면 실행 중인 특정 프로세서에 따라 커널의 동작을 동적으로 조정할 수 있습니다.
이 문서에서는 시스템에서 CPUID 명령을 사용할 수 있는지 확인하는 방법, 작동 방식 및 이를 사용하여 얻을 수 있는 정보에 대해 설명합니다.
전제조건
-
어셈블리 언어에 대한 약간의 지식(이 예에서는 FASM을 사용함)
-
운영 체제/커널에 대한 지식
-
낮은 수준의 디버깅 도구(예:GDB) 또는 QEMU와 같은 하드웨어 에뮬레이터에 액세스하여 다양한 플랫폼에서 부트로더/커널을 테스트합니다.
1단계:CPUID 가용성 확인
CPUID 명령을 실행하기 전에 프로세서가 이를 지원하는지 여부를 확인하는 것이 중요합니다. 모든 CPU에 이 기능이 보장되는 것은 아니기 때문입니다. 다음 코드는 EFLAGS 레지스터의 ID 비트(비트 21)를 수정하고 테스트하여 CPUID 명령어의 가용성을 확인합니다.
다음은 EFLAGS 레지스터의 각 비트를 보여주는 wiki.osdev.org의 사진입니다:

프로세서가 이 비트의 토글을 허용하면 CPUID가 지원됩니다. 그렇지 않으면 그렇지 않습니다. 감지 프로세스의 작동 방식은 다음과 같습니다.
(대부분의 사람들은 리얼 모드에서는 32개의 레지스터에 접근할 수 없다고 생각합니다. 이는 사실이 아닙니다. 모든 32비트 레지스터를 사용할 수 있습니다.)
cpuid_check:
pusha ; save state
pushfd ; Save EFLAGS
pushfd ; Store EFLAGS
xor dword [esp],0x00200000 ; Invert the ID bit in stored EFLAGS
popfd ; Load stored EFLAGS (with ID bit inverted)
pushfd ; Store EFLAGS again (ID bit may or may not be inverted)
pop eax ; eax = modified EFLAGS (ID bit may or may not be inverted)
xor eax,[esp] ; eax = whichever bits were changed
popfd ; Restore original EFLAGS
and eax,0x00200000 ; eax = zero if ID bit can't be changed, else non-zero
cmp eax,0x00
je .cpuid_instruction_not_is_available
.cpuid_instruction_is_available:
;handle CPUID exists
.cpuid_instruction_not_is_available:
;handle CPUID isn't supported
.cpuid_check_end:
popa ; restore state
ret
pusha :마지막에 원래 상태를 복원할 수 있도록 모든 범용 레지스터를 저장합니다.
pushfd :현재 EFLAGS 레지스터를 저장합니다.
pushfd :EFLAGS의 복사본을 저장합니다.
xor dword [esp], 0x00200000 :코드는 XOR 연산자를 사용하여 EFLAGS의 ID 비트(21)를 뒤집습니다.
popfd :ID 비트가 반전된 수정된 EFLAGS를 복원합니다.
pushfd :수정된 EFLAGS를 다시 스택에 푸시합니다.
pop eax :수정된 EFLAGS(ID 비트는 반전될 수도 있고 반전되지 않을 수도 있음)를 EAX 레지스터에 넣습니다.
xor eax, [esp] :XOR 연산 후 EAX에는 변경된 비트가 포함됩니다.
popfd :원본 EFLAGS를 복원합니다.
and eax, 0x00200000 :and 다른 모든 비트를 마스킹하여 21번째 비트(ID 비트)를 분리합니다. 이 작업 후 EAX 레지스터에는 0x00200000(21비트가 변경되어 CPUID가 지원됨을 의미) 또는 0×00(21비트가 변경되지 않았으며 CPUID가 지원되지 않음)이 포함됩니다.
cmp eax, 0x00 :CMP 명령은 이전 작업의 결과를 확인합니다. EAX가 0×00이면 ID 비트를 수정할 수 없으며 프로세서가 CPUID 명령을 지원하지 않는다는 의미입니다. 0이 아니면 ID 비트가 반전되었으며 프로세서가 CPUID 명령을 지원한다는 의미입니다.
CPU 기능 가져오기
CPUID 명령은 EAX 레지스터의 다른 값으로 다른 정보를 반환합니다.
mov eax, 0x1
cpuid
EAX를 1로 설정하면 CPUID는 다음 값을 포함하는 EDX의 비트 필드를 반환합니다. 브랜드마다 다른 의미를 부여할 수 있습니다(출처:https://wiki.osdev.org/CPUID)
enum {
CPUID_FEAT_ECX_SSE3 = 1 << 0,
CPUID_FEAT_ECX_PCLMUL = 1 << 1,
CPUID_FEAT_ECX_DTES64 = 1 << 2,
CPUID_FEAT_ECX_MONITOR = 1 << 3,
CPUID_FEAT_ECX_DS_CPL = 1 << 4,
CPUID_FEAT_ECX_VMX = 1 << 5,
CPUID_FEAT_ECX_SMX = 1 << 6,
CPUID_FEAT_ECX_EST = 1 << 7,
CPUID_FEAT_ECX_TM2 = 1 << 8,
CPUID_FEAT_ECX_SSSE3 = 1 << 9,
CPUID_FEAT_ECX_CID = 1 << 10,
CPUID_FEAT_ECX_SDBG = 1 << 11,
CPUID_FEAT_ECX_FMA = 1 << 12,
CPUID_FEAT_ECX_CX16 = 1 << 13,
CPUID_FEAT_ECX_XTPR = 1 << 14,
CPUID_FEAT_ECX_PDCM = 1 << 15,
CPUID_FEAT_ECX_PCID = 1 << 17,
CPUID_FEAT_ECX_DCA = 1 << 18,
CPUID_FEAT_ECX_SSE4_1 = 1 << 19,
CPUID_FEAT_ECX_SSE4_2 = 1 << 20,
CPUID_FEAT_ECX_X2APIC = 1 << 21,
CPUID_FEAT_ECX_MOVBE = 1 << 22,
CPUID_FEAT_ECX_POPCNT = 1 << 23,
CPUID_FEAT_ECX_TSC = 1 << 24,
CPUID_FEAT_ECX_AES = 1 << 25,
CPUID_FEAT_ECX_XSAVE = 1 << 26,
CPUID_FEAT_ECX_OSXSAVE = 1 << 27,
CPUID_FEAT_ECX_AVX = 1 << 28,
CPUID_FEAT_ECX_F16C = 1 << 29,
CPUID_FEAT_ECX_RDRAND = 1 << 30,
CPUID_FEAT_ECX_HYPERVISOR = 1 << 31,
CPUID_FEAT_EDX_FPU = 1 << 0,
CPUID_FEAT_EDX_VME = 1 << 1,
CPUID_FEAT_EDX_DE = 1 << 2,
CPUID_FEAT_EDX_PSE = 1 << 3,
CPUID_FEAT_EDX_TSC = 1 << 4,
CPUID_FEAT_EDX_MSR = 1 << 5,
CPUID_FEAT_EDX_PAE = 1 << 6,
CPUID_FEAT_EDX_MCE = 1 << 7,
CPUID_FEAT_EDX_CX8 = 1 << 8,
CPUID_FEAT_EDX_APIC = 1 << 9,
CPUID_FEAT_EDX_SEP = 1 << 11,
CPUID_FEAT_EDX_MTRR = 1 << 12,
CPUID_FEAT_EDX_PGE = 1 << 13,
CPUID_FEAT_EDX_MCA = 1 << 14,
CPUID_FEAT_EDX_CMOV = 1 << 15,
CPUID_FEAT_EDX_PAT = 1 << 16,
CPUID_FEAT_EDX_PSE36 = 1 << 17,
CPUID_FEAT_EDX_PSN = 1 << 18,
CPUID_FEAT_EDX_CLFLUSH = 1 << 19,
CPUID_FEAT_EDX_DS = 1 << 21,
CPUID_FEAT_EDX_ACPI = 1 << 22,
CPUID_FEAT_EDX_MMX = 1 << 23,
CPUID_FEAT_EDX_FXSR = 1 << 24,
CPUID_FEAT_EDX_SSE = 1 << 25,
CPUID_FEAT_EDX_SSE2 = 1 << 26,
CPUID_FEAT_EDX_SS = 1 << 27,
CPUID_FEAT_EDX_HTT = 1 << 28,
CPUID_FEAT_EDX_TM = 1 << 29,
CPUID_FEAT_EDX_IA64 = 1 << 30,
CPUID_FEAT_EDX_PBE = 1 << 31
};
위의 CPU 기능에 대한 간략한 설명:
-
PCLMUL, AES:빠른 암호화 및 복호화를 위한 암호화 명령 세트입니다. -
VMX, SMX:가상 머신 실행을 위한 가상화 지원. -
SSE3, SSSE3, SSE4.1, SSE4.2, AVX:더 빠른 멀티미디어, 수학 및 벡터 처리를 위한 SIMD 명령어 세트입니다. -
FMA:융합 곱셈-덧셈은 부동 소수점 계산 성능을 향상시킵니다. -
RDRAND:난수 생성기. -
X2APIC:다중 프로세서 시스템의 고급 인터럽트 처리. -
PCID:컨텍스트 전환 중 메모리 관리를 최적화합니다. -
FPU:더 빠른 수학 연산을 위한 하드웨어 부동 소수점 장치. -
PAE:물리적 주소 확장으로 4GB 이상의 메모리 주소 지정이 가능합니다. -
HTT:단일 CPU 코어가 여러 스레드를 처리할 수 있도록 합니다. -
PAT, PGE:캐싱 및 페이지 매핑을 제어하기 위한 메모리 관리 기능입니다. -
MMX, SSE, SSE2:멀티미디어 처리를 위한 이전 SIMD 명령어 세트.
CPU 공급업체 문자열 가져오기
CPU 공급업체 문자열을 얻으려면 CPUID 명령어를 호출하기 전에 EAX를 0×0으로 설정해야 합니다.
mov eax, 0x0
cpuid
공급업체 문자열은 AMD 및 Intel과 같은 CPU 공급업체가 사용하는 고유 식별자입니다. 예:GenuineIntel(Intel 프로세서용) 또는 AuthenticAMD(AMD 프로세서용). 기본적으로 CPU 제조업체를 지정합니다.
공급업체 문자열을 통해 커널은 CPU 제조업체를 식별할 수 있는데, 이는 제조업체마다 특정 기능을 다르게 구현하기 때문에 매우 유용합니다. 또한 소프트웨어나 드라이버는 호환성을 보장하기 위해 CPU 제조업체에 따라 다르게 상호 작용할 수 있습니다.
이와 같이 사용하면 공급업체 ID 문자열이 EBX, EDX, ECX 레지스터에 반환됩니다. 이를 버퍼에 쓰고 전체 12자 문자열을 얻을 수 있습니다.
예제 코드:
1단계:버퍼
12바이트를 저장할 수 있는 버퍼를 만듭니다:
buffer: db 12 dup(0), 0xA, 0xD, 0
2단계:버퍼 인쇄
문자열 인쇄 기능을 만드는 것부터 시작하겠습니다.
이 어셈블리 코드는 문자열을 문자별로 읽고 BIOS 인터럽트 0x10을 사용하여 화면에 인쇄합니다. print 함수는 문자열을 반복하고 lodsb을 사용합니다. al의 각 문자를 로드하는 명령 등록하세요.
그런 다음 print_char 함수는 인터럽트 0×10을 사용하여 화면에 인쇄합니다. 코드가 문자열 끝에 도달하면(널 종결자) 루프가 종료됩니다.
print_string:
call print
ret
print:
.loop:
lodsb ;read character to al and then increment
cmp al ,0 ;check if we reached the end
je .done ;we reached null terminator, finish
call print_char ;print character
jmp .loop ;jump back into the loop
.done:
ret
print_char:
mov ah, 0eh
int 0x10
ret
3단계:버퍼 채우기 및 인쇄
여기서 pusha를 사용하여 현재 상태를 저장한 후 지시 및 전화 cpuid EAX 레지스터에 0×0이 전달되면 ebx의 내용을 저장할 수 있습니다. , edx , ecx 버퍼에. 그런 다음 print_string을 호출합니다. 인쇄하세요.
get_cpu_vendor:
pusha
mov eax, 0x0
cpuid
mov [buffer], ebx
mov [buffer + 4], edx
mov [buffer + 8], ecx
mov si, buffer
call print_string
popa
ret
위의 코드를 자세히 구현하고 설명하는 YouTube 채널의 동영상
EAX 레지스터에 전달된 값에 따라 CPUID 명령어가 제공할 수 있는 정보에 대한 자세한 내용은 여기에서 확인할 수 있습니다:https://gitlab.com/x86-cpuid.org/x86-cpuid-db
에필로그
CPUID 명령어를 이해하고 사용하면 부트로더/커널을 다양한 프로세서에 더 잘 적용할 수 있습니다. 명령어의 가용성을 감지하고 CPU 기능, 캐시 크기, 지원 기술 등 중요한 시스템 정보를 검색하는 방법을 알면 성능과 호환성을 크게 향상시킬 수 있습니다.
이 기사를 읽은 후에는 CPUID 명령을 탐색하고 자신의 프로젝트에서 이를 사용하는 방법을 시작할 수 있는 도구와 지식을 갖게 될 것입니다!
즐거운 코딩 되세요!
무료로 코딩을 배우세요. freeCodeCamp의 오픈 소스 커리큘럼은 40,000명 이상의 사람들이 개발자로 취업하는 데 도움을 주었습니다. 시작하세요