DOSコールに慣れる

アセンブラを使ってプログラムを開発することができるようになると、
BASICやCと比べて高速処理ができるというのが目立つため、どうしても
シューティングゲームやアクション、画像(動画)処理などに興味が惹かれるものですが、
ここでは、敢えて地味なテーマを扱うことにします。

プログラミングを愉しむ目的は、コンピュータを思い通りに駆動することに尽きると思うのですが、
そうは言っても、I/Oからプロセス管理まで個人ユーザが用意するのは
(いつかやってみたいと思うことは度々ありますが)、知識、技術、時間の面で限度があります。

X68000というコンピュータシステムを思いっきり愉しむためには、せっかく用意されている
システムプログラムを利用するのが早道で現実的な選択ではないかと思います。
ただし、その引き換えに、OSの流儀に従ったプログラミングをする必要があります。

今回は、X68000のオペレーティングシステムであるHuman68kが提供するDOSコールという
サービスについて、復習してみます。

DOSコールの仕組み

前回のHello,world.表示プログラムでは、_PRINTと_EXITというDOSコールを使いました。
doscall.macを読むと、それぞれに$FF09, $FF00という番号が定義されており、
DOSマクロで.dc.wによってその値がプログラムコードに埋め込まれています。

この$FFxxというプログラムコードは、本来は68000CPUが解釈できる命令ではなく、
この命令を見つけるとCPUは「例外処理」という動作を始めます。
細かい仕組みは専門書に譲るとして、とにかく、例外処理の中では$FFxxのxxごとに分岐して、
_PRINTや_EXITなどのOSが定義する処理を行ってくれます。

また、DOSコールの流儀として、処理に必要なパラメータなどはスタックにプッシュして渡す、
という決まりがあります。
前回のサンプルでは、_PRINT処理で出力したい文字列のアドレスをプッシュしました。

またDOSコールが終わったら、プッシュしたサイズ分、スタックを補正する必要がありますが、
・8バイト以下の場合は、 addq.l #サイズ,sp
・それ以上の場合は、 lea.l サイズ(sp),sp
として使い分けるのが良いです。理由は、68000アセンブラに慣れてきたらわかります。

DOSコールの種類

DOSコール使いこなすためには、まずはどのようなサービスが提供されているのか、
ざっと見ておくと良いので、何かに使いそうなものを羅列してみました。

文字の出力 PUTCHAR 標準出力へ文字を1文字表示する
PRINT NULL文字(0)で終わる文字列を表示する
CONCTRL カーソルの移動や表示、非表示
文字入力 GETCHAR 文字を1文字入力する(Ctrl+CでプログラムEXITできる)
GETC 文字を1文字入力する
INKEY 文字を1文字入力する(Ctrl+Cチェック回避)
GETS リターンキーを押すまでの文字列を入力する
GETSS リターンキーを押すまでの文字列を入力する(Ctrl+Cチェック回避)
ファイル操作 CREATE ファイル新規作成してオープン
OPEN 既存ファイルをオープンする
CLOSE ファイルをクローズする
FGETC ファイルから1文字入力
FPUTC ファイルに1文字出力
FGETS ファイルから改行までの1行入力
READ ファイルから指定サイズ読み込む
WRITE ファイルに指定サイズ書き込む
SEEK ファイルポインタを移動
DELETE ファイル削除
RENAME ファイル名の変更、移動
ディスクドライブ操作 CHGDRV カレントドライブを変える
CURDRV カレントドライブを調べる
DRVCTRL ドライブのイジェクト管理や状態の参照、設定
DISKRED ディスクのセクタから直接読み込む
DISKWRT ディスクのセクタへ直接書き込む
ディレクトリ操作 MKDIR ディレクトリ作成
RMDIR ディレクトリ削除
CHDIR カレントディレクトリ変更
プロセス管理 EXIT プログラムを終了する
SUPER スーパーバイザーモード、ユーザモードの切り替え
KEEPPR プログラムの常駐終了
EXEC 他のプログラムをロードして実行
割り込み管理 INTVCS 割り込みベクタの設定
INTVCG 割り込みベクタの参照
メモリ管理 MALLOC メモリを確保
MFREE メモリを解放
SETBLOCK メモリブロックの変更
日付と時刻 GETDATE 日付の取得
GETTIME 時刻の取得
SETDATE 日付の設定
SETTIME 時刻の設定

数が多いので、管理人があまり使ったことがないものは省略しました。
必要なものは、プログラムを作るとき目的をはっきりさせてから改めて調べればよいと思います。

