x86リアルモードでモニターを作ろうシリーズです。
まずshell機能を独立させるかMonitorに取り込むかという問題ですが、shellとして独立させることにしました。そのうえ常駐します。このため、Monitorとほぼ同様のメモリに展開されます。アプリケーションとは完全独立です。
shellはTiny Thin Shell、小さい最低限のシェルということでTSSという名称にしました。
TTS本体
TTSはline_input(1行入力)を行います。
次にパースしたトップの文字列をファイル名とみなしてMonitorに実行依頼をします。
大枠で言うとこれだけです。
start:
PUTC 'T'
PUTC ':'
; --- COMライク初期化(CS=DS=ES=SS=自分、SP設定) ---
; mov ax, 0x1000
; mov ds, ax
push cs
pop ds
push cs
pop es
push cs
pop ss
mov sp, 0xfffe
mov bx, ax
; --- メッセージ表示 ---
mov si, msg_hello
mov ah, svc_write ; write(DS:SI, CX)
int 0x80
mov ah, svc_newline
int 0x80
; 入力バッファの初期化
mov ax, line_buf
mov bl, 0
mov cx, line_buf_size
call memset
.loop1:
mov di, line_buf ; バッファの準備
call line_input ; 行入力
xor cx, cx ; 0文字なら何もしない
mov ax, line_buf
call strlen
cmp cx, 0
je .skip
; 入力文字列をパース
; DS:SI = 入力文字列
; ES:DI = THS 構造体先頭
; al = 区切り文字
mov si, line_buf
mov di, line_buf_ths
mov al, ' '
call split
; ファイル名候補を抽出
mov ax, file_name
mov bx, [line_buf_ths + THS.off + 0]
call strcpy
; 内部コマンドかどうかを判定
; 内部コマンドならそれを実行
; 現在は内部コマンドがないので未処理
; パースした最初がファイル名なのでこれを送る
mov si, [line_buf_ths]
mov ah, svc_exec
int 0x80
; エラー判定
cmp ax, 0
je .nf_skip2
push ds
mov ax, TTS_SEG
mov ds, ax
; エラーならエラーメッセージを表示
mov si, ._s_msg_nf
mov ah, svc_write
int 0x80
pop ds
.nf_skip2:
; バッファのクリア
mov ax, line_buf
mov bl, 0
mov cx, cx
call memset
.skip:
jmp .loop1
split処理
パース処理についてもちょっと触れておきますね。パースとか言ってますけど、内容はsplitです。入力された文字列を空白を区切り文字として複数の文字列に分解するアレです。データ構造はこんな感じ。
;===============================
; Token Handle Structure
;===============================
struc THS
.off resw 126 ; 02h オフセット
.cnt resw 1 ; 04h Item Num
endstruc
先頭にアドレス配列があり、アイテム数を持っていてこれで管理します。
splitのコードはこちら。
;************************************
; split処理
;************************************
; DS:SI = 入力文字列
; ES:DI = THS 構造体先頭
; al = 区切り文字
split:
xor cx, cx ; cnt = 0
lea bx, [di + THS.off] ; off 配列先頭
.skip:
cmp byte [si], al
jne .check
inc si
jmp .skip
.check:
cmp byte [si], 0
je .done
mov [bx], si ; offset 登録
add bx, 2
inc cx
.scan:
cmp byte [si], 0
je .next
cmp byte [si], al
je .cut
inc si
jmp .scan
.cut:
mov byte [si], 0
inc si
.next:
jmp .skip
.done:
mov [di + THS.cnt], cx
ret
.dlm db 0
83形式編集処理
最後は83形式に変換する関数です。
『APP.BIN』の形式を『APP BIN』の11文字にします。
;************************************
; ドット表記のファイル名を83形式にする
; IN : なし (file_name グローバル変数を直接使用)
; OUT: file_name が 83形式に変換される
;************************************
to_83:
push bp
mov bp, sp
push ax
push ds
push es
push si
push di
push bx
push cx
; === 入力文字列(file_name)をCSセグメント内にコピー ===
mov si, file_name ; DS:SI = file_name
push cs
pop es
mov di, .input_copy
.copy_in:
lodsb
stosb
test al, al
jnz .copy_in
; === DS/ESをCSに設定してパース実行 ===
mov ax, cs
mov ds, ax
mov es, ax
mov si, .input_copy
mov di, file_name_ths
mov al, '.'
call split
; base/ext 取得
mov si, [di + THS.off]
mov [.base], si
mov si, [di + THS.off + 2]
mov [.ext], si
; === temp_bufを空白で初期化 ===
mov di, .temp_buf
mov al, ' '
mov cx, 11
rep stosb
mov byte [di], 0 ; NUL終端
; === NAME部分コピー (最大8文字) ===
mov si, [.base]
mov di, .temp_buf
mov cx, 8
.name_loop:
cmp byte [si], 0
je .name_done
movsb
loop .name_loop
.name_done:
; === EXT部分コピー (最大3文字) ===
mov si, [.ext]
test si, si ; NULLチェック
jz .no_ext
cmp byte [si], 0 ; 空文字列チェック
je .no_ext
mov di, .temp_buf
add di, 8
mov cx, 3
.ext_loop:
cmp byte [si], 0
je .ext_done
movsb
loop .ext_loop
.ext_done:
.no_ext:
; === 結果をfile_name(呼び出し元セグメント)にコピー ===
pop cx ; スタックからレジスタ復元開始
pop bx
pop di
pop si
pop es
pop ds ; DS = 元のセグメント
pop ax
; ここでDS = 呼び出し元, ES = CSにする
mov di, file_name ; DS:DI = file_name
push cs
pop es ; ES = CSセグメント
mov si, .temp_buf ; ES:SI = temp_buf
mov cx, 12 ; 11文字 + NUL
.copy_out:
mov al, es:[si] ; CSセグメントから読む
mov [di], al ; 呼び出し元セグメントに書く
inc si
inc di
loop .copy_out
pop bp
ret
主要な関数は以上になります。
これらをうまく組み込んでTTSは成り立っています。
実行結果図

実行結果はこんな感じになります。
所感
セグメントが辛かったです。かつてセグメントが面倒臭いとか、厄介だとかいろいろ話は聞いていたのですが、思っていたより10倍も面倒くさかったなぁ。
特に文字列が複数セグメントに設置されているような場合、セグメント合わせに多大な時間がかかることがわかりました。
元日そうそう丸一日セグメントと格闘してしまいました。
はい、今日はここまでです。お読みくださりありがとうございました。
このソースはgithubで公開しています。よろしければどうぞ。
https://github.com/CbWB-Inc/software/tree/main/laboratory/lab02/hyfax-03-hello-tts
《2026/01/02 06:12:12》