PCをレガシーブートさせてマシン語で遊んでみようというお話です。一応BIOSをメインにするつもりですが、どうなるかわかりません。
環境はAMD64+Windows11+VMware Workstation 17 Player+Debian 12.4です。
システム・タイマー・カウント・リード
BIOSの機能一覧とかは、例えばここなんかに詳しいんですけれど、色々と機能があって遊び甲斐がありそうです。文字の色を変えたりとか、ビデオモードを変えたりとか、目に見える形で遊べるものもたくさんあります。
とはいえ、そのあたりは皆さんの自由に任せるとして、今回はSystemTimerCountReadというBIOSコールを試してみようと思います。
このBIOSコールはとても単純な構造をしていて、引数とか全くなしで呼び出すだけです。
で、CF(キャリーフラグ)に成功か失敗か、ALにオーバーフローしたかしないか、CXとDXに起動してからのカウントが返ってきます。当然動かし続ければ32Bitだって超えちゃうんですが、そこはオーバーフローしたかどうかで大小判定するという仕様です。
見やすくするとこんな感じ。
CF:成功0
失敗 1
AH:0固定
AL:オーバーフローなし 0
オーバーフロー 1
CX:タイマカウントの上位2バイト
DX:タイマカウントの下位2バイト
たったこれだけです。呼び出して帰ってきた値を参照するだけです。
さて、コードはどんな感じになるかな?
はい。ドン。
;>===========================
;> BIOSで遊ぼっ!
;>===========================
section .data
_c_seg equ 0x07c0
_c_ex_area_addr equ 0x200
_c_seg equ 0x07c0
_c_ex_area_addr equ 0x200
section .text
boot:
; set segment register
mov ax, _c_seg
mov ds, ax
; disk read
mov ax, _c_seg
mov es, ax
mov bx, _c_ex_area_addr
mov ah, 0x02 ; Read Sectors From Drive
mov dl, 0x80 ; Drive
mov al, 0x20 ; Sectors To Read Count ;
mov ch, 0x00 ; Cylinder
mov cl, 0x02 ; Sector(starts from 1, not 0) ; set 2. becouse not need MBR
mov dh, 0x00 ; Head
int 0x13 ; Execute disk read
; ビデオモードの設定
mov ah, 0x0
mov al, 0x3 ; 16色テキスト、80x25
int 0x10
jmp main
;********************************
; ブートセクタ終端までゼロで埋める
;********************************
times 510-($-$$) db 0
;********************************
; ブートセクタシグネチャの書き込み
;********************************
db 0x55
db 0xAA
;>===========================
;> main
;>===========================
main:
; システムカウンタ
; BIOSコールの実行
mov ah, 0x00
int 0x01a
; 以下、結果の表示
jnc ._cf_nomal
; キャリーが立っていた場合
mov ah, 0x01
mov [._cf], ah
._cf_nomal:
mov [._of], al
; CFの表示
mov ax, ._s_hdr_cf
call disp_str
mov al, [._cf]
call disp_byte_hex
call disp_nl
; over fllow の表示
mov ax, ._s_hdr_of
call disp_str
mov al, [._of]
call disp_byte_hex
call disp_nl
; cxの表示
mov ax, ._s_hdr_cx
call disp_str
mov ax, cx
call disp_word_hex
call disp_nl
; dxの表示
mov ax, ._s_hdr_dx
call disp_str
mov ax, dx
call disp_word_hex
call disp_nl
call disp_nl
; 処理終了
call _hlt
._cf: db 0x00
._of: db 0x00
._s_hdr_cf: db ' CF : ', 0x00
._s_hdr_of: db ' over flow : ', 0x00
._s_hdr_cx: db ' cx : ', 0x00
._s_hdr_dx: db ' dx : ', 0x00
._s_crlf: db 0x0d, 0x0a, 0x00
;>===========================
;> サブルーチン
;>===========================
;********************************
; bin_nibble_hex
; 4bit整数を16進文字に変換する(下位4Bit)
; 0~15 -> '0'~'f'
; param : al : 変換する数値
; return : bl : 変換された文字
;******************************
bin_nibble_hex:
and al, 0x0f
cmp al, 0x09
ja .gt_9
add al, 0x30
jmp .cnv_end
.gt_9:
add al, 0x37
.cnv_end:
mov bl, al
ret
;********************************
; bin_byte_hex
; param : al : 変換したい数値
; return : bx : 変換した2文字の16進文字
;********************************
bin_byte_hex:
push cx
push dx
mov cl, al
sar al, 4
and al, 0x0f
mov ah, 0
call bin_nibble_hex
mov dh, bl
mov al, cl
and al, 0x0f
mov ah, 0
call bin_nibble_hex
mov dl, bl
mov bx, dx
pop dx
pop cx
ret
;********************************
; disp_byte_hex
; 1バイトの数値を16進で表示する
; param : al : 表示したい数値
;********************************
disp_byte_hex:
push ax
push bx
call bin_byte_hex
mov ah, 0x0e
mov al, bh
int 0x10
mov al, bl
int 0x10
pop bx
pop ax
ret
;********************************
; disp_word_hex
; 2バイト(1ワード)のデータを表示する
; param : ax : 表示するword
;********************************
disp_word_hex:
push ax
push bx
mov bx, ax
mov al, bh
call disp_byte_hex
mov al, bl
call disp_byte_hex
._end:
pop bx
pop ax
ret
;********************************
; disp_str
; display null-terminated string.
; param : ax : addr of mem where string is set.
;********************************
disp_str:
push ax
push si
mov si, ax
mov ah, 0x0E
._loop:
lodsb
or al, al
jz ._loop_end
int 0x10
jmp ._loop
._loop_end:
pop si
pop ax
ret
;****************************
; disp_nl
; 改行する
;****************************
disp_nl:
push ax
mov ax, _s_crlf
call disp_str
pop ax
ret
;>****************************
;> hlt
;>****************************
_hlt:
hlt
jmp _hlt
;==============================================================
; ファイル長の調整
;==============================================================
_padding:
times 0x100000-($-$$) db 0
PlayWithBIOS.3.1
なんと250行を超えてしまいました。実際の実行は61行目と62行目、この2行なんですが実行しただけでは何が起こったのかわからないので、結果を表示しています。その表示部分が200行とかあるわけです。いゃぁ、アセンブリ言語って楽しいですね♪
冗談はともかくとして、実行するとこんな感じになります。
ストップウォッチもどき
見出しでネタバレかもしれませんが、今回SystemTimerCountReadを題材に選んだのはこのためなんです。開始時のSystemTimerCountと終了時のSystemTimerCountをとれば、処理にどのくらいの時間がかかったかわかるじゃないですか。それで処理速度を比べてみたかったんですね。
なんの処理速度を比べたかったか。そうアレです。
cmp bx, 0 と
or bx, bx の処理速度です。
果たして声を大にして叫ばなければいけないほど処理速度に差があるんでしょうか?
実際に試してみたかったんです。
cmp bx, 0 と or bx, bxの速度比較
さて速度比較なんですが生半可な回数だと繰り返しても一瞬で終わってしまうので、32Bit整数回以上回したいと思います。具体的には0x1000000000回繰り返してみます。
8086CPUでは整数の最大値が0xffffつまり65535でしかないので、16bitフルを2連にしてそれを16回繰り返します。変更点はこんな感じ。
;>===========================
;> main
;>===========================
main:
; システムカウンタの取得と表示
call SystemCounter
mov bx, 0xffff
mov cx, 0xffff
mov dx, 0x000f
._Loop1
._loop2
._loop3
dec bx
cmp bx, 0
jne ._loop3
dec cx
or cx, cx
jne ._loop2
dec dx
or dx, dx
jne ._loop1
; システムカウンタの取得と表示
call SystemCounter
; 処理終了
call _hlt
cmp bx, 0
さて、cmp bx, 0はこんな結果になりました。
開始が0x0003064Bで終了が0x00030916Cなので、0x2CBつまり715カウントかかったことになります。
or bx, bx
ではor bx,bxのほうはどうでしょうか。先ほどのソースの真ん中あたりかな?
cmp bx, 0をor bx, bxに変えて実行してみます。するとこんな結果になりました。
開始が0x00038561で終了が0x00038827ですから0x2C6ということで710カウントかかっています。
つまり?
cmpを使う場合とorを使う場合の比較ですが、見ての通りほとんど差がないという結果になりました。本来こういった速度比較をする場合、数十回とか数百回行って平均をとって比べるとかすべきですし、そもそもVMwareという仮想化ソフト上で動かしているわけですから実際のCPUとまるっきり一緒ということもないのかもしれません。
けれど、仮想環境で、なおかつ勉強や趣味のために動かしてている状況では、orを使おうがcmpを使おうがどっちでも構わないといえると思います。
少なくても、ですが、cmpを使っていて「そこはorを使うんだ。素人はこれだから…」とかしたり顔で言われる筋合いはないということです。(結局これが言いたかっただけ)
さて、ちょうどキリもいいので今回はここまでです。
《2024/07/06 12:45:23》