DOSコールを使ったサンプル

改めて復習してみると、DOSコールというのは市役所のサービスみたいです。
必要があれば使うし、そうでなければそのサービスがあることすら知らない、
使うにしても、別に面白いことは何も期待していない、という具合です。

従って、DOSコールを使ったサンプルを作ると言っても、何か具体的な目的がなければ、
大概つまらない内容になりそうです。

とりあえず今回はDOSコールに慣れることが目的ですので、いくつかのサービスを組み合わせた
プログラムを書いてみて、その呼び出し手順や戻り値の扱いについて、慣れておくことで善しとします。

題材は、いわゆる対話型の入出力処理で、C言語などでもHello,worldの次に出てくるような、
どうにも役に立たないものになります。以下のような流れになります。
1. 名前を聞いて入力
2. 用件を選択
3. 入力内容を確認
4. ファイルに書きだす
5. おしまい

ざっくりと全体像を作る

プログラミングの段取りは人それぞれですが、この程度の規模のプログラムを作る場合は、
管理人はまず、動作確認ができる程度の骨組みを作り、仕様上の必要事項を書いてから、
中身のコーディングを進めます。

	.include doscall.mac

	.offset	0
Order:	.ds.w	1
FileHandle	.ds.w	1
GetsPtr:.ds.b	1
	.ds.b	1
Name:	.ds.b	64+1
	.even
	.ds.l	256
myStack:

	.text
	.even
START:
	lea	WORKAREA(pc),a6
	lea	myStack(a6),sp
MAIN:
	pea	TITLE(pc)
	DOS	_PRINT
	addq.l	#4,sp
	* 1. 名前を聞いて入力
	* 2. 用件を選択
	* 3. 入力内容を確認
	* 4. ファイルに書き出す

	* 5. おしまい
	pea	THANKYOU(pc)
	DOS	_PRINT
	addq.l	#4,sp
	DOS	_EXIT

	.data
TITLE:		.dc.b	'いらっしゃいませ。',$d,$a,'こちらは受け付けプログラムでございます。',$d,$a,0
THANKYOU:	.dc.b	'ありがとうございました。',$d,$a,0

	.bss
	.even
WORKAREA:

ちょっと長くなりましたが、この段階だとHello,world.プログラムとほとんど同じです。
最初にワークエリアのポインタをa6に求め、スタックポインタも設定している点が違いますが、
これも決まり文句みたいなものなので、まだあまり気にしなくていいです。

これを、sample.sなど適当なソースファイルに書いて、アセンブルしてみます。
動作結果は、予想できますね?

文字列を入力する

ここで、今回最初に導入するDOSコールとして、GETSを取り上げます。
上にも書きましたが、このDOSコールはリターンキーを押すまでの文字列を入力するものです。使い方は、

	* 1. 名前を聞いて入力
	bsr	GET_NAME
	(中略)
	DOS	_EXIT

* 名前を入力
GET_NAME:
	pea	name_prompt(pc)
	DOS	_PRINT
	addq.l	#4,sp
	move.b	#64,GetsPtr(a6)		* 入力可能文字数
	pea	GetsPtr(a6)
	DOS	_GETS
	addq.l	#4,sp
	move.w	#$a,-(sp)		* 改行
	DOS	_PUTCHAR
	addq.l	#2,sp
	rts
name_prompt:	.dc.b	'お名前を入力してください>',0
	.even

	.data

ここでGETSの使い方の前に、サブルーチンコールの説明が必要でした。
アセンブラのプログラムは、基本的にデータやメモリ操作でまどろっこしいため、
このような少し大きな処理はメインプログラム内に書かず、サブルーチン化します。

解説するまでもないかもしれませんが、呼び出したい場所で 「bsr ラベル」、
サブルーチン処理が終わったら「rts」で復帰します。

GETSの使い方は、上に示したように、
・1バイト目に入力可能文字数
・2バイト目は入力した文字が返ってくる場所
・3バイト目からが実際の文字列
という具合のデータ構造体のアドレスをプッシュして呼び出します。

文字列を適当に入力してリターンキーを押すと、現段階ではデバッガを使わないと見えませんが、
3バイト目からはNULL文字で終わる改行コードを含まない文字列が格納されます。
ただし、入力していた画面のカーソル位置は復帰処理が行われます
改行はされませんので次の表示に備えてサブルーチン内でPUTCHARを使って処理しておきます。

規定の文字だけ受け付ける入力

