thirtydaysosの最近のブログ記事

11 章: ついにウィンドウ

11 章はウィンドウ表示。しかし、まだドラッグも何もできず、ウィンドウの絵を描いたレイヤを表示しただけ。タイトルにはウィンドウとあるけど、ウィンドウを描くことはこの章の目的ではありません。

ウィンドウを表示したことで、表示中のレイヤは下から、背景、ウィンドウ、マウスとなっているので、ウィンドウを頻繁に再描画すると画面がちらついたりします。原因は再描画処理では範囲指定した領域に対して最下層から順にすべてのレイヤを再描画していたからで、それを以下のようにカイゼン。

  • VRAM と同じサイズをもう一面確保して、画面上の各点がどのレイヤを表示しているのかを管理するための map を作成。
  • 再描画時は再描画対象のウィンドウの z-index と map を考慮して必要最低限の VRAM のみ書き換える。

これで、1 つのウィンドウが頻繁に書き換えられて、その上でマウスを動かしていたとしても、画面がちらついたりすることはなくなりました。

10 章: 重ね合わせ処理

10 章は ウィンドウの重ね合わせ処理。ウィンドウといっても今あるのは、背景とマウスカーソルのみなのでレイヤといったほうが分かりやすいかもしれない。各レイヤの構造体が保持する情報は座標、位置、高さ(z-index)、VRAM などを持っている。で、画面の再描画は下のレイヤから指定された領域のみ VRAM を書いていくという処理。また、レイヤを移動させるときは移動前と移動後の 2 つの領域を描画。

と書けば、簡単なのだけど、本章の本質的な内容は再描画処理を如何に軽くするか、すなわち如何にループを少なくするかということでした。なぜループか。

たとえば、

a = hoge[foo + bar] + another_var1;
b = hoge[foo + bar] + another_var2;

とあるコードのインデックスの計算をまとめて

int i = foo + bar;
a = hoge[i] + another_var1;
b = hoge[i] + another_var2;

としても、こんなのはコンパイラの最適化処理の範疇なので生成されるバイナリは両者とも同じになる(メンテナンサビリティは違うけど)。なので、処理速度を早くしようと思ったら、まずはループを疑えということだ。ループ内の処理は×ループ回数で効いてくるからね。

次は改善するとしたら何にになるのかなぁ、数値演算とか? if とか switch とか ?

10 章終わり。

9 章: メモリ管理

9 章ではメモリマップに割り当てられていない空きメモリを OS が管理し、アプリケーションに割り当てる & アプリケーションが開放する仕組みを作ることが目的。

まずはメモリチェックから。 メモリチェックはアドレスに 0xaa55aa55 を書いて、bit 反転させてチェックするという仕組み。でも、これを律儀に全アドレスにやっても無駄なので、1K ごととかそういう感じでメモリチェックをし、 OS が空きメモリサイズを獲得するという流れ。ここで面白かったのは「アドレスに値を格納して反転させてチェックする」というこんな感じの処理:

unsigned int old, *p, pat0, pat1;
unsigned *p        /* チェックするアドレス        */
old = *p;          /* いじる前の値を覚えておく    */
*p = 0xaa55aa55;   /* ためしに書いてみる          */
*p ^= 0xffffffff   /* そしてそれを反転してみる    */
if (*p != 0x55aa55aa ) {  /* 反転結果になったか? */
  *p = old;
  break;           /* チェック失敗                */
}

がコンパイラによって最適化されて「なにもしない」になっていたこと。本の中ではこのメモリチェック処理をアセンブラで書いて最適化されることを回避していたが、実は変数 p の定義を

volatile unsigned int *p;

とすることで最適化を回避しても OK。実際この方法の方がスマートだし、動作させてみたがメモリチェックも正しく動作した。じゃあ、なんでアセンブラで関数を用意したのかというと、おそらく OS 作りとは直接関係のないコンパイルの最適化の解説の後にさらに volatile の解説をするのを避けたのではないかなというのが僕の予想。普通はアセンブラでわざわざ作るよりは volatile で…となるんだろうけど、アセンブラの関数を作る方法は 9日目までに既知のものとなっているし。

で、本題のメモリ管理のほうはテーブルを用意して「空き領域の開始アドレスとサイズ」を覚えさせておくという方法。テーブルの数は 1000 個。これが充分かどうかは分からないのだけど隣り合う領域をマージして空きテーブルを最大限増やす工夫もできているから充分なのかなぁ。

この処理はテーブルに配列を使っているのだけど、malloc 使って動的リストで管理したほうがいいんじゃないかと、おろかなことを考えてしまった。malloc を使えるようにするためのメモリ管理なのに、そこで malloc を使えるわけがない…。

