前回、DIレジスタをベースに使うメモリアクセスではDSがデフォルトになる件練習。しかしDIレジスタは一部命令でESと「不可分に結びついている」のです。x86式には「ストリング転送命令」、一般には「ブロック転送命令」においてです。今回はその代表 MOVS 命令について見ていきたいと思います。コマケー話の宝庫なのよ。
※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら
※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。
ストリング命令群
x86には、ストリング命令と称する命令群があります。8086の前の時代、インテル8080、8085にはブロック転送命令が無く、一方ザイログZ80にはあったです。Z80が「受けた」理由の一つじゃないかと思います。そこでインテルも8086では同様な命令を導入したのだと思うけれども同じような名前にするのが嫌だったのか?今となってはこういう命令は百害あって一利なしとか言われそうなんだが。まあ、80286くらいまではDMA転送の代わりにディスク・ブロックをまとめて読み込むとかに重宝されていた遠い記憶。
-
- MOVS
- CMPS
- STOS
- LODS
- INS
- OUTS
確かにストリング(文字列)を操作するのにも使えますが、ぶっちゃけ「ブロック転送」系の命令群です。あるメモリの塊をまとめて移動したりできるもの。今回は、その代表としてMOVS命令についてその動作を復習してみたいと思います。
オペコード 0xA4、0xA5を占めているのがMOVS命令です。2個あるのは0xA4がバイト幅の操作、0xA5がワード幅の操作ってこってす。
しかし、下の方をみると、REP、CLD、STDなどという命令にも赤枠があります。こいつらはブロック転送やるときに無くてはならないものどもなのです。
-
- MOVSは単体命令では転送1回のみ
- REPプリフィックスをMOVSに冠すると複数回の転送が起こる
- 転送に使うポインタSI、DIの更新方向はDF(ディレクション・フラグ)で制御される。DFを操作するのがCLDとSTD
MOVSはストリング命令といいつつも、単体では転送一回です。転送はデフォルトのデータ・セグメントのSIで指されるアドレスから、ES:DIで指されるアドレスに対してです。そして転送実行後、DFが0の場合、SIとDIの値は転送がバイトなら+1、ワード(x86では16ビットです)なら+2加算されます。DFが1の場合は反対方向に1もしくは2減じられます。よって転送のアドレス順序が問題になる場合(転送元と転送先の領域に重なりがある場合)は事前にCLDもしくはSTD命令でポインタの更新方向を決定しておく必要があります。
なお、SIの指すメモリはデフォルトセグメントがDSですが、オーバライド・プリフィックスにより上書き可能です。一方、DIの方はES固定でオーバライドは不可です。しかし、後に述べる理由でオーバライドプリフィックスを使うのはあまりお勧めできないデス。
さてMOVSの頭にREPプリフィックスをつけると複数回の転送が起こりますが、これは、「CXレジスタが0でない間、MOVS命令を繰り返し実行し、毎回の実行後にCXレジスタを1減じる」という操作により実現されます。ここでもx86(16ビット)の汎用じゃないレジスタの使用を見ることになります。CXは「カウンタ」レジスタなのよね。
CXは16ビットなので最大値は65535です。ここから容易に分かるのが
REP MOVSの実行時間、とんでもなく長くなる可能性あり
ということです。通常、割り込みは命令の末尾で受け付けるものです。しかしREP MOVSのような命令の最中ずっと割り込みを受け付けないと割り込み使うシステムではわけわからなくなるので、REP MOVSは途中で割り込みを受付可能となってます。また、目出度く割り込み処理を完了したのち、中断されているREP MOVSをレジュームしてやらないとこんどはブロック転送の辻褄があわなくなります。REP MOVSは、このレジューム処理にも対応しとります。しかし、復旧には条件があるみたいです。セグメント・オーバライド・プリフィックスやLOCKプリフィックスなど複数個のプリフィックスをREP MOVSにつけていても、複数個のプリフィックス全部を復旧できるわけじゃない、という制限です。まあこういう命令にLOCKつけるとか言語道断な気もしますが(バスサイクルを長期占有してしまうので。)素直に素のREP MOVS として使っている方が安全ね。
即値MOV命令のアセンブリ言語ソース
MOVS命令1個を動かしてみてみるだけのソースです。ワード幅転送で8ワードのこじんまりした転送例です。
segment code ..start: mov ax, data mov ds, ax mov ax, stack mov ss, ax mov sp, stacktop mov ax, edata mov es, ax test: mov si, srcblk mov di, dstblk mov cx, 8 cld rep movsw fin: mov ax, 0x4c00 int 0x21 resb 2048 segment data align=16 srcblk: dw 8 dup (0x1234) resb 1024 * 63 segment edata align=16 dstblk: dw 8 dup (0) resb 1024 * 63 segment stack class=STACK resb 2048 stacktop:
転送元の DS:srcblkには、0x1234という値が詰めてあり、転送先のES:dstblkは0詰めです。
アセンブルして実行
さて、FreeDOS上、以下のステップで上記アセンブラソース movs.asm から実行可能なオブジェクトファイルを得ることができます(nasmとwatcom Cがインストール済であること。)
nasm -f obj movs.asm wlink name movs.exe format dos file movs.obj
実行は例によって御本家 MS-DOSのdebugとクリソツな debugです。
debug movs.exe
上記赤矢印のREP MOVSW(ワード幅指定)の直前のレジスタ状態が以下に。
赤線が注目すべきところです。
そして、1命令 REP MOVS を実行した直後のレジスタ状態が以下に。
CXは0になり、SI、DIは0x10までオートインクリメントされとります。
ブロック転送されとるなあ。あたりまえか。