-
Notifications
You must be signed in to change notification settings - Fork 2
TSR
常駐プログラムは、実行が終わったあともメモリに残り続けるプログラムです。 割り込みベクタに自分の処理を登録しておくと、他のプログラムが動いている間も そこから呼び出されます。FEP や常駐ユーティリティはこの仕組みで作られています。
やることは2つだけです。
-
割り込みベクタに自分のハンドラを登録する(
INT 21hAH=0x25) -
常駐して終了する(
INT 21hAH=0x31)
mov ax, 0x2560 ; AH=25h ベクタ設定 / AL=登録する割り込み番号
mov dx, handler ; DS:DX = ハンドラの位置
int 0x21
mov dx, resident_end ; 常駐させる最後のラベル
add dx, 15 ; パラグラフ単位へ切り上げる
mov cl, 4
shr dx, cl
mov ax, 0x3100 ; AH=31h 常駐終了
int 0x21.COM では DS = CS なので、DX にラベルのオフセットをそのまま入れれば
DS:DX がハンドラを指します。
- 常駐サイズはパラグラフ(16バイト)単位です。バイト数ではありません。
- オフセットは
CS:0起点なので、PSP の 256 バイトも自動的に含まれます。 ラベルのオフセットをそのまま使えば辻褄が合います。 -
ハンドラが呼ばれたとき、
DSは呼び出し元のものです。 自分のデータを触るなら ハンドラの中でDSを自分のセグメントに入れ直し、抜ける前に戻してください。
handler:
push ds
push cs
pop ds ; DS を自分のものにする
inc word [counter]
mov bx, [counter]
mov ax, 0x1234
pop ds ; 呼び出し元の DS に戻す
iret
counter:
dw 0
resident_end: ; ここまでが常駐するresident_end より後ろに書いたものは常駐しません。
常駐後に使うデータやテーブルは、必ずこのラベルより前に置いてください。
%include でテーブルを取り込むときは特に注意が必要です。ファイルの末尾に置くと
テーブルが常駐範囲の外に出ます。この間違いはインストール時にはエラーにならず、
別のプログラムを実行するまで表面化しません。
常駐した直後に呼んで動いても、それだけでは確認になりません。 他のプログラムを実行してから、もう一度呼んでください。 常駐範囲の外に出たコードは、次にプログラムが読み込まれたときに上書きされます。
実測では、正しいサイズで常駐させた場合、
- 常駐直後に呼ぶ → 動く
- もう一度呼ぶ → 動く(常駐データのカウンタも増える)
- 別のプログラムを実行してから呼ぶ → 動く
一方、常駐サイズを小さくしすぎた版では、呼んだ瞬間に Invalid Opcode になりました。
このときハンドラは1命令も実行されていません。
解除するには2つやります。
-
割り込みベクタを元へ戻す(
AH = 0x25) -
メモリを解放する(
AH = 0x49。ESに解放するセグメント)
そのためには、常駐する側が「元のベクタ」を解除する側から読める場所に 置いておく必要があります。
; 常駐する側: 先頭に目印と元のベクタを置く
jmp near start ; near を明示する(下記)
sig: db 'MYTSR!!!' ; 目印
old_off: dw 0 ; 元のベクタ
old_seg: dw 0
start:
...解除する側は、ベクタから常駐先のセグメントを求め、目印を確かめてから 元のベクタを書き戻し、メモリを解放します。
実測では、解除後に割り込みベクタが常駐前の値に戻り、メモリの解放も成功しました。
目印を確かめずに解放しないでください。 別のプログラムがベクタを 横取りしていた場合、無関係なメモリを解放してしまいます。
先頭に jmp start と書くと、アセンブラが2バイトの短いジャンプに縮めることがあります。
すると目印の位置が1バイトずれ、解除する側から見つけられなくなります。
実測でこれを踏みました。解除side は「見つからない」と表示し、常駐は残ったままでした。
jmp near start と書けば3バイトに固定されます。
決まった位置に情報を置く設計では、その前にあるものの長さが変わらないようにしてください。
- 常駐サイズにバイト数を入れる。 パラグラフ(16バイト)単位です。
-
常駐させたいデータを
resident_endより後ろに置く。 常駐しません。 インストール時はエラーにならないので気づきにくいです。 -
ハンドラの中で
DSを直さずに自分のデータを読む。 呼び出し元のDSが 入っているので、まったく別の場所を読みます。 - 常駐直後に1回呼べただけで動作確認とする。 他のプログラムを実行してから もう一度呼ぶまでは、サイズが正しいかどうか分かりません。
- 目印を確かめずにメモリを解放する。 別のプログラムがベクタを横取りしていると、 無関係なメモリを解放してしまいます。
-
先頭の
jmpにnearを書かない。 縮められて、後ろの目印の位置がずれます。 -
INT 21hをフックしたハンドラの中でint 21hを呼ぶ。 自分のハンドラに 再入します。退避しておいた元のベクタをpushf+call farで直接呼んでください (→ エスケープシーケンス)。
現時点で確認できているものはありません。 ただし 測定は np2kai 上で行っています。 実機での確認は行っていません。
- 常駐サイズが「ぎりぎり足りない」場合の挙動
- EMS / XMS を使って常駐領域を小さくする方法
- 常駐プログラム同士がベクタを取り合った場合の順序
検証: 2026-08-24 / WebNP2 (np2kai) + FreeDOS(98) /
再現: guest/probes/TSR60.ASM(常駐側), CALL60.ASM(呼び出し側), TSR60R.ASM + UNTSR.ASM(解除),
guest/probes/TSR60S.ASM(常駐サイズを小さくした故障注入版)
正常版が「常駐 → 他を実行 → 再使用」を通ること、故障注入版がその検査で
Invalid Opcode になることの両方を確認しています。
テキスト画面
キーボード
グラフィック画面
時間と割り込み
BIOS / DOS
ディスク
サウンド
メモリ