OS学習メモ11(30日OS自作入門11日)
やっていきましょう。
スタートはここから。
11日目から pic.twitter.com/md94FLc9sU
— Yamahiro (@ymhr_s) 2018年6月8日
ご覧の有様です。
右端に行ったら変な動きをしているので、画面外に出てしまった時には値を補正するようにします。このくらいであれば本を読まなくても自分で直せました。 そんなに大きな変更も加えてないのでプログラムは省略します。
次に「shtctlの指定を省略する」ということをやります。難しいことは特になくて(前回作ったsheet_updown関数を見てもらえればわかるんですけど)下敷きを上げ下げするために毎回ctlを指定しているのが面倒くさいからそれを改良しようというだけのことです。
(久しぶりなので前回作ったsheet_updown関数を簡単におさらいしておくと、この関数はまず引数として「SHTCTL型の*ctl」「SHEET型の*sht (高さを変更したい下敷きの情報)」「int型のheight(変更後の下敷きの高さ)」の3つを受け取ります。受け取ったheightによって、ctlの情報をどんどん書き換えて行くことで下敷きの高さ関係を変更していくというのが、このsheet_updown関数でした。)
SHEET構造体の中にSHTCTL型の構造体を追加して、sheet_updownやらshtctl_unitやら関係するところを書き換えていくだけです。詳しい説明は省略しますね。
struct SHEET { unsigned char *buf; int bxsize, bysize, vx0, vy0, col_inv, height, flags; struct SHTCTL *ctl; };
void sheet_updown(struct SHEET *sht, int height) { struct SHTCTL *ctl = sht->ctl; int h, old = sht->height; /*略*/ for (h = old; h > height; h--) { ctl->sheets[h]=ctl->sheets[h - 1]; //こんな感じで使ってます ctl->sheets[h]->height = h; } ctl->sheets[height] = sht; /*略*/ }
引数の数が3つ→2つになったり、プログラムがちょびっとコンパクトになりました。
さてここからが本当の11日目という所でしょう。ウィンドウを出していきます。…とは言っても、すでに土台は出来上がっているのでそれらを駆使するだけです。プログラムはコピペさせてください。
init_palette(); shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny); sht_back = sheet_alloc(shtctl); sht_mouse = sheet_alloc(shtctl); sht_win = sheet_alloc(shtctl); buf_back = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny); buf_win = (unsigned char *) memman_alloc_4k(memman, 160 * 68); sheet_setbuf(sht_back, buf_back, binfo->scrnx, binfo->scrny, -1); sheet_setbuf(sht_mouse, buf_mouse, 16, 16, 99); sheet_setbuf(sht_win, buf_win, 160, 68, -1); init_screen8(buf_back, binfo->scrnx, binfo->scrny); init_mouse_cursor8(buf_mouse, 99); make_window8(buf_win, 160, 68, "window"); putfonts8_asc(buf_win, 160, 24, 28, COL8_000000, "Welcome to"); putfonts8_asc(buf_win, 160, 24, 44, COL8_000000, " Haribote-OS!"); sheet_slide(sht_back, 0, 0); mx = (binfo->scrnx - 16) ; my = (binfo->scrny - 28 - 16) / 2; sheet_slide(sht_mouse, mx, my); sheet_slide(sht_win, 80, 72); sheet_updown(sht_back, 0); sheet_updown(sht_mouse, 1); sheet_updown(sht_win, 2); sprintf(s, "(%3d, %3d)", mx, my); putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s); sprintf(s, "memory %dMB free : %dKB", memtotal / (1024 * 1024), memman_total(memman) / 1024); putfonts8_asc(buf_back, binfo->scrnx, 0, 32, COL8_FFFFFF, s); sheet_refresh(sht_back, 0, 0, binfo->scrnx, 48);
特に理由は無いんですけど、なんとなくsheet_updown関数が好きです。
実行結果↓
ここまで順調にやってこれました。次はカウンタを作ります。その名の通り0から数を数えていうというもので、ループの中でsheet_refreshとカウントを行います。文字が出力できるのだから、当然こういうこともできるよなぁという感じですね。
unsigned int memtotal, count = 0; for (;;) { count+=20000; sprintf(s, "%010d", count); boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43); putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s); sheet_refresh(sht_win, 40, 28, 120, 44); }
で、動かしてみると
チラチラチラチラしてる。 pic.twitter.com/JgzGlkeSUq
— Yamahiro (@ymhr_s) 2018年6月8日
書き換える必要のない背景の下敷きまで書き換えてるからこういったことが起きてしまってます。というわけで、必要な部分だけ書き換えるように書き換えたものがこれです。高さを指定してます。
void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0) { /*略*/ for (h = h0; h <= ctl->top; h++) { /*略*/ } return; }
動画は載せませんが、さっきみたいにカウントする度にちらちらする現象は直りました。が、これでもまだ不十分で、カウントのところにマウスをもっていくとマウスがちらちらしてしまいます。マウスまで一緒に描いて消してを繰り返しているのが原因なので、これを直すためにはウィンドウを描画する際にマウスカーソル部分を避けて描き込むしかありません。
一応自分でも解決方法を数分考えてみたのですが、どうにも思いつかないので本に沿ってやっていくことに。今回は”map”を使う方法です。言葉でうまく説明できませんが下の図が全てです。
例えば図中で一番下の下敷きをリフレッシュするなら、右側で”1”になっているところだけを書き変えればいい(2の所まで書き直す必要はない)というわけです。これを使ってマウスカーソルの所を避けて画面をリフレッシュしようというわけですね。
...と、いうのが仕組みの話で….ここまではわかったのですが…、正直、実装の所はコードを読んで考えても良くわかりませんでした。この本、後になって読み返したらすっと分かるとかよくあるのでちょっとこの辺り一旦スキップしようと思います。というわけで半端ですがここまで。
CTFに初参加した(SECCON Beginners CTF)
5月26日から27日にかけてSECCON Beginners CTFに一人で参加しました。これまで何度か常設のものを解いてみたことはありましたが、こうして大会っぽい大会に参加するのは初めてでした。
warm up5つしか解けず残念な結果に終わってしまいました。
この記事を書いている5月27日22時45分現在、まだ他の人が書いたwrite upには全く目を通していないのですが、僕もCTFの大会に出た以上はwrite upを書いたりしてアウトプットとかしないと成長しないと思うので、(正直今回に関しては恥ずかしくて書きたくないんですけど)今後出場したものに関してはなるべく書いていこうと思います。
************************
[Warmup] Veni, vidi, vici 51 (Crypto)
3つのテキストファイルが渡される。
1つ目は見た感じがもうシーザー暗号っぽいので、ROT13したら"The first part of the flag is: ctf4b{n0more"が出てきた。
2つ目もシーザー暗号っぽかったためROT13したけど違ったので、ROT1から試してみたらROT8のところでフラグ”The second part of the flag is: _cLass!cal_c”が出た。
3つ目は”{ʎɥdɐɹɓ0ʇdʎᴚ :sı ɓɐlɟ ǝɥʇ ɟo ʇɹɐd pɹıɥʇ ǝɥ⊥”で、macbookひっくり返して読んだ。
それでつなげるとFLAGに。
[Warmup] Simple Auth51 (Reversing)
実行ファイルが渡さて認証に使われているパスワードを使えとのことだった。いつ入れたかも分からない使い方も分からないディスアセンブラに突っ込んでみて、深く考えないで眺めてたらそれっぽいのがあったので、とりあえずASCIIコードだと思って文字列にしたらあっさりFLAG出てしまった。なんかごめんなさい
[Warmup] Greeting 51(Web)
”admin”にならないとフラグが表示されないwebサイト。名前を変更するフォームはあるが、”admin”と入力すると”偽管理者”に勝手に名前変更されてしまう仕様になっている。コードを見たらhtmlspecialchars関数でサニタイジングされているのでSQLインジェクションでも無さそう(なぜかそこは知ってた)。
...よく見てみたらCookieを使っていたので、名前のところにadminと入力してページを再読み込みしてみたところFLAGが出てた。
[Warmup] plain mail 51 (Misc)
ダウンロードしたpcapファイルをwiresharkで見てみたはいいものの、全然勉強したことなかったのでわからなかった。調べながら解いた。
TCPストリームを追跡して見たら、"I will send secret information .First, I will send encrypted file. Second, I wll send you the password"というのがあった。別のTCPストリームを追跡してみたら内容が見事にそれっぽかったので、raw(無加工)形式で保存してみたらちゃんと暗号化されたzipファイルが添付されていた。”_you_are_pro_”なんていう皮肉パスワードもメールに書いてあったので、それで中身をみたらテキストファイルにFLAGがいた。なんか「他人のメール盗み見てる感」が気持ちよくてこれがCTFなのかって興奮した自分がいた(気がする)
[Warmup] Welcome 51 (Misc)
公式IRCチャンネルみたら堂々と書いてあった
************************
恐れ恥ずかしながらこんな感じです...
やっぱり解けないからwriteupを見て勉強しよう...。初CTFは悔しい結果に終わってしまったようだ。
— Yamahiro (@_YMHR_U) May 26, 2018
とりあえず本当に悔しかったので、ちゃんと勉強して強くなろうと思いました。
OS学習メモ10(30日OS自作入門10日目)
(通信制限のためか画像アップロードできないので後ほど修正加えます)
※修正しました(5/24 8:40)
さて10日目,ようやく3分の1ですね。やっていきしょう。
最初の方でちょっとメモリ管理の続きでmallocっぽいのを作ったり、切り上げ切り捨てのやり方の話とかの話もあるのですが、ちょっとその辺りはスキップしまして早速8日目の続きからいきたいのですが、8日目の記事書いたのだいぶ前なので、ちょっと振り返るところから。
8日目の所でマウスカーソルが動くようになったのですが、その処理を「カーソルの上に色を上塗りして見えなくする→再びカーソル描画」とやっていたために、マウスを動かす度に画面を破壊してしたところで終わりましたね。
さて今回はこれを解決するために重ね合わせをやっていこうという所です。
イメージとしてはまさにお絵かきする時とかに使う”レイヤー”ですね。
ここでは”透明な下敷きにかいた絵”を重ねると表現されています。
というわけで、まずはその下敷きとやらを定義します。
struct SHEET{ unsigned char *bar; int bxsize,bysize,vx0,vy0,col_inv,height,flags; }
(そういえば、今回は構造体があたまの中でごちゃごちゃしたのでいつにも増して図が多いです。自分の備忘録のつもりですが、誰かの理解の助けになったりすれば嬉しいです)
構造体SHEETのイメージはこんな感じ。
これで一枚分の情報が出来ましたね。
では次に、複数枚の下敷きを管理するための”下敷き管理用構造体”を作ります。
struct SHTCTL{ unsigned char *vram; int xsize,ysize,top; struct SHEET *sheets[256]; struct SHEET sheets0[256]; }
sheets0で一枚一枚に下敷きの情報を保持しておきます。この時、どの下敷きが上にあってどれが下かとかは関係なくバラバラに格納されているので、それを下層から順に並べて番地だけを並べた構造体配列がsheetsです。イメージを見たほうが早い気がします。
これで管理の準備が整いました。この下敷き管理構造体を記憶するためのメモリを確保してぱぱっと値を設定。flagはそのシートが未使用であるか否かを示していて、はじめは全て0にしておいて使うときには1にします。
struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize, int ysize) { struct SHTCTL *ctl; int i; ctl = (struct SHTCTL *) memman_alloc_4k(memman, sizeof (struct SHTCTL)); if (ctl == 0) {goto err;} ctl->vram = vram; ctl->xsize = xsize; ctl->ysize = ysize; ctl->top = -1; for (i = 0; i < MAX_SHEETS; i++) {ctl->sheets0[i].flags = 0; } err: return ctl; }
次に未使用の下敷きを持ってくる関数。
気になるのは高さを-1にしている所だと思いますが、これは後から設定するからここでは気にしなくても大丈夫です。
struct SHEET *sheet_alloc(struct SHTCTL *ctl) { struct SHEET *sht; int i; for (i = 0; i < MAX_SHEETS; i++) { if (ctl->sheets0[i].flags == 0) { sht = &ctl->sheets0[i]; sht->flags = SHEET_USE; sht->height = -1; return sht; } } return 0; }
次に下敷きの高さを設定する関数です。
void sheet_updown(struct SHTCTL *ctl, struct SHEET *sht, int height){ int h, old = sht->height; if (height > ctl->top + 1) {height = ctl->top + 1;} if (height < -1) {height = -1;} sht->height = height; if (old > height) { if (height >= 0) { for (h = old; h > height; h--) { ctl->sheets[h] = ctl->sheets[h - 1]; ctl->sheets[h]->height = h; } ctl->sheets[height] = sht; } else { if (ctl->top > old) { for (h = old; h < ctl->top; h++) { ctl->sheets[h] = ctl->sheets[h + 1]; ctl->sheets[h]->height = h; } } ctl->top--; } sheet_refresh(ctl); } else if (old < height) { if (old >= 0) { for (h = old; h < height; h++) { ctl->sheets[h] = ctl->sheets[h + 1]; ctl->sheets[h]->height = h; } ctl->sheets[height] = sht; } else { for (h = ctl->top; h >= height; h--) { ctl->sheets[h + 1] = ctl->sheets[h]; ctl->sheets[h + 1]->height = h + 1; } ctl->sheets[height] = sht; ctl->top++; } sheet_refresh(ctl); } return; }
プログラムはちょっと長いですが、こでまでのプログラムをなんとか読めるだけの力がついていればこれも読みこなせるはずです。最初の頃はつらいなぁとおもうわけですが、それでもめげずに頑張っていると、どんどん読む能力が上達してきます。
ということなので丁寧に読んでいきましょう。
このsheet_updown関数では、ある下敷きの高さを、引数で渡しているhightの位置に設定します。
int h, old = sht->height;
もともとどこのレイヤにいたのかをoldに。
if (height > ctl->top + 1) {height = ctl->top + 1;} if (height < -1) {height = -1;} sht->height = height;
一番上のレイヤがctl(管理用構造体)のtopなので、hightをtopよりも上に指定する必要はないし、同様に低すぎる値にする必要もありません。最下層を0ではなく−1にしていますが、これは下敷きを非表示にする時のためです(地下1階みたいなイメージ?)
if (old > height) { if (height >= 0) { for (h = old; h > height; h--) { ctl->sheets[h] = ctl->sheets[h - 1]; ctl->sheets[h]->height = h; } ctl->sheets[height] = sht; } else { if (ctl->top > old) { for (h = old; h < ctl->top; h++) { ctl->sheets[h] = ctl->sheets[h + 1]; ctl->sheets[h]->height = h; } } ctl->top--; } sheet_refresh(ctl); }
細かく分けてみていきます。
if (old > height) {
old>hightはつまり、下敷きの”レイヤを下げる”場合です。
if (height >= 0) { for (h = old; h > height; h--) { ctl->sheets[h] = ctl->sheets[h - 1]; ctl->sheets[h]->height = h; } ctl->sheets[height] = sht; }
ここは、コードと図を一緒に見てください。
(ここでは青を一番下に持っていく例で考えます)
一回のループで↓をやります。
最終的にはこうなります。
下敷きを非表示にした場合はtopを1減らすのを忘れないようにしましょう。
並び順がちゃんと整理されたので、これに基づき再描画する処理が後に続きます。
ここまでわかれば残りの部分は”レイヤーを上げる処理”で、ほとんどやっていることは同じなのでスラスラと読めるはずです。
で、保留した再描画の処理がこちら
void sheet_refresh(struct SHTCTL *ctl){ int h, bx, by, vx, vy; unsigned char *buf, c, *vram = ctl->vram; struct SHEET *sht; for (h = 0; h <= ctl->top; h++) { sht = ctl->sheets[h]; buf = sht->buf; for (by = 0; by < sht->bysize; by++) { vy = sht->vy0 + by; for (bx = 0; bx < sht->bxsize; bx++) { vx = sht->vx0 + bx; c = buf[by * sht->bxsize + bx]; if (c != sht->col_inv) { vram[vy * ctl->xsize + vx] = c; } } } } return; }
下敷きを下層から描いていきます。
OS自作入門の初めの方で作ったboxfill関数とやってることは同じです。
あとは、下敷きをずらしたり,使い終わった下敷きを解放したり,といった関数をちょこっと追加してやると動きます。
10日目 pic.twitter.com/6Rq1DCUAKL
— Yamahiro (@ymhr_s) 2018年5月22日
(平井堅「トドカナイカラ」いい曲です)
だいぶGUIっぽく、OSっぽくなってきましたね!
画像、くどかったかも...
OS学習メモ9(30日OS自作入門9日目)
(完成直前の記事が誤操作で一度全部消えたので沈みムード)
1週間ぶりのOS本の進捗です。ここ1週間は中間試験の勉強やseccampの選択課題の勉強であまりこちらに手を回せていませんでしたが、意欲は全く下がっていません。張り切ってやっていきましょうー。
前回は「ようやく動いたマウスカーソル君がせっかく作った画面をどんどん破壊してしまう」という状態で終了したので、今回はそれを直していく...のかなと思いきやちょっと休憩。メモリ管理のお話です。
さてメモリ管理とは言っても、そもそもメモリがどのくらいの容量なのかが分からなければ管理もへったくれもないので、まずはそこから調べていきます。どうやら起動時にBIOSがチェックしてくれているらしいのですが、それを見るのはややこしいこといっぱいあって大変なのでBIOSを使わないでメモリチェックしていきます。
...って本には書いてあったんですが、そう言われちゃうとBIOSを使ってメモリチェックしてみたくなり、自分にはまだ早いかなー出来ないだろうなぁ..ーとは思いつつ、
http://oswiki.osask.jp/?(AT)BIOS
http://softwaretechnique.jp/OS_Development/Tips/Bios_Services/video_services.html
あたりを見ながら一応自分でも色々試してみました。3時間くらいは粘ったんですが案の定うまく行かなくて「やっぱり一旦本に沿って終わらせよう...」と改めて思うのでした...(悔しい)
— Yamahiro (@_YMHR_U) 2018年5月13日
まぁそんなわけで今回も本に沿って進めていきました。
32MB。
更に進めて...
空き容量も表示できましたよ
(正直書くのがちょっと面倒くさくなっちゃっているんですが...笑)説明これだけというのはさすがに雑なので、簡単に何をやったのかだけ書いて今日は終わろうと思います。
まずはコードをコピぺして置いておきます
void memman_init(struct MEMMAN *man){ man->frees = 0; /* 空き情報の個数 */ man->maxfrees = 0; /* 状況観察用:freesの最大値 */ man->lostsize = 0; /* 解放に失敗した合計サイズ */ man->losts = 0; /* 解放に失敗した回数 */ return; } /*確保*/ unsigned int memman_alloc(struct MEMMAN *man, unsigned int size){ unsigned int i, a; for (i = 0; i < man->frees; i++) { if (man->free[i].size >= size) { /* 十分な広さの空きを発見した時 */ a = man->free[i].addr; man->free[i].addr += size; man->free[i].size -= size; if (man->free[i].size == 0) { man->frees--; for (; i < man->frees; i++) { man->free[i] = man->free[i + 1]; } } return a; } } return 0; /* 空きが無かった場合 */ } /* 解放 */ int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size){ int i, j; for (i = 0; i < man->frees; i++) { if (man->free[i].addr > addr) { break; } } /* free[i - 1].addr < addr < free[i].addr */ if (i > 0) { if (man->free[i - 1].addr + man->free[i - 1].size == addr) { man->free[i - 1].size += size; if (i < man->frees) { if (addr + size == man->free[i].addr) { man->free[i - 1].size += man->free[i].size; man->frees--; for (; i < man->frees; i++) { man->free[i] = man->free[i + 1]; } } } return 0; } } if (i < man->frees) { if (addr + size == man->free[i].addr) { man->free[i].addr = addr; man->free[i].size += size; return 0; } } if (man->frees < MEMMAN_FREES) { for (j = man->frees; j > i; j--) { man->free[j] = man->free[j - 1]; } man->frees++; if (man->maxfrees < man->frees) { man->maxfrees = man->frees; } man->free[i].addr = addr; man->free[i].size = size; return 0; } man->losts++; man->lostsize += size; return -1; }
メモリ管理用に作った構造体MEMMANには「frees,maxfrees,lostsize,losts,free」の5つが入っていて、その中のfree[ ]は「空いているメモリの番地と大きさの情報が(○○から--だけ空いていると言った感じに)入っている構造体FREEINFO」の配列です。
メモリが必要になったら、MEMMANの中のfree[ ]から(free[i].sizeを見て)必要な大きさを確保できるところを探し、見つかったらその部分を確保。その部分は空き情報リストから削除します。反対に解放する時は、空き情報を追加して隣の空き地とひとまとめに出来ないかを調べ可能であればまとめるといった作業をしています。
後半は結構本の説明を借りちゃってますが..まぁ9日目はこんなところで。MINIX本の方のメモリ管理の章は手を付けていなかったと思うので、なるべく早めに読んでみようと思います。
おやすみなさい〜
OS学習メモ8(30日OS自作入門8日目)
8日目、今度こそマウスを動かす。
ここまでやってきてマウスからのデータを受け取れるようになったので、あとはそれを解読し、それにあわせてカーソルを動かせばよいだけ。ここからは本に沿ってやっていく。
i = fifo8_get(&mousefifo); io_sti(); if (mdec->phase == 0) { if (i== 0xfa) { mdec->phase = 1; } return 0; } if (mdec->phase == 1) { mdec->buf[0] = i; mdec->phase = 2; return 0; } if (mdec->phase == 2) { mdec->buf[1] = i; mdec->phase = 3; return 0; } if (mdec->phase == 3) { mdec->buf[2] = i; mouse->phase=1 sprintf(s, "%02X %02X %02X", mouse_dbuf[0], mouse_dbuf[1], mouse_dbuf[2]); boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32 + 8 * 8 - 1, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s); }
マウスからデータが3バイト送られてくるので3バイトたまった時点で画面に表示。
途中経過
こんな感じで解読はできた。
あとは表示部分を変えていく。
i = fifo8_get(&mousefifo); io_sti(); if (mouse_decode(&mdec, i) != 0) { //マウスを消す boxfill8(binfo->vram, binfo->scrnx, COL8_00FF00, mx, my, mx + 15, my + 15); mx += mdec.x; my += mdec.y; if (mx < 0) {mx = 0;} if (my < 0) {my = 0;} if (mx > binfo->scrnx - 16) {mx = binfo->scrnx - 16;} if (my > binfo->scrny - 16) {my = binfo->scrny - 16;} putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16); //マウスを描く }
カーソルがあった位置に背景色を書き込んでマウスカーソルを消して再度カーソルを描くという内容(↓の画像ではなんとなく色を変えてみたのでお絵描きみたいになっている)
見て分かる通り、下側の部分もboxfill関数で作ったただの四角形なので、カーソルを持っていくと上書きされてしまう。この問題は10日目から直していく。
簡単だけどこの辺で。
OS学習メモ7(30日OS自作入門7日目)
さてさて続き。
前回はマウスが反応しなかったところで終わりました。
今日3本目の記事でちょっと疲れてきましたがやっていきましょう。
int.cを見た。
void inthandler21(int *esp) { struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; unsigned char data, s[1]; io_out8(PIC0_OCW2, 0x61); data = io_in8(PORT_KEYDAT); sprintf(s,"%02x",data); boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31); putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s); return; }
キーボードを入力した時に、受け取ったキーコードを出力して割り込み処理を終了するプログラム。sprintfについてすっかり知識が抜けていたのでちょっと復習した。あとはそんなに難しいところはない。
順調に先に進めて…
FIFOバッファの説明とかを読みまして…
だいぶ飛びますがマウスの説明に入ります
(疲れてきたから説明端折ってラクしようとかじゃないですよ…多分)
初期の頃のPCにはマウスが標準でついておらず、当然当時のほぼ全てのOSはマウスに対応していなかった。後になって対応したけれど、それを使おうと思おうと思ったら有効化命令を実行しなければならない仕組みになっていた(だから6日目の時はマウスを動かしてもクリックしても反応しなかった)
というわけで、「制御回路」と「マウスそのもの」の2つに有効化命令を出す。実はマウスの制御回路はキーボード制御回路の中に入っているらしいので、そっちをうまく設定すればいい感じにできるとのこと(説明が雑になってきた)
void wait_KBC_sendready(void) { for (;;) { if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) { break; } } return; }
CPUとキーボード制御回路の回路の速さを比べた時、CPUの方が早いので、制御回路側で命令を受け取る準備ができていないのにCPUが命令を出してしまうと誤作動を起こしかねない。CPUはキーボード制御回路側が命令を受けられるようになるまで待つ必要があり、このプログラムがやっているのはまさにそれ。命令を受けられるようになると0x0064番の装置から読み取ったデータの中のあるビットが0になるので、それが確認できるまではループする(これをポーリングって言ったっけ?)
そして
void init_keyboard(void) { wait_KBC_sendready(); io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE); wait_KBC_sendready(); io_out8(PORT_KEYDAT, KBC_MODE); return; }
キーボード制御回路にデータを送れるようになるのを待ち、マウスを利用するモードにすることを伝える。
...ここまでが制御回路有効化
ここからがマウスの有効化
#define MOUSECMD_ENABLE 0xf4 void enable_mouse(void) { wait_KBC_sendready(); io_out8(PORT_KEYCMD,0xd4); wait_KBC_sendready(); io_out8(PORT_KEYDAT, MOUSECMD_ENABLE); return; }
マウス有効化もキーボード制御回路を使う。制御回路に0xd4と送ると次のデータをマウスに送信してくれるのを利用し、マウスに有効化命令を送っている。
さて実行し、ちゃんとマウスから割り込みがきていることを確認した。
あとは本にあるとおりにプログラムを書いていけば…
ちゃんとマウス反応してくれました。
キーボードも今までと同じようにちゃんと動いてくれてます。
さて、今日はいっぱいやったので、これでおしまいです。明日はこのマウスデータを解読して、ついにマウスカーソルを動かしましょう!楽しみですねえ。……あ、そこのきみ、だめだよ先を読んじゃ。今日は寝るの。続きはあした。分かった?
分かりましたーーー!!
んーーっ!疲れた!
OS学習メモ6(30日OS自作入門6日目)
やり残したGDTRの説明から。
_load_gdtr: ; void load_gdtr(int limit, int addr); MOV AX,[ESP+4] ; limit MOV [ESP+6],AX LGDT [ESP+6] RET
出鼻をくじくようだけどもうこのコードに関してはこれ以上説明することがなさそう…と思ったけど、そういえば初見のときはリミットってなんだろうって思ってたような気がする。GDTRは48ビットのレジスタだがそのうち下位16ビットはこのリミットというのを表していて、これはセグメントディスクリプタテーブル(GDT)の大きさのこと。上位32ビットはGDTのある番地を表している。
では次に、GDTにセグメントの情報を設定するset_segdmesc関数を見ていく。
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar) { if (limit > 0xfffff) { ar |= 0x8000; /* G_bit = 1 */ limit /= 0x1000; } sd->limit_low = limit & 0xffff; sd->base_low = base & 0xffff; sd->base_mid = (base >> 16) & 0xff; sd->access_right = ar & 0xff; sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0); sd->base_high = (base >> 24) & 0xff; return; }
5日目の記事にも書いたが、ここで入れるべき情報はセグメントの「①大きさ②番地③属性」の3つ。これらを1つのセグメントにつき8バイトで表現する。
①はセグメントが何バイトであるかを表す”リミット”だが、セグメントの最大サイズが4GBであることを考えると、大きさを表現するだけで32ビットの数値になってしまってこれだけで用意された8バイトの半分を使ってしまう。ということで、リミットを20ビットで表現する別の方法がある。後述するセグメント属性にはGビットというフラグがあって、これを1にすると、リミットをバイト単位ではなくページ単位であると解釈するようになる。PCのCPUでは1ページ4KBなので,これでちゃんと4GBを表現できるようになった。
②はセグメントのある位置を表すが、メモリは4GBなのでこれを表現しようとするとどうしても32ビットになる。
③セグメント属性は、セグメントのアクセス権を表す。12ビット。
これでちゃんと8バイト。
これで、長かったGDTとIDTの説明が全部片付いたので早速マウスを動かしていこう...と思ったらまだPICの初期化が終わってない。PICはprogrammable interrupt controller(設定可能な割り込みコントローラ)の略で、8個の割り込み信号を1つの割り込みにまとめる装置。CPUは単独では1つしか割り込みを扱えないのでPICを使って制御しなくてはならない。
そんなPICの初期化のプログラム↓
void init_pic(void) { io_out8(PIC0_IMR, 0xff ); //全ての割り込みを受け付けない io_out8(PIC1_IMR, 0xff ); //全ての割り込みを受け付けない io_out8(PIC0_ICW1, 0x11 ); //エッジトリガモード io_out8(PIC0_ICW2, 0x20 );//IRQ0-7をINT20-27で受ける io_out8(PIC0_ICW3, 1 << 2); //PIC1とIRQ2で接続 io_out8(PIC0_ICW4, 0x01 ); //ノンバッファモード io_out8(PIC1_ICW1, 0x11 ); //エッジトリガモード io_out8(PIC1_ICW2, 0x28 ); //IRQ8-15をINT28-2fで受ける io_out8(PIC1_ICW3, 2 ); //PIC1とIRQ2で接続 io_out8(PIC1_ICW4, 0x01 ); //ノンバッファモード io_out8(PIC0_IMR, 0xfb ); //PIC1以外の割り込みを受け付けない io_out8(PIC1_IMR, 0xff ); //全ての割り込みを受け付けない return; }
IMRはinterrupt mask register(割り込み目隠しレジスタ)の略。全8ビットで、それぞれIRQ信号(interrupt request つまり割り込み要求信号)の8個に対応しており、このビットが1になると対応しているIRQ信号の割り込みを無視する。ICWはinitial control word(初期化制御データ)の略。ICWは1~4まであって合計で4バイトのデータになる。ICW1,4に関しては割り込み信号の電気的特性に関することなのでいじらない。ICW3は何個のIRQにスレーブが繋がれているかを8ビットで設定するもの。一方でスレーブの方では、それがマスタの何番に付いているかを3ビットで設定するがこれも変にはいじれないのでこの値のまま変更しない。この辺り詳しいことは「はじめてよむ486」が参考になる。
まぁまぁそんなわけでICW2のみを設定していく。これは、IRQをどの割り込み番号としてCPUに通知するかを決めるもので、今回の設定ではIRQ0-15の通知をINT 0x20-0x2fで受けるようにしている(0x00-0x0fは別の処理で使うため割り込みを受けるためには使えない)
これ以降はもう本通りに進めて…
キーボードを押すと割り込み処理で文字列表示
マウスはまだ動きません。7日目に。
それではまた〜