เมื่อพัฒนาบูตโหลดเดอร์/เคอร์เนล การทำความเข้าใจสถาปัตยกรรมพื้นฐานเป็นสิ่งสำคัญในการเพิ่มประสิทธิภาพและความเข้ากันได้ระหว่างซอฟต์แวร์และฮาร์ดแวร์
เครื่องมือที่สำคัญอย่างหนึ่งแต่บางครั้งก็ถูกมองข้ามสำหรับวิศวกรในการสืบค้นและเรียกข้อมูลระบบคือคำสั่ง CPUID
คำสั่ง CPUID คืออะไร
คำสั่ง CPUID เป็นคำสั่งระดับต่ำ ซึ่งอยู่ภายในหัวใจของโปรเซสเซอร์ x86 และ x86-64 สมัยใหม่ทุกตัวที่อนุญาตให้ซอฟต์แวร์ค้นหา CPU สำหรับข้อมูลเกี่ยวกับโปรเซสเซอร์และคุณสมบัติที่รองรับ
ด้วยการเรียกใช้คำสั่งนี้ คุณสามารถรวบรวมข้อมูล เช่น รุ่นของโปรเซสเซอร์ ตระกูล ขนาดแคชภายใน และคุณสมบัติที่รองรับ เช่น SIMD หรือการจำลองเสมือนสำหรับฮาร์ดแวร์ สิ่งนี้สามารถช่วยคุณเพิ่มประสิทธิภาพและเปิดหรือปิดใช้งานคุณสมบัติที่รองรับแบบไดนามิกได้
สำหรับบูตโหลดเดอร์หรือนักพัฒนาเคอร์เนล การทำความเข้าใจคุณสมบัติที่โปรเซสเซอร์รองรับ เช่น การจำลองเสมือนสำหรับฮาร์ดแวร์ ขนาดแคช หรือคำสั่ง SIMD สามารถรับประกันได้ว่าระบบจะทำงานได้อย่างมีประสิทธิภาพ และโค้ดที่คุณเขียนนั้นเข้ากันได้กับ CPU ที่แตกต่างกัน ด้วยการใช้คำสั่ง CPUID คุณสามารถปรับพฤติกรรมเคอร์เนลของคุณแบบไดนามิกตามโปรเซสเซอร์เฉพาะที่เคอร์เนลทำงานอยู่
ในบทความนี้ คุณจะได้เรียนรู้วิธีตรวจสอบว่าคำสั่ง CPUID พร้อมใช้งานสำหรับระบบของคุณหรือไม่ รวมถึงวิธีการทำงาน และข้อมูลใดบ้างที่คุณจะได้รับจากการใช้งาน
ข้อกำหนดเบื้องต้น
-
ความรู้บางอย่างเกี่ยวกับภาษาแอสเซมบลี (สำหรับตัวอย่างนี้ ฉันใช้ FASM)
-
ความรู้บางประการเกี่ยวกับระบบปฏิบัติการ/เคอร์เนล
-
สิทธิ์เข้าถึงเครื่องมือแก้ไขข้อบกพร่องระดับต่ำ (เช่น GDB) หรือโปรแกรมจำลองฮาร์ดแวร์ เช่น QEMU เพื่อทดสอบโปรแกรมโหลดบูต/เคอร์เนลบนแพลตฟอร์มต่างๆ
ขั้นตอนที่ 1:ตรวจสอบความพร้อมใช้งานของ CPUID
ก่อนที่จะดำเนินการคำสั่ง CPUID สิ่งสำคัญคือต้องตรวจสอบว่าโปรเซสเซอร์รองรับหรือไม่ เนื่องจาก CPU ไม่รับประกันว่าจะมีฟังก์ชันการทำงานนี้ทั้งหมด รหัสต่อไปนี้ตรวจสอบความพร้อมใช้งานของคำสั่ง CPUID โดยการแก้ไขและทดสอบบิต ID (บิต 21) ในการลงทะเบียน EFLAGS
นี่คือรูปภาพจาก wiki.osdev.org ที่แสดงการลงทะเบียน EFLAGS แต่ละบิต:

