在 CPUID 指令 (Pentium 才引入) 普及之前,軟體必須靠「副作用差異」來辨識 CPU 型號:旗標暫存器保留位行為、指令預取佇列長度、移位指令計數遮罩、特權指令可用性等。這份由 Richard C. Leinecker 撰寫的經典程式碼 (發表於 Dr. Dobb's Journal) 系統性實作了從 8088/8086 到 80486 的偵測邏輯,並附贈 Pentium 偵測補充 (LISTING TWO) 及記憶體對齊效能測試 (LISTING THREE)。程式碼結構模組化,每個偵測步驟獨立成 PROC,極具教學與參考價值。
主程式 _Processor 依序呼叫:
IsItAn8088:區分 8088/8086 (回傳 0) 與 286 以上 (回傳 2)IsItAn8086:若為 8088/8086 組,進一步區分 8088 (0) vs 8086 (1)IsItA286:若 ≥286,測試旗標 NT/IOPL 位 (bits 12-14) 是否可設定,區分 286 (2) 與 386+ (3→4)IsItA386:若 ≥386,測試 EFLAGS AC 位 (bit 18) 可否設定,區分 386 (3) 與 486 (4)Pentium (LISTING TWO):嘗試切換 EFLAGS ID 位 (bit 21),成功則為 Pentium+,再用 CPUID 取 Family 欄位。IsItAn8088 利用 8088/86 的 FLAGS 高 4 位 (bits 12-15) 永遠為 1 的特性:
pushf
pop bx
and bx, 00fffh ; 清除高 4 位
push bx
popf ; 嘗試寫入 FLAGS
pushf
pop bx
and bx, 0f000h ; 讀回高 4 位
cmp bx, 0f000h ; 若仍為 1 → 8088/86
je Not286
mov al, 2 ; 否則 ≥286
286+ 將這些位定義為 NT/IOPL/RF/VM,可被修改;8088/86 硬體強制為 1。
IsItAn8086 利用 8088 (4 bytes) vs 8086 (6 bytes) 預取佇列差異:
mov di, OFFSET EndLabel
mov al, 90h ; NOP 機器碼
mov cx, 3
rep stosb ; 向後寫入 3 個 NOP (修改 EndLabel 前 5 位元組)
cld
nop ; 3 個 NOP 填充
nop
nop
dec dx ; 只有 8088 會執行到這裡 (佇列未刷新)
nop ; 第 6 位元組
EndLabel: nop
若 CPU 為 8088,預取佇列僅 4 位元組,dec dx 指令已被預取進佇列,自我修改的 NOP 不生效,DX 保持 1;8086 佇列 6 位元組,修改生效,dec dx 執行,DX 變 0。極具巧思的「自我修改程式碼」偵測法。
IsItA286 嘗試將 7000h (bits 12-14) 寫入 FLAGS:
mov ax, 7000h
push ax
popf ; 載入 FLAGS
pushf
pop ax
and ah, 70h ; 檢查 bits 12-14 是否保留
jz YesItIsA286 ; 清零 → 286
inc ax ; 置位 → 386+ (回傳 4)
286 的 FLAGS 只有低 16 位,高位寫入會被忽略;386 擴展為 32 位 EFLAGS,保留這些位。
IsItA386 需 .386 指令集支援,操作 EFLAGS bit 18 (Alignment Check):
.386
pushfd
pop eax
or eax, 40000h ; 設定 AC 位
push eax
popfd
pushfd
pop eax
test eax, 40000h ; 若保留 → 486
jz YesItIsA386
add di, 2 ; 回傳 4 (486)
386 無 AC 位,寫入無效;486 實作了對齊檢查機制,該位可讀寫。注意 .8086 切回指令集以相容連結器。
LISTING TWO 偵測 Pentium:
pushfd
pop eax
mov ebx, eax
xor eax, 200000h ; 切換 ID 位 (bit 21)
push eax
popfd
pushfd
pop eax
push ebx
popfd ; 恢復原旗標
and eax, 200000h
and ebx, 200000h
cmp eax, ebx
je Is486 ; 無法切換 → 486
mov eax, 1
cpuid ; 取得 Family (bits 8-11)
shr eax, 8
and ax, 0Fh ; 5 = Pentium
附贈 C 內嵌組合語言測試:比較 LOOP 指令在 16-byte 對齊 vs 非對齊位址的執行速度。486+ 對齊存取顯著更快,展示指令預取與快取行對齊的效能影響。
_PTEXT SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:_PTEXT, DS:_PTEXT
public _Processor
_Processor proc far
push bp
mov bp,sp
push ds
push di
mov ax,cs
mov ds,ax
call IsItAn8088
cmp al,2
jge AtLeast286
call IsItAn8086
jmp short ExitProcessor
AtLeast286:
call IsItA286
cmp al,3
jl short ExitProcessor
AtLeast386:
call IsItA386
ExitProcessor:
pop di
pop ds
pop bp
ret
_Processor endp
... (四個偵測 PROC 完整實作) ...
_PTEXT ENDS
END
這套演算法曾廣泛用於:
現代 x86 皆支援 CPUID,偵測邏輯已標準化 (葉功能 01h 取得 Family/Model/Stepping/Features)。但理解這些「副作用偵測」技巧,對於:
依然具備核心價值。Leinecker 的程式碼以清晰的模組化、詳細的註解、嚴謹的暫存器保存,堪稱系統程式設計的典範之作。