Computer >> Hướng Dẫn Máy Tính >  >> Phần Cứng >> CPU

Giải Mã Bí Mật CPU: Truy Xuất Thông Tin Hệ Thống Với Lệnh CPUID

Khi phát triển bootloader/kernel, hiểu kiến trúc nền tảng là quan trọng để tối ưu hiệu suất và tương thích giữa phần mềm và phần cứng.

Một công cụ quan trọng nhưng đôi khi bị bỏ qua, có sẵn cho kỹ sư truy vấn và truy xuất thông tin hệ thống, là lệnh CPUID.

Lệnh CPUID Là Gì?

Lệnh CPUID là lệnh cấp thấp, nằm ở trung tâm của mọi bộ xử lý x86 và x86-64 hiện đại, cho phép phần mềm truy vấn CPU để lấy thông tin về bộ xử lý và các tính năng được hỗ trợ.

Bằng cách gọi lệnh này, bạn có thể thu thập thông tin như model processor, family, kích thước cache nội bộ, và các tính năng được hỗ trợ như SIMD hoặc ảo hóa phần cứng. Điều này giúp tối ưu hiệu suất và bật/tắt động các tính năng được hỗ trợ.

Đối với nhà phát triển bootloader hoặc kernel, hiểu các tính năng processor hỗ trợ — như ảo hóa phần cứng, kích thước cache, hoặc lệnh SIMD — đảm bảo hệ thống chạy hiệu quả và mã bạn viết tương thích trên các CPU khác nhau. Bằng cách sử dụng lệnh CPUID, bạn có thể điều chỉnh động hành vi kernel dựa trên processor cụ thể đang chạy.

Trong bài này, bạn sẽ học cách kiểm tra lệnh CPUID có sẵn cho hệ thống, cách nó hoạt động và thông tin nào có thể lấy được.

Điều Kiện Tiên Quyết

  • Một chút kiến thức assembly language (ví dụ này dùng FASM)
  • Một chút kiến thức hệ điều hành/kernel
  • Truy cập công cụ debug cấp thấp (ví dụ GDB) hoặc emulator phần cứng như QEMU để test bootloader/kernel trên các nền tảng khác nhau.

Bước 1: Kiểm Tra CPUID Có Sẵn

Trước khi thực thi lệnh CPUID, quan trọng phải xác định processor có hỗ trợ không, vì không phải CPU nào cũng đảm bảo có chức năng này. Mã sau kiểm tra sự có sẵn của CPUID bằng cách sửa đổi và kiểm tra bit ID (bit 21) trong thanh ghi EFLAGS.

Đây là hình từ wiki.osdev.org hiển thị từng bit của thanh ghi EFLAGS:

Giải Mã Bí Mật CPU: Truy Xuất Thông Tin Hệ Thống Với Lệnh CPUID

Nếu processor cho phép bit này đảo ngược, CPUID được hỗ trợ; ngược lại, không. Cách phát hiện hoạt động như sau:

(nhiều người nghĩ ở Real mode thanh ghi 32-bit không truy cập được. Đó không đúng. Tất cả thanh ghi 32-bit đều dùng được)

cpuid_check: pusha ; lưu trạng thái pushfd ; Lưu EFLAGS pushfd ; Lưu bản sao EFLAGS xor dword [esp],0x00200000 ; Đảo bit ID trong EFLAGS đã lưu popfd ; Nạp EFLAGS đã sửa (bit ID đảo) pushfd ; Lưu EFLAGS lại (bit ID có thể đảo hoặc không) pop eax ; eax = EFLAGS đã sửa (bit ID có thể đảo) xor eax,[esp] ; eax = bit nào bị thay đổi popfd ; Khôi phục EFLAGS gốc and eax,0x00200000 ; eax = zero nếu bit ID không đổi, khác zero nếu đổi cmp eax,0x00 je .cpuid_instruction_not_is_available .cpuid_instruction_is_available: ;xử lý CPUID tồn tại .cpuid_instruction_not_is_available: ;xử lý CPUID không hỗ trợ .cpuid_check_end: popa ; khôi phục trạng thái ret 

pusha: Lưu tất cả thanh ghi chung để khôi phục trạng thái gốc cuối cùng.

pushfd: Lưu thanh ghi EFLAGS hiện tại.

pushfd: Lưu bản sao EFLAGS.

xor dword [esp], 0x00200000: Mã đảo bit ID (21) của EFLAGS dùng toán tử XOR.

popfd: Khôi phục EFLAGS đã sửa với bit ID đảo.

pushfd: Đẩy EFLAGS đã sửa trở lại stack.

pop eax: Đưa EFLAGS đã sửa (bit ID có thể đảo) vào thanh ghi EAX.

xor eax, [esp]: Sau XOR, EAX chứa bit bị thay đổi.

popfd: Khôi phục EFLAGS gốc.

and eax, 0x00200000: Toán tử and cô lập bit 21 (bit ID) bằng cách mask các bit khác. Sau phép này, EAX chứa 0x00200000 (nếu bit 21 thay đổi — CPUID hỗ trợ) hoặc 0×00 (bit 21 không đổi — CPUID không hỗ trợ).