9 章終わり。1 章分で増えるコードも量が大きくなってきた。理解にも時間がかかるなぁ。

8 章: マウス制御と32ビットモード切り替え

7 章で取れるようになったマウスの割込みのデータを解析しながら、ついにマウスカーソルを動かすことが出来るようになりました。でも、重ね合わせの処理がまだできてない、これは後ほど。マウスから送られるデータは 3バイトが 1 つの塊になっていてそれぞれ 「ボタンの情報+X,Y軸の動いた向き」「X 軸の変位」「Y 軸の変位」となっている。なので、マウスの制御は 3 つの状態を持つ簡単な状態遷移プログラムな感じになった。

7 章の残りはブートプログラムの処理。32 ビットモードに移行するために 1M 以上のメモリアクセスの可能にするための設定や、スタックを設定したり、IPL でロードしたフロッピーの中のデータを再度 1M 移行のアドレスにコピーしなおしたり。コピーし直す理由がよく分からないのだけど、メモリマップをきちっと隙間なく作っておきたいからということでいいのかな。

あと、今日のソースで "ALIGNB 16" というアセンブラの擬似命令がでてきました。データ定義の擬似命令が連続する場合、アライメントを 16 バイトに揃えるという擬似命令です。アセンブラで定義したデータ領域を C の構造体からも扱う場合なんかに使いますね。たとえば、

struct foo{
    unsigned char a,
    int b;
}

の時に

   sizeof( struct foo )

が、メンバ変数のサイズをそのまま足した 5 バイトではなくて、8 バイトになるというやつですね。8 になるのであれば、アセンブラ側は ALIGNB 4 を書いてやらないといけません。こういう構造体のサイズは C 言語の理解度テストなんかによく出るけど、サイズが 8 バイトになる理由は以前に少し書いたけどCPU のアーキテクチャに関るということですね。

8 章終わり。次回はメモリ管理。これまでやった画面描画とか、マウス制御って結局はデバドラだから厳密には OS なのか?という雰囲気でしたけど、いよいよ OS の機能って感じがしてきました。

intermission

| | トラックバック(0) http://tsuyuguchi.com/masaaki/mt/archives/2006/05/12-0011.php のはてなブックマーク件数

ここのところ更新が止まってます。本は読んでいるのですが、ソースを追っかけれていないのです。3 章でほったらかしになっていた 32 ビットモード移行の説明のところはちょっとじっくりと読みたいので、それを読んで、ソースも見れたら更新します。

7 章: FIFO とマウス制御

マウスの割り込みも取ることができました!章タイトルに「マウス制御」とありますが「マウスカーソル制御」はまだできてないので、コントローラから上がってくるデータを画面に表示させるだけ。だけといっても、付録にソースコードが付いているからといっても、インタラクティブに画面が変化するのは気持ちいいもんですなぁ。手を動かして PC を動かす。これこそ人間の支配。プログラムロードして動かすだけじゃ、パソコンを使っているのか使われてるのかわかんないですからね~。

と、いつになく強気になったところで今日のポイント:

  • 割込み処理は極力短く
  • キーボードとマウスからのデータは FIFO(リングバッファ)で管理する

というあたりでしょうか。割込み処理で描画とかしたら絶対アカンで。そんな遅い処理。描画しても見る相手は人間なんだからそんなμ秒とかミリ秒のレンジで処理が遅れても区別が付かないから気にするなって事。
それよか、同じ I/O でもネットワークとかシリアル通信とかカメラとか無線とか機械のほうを相手をしてやらんと。

6 章の続き。

割り込みの仕組みはざっとこんな感じ。外部割込みは PIC という装置が CPU とつながっていて、割り込みの種類 (IRQ) は全部で 16 個あると。PIC と CPU はどういう風につながっているかというと、割込み信号ピンが 1 つと、何番が割り込んだを伝える 2 バイトのデータを伝える信号線の 2 種類の信号線。CPU は割込み信号で割り込みの有無を検知し、同時に PIC からの 2 バイトデータで何番の IRQ が割り込んだかがわかるようになっている、という具合みたいです。で、その番号を伝える信号線から送られる 2 バイトのデータは実は CPU の INT 命令そのものになっていて、PIC から送られるデータをそのまま実行してハンドラに飛ぶとのこと。なんかすごい離れ業ですね、コレ。

また、割込みハンドラでは

  1. 全レジスタをスタックに退避
  2. 割込みで行いたい処理を実行
  3. スタックからレジスタの値を復帰
  4. IRETD 命令で終了

