在多工尚未普及的 DOS 年代,Terminate-and-Stay-Resident (TSR) 程式是實現「背景執行」的唯一手段。這份 ALARM.ASM 原始碼展示了一個功能完整的掛鐘/鬧鐘常駐程式:它攔截定時器中斷 (INT 1Ch),在螢幕右上角即時顯示系統時間,並支援命令列設定鬧鐘時間與開關顯示。程式碼涵蓋中斷向量鏈結、視訊記憶體直接操作、喇叭聲音產生、命令列解析等核心系統程式技術。
程式分為兩大區塊:
安裝時 (p150),程式先檢查 INT 62h 是否已有同款程式常駐(透過 whozat = 1234h 簽章),若是則只更新狀態/鬧鐘時間並退出;否則攔截 INT 1Ch,將原向量存入 jumpval,並以 int 27h 常駐結束。
ISR 進入時先檢查 wait 計數器(預設 18 ticks ≈ 1 秒),未到時間直接 jmp cs:[jumpval] 連接原中斷。到時間時:
cli ; 關中斷
mov ax, ss ; 保存原堆疊
mov holdss, ax
mov holdsp, sp
mov ax, ds
mov ss, ax ; 切換至內部堆疊
mov sp, offset estack
sti ; 開中斷,允許巢狀中斷
這段「堆疊切換」技巧確保 ISR 有獨立堆疊空間,避免破壞被中斷程式的堆疊,是 TSR 程式的標準作法。
呼叫 int 1Ah, AH=0 取得午夜起經過的 tick 數 (CX:DX),再乘以 16 並除以 mfactor (17478) 換算為分鐘數。後續以 60 為基數連續除法拆解出時、分、秒,並處理 12/24 小時制轉換 (AM/PM)。
程式偵測 ES:[410h] 設備位元組判斷螢幕類型:
mov ax, 0b000h ; 單色卡預設 B000h
cmp al, 30h ; 色色卡?
jz p061
mov ax, 0b800h ; 彩色卡 B800h
接著操作 6845 CRT 控制器狀態埠等待垂直回掃期 (test al,1),以 stosw 將屬性+字元一次寫入兩位元組,實現無閃爍更新。這段「輪詢狀態埠 + 直接寫顯存」是 DOS 時代繪圖/文字輸出的經典模式。
p120 子程式設定 8253 計數器 2 為方波產生器,頻率由 atone 決定;再操作 61h 埠 (PPI Port B) 第 0、1 位開啟喇叭閘門:
mov al, 182 ; 8253 控制字:Counter 2, Mode 3, LSB/MSB
out 43h, al
mov al, 0 ; 低位元組
out 42h, al
mov al, atone ; 高位元組決定音調
out 42h, al
in al, 61h
or al, 3 ; 開啟喇叭
out 61h, al
aleng 迴圈控制持續時間,最後恢復 61h 埠關閉喇叭。整段聲音邏輯無需任何外部驅動程式,純硬體操作。
p200 解析 PSP:80h 起的命令列,支援 ALARM hh:mm x 格式:
- 關閉顯示 (state = '-'): 前累積小時,乘 60 換算分鐘: 後忽略秒數atime (午夜起分鐘數)acount=5 (鳴響 5 秒)cseg segment para public 'code'
org 100h
alarm proc far
intaddr equ 1ch*4
segaddr equ 62h*4
mfactor equ 17478
whozat equ 1234h
color equ 14h
assume cs:cseg,ds:cseg,ss:nothing,es:nothing
jmp p150
jumpval dd 0
signature dw whozat
state db 0
wait dw 18
hour dw 0
atime dw 0ffffh
acount dw 0
atone db 5
aleng dw 8080h
dhours dw 0
db ':'
dmins dw 0
db ':'
dsecs dw 0
db '-'
ampm db 0
db 'm'
tstack db 16 dup('stack ')
estack db 0
holdsp dw 0
holdss dw 0
p000: ; ISR 入口
push ax
push ds
pushf
push cs
pop ds
mov ax,wait
dec ax
jz p010
mov wait,ax
jmp p080
p010: cli
mov ax,ss
mov holdss,ax
mov holdsp,sp
mov ax,ds
mov ss,ax
mov sp,offset estack
sti
push bx
push cx
push dx
push es
push si
push di
push bp
mov ax,18
mov wait,ax
mov al,state
cmp al,'-'
jnz p015
jmp p070
p015: mov ah,0
int 1ah
... (時間換算與顯示邏輯) ...
p150: ; 安裝入口
mov dx,offset copyr
call p220
mov ax,0
mov es,ax
mov di,segaddr+2
mov ax,es:[di]
mov es,ax
mov di,offset signature
mov cx,es:[di]
cmp cx,whozat
jnz p160
call p200
int 20h
p160: ... (攔截 INT 1Ch, 常駐) ...
int 27h
alarm endp
cseg ends
end alarm
ALARM.ASM 操作了 x86 即時模式下幾乎所有核心資源:中斷向量表、視訊記憶體、可程式定時器、PPI、PSP 結構、記憶體常駐機制。雖然現代作業系統以保護模式、驅動模型、排程器取代了這些手工操作,但理解這些底層機制對於:
仍具不可替代的教學價值。這份原始碼以清晰的標號、註解、模組化結構,堪稱 DOS 系統程式設計的教科書級範例。