次に使うのは、1文字入力するGETCです。これは非常に簡単に使うことができます。
スタックに何も積む必要はなく、単に「DOS _GETCHAR」でキーボードから1文字打たれるのを待ち、
その文字コードをd0レジスタに返してきます。GETCHARと違って、入力した文字は表示されません。

この処理ではむしろ、入力された文字が適正であるかどうかのチェックが課題です。
そのためには、いろいろなマシン語の命令を駆使することになります。

	* 2. 用件を選択
	bsr	GET_ORDER
	move.w	d0,Order(a6)
	tst	d0
	bmi	MAIN

まず、先ほどと同様、メインルーチン無いにあまり長い処理を入れたくないため、
後で定義するGET_ORDERサブルーチンを呼び出します。
このサブルーチンは、戻り値としてd0に選択した用件番号(内部コード)を入れて返すつもりでいて、
ESCキーなら-1が返ってきて中断し、名前入力からくるつもりの設計です。

	(先ほどのGET_NAMEの続きあたりから)
order_list:
	.dc.b	'1:住民票の発行',$d,$a
	.dc.b	'2:転入・転出届',$d,$a
	.dc.b	'3:公共料金関係',$d,$a
	.dc.b	'御用件を選んでください>',0
	.even

GET_ORDER:
	pea	order_list(pc)
	DOS	_PRINT			* メニュー一覧を表示
	addq.l	#4,sp
order_loop:
	DOS	_GETC			* 1文字入力
	cmp.b	#'1',d0
	beq	order_1
	cmp.b	#'2',d0
	beq	order_2
	cmp.b	#'3',d0
	beq	order_3
	cmp.b	#$1b,d0
	beq	order_escape
	bra	order_loop

ここではGETCを使って文字を入力し、'1','2','3',$1b(ESC)と比較(cmp)して、
一致しているならそれぞれの処理へ分岐(beq)挿せています。
それ以外の文字が押されたらorder_loopを繰り返します。

order_1:
	bsr	print_d0_CRLF
	moveq.l	#0,d0
	rts

order_2:
	bsr	print_d0_CRLF
	moveq.l	#1,d0
	rts

order_3:
	bsr	print_d0_CRLF
	moveq.l	#2,d0
	rts

order_escape:
	moveq.l	#'*',d0
	bsr	print_d0_CRLF
	moveq.l	#-1,d0
	rts

print_d0_CRLF:
	move.w	d0,-(sp)
	DOS	_PUTCHAR
	move.w	#$d,(sp)
	DOS	_PUTCHAR
	move.w	#$a,(sp)
	DOS	_PUTCHAR
	moveq.l	#2,d0
	rts

注意点として、押すキーの番号は1,2,3ですが、内部取扱番号は0,1,2にしています。
また、同じような処理が並んでいるので、うまくすればまとめられますが、わかりやすくするため、
冗長に書いています。もっと選択肢が増えてきた時は、良く考えることにしましょう。

内容の確認

ここは、これまでに使ったDOSコールのおさらいになります。

	* 3. 入力内容を確認
	bsr	CONFIRM
	tst	d0
	bmi	MAIN
	(中略)
CONFIRM:
	pea	Name(a6)
	DOS	_PRINT
	pea	goyouken(pc)
	DOS	_PRINT
	move.w	Order(a6),d0
	add.w	d0,d0
	move	youken_table(pc,d0.w),d0
	pea	youken_table(pc,d0.w)
	DOS	_PRINT
	pea	yorosii(pc)
	DOS	_PRINT
	lea	16(sp),sp		* スタックを積んだ分だけまとめて戻す
confirm_loop:
	DOS	_GETC			* 1文字入力
	ori.b	#32,d0			* 小文字化
	cmp.b	#'y',d0
	beq	confirm_yes
	cmp.b	#'n',d0
	beq	confirm_no
	bra	confirm_loop

confirm_yes:
	bsr	print_d0_CRLF
	moveq.l	#0,d0
	rts
confirm_no:
	bsr	print_d0_CRLF
	moveq.l	#-1,d0
	rts

youken_table:
	.dc.w	youken1-youken_table
	.dc.w	youken2-youken_table
	.dc.w	youken3-youken_table
goyouken:	.dc.b	'さんのご用件は、「',0
youken1:	.dc.b	'住民票のご請求',0
youken2:	.dc.b	'転入、転出届のご提出',0
youken3:	.dc.b	'公共料金に関するお問い合わせ',0
yorosii:	.dc.b	'」でよろしいですか(Y/N)?>',0
	.even

用件入力のところと同じような処理です。YかNを入力するときは、大文字小文字を
統一して扱うため、32でORをとって小文字化してから判定しました。
よくわからない人は、「ASCIIコード表」で検索して良く眺めてみてください。