の処理を行います。そのうち 1, 3, 4 はアセンブラで実装。2 は普通の C の関数。レジスタの退避 & 復帰は割込み処理が終了した時点で割込み前の状態に戻すためのもの。そして最後の仕上げとしてハンドラを IDT に登録して、マウスとキーボードの割込みマスクを解除し、割込み禁止フラグをクリアすることで、実際に割り込みが起こったときにハンドラが初めて実行されるという具合です。じゃあ、マウスとキーボード以外の割込みはどうするかというと、マスクして CPU が割込みを受け付けないようにしておきます。そうしないと、静電気などのノイズで割り込みが入ったときにハンドラが登録されてないとかで変なことになりますので注意が必要とのこと。

これで、やっとキーボード割り込みを受け付けることができました。長かった…。でも、まだマウスの割り込みは受け付けられず。IRQ12 が上がってきてない模様。

6 章終了。ハードウェアの話も出てきましたし、この章から急に難易度が上がりました。

6 章: 分割コンパイルと割込み処理

分割コンパイルはこれまでに幾度となくやってきて、そのメリットなんかも充分に理解できているのでさっと読み飛ばしました。

6 章のメインは、機能の GDT, IDT の説明と、割込みハンドラの作成。

まずは GDT から。これは

  • セグメントのサイズ
  • セグメントの開始番地
  • セグメントの管理用属性

を 8 バイトに設定します。ただし、CPU の後方互換のためにサイズや開始番地は連続する領域ではなく、細切れに設定することになっています。今回のプログラムではこの設定を構造体を介して行っているのですが、特定のアドレス領域に C の構造体で値を設定する場合はメンバ変数のアライメントが気になりますね。なりませんか?私は気になります。

GDT の先頭アドレスは開発者が自由に決めることができますが、GDT を設定するための構造体は short, short, char * 4 の構成になっています。ということは先頭を奇数番地にしたらまずいんじゃないかなと思ったので…

実際にやってみた (トリビアの泉風で)。

結果、GDT の先頭アドレスを奇数番地にしても、とくにエラーもなく実行できてしまった…。

コンパイルして作られたアセンブラを見てみると、

    MOV    ESI,2555905  <-- GDT の先頭アドレス(奇数番地)
    MOV    EBX,8191
L6:
    PUSH   0
    PUSH   0
    PUSH   0
    PUSH   ESI   <-- 先頭アドレスをスタックに積む
    ADD    ESI,8 
    CALL   _set_segmdesc

とあって、_set_segmdesc では

_set_segmdesc:
    PUSH   EBP
    MOV    EBP,ESP
    PUSH   EBX
    MOV    EDX,DWORD [12+EBP]
    MOV    ECX,DWORD [16+EBP]
    MOV    EBX,DWORD [8+EBP]    <-- 2555905  
    MOV    EAX,DWORD [20+EBP]
    CMP    EDX,1048575
    JBE    L17
    SHR    EDX,12
    OR     EAX,32768
L17:
    MOV    WORD [EBX],DX <-- 奇数番地にワード書き込み

となっています。確かに L17 の時点で EBX には 2555905 が入っているから奇数アドレスへのワード書き込みで吹っ飛びそうな気がするんだけど…。x86 はアライメントルールなしですか?

ということで、はるばる Intel のサイトまで出かけて Pentium4 のドキュメント(pdf)を調べてみると 4.1.1 Alignment of Words, Doublewords, Quadwords, and Double Quadwords にこんな記述がありました。

Words, doublewords, and quadwords do not need to be aligned in memory on natural boundaries. The natural boundaries for words, double words, and quadwords are even-numbered addresses, addresses evenly divisible by four, and addresses evenly divisible by eight, respectively.

ということで、アライメントルールはないことが分かりました。

( ・∀・)つ〃∩ ヘェーヘェー

もちろん補足トリビアも用意してございます。上で引用した部分の続きには、

However, to improve the performance of programs, data structures (especially stacks) should be aligned on natural boundaries whenever possible. The reason for this is that the processor requires two memory accesses to make an unaligned memory access; aligned accesses require only one memory access.

とありました。アライメントルールはないものの境界を跨いだメモリアクセスなど出来るわけなく、実際には CPU が頑張ってと 2回メモリアクセスするので、(特にスタックへのアクセスは) 効率悪くなりまっせということらしいです。

( ・∀・)つ〃∩ ヘェーヘェー

私はアライメントエラーで吹っ飛ぶ CPU しか知らなかったので、ちょっと Intel 系 CPU の真面目さにちょっと感動しました。

以上、6-5 まで終了。割込みハンドラはこの後すぐ!

5 章: 構造体と文字表示と GDT/IDT 初期化

VRAM に表示する方法は出来たので次は文字表示、I/O に依存する printf は使えないけど sprintf は I/O に関係ないので、コンパイラ付属のライブラリからリンク。フォントデータは '*' と '-' からなるアスキーアートで「A」などを書いたテキストファイルから生成。マウスカーソルも同じように生成し、文字とマウスカーソルの表示に成功。

