VRAM直接書き込み
VRAMとは「Video RAM」のことだ。 表示装置を持つコンピュータは通常,記憶領域の一部がVRAMとして使われている。 VRAMに文字コードを書き込むと,書き込んだ番地に応じた画面上の位置に,その文字が表示される (テキスト表示モードの場合。表示用ハードウェアをグラフィックモードに切り替えた場合は,VRAM中の1ビットが1画素に対応し,書き込んだビット列に応じた画素の列が画面に表示される)。 これらのコンピュータには,60分の1秒程度の一定時間ごとにVRAMの内容を読み出し,表示装置に送る画像信号を生成するようなハードウェアが搭載されている。
この章では,「OSを介さずにハードウェアを操作する」プログラムの例として,表示したい内容をVRAMに直接書き込むプログラムを作る。 これは第II部で行う実験の事前練習でもあるし,「OSがやっている仕事を理解するために自分の手でプログラムしてみる」ためでもある。
さてしかし,OSの上でプログラムを実行する場合,OSの保護機能が働いて,VRAMを直接読み書きすることはできない。 ここでは以下のような方法で,「VRAMに直接書き込むプログラム」を実行する。
- ハードディスク中の「ブートストラップローダ」(OSをハードディスクから読み出して起動するプログラム)を,自作の「VRAMに直接書き込むプログラム」に置き換える。コンピュータに電源を入れると自動的にブートストラップローダ(のふりをする自作プログラム)が実行されるが,当然ながらOSが起動する前に実行されるので,すべてのハードウェアに自由にアクセスすることができる。
- とは言え,ワークステーション室のコンピュータのブートストラップローダを書き換えることはもちろん禁止されているので,「パソコンハードウェアの動作を模倣するソフトウェア(PCエミュレータ)」を使って,ブートストラップローダを書き換えたときの動作を観察することにする。
実際の実験手順は以下のようになる。
- 「VRAMに直接書き込むプログラム」を記述し,アセンブルする。 サンプルを配付するので,それを改造すればよい。 「ブートストラップローダのふり」をさせるため,OS上で動くプログラムとはちがった制約がいくつかある。
- 上で得られたファイルを「ハードディスクの中身」として指定して,PCエミュレータである bochs を実行する。
準備: サンプルプログラムとbochs設定ファイルの配付
upstreamリポジトリ (https://github.com/kut-info-pl2/base-q1-i386) からpullしなさい。 vram/bochsrc 及び vram/vram.s がリポジトリに追加される。
- チーム中の一人がupstreamから自分のローカルリポジトリにpullし,共有リポジトリにpushする。
- upstreamの設定がまだの場合は以下を実行
(登録済みのURLを変更したい場合は remote add の代わりに remote set-url を実行)。
* 以下を実行。$ git remote add upstream https://ユーザ名@github.com/kut-info-pl2/base-q1-i386.git
$ git pull -- 念のためoriginと同期 $ git pull upstream master $ git push -- originにpush
- upstreamの設定がまだの場合は以下を実行
(登録済みのURLを変更したい場合は remote add の代わりに remote set-url を実行)。
- その後,残りのメンバーがoriginからpullする。
$ git pull
bochsの起動
vramディレクトリをカレントディレクトリとして,以下を実行する。
$ ~y-takata/bochs
bochs がカレントディレクトリの設定ファイル bochsrc を読み込み,パソコン模倣画面を表示する。 その後,「起動デバイスが接続されていない」というエラーで停止するので,Quit を押して終了する。
サンプルプログラムの実行
以下のようにアセンブルを行う。
$ nasm -fbin vram.s -- ファイルvramが生成される
vram という名前のファイルが生成される。オプション
-fbin
は,「.o ファイルではなく機械語のバイト列だけからなるファイルを出力せよ」という意味。エディタで bochsrc の先頭行の
#
を取り除く。ata0-master: type=disk, path=vram, cylinders=1, heads=1, spt=1 boot: disk
「カレントディレクトリのvramというファイルをハードディスクと思いなさい」という設定。
この状態で bochs を起動すると,vram.s に書かれたプログラムがブートストラップローダの代わりに実行される。
- 参考: ハードディスクの構造 (図)
- 上記のbochsrcは,「ファイル vram を『シリンダ数1, ヘッド数1, トラック当たりのセクタ数1の(つまりただ1個のセクタからなる)ハードディスク』と思いなさい」と設定している。
- 「ハードディスク」として指定するファイルの大きさが「総セクタ数 × 512バイト」に一致しないとエラーになる。
サンプルプログラムの中身
vram.s
; 下記に掲載のプログラムを基に作成した:
; http://d.hatena.ne.jp/rudeboyjet/20080108/p1
; テキストVRAMの使い方は下記を参照:
; https://en.wikipedia.org/wiki/VGA-compatible_text_mode
; ブートストラップコードは Real-address mode で実行されるので,
; 32ビットの番地を指定しても下位16ビットのみ使用される.
; AX, CX, DXは番地の指定に使えない (EAX, ECX, EDXは使える).
org 0x0 ; 開始番地
mov ax, 0xB800
mov ds, ax ; セグメントレジスタにVRAMの番地を代入
mov bx, 0 ; VRAM上の番地
mov cx, 25 ; ループ回数
mov al, 'A' ; 表示する文字
mov ah, 0x1c ; 表示属性 (青背景, 赤字)
loop0:
mov [bx], ax ; 文字と属性をVRAMに書き込む
add bx, 2 * 82 ; 82文字後ろ (= 右下)
inc al ; 次の文字
dec cx ; 残り回数を減らす
jnz loop0 ; 0でなければループ
hlt ; HALT (CPUを停止)
times 510-($-$$) db 0 ; セクタ末尾まで0で埋める ($$は開始番地)
db 0x55, 0xaa ; Boot Signature
- section指定は必要ない(指定しても無意味)。
- ORG (origin) は機械語プログラムの開始番地を指定する疑似命令。 ブートストラップローダは開始番地が 0。
テキストVRAMは 0xb8000 番地にある。1画面 80×25 文字表示で,1文字当たり2バイトで表される(4000バイトで1画面を表す)。
- 画面左上隅の文字が 0xb8000 番地に格納される。右隣の文字は 2 番地後ろに格納される。行の右端の文字の2番地後ろに,次の行の左端の文字が格納される。同じ列の1行下の文字は 160 番地後ろに格納される。
- 偶数番地に文字コード,奇数番地に表示属性(文字色,背景色など)を格納する。
- (詳しくは,プログラム中のコメントに書いたURLなどを参照。)
参考: 起動直後の i386 は Real-address mode という状態になっている。
- 番地は20ビットであり,これを16ビットのセグメントレジスタ ds と16ビットのセグメント内オフセットとで指定する。
- ds = 0xb800,bx = 0xa4 のとき,
mov [bx], ax
は 0xb80a4 番地にAXの値を書き込む。 つまり,「dsの16倍(4ビット左シフト)+ セグメント内オフセット」が実際にアクセスされる番地になる。 mov [ebx], ax
のように32ビットレジスタを使っても,下位16ビットのみ使用される。- (Real-address modeについて詳しくは,i386 のマニュアルを参照。)
HLT (halt) はCPUを停止する命令(OS管理下では実行できないが,OS起動前なら実行できる)。
- ブートストラップローダはハードディスクの第0セクタ(MBR; Master Boot Record と言う)に格納しなければならない。MBRの末尾の2バイトは 0x55, 0xaa でなければならない。
- 「第0セクタをある番地にコピーし,第510〜511バイトが 0x55, 0xaa であることを確認した後,第0バイトにジャンプする」というプログラムが,マザーボード上のROMに格納されている。
練習問題
演習1.9-1 vram.s を改造し,bochs の画面の上に好きな文字列や模様を表示するプログラムを作りなさい。 芸術性や技術的高度さの観点から,どのような模様を表示するとよいと思うか,チームで相談して決めなさい。
以下のファイルを共有リポジトリにpushしなさい。
- 改造後の vram.s(複数のソースファイルに分割した場合は,実行ファイルを作るのに必要なソースファイルすべて)
- Makefile
- bochsrc(make を実行して bochs を起動するだけで,成果物であるプログラムが実行されるようにすること)
- 実行結果のスクリーンショット画像 Screenshot.png(先頭大文字)
スクリーンショットは「アプリケーション」メニューの「アクセサリ」の中の「スクリーンショットの取得」を使って得ることができる。
- 「現在のウィンドウ」を選ぶ → 「取得するまでの待ち時間」を0より大きい値にする →「スクリーンショットの取得」ボタンを押す → 待ち時間が過ぎる前にbochsのウィンドウを選択する。
使ってよい機能:
- 表示モードは80×25文字テキスト表示モードから変えないこと。
- BIOS (Basic I/O System) について自分で調べて使うことは構わない。
- ローダを自作して,512バイトを超えるプログラムをロードするようにしてもよい。
- 「表示内容をだんだん変える」ことによってアニメーションを表示するプログラムを作ってもよい。 ある画面を描いてから次の画面に書き換えるまでの間,待ち時間が必要だが,例えば空ループを実行するなどして時間を消費するようにすればよい。 内蔵タイマーについて調べてそれを使ってもよい。
(補足)複数人が bochsrc や vram.s を編集すると衝突が起こるので注意。
自分の変更を取り消したければ,git checkout ファイル名
を実行すればよい(そのファイルが最終コミットの状態に戻る)。
(補足)表示内容を消したいときは,画面全体を空白文字で埋めればよい(あるいはBIOSコールを使う)。
(補足)文字コード0〜31や127〜255にも文字が割り当てられている(VRAMにこれらの値を書き込むと割り当てられている文字が表示される)。 どのような文字を表示できるかは自分で調べなさい(実際に表示させてみるのも一手)。