OS自作入門 onLinux 8日目
8日目上手くいきましたよー。
8日目ソースコード
では続きから。
==== 8日目は、7日目でマウスから受け取れるようになったデータを使ってマウスを動かすだけです。
ですから、そんなに難易度が高いわけではないのですが、やはりバグってしまいました^^;
マウスの動きが不連続になる(カクカクする)のです。
その話の前に、自作本ではマウスから受け取ったデータについての記述が
あまりまとまっていないように感じたので(スミマセン…)、ちょっとだけまとめておきます。
とりあえず、こんな感じで理解しています。
1バイト目のx、y成分を拡張する部分はよくわかってませんが、とりあえずこういうものだと
思っています。
ついでに、もう一か所大きく修正したところがあります。
今までは完全にバグのまま、素通りしていました。尤も、ほとんど使わない機能でしたけれども。
lib.cのlsprintfの%d指定子です。
%d指定子では、追加の引数から整数値を読み込んで、文字列に変換して
埋め込んでいました。
整数値を文字列に変換するときに、int2dec関数を使っていましたが、この関数にバグがありました。
// 10進数valueのn桁目を返す int figure(int value, int n) { int i; for(i = 0; i < n-1; i++) value /= 10; return value % 10; } // 数値を10進数文字列に変換する void int2dec(char *s, int value) { int i; char zero = 1; if(!value) { s[0] = '0'; s[1] = '\0'; return; } if(value < 0) { s[0] = '-'; s+= 1; value = -value; } for(i = 0; i < 10; i++) { if(zero && figure(value, 10-i) != 0) zero = 0; if(!zero) *s++ = '0' + figure(value, 10-i); } *s = '\0'; }
この部分は詳しい説明はなく流していたので、少しだけ説明を入れておきたいと思います。
簡単に言えば、int2dec関数は、整数値valueを上位の位から一桁ずつ文字にして、
sにつめていきます。
int型はunsignedでも最大値が2^32=4294967296(10桁)なので、上から一桁ずつ処理していけば
ループ回数は10回ですみますね。
ループ内では、'0'にfigure関数の戻り値を足しています。
'0'は「0」の文字コードで、ASCII文字コードでは0~9に連続の文字コードが割り当てられているので、
'0'に数字を足せば、足した数字の文字コードが得られるという寸法です。
【参考URL】ASCIIコード表
つまり、figure関数は、valueの上からn桁目を返す関数です。
上からn桁目というのは、int型は最大10桁なので、1892のような数も「0000001892」と考えて、
上からn桁目ということです。
どういう仕組みになっているかといいますと…。
***************************************************************
C言語の仕様、というよりコンピュータの仕様で、整数除算では小数点以下も剰余も無視され、
商だけが戻ります。「13 / 3 = 4」といった具合です。(勿論本当は13÷3=4.33… or 4 … 1)
これを逆手に取ると、整数値を10で割れば一桁落ちてくるのです。
(1543523 / 10 = 154352、5261 / 10 = 526のように)
わかる人は、この辺でビット演算のシフトなんかを想像してもらうとありがたいです。
これは、シフトの10進数版だと思ってください。
13の下から1桁目は3です。これは「13 % 10 = 3」でよいでしょう。
※以降、「下から」~桁目と「上から」~桁目の違いに注意してください。
もっと説明しやすいプログラムを書けばよかったんだけどなあ…(後悔先に立たず)
同じく、5723の下から1桁目も3です。これも「5723 % 10 = 3」でよいですね。
7の下から1桁目は…言うまでもなく7です。これも「7 % 10 = 7」と考えましょう。
13の下から2桁目は1です。これはどうすればよいでしょうか。
そうです。とりあえず一桁落とせばいいのです。つまり、「(13 / 10) % 10 = 1」でいいのです。
これで、5723の下から3桁目もわかりますね。「5723 / 10 / 10 % 10 = 7」です。
1桁目(上から10桁目)を求めるときは(0回除算を行って)、剰余を求めました。
2桁目(上から9桁目)を求めるときは、1回除算を行って、剰余を求めました。
3桁目(上から8桁目)を求めるときは、2回除算を行って、剰余を求めました。
…(つまり)…
10桁目(上から1桁目)を求めるときは、9回除算を行って、剰余を求めますね。
さて、int型は先述の通り、10進数にして最大10桁です。
今欲しいのは、valueのn桁目です。…おわかりになったでしょうか?
以上のことを一般化すると、
(valueの上からn桁目) = (value / 10^(10-n)) % 10
です。
これをコード化すると、figure関数になります。
***************************************************************
さて、戻ります。ここまでで整数値を文字列に変換できました。
いや、このままだと123が「00000123」と表示されてしまい、恰好も悪いです。
そこで、zeroというフラグをつけました。
zero==1なら、上から現在の桁まで、まだすべて0という意味です。
zero==0なら、上から現在の桁までにすでに0以外の桁があった、という意味です。
さらに、zeroフラグをつけるとvalue==0のときに変換した文字列が空になるので、
value==0のときだけ処理をわけました。(95~98行目)
****************************************************************
話はここからでした(笑)
どこにバグがあったか、という話ですね。
負の数に対応していませんでしたw(爆
100~103行目で対応です。
しょうもねえ…
☆おまけ
ここまでの道のりで、C言語(というか多くのプログラミング言語)の面白い仕様を見つけた。
オペランドに負の数がある剰余演算子(%)の挙動だ。
数学の世界では、「-1 % 16 = 15」だ。
なぜなら、数学で剰余とは「a % b = q」つまり「a ÷ b = p 余り q」のとき、
あくまで「a = b × p + q (p ≧ q, q ≧ 0)」と定義されているからだ。
だから「-1 = 16 × -1 +15」なのだ。
しかし、なぜかプログラミング言語ではそうはならなかった。しかも、言語によって挙動が違う。
C(やJava)では「-1 % 16 = -1」らしい。つまり、「a = b × p + q (-p ≦ q, q ≦ 0)」なのだろうか。
詳しいことはこちらを参照されたい。
【参考URL】こんなプログラムはいやだ:負の剰余
さて、マウスのバグの話に戻ります。あー疲れた…
プログラムのバグではないのです。
NONAMEさんから指摘していただいたのです(感謝です!)が、仮想マシン上のQEMUで動かすと
マウスの動きが不連続になるようなのです。
そのため、今まではLinux Mint(on VirtualBox)のスクリーンショットを撮っていたのですが、
今回は実機のVistaのスクリーンショットになっています。
実機のQEMUだったら問題なく動作します。
比較したのがこちらです。
なんとかして原因を調べたいと思っていますので、何か心当たりのある方がいらっしゃいましたら、
是非ともお願いします。
とまあ、これからはとりあえずのところ、"on Linux"ですが、マウスを動かすときはWindowsの
スクリーンショットになっちゃうと思います。
コンパイルはすべてLinuxで行ってるので、このままon Linuxを冠したままにしたいと思います。