次に「セグメント」のお話。メモリアドレスは「0x00000000 から 0xffffffff」まであるけど、このままでは、将来複数のプログラムを実行するときに、いちいち開いているアドレスを探してそこにロードするという今年なくてはならない。それを回避するひとつの技がセグメントということらしい。具体的にはメモリ空間をセグメント単位で分けることで、セグメントの先頭を 0 番地として扱うことが出来るというもの。セグメントごとに実行プログラムを配置して実行すればアドレスが重なることなく実行できるということらしい。仮想アドレスを実現するページングはこの本では取り扱わないとのこと。

セグメントについてかいつまんで説明すると

  • 開始アドレス、サイズなどの各セグメントの情報のサイズは 8 バイト
  • セグメントの個数は 8191 個。どのセグメントを利用するかは セグメントレジスタ(16bit) の上位 13 ビットに 0 ~ 8191 の値を設定し、指定する。
  • すべてのセグメント情報、つまり 8 バイト× 8191 = 65536 バイトはメモリ上に確保する。
  • 65536 バイトのテーブルを GDT(= global (segment) descriptor table) と呼ぶ。
  • そして、GDT の先頭アドレスは GDTR に設定しておく。

とのこと。肝心の segment が括弧という GDT の名称が少し気になりますが…。まあそれは置いといて、これで

GDTR + セグメントレジスタに入っている値 * 8

で各セグメントの情報を取得できるという算段のようだ。

次 IDT。interrupt description table の略で、これは全部で 0 ~ 255 のエントリからなる外部割込み用のベクタテーブルのようだ。

5章は GDT、IDT について概要の説明とそれぞれを設定するプログラムを作成して終わり。6章以降で実際に割込み制御などを行っていくようだ。が、まだ 8 バイトの中身の説明などは詳細の説明がないので、個の後も延々説明が続くような気がする。

5日目にして急に内容が難しくなってきた。でも、ポインタ変数が明後日なアドレスを指していたときに Segmentation fault でプログラムが止まる理由が少し分かったような気がします。セグメントのサイズを越えてアクセスしようとしたときに出るエラーって事だな。

5章終わり。

今日は 4-6 から。アセンブラといえば、割禁。割禁といえばアセンブラというほど、重要な割禁(割り込み禁止)が、ようやく出てきました。割り込みフラグは EFLAGS レジスタの ビット 9。EFLAGS には他にもキャリーとかゼロフラグなどがあります。ということで、フラグレジスタも登場しましたね。割禁の許可、禁止 は 1 命令で出来るらしい。とか説明しておきながら、CPU の割込み動作事態の説明は後でするとのこと。今回は VGA のパレット設定のときには割禁にしなくてはいけませんというお話だけ。おそらくキーボード、マウスの制御まで割り込みの詳細な話はお預けの模様です。あくまでも流れ重視の OS 作りです。30 日で終わらせるというのですから流れ最優先、それは仕方ないことです。この流れ重視の体裁は、伏線が多いミステリーなもので、OS 作りとは別の意味でドキドキしています。もちろん撒いた伏線は全部回収してくれると信じてます(そうでないと困ります)。

パレットの読み書きは特定のメモリにパレット番号を設定して、特定のアドレスにデータを R,G,B の順に 3 回書けばオッケイ。このとき命令は MOV じゃなくて、OUT を使うとのこと。つまり、PC の CPU はメモリアドレスとと I/O アドレスが別々に I/O マップド I/O 方式なわけです。メモリアドレスも I/O アドレスも同じ空間を使うメモリマップド I/O 方式ならなら入出力全てを C 言語で書けるわけですが、今回は入出力用の関数をすべてアセンブラで用意することとなりました。

以上、4-6 終わり。

<< 1 2

最近の画像

Zero History - ギブスンの新作が 9 月に発売 -
虐殺器官
ドミニオンのランダマイザアプリ、dominion minion が日本語対応!
ハイペリオンの没落 ~ 完結...でもまだ続く~
ハイペリオンの没落 ~ 完結...でもまだ続く~
ハイペリオン - 長大な叙事詩 SF のスタート -
ハイペリオン - 長大な叙事詩 SF のスタート -
「都市と星」 - 10億年と言われてもよく分からない -
「星を継ぐもの」 - 良い Sci-Fi -
トールサイズのニューロマンサーを買った
虎よ、虎よ! - 主人公フォイルの執念の物語 -

私の本棚

 

自転車走行距離

自転車走行距離

Flickr Photos

FlickrPhotos

なかのひと

Y!ログール

このサイトについて

2015年2月

1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28

アーカイブ

My Update

  • Loading...

その他

あわせて読みたいブログパーツ