หากโปรเซสเซอร์อนุญาตให้สลับบิตนี้ได้ CPUID ก็ได้รับการสนับสนุน ไม่อย่างนั้นมันก็ไม่ใช่ ต่อไปนี้เป็นวิธีการทำงานของกระบวนการตรวจจับ:
(คนส่วนใหญ่คิดว่าในโหมด Real 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
09รหัส> :บันทึกการลงทะเบียนวัตถุประสงค์ทั่วไปทั้งหมดเพื่อให้แน่ใจว่าสามารถเรียกคืนสถานะดั้งเดิมได้เมื่อสิ้นสุด
19รหัส> :บันทึกการลงทะเบียน EFLAGS ปัจจุบัน
26รหัส> :เก็บสำเนาของ EFLAGS
33รหัส> :โค้ดจะพลิกบิต ID (21) ของ EFLAGS โดยใช้ตัวดำเนินการ XOR
49รหัส> :เรียกคืน EFLAGS ที่แก้ไขโดยกลับบิต ID
53รหัส> :ดัน EFLAGS ที่แก้ไขแล้วกลับไปที่สแต็ก
65รหัส> :ใส่ EFLAGS ที่แก้ไขแล้ว (บิต ID อาจจะกลับด้านหรือไม่ก็ได้) ในรีจิสเตอร์ EAX
78รหัส> :หลังจากการดำเนินการ XOR EAX จะมีบิตที่มีการเปลี่ยนแปลง
85รหัส> :คืนค่า EFLAGS ดั้งเดิม
98รหัส> :105รหัส> การดำเนินการแยกบิตที่ 21 (บิต ID) โดยการปกปิดบิตอื่นๆ ทั้งหมด หลังจากการดำเนินการนี้ รีจิสเตอร์ EAX จะมี 0x00200000 (หากมีการเปลี่ยนแปลง 21 บิต ซึ่งหมายความว่ารองรับ CPUID) หรือ 0×00 (ไม่มีการเปลี่ยนแปลง 21 บิต ไม่รองรับ CPUID)
116รหัส> :คำสั่ง CMP จะตรวจสอบผลลัพธ์ของการทำงานก่อนหน้านี้ หาก EAX เท่ากับ 0×00 หมายความว่าบิต ID ไม่สามารถแก้ไขได้ และโปรเซสเซอร์ไม่รองรับคำสั่ง CPUID หากไม่เป็นศูนย์ แสดงว่าบิต 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 ด้านบน:
-
121รหัส> :ชุดคำสั่งการเข้ารหัสเพื่อการเข้ารหัสและถอดรหัสที่รวดเร็ว -
139รหัส> :การสนับสนุนการจำลองเสมือนสำหรับการรันเครื่องเสมือน -
148รหัส> :ชุดคำสั่ง SIMD เพื่อการประมวลผลมัลติมีเดีย คณิตศาสตร์ และเวกเตอร์ที่รวดเร็วยิ่งขึ้น -
157รหัส> :Fused Multiply-Add ปรับปรุงประสิทธิภาพในการคำนวณจุดลอยตัว -
161รหัส> :เครื่องกำเนิดตัวเลขสุ่ม -
170รหัส> :การจัดการการขัดจังหวะขั้นสูงในระบบมัลติโปรเซสเซอร์ -
184รหัส> :ปรับการจัดการหน่วยความจำให้เหมาะสมในระหว่างการสลับบริบท -
199รหัส> :หน่วยจุดลอยตัวของฮาร์ดแวร์เพื่อการดำเนินการทางคณิตศาสตร์ที่รวดเร็วยิ่งขึ้น -
203รหัส> :ส่วนขยายที่อยู่ทางกายภาพ อนุญาตให้ระบุที่อยู่หน่วยความจำมากกว่า 4 GB -
214รหัส> :อนุญาตให้แกน CPU ตัวเดียวจัดการหลายเธรดได้ -
224รหัส> :คุณสมบัติการจัดการหน่วยความจำสำหรับการควบคุมแคชและการแมปหน้า -
236รหัส> :ชุดคำสั่ง SIMD รุ่นเก่าสำหรับการประมวลผลมัลติมีเดีย
รับสตริงผู้จำหน่าย CPU
หากคุณต้องการรับสตริงผู้จำหน่าย CPU EAX ควรตั้งค่าเป็น 0×0 ก่อนที่จะเรียกใช้คำสั่ง CPUID
mov eax, 0x0
cpuid
สตริงผู้จำหน่ายคือตัวระบุเฉพาะที่ผู้จำหน่าย CPU เช่น AMD และ Intel ใช้ ตัวอย่างได้แก่:GenuineIntel (สำหรับโปรเซสเซอร์ Intel) หรือ AuthenticAMD (สำหรับโปรเซสเซอร์ AMD) โดยพื้นฐานแล้วจะระบุผู้ผลิต CPU
สตริงผู้ขายอนุญาตให้เคอร์เนลระบุผู้ผลิต CPU ซึ่งมีประโยชน์มากเนื่องจากผู้ผลิตแต่ละรายใช้คุณสมบัติบางอย่างแตกต่างกัน นอกจากนี้ ซอฟต์แวร์หรือไดรเวอร์สามารถโต้ตอบที่แตกต่างกันไปตามผู้ผลิต CPU เพื่อให้มั่นใจถึงความเข้ากันได้
เมื่อใช้เช่นนี้ สตริงรหัสผู้ขายจะถูกส่งกลับในการลงทะเบียน EBX, EDX, ECX คุณสามารถเขียนลงในบัฟเฟอร์และรับสตริงอักขระเต็ม 12 ตัว
รหัสตัวอย่าง:
ขั้นตอนที่ 1:บัฟเฟอร์
สร้างบัฟเฟอร์ที่สามารถเก็บได้ 12 ไบต์:
buffer: db 12 dup(0), 0xA, 0xD, 0
ขั้นตอนที่ 2:พิมพ์บัฟเฟอร์
เราเริ่มต้นด้วยการสร้างฟังก์ชันการพิมพ์สตริง
รหัสแอสเซมบลีนี้จะอ่านอักขระสตริงทีละอักขระและพิมพ์ไปที่หน้าจอโดยใช้ BIOS ขัดจังหวะ 0x10 247รหัส> ฟังก์ชันวนซ้ำสตริงและใช้ 259 คำแนะนำในการโหลดอักขระแต่ละตัวใน 263 ลงทะเบียน
จากนั้น 274 ฟังก์ชั่นใช้อินเทอร์รัปต์ 0×10 เพื่อพิมพ์บนหน้าจอ เมื่อโค้ดถึงจุดสิ้นสุดของสตริง (ตัวยุติค่า null) การวนซ้ำจะสิ้นสุด
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:เติมบัฟเฟอร์แล้วพิมพ์
ที่นี่ หลังจากบันทึกสถานะปัจจุบันโดยใช้ 286 คำแนะนำและการโทร 297 เมื่อผ่าน 0×0 ในการลงทะเบียน EAX เราสามารถจัดเก็บเนื้อหาของ 306 , 312รหัส> , 325รหัส> ไปที่บัฟเฟอร์ จากนั้นเราโทรไปที่ 339 เพื่อพิมพ์
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 ของฉันที่ฉันใช้งานและอธิบายโค้ดด้านบนโดยละเอียด
ป>
ข้อมูลเพิ่มเติมเกี่ยวกับข้อมูลที่คำสั่ง CPUID สามารถให้คุณตามค่าที่ส่งในการลงทะเบียน EAX สามารถพบได้ที่นี่:https://gitlab.com/x86-cpuid.org/x86-cpuid-db
บทส่งท้าย
ด้วยการทำความเข้าใจและการใช้คำสั่ง CPUID คุณสามารถทำให้บูตโหลดเดอร์/เคอร์เนลของคุณสามารถปรับให้เข้ากับโปรเซสเซอร์ที่หลากหลายได้มากขึ้น การรู้วิธีตรวจจับความพร้อมใช้งานของคำสั่งและดึงข้อมูลระบบที่สำคัญ เช่น คุณลักษณะของ CPU ขนาดแคช และเทคโนโลยีที่รองรับ สามารถเพิ่มประสิทธิภาพและความเข้ากันได้ได้อย่างมาก
หลังจากอ่านบทความนี้ คุณควรมีเครื่องมือและความรู้เพื่อเริ่มสำรวจคำสั่ง CPUID และวิธีใช้คำสั่งดังกล่าวในโปรเจ็กต์ของคุณเอง!
ขอให้สนุกกับการเขียนโค้ด!
เรียนรู้การเขียนโค้ดฟรี หลักสูตรโอเพ่นซอร์สของ freeCodeCamp ช่วยให้ผู้คนมากกว่า 40,000 คนได้งานในตำแหน่งนักพัฒนา เริ่มต้น