また、youken_tableを使ったアドレッシングについては一見トリッキーですが、
68000プログラミングではたまに使う方法ですので参考にしてください。

ファイルに書き出す

名前と用件を伺ったのですが、このプログラムでは実際にサービスするつもりはないので、
とりあえずそれぞれのサービスごとにテキストファイルを作って、名前を記録することにします。

DOSコールの主役とも言えるファイル操作関連のファンクションコールを駆使しますが、
念のため、先に基本的な注意事項を解説しておきます。

Human68kに限らず、ファイルを扱うプログラムは一般に、
1.ファイル名を指定してファイルをオープンし、ファイルハンドルを得る
2.ファイルハンドルを指定して、オープンしているファイルに対して読み書き追記をする
3.処理が終わったら、ファイルをクローズする
という手順をとる、という約束があります。
赤字の用語は絶対に覚えておくべきでしょう。

OPEN_FILE:
	move.w	Order(a6),d0
	add.w	d0,d0
	move	file_table(pc,d0.w),d0
	lea	file_table(pc,d0.w),a1	* a1:ファイル名

	move.w	#2,-(sp)	* 読み書きモード
	pea	(a1)
	DOS	_OPEN
	addq.l	#6,sp
	tst.l	d0
	bpl	open_ok

	move.w	#$20,-(sp)	* 通常のファイル
	pea	(a1)
	DOS	_CREATE
	addq.l	#6,sp
	tst.l	d0
	bpl	open_ok

	pea	open_error(pc)
	DOS	_PRINT
	addq.l	#4,sp
	DOS	_EXIT

open_ok:
	move.w	d0,FileHandle(a6)

	move.w	#2,-(sp)	*ファイル末尾をアクセス
	clr.l	-(sp)
	move.w	FileHandle(a6),-(sp)
	DOS	_SEEK
	addq.l	#8,sp
	rts

file_table:
	.dc.w	file1-file_table
	.dc.w	file2-file_table
	.dc.w	file3-file_table
file1:	.dc.b	'住民票.txt',0
file2:	.dc.b	'転出入届.txt',0
file3:	.dc.b	'公共料金.txt',0
open_error:	.dc.b	'ファイルがオープンできませんでした。異常終了します。',$d,$a,0
	.even

ファイルをオープンするときは、基本はOPENですが、ファイルが存在しない時には、
CREATEを使って新規に作成することになります。
このプログラムでは、OPENの戻り値d0が負数のときに、改めてCREATEしています。
これが失敗するケースは、ディスクが書き込み禁止か、ファイル名が不正かわかりませんが、
続行しても無意味なのでプログラムを終了させています。

WRITE_LOG:
	move.w	FileHandle(a6),-(sp)
	pea	Name(a6)
	DOS	_FPUTS
	addq.l	#6,sp

	move.w	FileHandle(a6),-(sp)
	move.w	#$d,-(sp)
	DOS	_FPUTC
	move.w	#$a,(sp)
	DOS	_FPUTC
	addq.l	#4,sp

	move.w	FileHandle(a6),-(sp)
	DOS	_CLOSE
	addq.l	#2,sp
	rts

係ごとのファイルを開いたら、お客さんの名前をFPUTSで出力し、
引き続き改行コード2バイトをFPUTCで出力しています。
さらに、クローズを行いました、

C言語だったらprintf("%s\n",Name)が使いたい場面ですが、アセンブラを使うのでしたら
「これぐらいの処理は自分で作る」という気概で取り組みたいものです。

この節で作った2つの関数は、もうお分かりかと思いますが、
メインルーチンから以下のようにして呼び出して使います。

	* 4. ファイルに書き出す
	bsr	OPEN_FILE
	bsr	WRITE_LOG

動作確認

ちゃんと動作すれば、選んだ係別のファイルに、入力した名前が追記されていきます。

実用性はありませんけれども、DOSコールの基本的な使い方はばっちり復習できました。

また、今回はDOSコールに関連してスタック操作やポインタ操作、分岐判断など、
68000アセンブラプログラミングのいろいろなテクニックも出てきましたので、
いっぺんに覚えきれなくても、徐々に身につけていくと応用できる場面はたくさんあると思います。

長々としたページを最後まで読んでいただき、ありがとうございました。
コピペでも構いませんので、一度お手元の環境でプログラミングしてもらえるとうれしいです。


トップページへ

Copyright©2014 甘亀庵管理人

inserted by FC2 system