cmp eax, 0x00: Lệnh CMP kiểm tra kết quả phép trước. Nếu EAX = 0×00, bit ID không thể sửa đổi và processor không hỗ trợ CPUID. Nếu khác zero, bit ID đã đảo và processor hỗ trợ CPUID.

Lấy Tính Năng CPU

Lệnh CPUID trả về thông tin khác nhau với giá trị khác nhau trong thanh ghi EAX.

mov eax, 0x1 cpuid 

Với EAX = 1, CPUID trả về bitfield trong EDX, chứa các giá trị sau. Thương hiệu khác có thể cho ý nghĩa khác (nguồn 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 }; 

Giải thích ngắn gọn các tính năng CPU:

  • PCLMUL, AES: Tập lệnh mật mã cho mã hóa/giải mã nhanh.

  • VMX, SMX: Hỗ trợ ảo hóa chạy máy ảo.

  • SSE3, SSSE3, SSE4.1, SSE4.2, AVX: Tập lệnh SIMD cho đa phương tiện, toán học, xử lý vector nhanh hơn.

  • FMA: Fused Multiply-Add, cải thiện hiệu suất tính toán dấu phẩy động.

  • RDRAND: Bộ sinh số ngẫu nhiên.

  • X2APIC: Xử lý ngắt nâng cao hệ thống đa xử lý.

  • PCID: Tối ưu quản lý bộ nhớ khi chuyển ngữ cảnh.

  • FPU: Đơn vị dấu phẩy động phần cứng cho toán học nhanh.

  • PAE: Physical Address Extension, cho phép truy xuất hơn 4 GB bộ nhớ.

  • HTT: Cho phép một nhân CPU xử lý nhiều luồng.

  • PAT, PGE: Tính năng quản lý bộ nhớ kiểm soát caching và ánh xạ trang.

  • MMX, SSE, SSE2: Tập lệnh SIMD cũ cho đa phương tiện.

Lấy Chuỗi Nhà Cung Cấp CPU

Muốn lấy chuỗi vendor CPU, EAX đặt 0×0 trước khi gọi CPUID.

mov eax, 0x0 cpuid 

Chuỗi vendor là định danh duy nhất CPU vendor như AMD và Intel dùng. Ví dụ: GenuineIntel (Intel) hoặc AuthenticAMD (AMD). Cơ bản chỉ định nhà sản xuất CPU.

Chuỗi vendor cho phép kernel xác định nhà sản xuất CPU, rất hữu ích vì các nhà sản xuất triển khai tính năng khác nhau. Phần mềm hoặc driver có thể tương tác khác nhau dựa trên nhà sản xuất CPU để đảm bảo tương thích.

Khi dùng như vậy, vendor id string trả về trong thanh ghi EBX, EDX, ECX. Ghi vào buffer để lấy chuỗi 12 ký tự đầy đủ.

Mã ví dụ:

Bước 1: Buffer

Tạo buffer chứa 12 byte:

buffer: db 12 dup(0), 0xA, 0xD, 0 

Bước 2: In Buffer

Bắt đầu bằng hàm in chuỗi.

Mã assembly đọc chuỗi ký tự per ký tự và in màn hình dùng ngắt BIOS 0x10. Hàm print lặp qua chuỗi và dùng lodsb nạp từng ký tự vào thanh ghi al.

Sau đó print_char dùng ngắt 0×10 in lên màn hình. Khi đến cuối chuỗi (null terminator), vòng lặp kết thúc.

print_string: call print ret print: .loop: lodsb ;đọc ký tự vào al và tăng cmp al ,0 ;kiểm tra hết chuỗi je .done ;đến null terminator, xong call print_char ;in ký tự jmp .loop ;nhảy về vòng lặp .done: ret print_char: mov ah, 0eh int 0x10 ret 

Bước 3: Điền Buffer Và In Ra

Ở đây, sau khi lưu trạng thái bằng pusha và gọi cpuid với 0×0 trong EAX, lưu nội dung ebx, edx, ecx vào buffer. Sau đó gọi print_string in ra.

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 

Video từ kênh YouTube tôi triển khai và giải thích mã trên chi tiết

Thêm thông tin về CPUID theo giá trị truyền trong EAX: https://gitlab.com/x86-cpuid.org/x86-cpuid-db

Lời Kết

Bằng cách hiểu và dùng lệnh CPUID, bạn làm bootloader/kernel thích nghi hơn với nhiều processor. Biết cách phát hiện sự có sẵn của lệnh và truy xuất thông tin hệ thống quan trọng — như tính năng CPU, kích thước cache, công nghệ hỗ trợ — có thể tăng đáng kể hiệu suất và tương thích.

Sau đọc bài này, bạn nên có công cụ và kiến thức bắt đầu khám phá lệnh CPUID và dùng nó trong dự án!

Happy coding!

Học lập trình miễn phí. Chương trình mở của freeCodeCamp đã giúp hơn 40.000 người có việc làm lập trình viên. Bắt đầu ngay