2009年12月9日水曜日

Lions' Commentary (8) - lshift -

なんかまた色々と忙しくなってきちゃって進みが悪いんだけど、read / write 周りのsystem callが一通り追いかけられた感じなのでブログにエントリしてみようかと。

readi / writei は今までに読んだbio.c周りのバッファを使ってブロック単位に読み書きしてます。inodeから論理ブロック番号に変換して、最終的に物理ブロック番号を求める当たりもこの辺に。ファイルのオフセットからブロック番号とブロック内オフセット位置を求めるところでlshiftというアセンブラルーチンを呼んでまして、普通に書けば簡単な関数なんですが、アセンブラで良くわからん命令があって、ちょっと読解に苦戦しました。これは後述します。

ファイルの読み書きは基本512バイトのブロック単位で行うので、書き出しの際に1ブロック丸々上書きにならないような場合にはread modify writeの処理が入ります。また書き出し時には512バイト境界をまたぐごとに非同期書き込み要求を出し、境界に達しない半端な書き込みはバッファブロックにDIRTYと印を付けておき、バッファがLRU制御で追い出される際に遅延書き込みで吐き出されるようになってます。ブロック番号が直前に読み出したブロック番号+1になっていると、シーケンシャルプリフェッチが働くようになっており、古典的とは言えI/O処理の基礎は一通り完成している感じです。

プロセス管理やメモリ管理はLions'のみで一通り理解できてきた感じですが、I/O周りとなるとソース読解だけではなかなか理解が進みませんね。この辺はBach本の併読が遠回りなようで近道かも。

で、問題のlshift。例によってconf/m40.sです。

.global _lshift
_lshift:
    mov    2(sp),r1
    mov    (r1)+,r0
    mov    (r1),r1
    ashc   4(sp),r0
    mov    r1,r0
    rts    pc
「ashc」の使い方が良くわからなくて悩んだんですが、右オペランドがデスティネーションで、偶数レジスタを上位、奇数レジスタを下位としたペアレジスタに対して算術シフトを施す、という事ですね。この場合、r0が指定されているので、(r0 << 16) | r1 が操作対象というわけです。で、左オペランドがまた4(sp)なんて書かれているもんだから意味がわからなかったんですが、これがシフト量ですね。すっかりRISCが染み込んでしまっていたので、シフト演算の入力がオフセット付きレジスタ間接アドレッシングというのは、ちょっと斜め上をいく感じでした。結局、2(sp)が第一引数、4(sp)が第二引数なので、C言語で書けば

u16 lshift(u16 *vptr, s16 n)
{
    u32 v = (vptr[0] << 16) | vptr[1];
    if (n >= 0) v <<= n;
    else v >>= -n;
    return v & 0xffff;
}

これだけの処理。というか、やりたい事はu32値をブロックサイズ512で割りたいだけなんですが、なんとも面倒な話です。同様にdpcmpなんて関数もありまして、こいつも長々と書かれてますが32bitでcompareとるだけのアセンブラ関数でした。実際には減算結果を返し、結果は-512〜512に丸められます(という概要だけでも知ってればだいぶ読みやすいはず)。

PDP-11の命令フォーマットはオペランドに関しては基本的に6bitがアサインされており、3bitでレジスタ番号、残り3bitでアドレッシングモードが指定できるんですね。 CISCという意味では直行性があり洗練されてたのかもしれません。
#レジスタ間接アドレッシングってPDP-1のINDビット以前は存在したんだろうか。。。ディレイスロットもそうだけど、プロセッサの歴史の中で誤った方向へ進んでしまった最初の一歩の1つのような気がする。

2009年11月25日水曜日

Lions' Commentary (7) - closeとunlink -

I/Oバッファから、i-node関連の処理、あるいはファイルデスクリプタの実体操作、ファイル処理などの関数に取りかかってきました。パイプ用の処理が分岐で書かれているのがちょっと気になります。キャラクタでバイスやブロックデバイスはそれぞれの特殊処理はドライバ側あるいはその上位層で処理しているのですが、パイプだけは各処理の中で直接分岐して書かれています。この頃アドホックにインプリされた物が後継版で整理されていくのでしょうか。。。名前付きパイプなんて出てくる頃には綺麗に書き直されている気がしますね。System IIIで実装済みなのは知ってるので、どんな実装か確認しようと思えばできるのですが、ちょっと後回しかな。しかし、正確に発明されたタイミングはどの時点だろう?

あと、UNIXでは伝統的な挙動、ファイルは消しても誰かがオープンしている間は実体が消えない、というやつ。自分は大学の先輩(mick'n)から教えてもらった気がする。これもcloseのsystem callを追いかけていると見つかります。fork時に実体の参照カウントはインクリメントされるので、close時に参照カウンタが1の時のみファイルデスクリプタの実体を閉じます。んで、この時にi-nodeのlink数を調べて0以下だったらtruncateしてi-nodeを解放する、つまり実体を削除する、という処理になっていました。

Lionsを読むと、このあたりの実装はテンポラリファイルの運用を簡便にするための物のようですね。プロセス生存中はpidがシステムでユニークである事をOSが保証しているので、そのpidを交えた名前はプロセス内で衝突しない限りはシステムでユニーク。で、プロセス内では生成直後にunlinkしちゃえば、他からは触れないし、上書きされる心配もない。で、使い終わったら明示的にcloseすれば削除されるし、segvで落ちちゃった場合なんかでもOSがプロセスの解放処理をする際に全ファイルをcloseしてくれるので自然に実体も削除されるという。simple is bestな実にUNIXらしい考え方です。

2009年11月20日金曜日

Lions' Commentary (6) - I/Oバッファ -

書籍のAmazonへのリンクの出し方がわかったので入れてみました。

Lionsのほうはドライバの構造から入って参考にRKのドライバ周りを一通り読みました。
複数のドライブを1つの大容量ドライブに見せる仕掛けとか入ってました。

Bach本ではバッファキャッシュの話が最初にあって、読んでいて「あぁ、文明の光・・・」って感じでした。6版の場合、複雑に作ってもむしろ性能が落ちるような規模のシステムなので、リソース管理は非常にシンプル。一方でこちらはいきなり複数の双方向リンクでバッファを管理するような話で、いっきに近代化された印象を受けます。

が・・・Lions本で先に進むとdmr/bio.c つまり バッファ周りのソース。ここがまさにバッファキャッシュになっていて、Bach本とほぼ同じ構造で管理してました。違うのはハッシュを使ってるかどうかってところくらい。高々15エントリなんですが、LRUで置き換える事を考えるとやっぱり真面目にリストで管理するしかないですかね。

という感じで、わりと相補的に読めます、この2冊。
実際、Bach本で読んでスルーしてた循環リスト構造、Lions本でincore()を読んでいて、なんでこのループで正しく処理できるんだ・・・みたいにハマってしまって、再びBach本を見てみたら図解で細かく説明されていたという。。。


2009年11月15日日曜日

The Unix I/O System

Lions' Commentary 15章末尾に参考文献としてあげられているにも関わらず、ネット上で探すのに苦戦。Dennis Ritchieの文献として論文検索してもTime-Sharing〜のほうしか出てこない。

で、灯台下暗しというか、2238 Clubの参考文献にコピーが置いてあったのでメモとしてここに書いておきます。Lions' Commentaryで検索すると結構上に来るので。

地味にお世話になりっぱなしですね、2238 Club。

Go

Google発の新言語、というトピックには興味がなかったんだけど、なにやら別の面で気になる存在に。

という事で、ベル研からGoogleへ移籍したUNIXチームの面々が開発者なんですね。どうりで変なキャラがマスコットなわけだ。6版読んでる関係からPlan 9周辺もそれなりに調べてまして「間違えて“Inferno Programming with Limbo”を二冊買っちまったー」とか、こんなコレクターアイテム重複させてどうすんだろ・・・と思ってた矢先でした。
(VM絡みも別件で調べてて、Disもちょっとしたマイブームなんですw)

新言語習得という意味では、今はOCamlで苦戦してるので、すぐに手を出すつもりはないんですが・・・とりあえず処理系だけでもインストール、という事で非gcc版をインストールしてみました。

GNUなconfigureの作法に従わないあたり、らしいな〜と思いましたが(笑)。Plan9portとかInfernoと似たようなbuild方法ですね。ターゲットでDisもサポートしないかな〜。Infernoの裾野が広ければひと月もまてば誰かがportするんだろうけど。。。

次にGoogleが戦略的に出してくるとしたらVMですかね。個人プレイ中心ってイメージなので、企業としてのポートレートみたいなのがあるのか知りませんが。まぁ、悩みどころですね、うん。。。

2009年11月10日火曜日

Lions' Commentary (5) - スワップ処理、シグナルほか -

前回から読み進めた結果のまとめ。

まず、プリエンプションについては前回読み解いた通りの模様。
ただし端末から操作してるプロセスに関してはttyからの入出力で絶えず割り込みによるタスクスイッチが発生するので1秒という持ち時間は気にならない。
実際、この1秒のタスクスイッチを実験で観測しようとするとかなり難しい。システムコールも割り込みもなしにプロセスの状況を計測する手段がない。例えば、前回みたいな空ループのプロセスと、ひたすら“hello”と出力し続けるプロセスを同時に実行しても、どちらも違和感無く同時に実行されているように見えてしまう。これはhello側の実行を律速しているのがtty出力待ちであって、一文字書いてはタスクスイッチを行い、tty出力が終わると再びタスクスイッチでhelloに実行が戻る、といった処理が行われるため。一応、これについてはUNIX 7版上で実際に試してみました。

また(Lionsの順番通り)スワップアウトの処理を理解した上でスケジューラを読み直してみると、スワップアウト後は3秒経たないとスワップインさせない仕様や、スワップイン後にアクティブなプロセスが残っている状況では2秒経たないとスワップアウトさせない、といった仕様が目につく。これは4秒ごとの神の雷や、1秒ごとのプリエンプションとの相性を考えると妥当な数字のようだ。一度スワップすると3秒は戻ってこない、というのは凄いけど。。。アプリケーションのサイズの物理メモリの搭載サイズの差が小さかった状況を考えると、当然の実装かもしれない。

あと、順序的にはclockの話とスワップの話の間になりますが、システムコール入口やsignalの処理についても読み終わりました。
システムコールについてはプロセスの起動、終了やsignal関係、つまりはプロセス処理関連を読み終えました。ちょっと前の日記で疑問として書いた件ですが、execによるとやはり命令空間分離やデータ領域分離は実行ファイルの持つマジックナンバーが違うようです。またexitとwaitの関係とか。exitすると終了コードをswap空間に残して親がwaitで回収するのを待ちます。親が死ぬとinitの子供になるとか、ゾンビプロセスについての説明で出てくるような話も当時から存在した模様(swap空間に残す、というのが独特かもしれませんが)。
signalはSIGKILのみ上書きはできないけど、最後に呼ばれた1つしか覚えていない、という実装になってますが、これはそういうものなのかな。。。signalは取りこぼさないと思ってたんだけど、保証はしてないのかな。
まだ確証が得られるまで読み込んでないけど、signalが設定されたら即座に対象プロセスを起こしてハンドラを起動させているように見える。。。けど、たぶんスワップアウトしていたらスワップインを起動して再びスケジューラを起こすので、別プロセスから多重でsignalを設定される事はありそう。。。 まぁ、またそのうち見直すとします。

あとは特殊なsignalとしてptraceの処理。ptrace自体使った事がなかったので、こんな機能があったのかと勉強になりました。子プロセスのメモリ空間なら触る事ができるんですね、UNIXは。子供側からも許可を出す必要があるみたいだけど、これはforkしてから許可を出してexecすれば済む話のはず。
この辺はC言語からの引数とsystem call時の引数の順番が違っていて、読んでいて少しはまりました。7版でmanを見たらアセンブラからの呼び出し例が書いてあってようやく誤解に気づく罠。

Lions本とは関係ないけどBach本も入手。こっちはファイルシステムから解説に入るんですね。単独で読めるのでしばらくはトイレに常駐させときます。ぱっと見た感じではSystem Vになっても6版の構造はかなりひきずってるみたいですね。u区域とか重要な関数の名前なんかはそのままなので疑似コードからソースが想像しやすそう。
6版のソースは逆に残すところファイルシステムとドライバ関連のみ。どっちを読んでも、しばらくはファイルシステムべったりです。こっちは昼休みとか使ってコメント埋め込みつつMercurialでログをとってます。

今回は長文でした。

2009年11月5日木曜日

Lions' Commentary (4) -プリエンプション-

setpri()の疑問はclock周りまで読み進めれば理解できるかな、と思ってたんだけど。。。どうもclock.cで実装されているはずのプリエンプションが理解できない。というか、やってないように見える。

タスクスイッチするにはrunrunが非0にならなければいけないわけだけど、runrunを非0に設定するのはslp.cのsetpri()とsetrun()、あとはclock.cの中だけ。だけど、clock.cの中の処理といえば、タイマーの再設定、コールバック関数の呼び出し、CPU時間の累積くらい。それ以外は1秒に1回プロセス全体の優先度補正をしてるのと、4秒に1回、神の雷?で全プロセスを起こしてるだけ。優先度補正ではsetpri()を呼び出すし、神の雷でもrunrun++してるんだけど、これだと1秒に1回しかプリエンプションが起きない。そんなはずないよなぁ・・・(7版と見比べたけど神の雷が毎秒にかわってるくらいで本質的には差はなかった)。

実際以下のbusy loopのコードを走らせてみたけど 、普通に別端末でshellが滞りなく動いてるし。ひょっとしてI/Oで割り込みがかかるから? busy loopのコードどうしなら1秒単位でタスクスイッチする? う〜ん・・・まさか。
.globl _main
.text
_main:
jbr _main
.globl
.data
という事で、Lions' Commentaryでは11章まできましたが、まだまだプロセス制御でわからない事は多いです。風邪が治ってきたのでまた進捗は芳しくなくなるかもだし(笑。

あと、clock.cでタイマ割り込みハンドラを処理するあたり、callout配列の操作が末尾に暗黙の空エントリを期待してる。timeout()を見ると配列の大きさは気にせずに追加しちゃってるし。この辺りはまったく安全性を考慮されていないアドホックな実装になってますね。Lions'本によれば実際にはttrstrtからしか使われてないらしいので、まぁそういう事なんでしょうね。

2009年11月3日火曜日

Lions' Commentary (3) - setpriの真相はいかに -

setpri(2156)にはちょっとした問題がある。

関数のコメントには以下のように書かれている。

2150 /*
2151  * Set user priority.
2152  * The rescheduling flag (runrun)
2153  * is set if the priority is higher
2154  * than the currently running process.
2155  */
つまり、優先度を再計算して、現在実行中のプロセスより優先度が高くなっていたら再スケジューリングのためにrunrunを++する、と書かれている。ところが実際のコードは
2156 setpri(up)
2157 {
2158         register *pp, p;
2159 
2160         pp = up;
2161         p = (pp->p_cpu & 0377)/16;
2162         p =+ PUSER + pp->p_nice;
2163         if(p > 127)
2164                 p = 127;
2165         if(p > curpri)
2166                 runrun++;
2167         pp->p_pri = p;
2168 }
 であり、再計算した優先度が、実行中のプロセスの優先度より低ければ(if (p > curpri))再スケジューリング(runrun++)となっている。

で、Lions本によれば「自力でバグでない事を確認せよ(ヒント:呼び出し時の引数)」との事。さっそくgrepした感じではsetpriはプリエンプションの時に呼ばれているように見える。つまり、走行中のプロセスが持ち時間を使い切ってタイマーによって割り込まれた際に、CPU使用時間を計上してからsetpriを呼んでいる。優先度を下げる方向で再計算させているのであり、curpriは走行中のプロセスが選択された時の優先度。 下げてるんだからここでは常に「p > curpri」が成立するのは自明であり、再スケジューリングが走るのはしかるべきである。つまりコメント側が間違っている、という理解。バグではないという事で納得した・・・と思ったんですが。。。

例の2238 Clubにはバグという記述がありました。確かにベル研の資料には以下のように書かれている模様。
30)
    Bug fix in "setpri()": p>curpri should be p<curpri.
むぅ・・・公式にバグと言ってるわけだから、やっぱりバグなのだろうか。。。
真実を知っている人がいたら、ぜひ答えを教えて下さい。

simhと7th UNIX

我が家ではsimh上でAncient UNIXを動かしています。
具体的にはPDP11版の7th Editionに2BSDをインストール。自前パッチとして
  • date 2000年問題対応
  • vi hjklカーソル移動対応
  • vi 大画面対応
あたりを施した状態なんですが、動作中にCPUを食い潰しにいくのが気になってました。大昔のUNIXなんて電力制御も糞もないので、idle時にbusy loopでもしてるんだろう、と思ってたんですが、スケジューリング周りのコードを理解すると、きちんとスケジューリング対象がいない時にはidle()からwait命令を発行してCPUが割り込み待ちで寝るように作ってあります。
という事は・・・と思ってsimh側を見てみたら、どうやら「set cpu idle」を設定してあげないといけないようでした。

という事で、うちの設定は(ストレージ周りの設定を除くと)
set cpu u18
set cpu idle
set tti 7b
set dz 7b lines=16
att dz -am 4096
こんな感じです。
コンソールはマルチプレクサ(dz)通してtelnetで入れるようにしてあります。
7bってのはvi使うときに必要な設定で、これがないとtermcap使った画面制御がうまくいきません(制御ビットがマスクされちゃう)。viのbuildに成功して最初に起動したときは相当はまりました。termcapの設定ミスかと思って色々追いかけましたが、まさかsimh側の設定に問題があったとはorz 最近の環境に慣れ親しんだ身としてはcatとedだけの探検というのはジャングルでの生活に匹敵しますね。

2009年11月2日月曜日

Lions' Commentary (2)

ちょっと忙しくなって進捗が停滞してました。
仕事のコードレビューとかいつも数万行単位で実施してて、その時間を6版のレビューにあてられたら、すぐなのになぁ・・・とか思ったりします。って前回も同じ事書いてたか(汗

という事で、風邪をひいて熱を出してしまい、せっかくだから病院で読み進めました。
Lions'本でいうところの7章プロセスのあたりを読みました。外なので訳本で読んでたんですが、P.322の表に誤字がありますね。t+16,t+144って書いてあるところ、d+16,d+144が正しい。オリジナルの資料を確認したらそっちは正しく書いてありました。

Kernel空間と違ってUser空間はブロック(64B)単位(MMUが扱える最小単位)に細かくMMUを張ってますね。ページ単位に慣れた現代人としてはちょっと気持ち悪いですが。suregの最後(1762)のアドレス補正はちょっと難しい。アドレス空間分離モードだと必ずx_caddrが設定される、という前提のコードという理解で良いのだろうか。。。

まだローダとか見てないんだけど、命令/データ分離の場合とそうでない場合で実行ファイルの互換性ってないのかな?

完全には消化しきれていない感じです。

2009年10月25日日曜日

Lions' Commentary

積まれてたタネンバウム先生のModern Operating Systems(しかも初版)を最近になって読みました。
後半の分散OSのところはどこまで生き残ってる話かわからなかったので、RPCやNFS以外は流し読みでしたけど。

で、いっそのことOS関連をざっと勉強し直そうかな・・・と思って資料をあさっていたところで見つけたのがLions' CommentaryのPDF版。おぉ、まずはこれを読もう!ということで、ちびちびと読み始めてます。Kernel部分は1万行程度だったりするので、仕事でレビューさせられてる大量のコードに比べるとかわいいもんです。がぜんやる気が出てきました。

現在のところ、initを起動するところまでの流れを見渡して、プロセス管理を深く見直すあたりまでいってますが、とりあえず感想とか少しずつ書いてみようかと。

まず最初に面食らうのはCの文法。K&R前の代物で、型チェックがまったくないとか、構造体が無名でグローバルに参照可能とか。構造体の要素にp_とかプレフィックス付けてるのは、名前空間が存在しないがゆえか。。。型に関係なくいきなりfoo->intvalみたいな形でポインタの読み替えをしてくるので油断できません。

最初に見るのがメモリ管理。といってもページ管理みたいな大層なものではなく、ほんとにナイーブなmalloc/freeの実装。というか、アドレス順に並んでるのにリストですらない!!途中の要素を取り出すと、後ろの要素を全部1つずつ前に詰めるという・・・。ちょっと驚きました。

初期化部分はPDP-11のアセンブラで、ここも変な文法に悩まされるわけですが、2238 Clubに助けられました(笑。

ようやくCの世界に入るmain呼び出しの直前にMMUの設定がありますが、ここでまだジェネレーションギャップというか。MMUとは言っても最大8KBページが8エントリまでしかないんですね。つまり論理アドレス空間は64KB。おぉぉぉ・・・そっか純粋な16bit CPUじゃそうだよな。。。Z80以降の感覚だと8bit CPUでもメモリ空間だけは16bitだったり、16bit CPU(例えばMC68000)でもメモリ空間だけは24bitだったりしてたので、これはちょっと焦りました。そっか・・・初期のUNIXは64KBで動けたんだ・・・。

で、この8エントリのうち6エントリまでがKernelが使ってまして7エントリ目にユーザプロセス空間、8ページ目にI/O空間がマップされます。確かにこういうところからスタートしてると、スワップとかページ単位じゃなくてプロセス単位にもなりますね。。。という事で、歴史を堪能しております。

2009年9月26日土曜日

Spacewar!

しばらく前に作ったDEC PDP-1のエミュレータをベースにActionScript(Flash)版のエミュレータを作ってみました。せっかくのFlash版なので表示はそれっぽく・・・と思ったのですがセンスが足りないためイマイチな表示になってしまいました。

太古のマシンなわけですが、表示が1024x1024の解像度があり、残像やらボカシやら縮小やらで、表示系がずっしり重たい処理になってしまいました。エミュレーション自体はたいした事ないんですけどね。。。後でソースを1つにまとめてWonderflにも投稿したいと思います。

ちなみに動いているのはSpacewar! 知っている人は知っていると思いますが、世界で最初のテレビゲームと言われている物です。パンチカードの情報をデジタル化したファイルがありまして、そいつを読み込んで実行しています。

個人的な目的としては、プリプロセッサを使ってC++向けのコードとActionScript向けのコードを共通化する実験とか、版管理ツールとしてMercurialを使ってみる実験とか。

追記:さっそくWonderfl向けにカスタマイズして投稿してみました。

2009年8月12日水曜日

wonderfl続き

Frocessing版があまりにも重たかったのでBitmapData使って直接描画するバージョンも作ってみました。私の使い方が悪いだけなんですが。。。画面の再描画が常に走ってたのがいけないのですが、その辺の制御の仕方がわかりませんでした。どうせflashで描くならドット単位の描画ではなく、ベクターで描くタイプのプログラムを組んでみたいですね。


追記:再描画はnoLoop();で抑制できるようでした。また、pointを使っていたのがまずかったようです。F5MovieClip2Dではなく、F5MovieClip2DBmpを継承すればpixelが使えて高速化できるそうです。前者では画素ごとにシェイプが作られる・・・だそうで。。。すみません。

2009年8月10日月曜日

ProcessingとかActionScriptとか

今更だけどProcessing(*1)を試してみました。
事始めに参考書としてJohn Maeda氏のDesign By Numbers(*2)とかBen Fry氏のVisualizing Data(*3)も読んでみました。前者はプログラムを全く知らないデザイナ向けのCG入門書でしょうか。後者はエクセルを使ったグラフ作成より一歩進んだ事をプログラムを使って表現したい人向け。ただ、スクリーンスクレイピングの説明をはじめとして、本筋ではない雑多な話題についてのページが多く、読んでみると中途半端な感じがしました。やはりプログラマ向けの書籍ではないために感じる違和感かもしれません。
実際ちょろっと使ってみると確かに便利は便利。ただ最初の敷居が低いだけで、アルゴリズム組み始めるとその先は他のライブラリ使って組み上げるのとあまり手間は変わらないかなぁ・・・。

で、こちらも今更なんだけど、ActionScriptを使える開発がオンラインで行えるサイトWonderfl(*4)も試してみました。せっかくなので、Processing互換のライブラリFrocessing(*5)を使って遊んでみましたが・・・この手のサービスとの相性は良いですね。
最近ウェブを漁っていてマンデルブロと類似の図形でバーニングシップというのがあるのを知って、面白そうだったので練習題材にしてみました。やっぱりちょっと重いかなぁ・・・。マルチコアのマシンでないとブラウザが操作効かなくなるかも。。。






*1:Processing 1.0
*2:Design By Numbers
*3:ビジュアライジング・データ —Processingによる情報視覚化手法
*4:wonderfl
*5:Frocessing

2009年6月25日木曜日

AVRでも使ってみようかと

今までマイコンはPICを使い続けてたんですが、ATTiny2313が機能は少ないまでも100円というお手軽価格だったりするので、ちょっと使ってみる事にしました。

AVR Writerはどうしようかなぁ・・・と思って探してみるとhidaspx(*1)がUSBから使えて便利そう。っていうかAVRはソフトウェアUSBの実装がフリーで公開されているというのが驚き。上記の100円AVRをUSBコントローラとして使えてしまいます。AVR Writer自身にAVRを使うので、鶏と卵問題があったりしますが、確かAKI-PICでもAVRに書けたよなぁ・・・という気軽な気持ちで配布プリント基板をぽちっと購入。基板を待つ間にファームをAVRに書き込んでおくか・・・と思ったら・・・AKI-PICはATTiny2313に対応していなかった。。。ショック。

という事で、まずはAVRSP-COM(*2)互換のWriterを作ることにしました。最初はRS-232Cの信号から5Vを作るつもりだったんだけど、AVRSP-COMのプロトコル的に常時アサートされてる信号がなかったので、諦めてUSBから電源をとることにしました。最近は自作回路の電源をとる時はもっぱらUSBを使ってます。USBなら電力制限で保護がかかってるので、うっかりショートさせてもブレーカー落ちないし(笑。

で、次に作ったのが購入した基板で作ったhidaspx。わりと融通のきく回路になってて、基板と一緒に買えなかったものは手持ちの部品で事足りました。これだけでUSBデバイスとして動くのだから凄いですね。

その後、avraを使ったアセンブラでの開発をちょろっと体験し、すぐに面倒になってavr-gcc環境を構築しました(汗。まぁ、速度が必要になったらgccからインラインアセンブラ使えばいいや・・・って感じで。動作確認するまではWindowsで作業し、うまくいったのでメインマシンのMacで環境を整えることにしました。avr-gccはMacPortsからインストールして、hidspxは自前でコンパイル。なんとなくGUIも欲しかったので、Windows版hidspx-GUIを参考に見た目移植をしてみました。

開発環境はtcl/tkなので、MacでもLinuxでもOK。なんでtcl/tkなんか・・・ってのはご愛嬌。最近CADでtclを書く事が増えてきたので、その練習も兼ねて。M.Hiroさんのページ(*3)とか、「もっとTcl/Tk(*4)」を参考にさせてもらいました。前者のページはOh!Xとか、懐かしいですねぇ(しみじみ)。一応このGUIもアップしてみました(*5)。一部機能が未実装かつATtiny2313でしか動作確認していませんが。。。

という事で、まずはお約束の8x8 LEDを使って遊んでいます。LEDの制御だけでI/Oがわりといっぱいいっぱいなので、LED用の出力ポートを時分割でボタン入力にも使ってみました。子供のおもちゃにいいかもw


*1: 千秋ゼミ AVR/HIDaspx00
*2: ELM AVRライタの製作
*3: Tcl/Tk お気楽 GUI プログラミング
*4: もっとTcl/Tk
*5: hidspx-GUI/tk ver. 0.5.0/0.1

2009年3月2日月曜日

Java 3種の神器

最近eclipseを使う機会が増えました。Ruby on RailsとかOVMとかで良く使ってます。メインはOVMかな。そうなるとわりとJavaのお世話になる機会も増えたりして。例外はいて落ちたりすると中を追いかけたくなるのが人情というもの。という事で便利に使ってるのが以下のツール。
  1. Jad (decompiler)
  2. ClassFileAnalyzer (disassembler)
  3. jasmin (assembler)
1つ目はいわゆる逆コンパイラですね。classファイルにはメソッド名やメンバ変数名に関する情報も残ってたりするので、かなり可読性の高いコードに戻してくれます。とりあえず落ちた場所の周辺をざっと見るのに便利です。んで、単純な場合はこのまま逆コンパイルで得られたソースを直して、再びコンパイルすれば片付くんですが・・・解析が不完全に終わるとコンパイルできないソースを吐く事が多々あります。そんな時は2つ目のツール。こいつはいわゆる逆アセンブラで、classファイルを3つ目のツールに対応したアセンブラコードに変換してくれます。そんなわけで3つ目はアセンブラ。逆コンパイルが不完全な時にも、2と3のツールがあればソースに戻してからパッチを当てて、再びclassファイルに戻す事ができます。HAS、HLK、DISがあればなんでもできた、古き良きX68kユーザ時代を思い出します。

*1: http://web.archive.org/web/20080214075546/http://www.kpdus.com/jad.html
*2: http://classfileanalyzer.javaseiten.de/
*3: http://jasmin.sourceforge.net/

2009年2月15日日曜日

The Benchmark Handbook

ベンチマークについての古い資料で、昔はウェブで無料公開してたものらしいんだけど。今は公開サイトのドメインも失効してしまい、Morgan Kaufmannから出てた書籍も中古(しかも、わりと高価)でしか入手できない・・・と思ってたんだけど、試しにweb.archive.orgにかけてみたら、PDF含めてしっかりと残ってました。

というわけで、The Benchmark Handbook Online版。たまに論文に出てくるThe Wisconsin Benchmarkについて、それなりに詳しく書かれた唯一の資料でしょうか。

2009年1月31日土曜日

つまらないけど重要

趣味で開発している時には問題にならないけど、仕事で開発となるとついてまわるのが仕様書の問題。特に大きい会社では仕様書を書くのに割く時間は馬鹿にならない。ソフトウェアの場合、doxygenみたいなツールを使えば、わりとつまらない図を書いたりする時間も削減できたりするんだけど、ハードウェアだとなかなか良いツールがない(知らないだけ?)。

という事で、ソースから図を自動生成しようかな・・・とか考える。とりあえず
  • スクリプトでお手軽かつ適当に生成したい(テキストベースが楽ちん)
  • Wordに貼付けた際に汚くならない(ベクトルデータが良い)
って方針ですか。そう考えて思い出したのがSVG。XMLが出てきた当時、どう使うと便利なんだろう・・・とか思い悩んだもんですが、今や空気みたいなもんですね。出力するにはテキストが楽。でも入力時に解析するにはバイナリの方が楽。。。そんなわがままを受け入れてくれるのがXMLのうれしいところです。

さっそくSVGの仕様書(*1)を眺めてみたわけですが、結構シンプルで簡単。仕様書にも図付きでサンプルが載っていて、みながら数分で基本的な図形は作れるようになります。あとは、ここで理解したタグをスクリプトから自動生成してやるだけ。RTLからの変換にはRubyを使いました。って事でできたのが下の図。こんな馬鹿げた図でも、VisioやExcelでヘロヘロ作っていたらどんどん時間が飛んでいきます。くだらない事にタラタラ時間使って「忙しい」とか言う人間にはなりたくないものです。作業の効率化は研究者、技術者にとって永遠の課題です。

ソースはこんな感じ。お手軽ですよね。
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<g>
<text fill="black" x="62" y="50" font-size="6">RI2S1_DATA</text>
<line stroke="black" stroke-width="1" x1="50" y1="53" x2="250" y2="53"/>
<polygon fill="black" stroke="black" stroke-width="1" points="244,50 244,56 250,53"/>
</g>
... 中略 ...
</svg>
生成したSVGのプレビューや変換にはInkscape(*2)を使いました。Ubuntuからはapt-getでヘロっとインストールできました。EPSへの変換ができます。SVG Import Filter(*3)を使えばOpenOffice経由で色々と変換も可能。いきなりWMFとかにも出来るんだけど、グループ化を解除すると黒い豆腐になる問題があって、どういう経路で変換するのがベストかは今のところ模索中です。

*1: Scalable Vector Graphics (SVG) 1.1 Specification
*2: Inkscape
*3: SVG Import Filter for OpenOffice 2.0

2009年1月4日日曜日

LinuxでDLLを流用する

XMMSのpluginなんかでは昔から使われていた方法ですが、wineを使ってDLLを読み込む方法があります。・・・というか、ありました、なのかな?

古いwineの実装では、PROCESS_InitWineとか使ってexec的な事ができたんだけど、今は実装が変わっちゃってるみたい。

wine/library.hを見ると、wine_dll_loadとかwine_dlopenなんて関数があって期待しちゃうんですが、こいつらはwine_initを叩いた後でないと動作しません。ではwine_initを・・・とか思うと、ntdll.dll内で落ちます。実は(少なくとも今現在のcurrentに関しては)wine_initだけでは不十分で、wine_pthread_set_functionsという関数を使って、スレッド回りの処理をする関数群をまとめた関数テーブルをセットしてやる必要があります。この関数はlibrary.hには見えてこないけど、objdump -T libwine.soで確認でき、実際にextern指定してあげればリンクして呼び出すことができます。ではここでセットするwine_pthread_functions構造体を用意すれば良いのか・・・と思って準備を進めると・・・必要となる関数は、
  • init_process
  • init_thread
  • create_thread
  • init_current_teb
  • get_current_teb
  • exit_thread
  • abort_thread
  • pthread_sigmask
これだけあります。プロセスとスレッドの管理はまぁpthreadをラッパーを書いてあげれば済む話なんだけど、TEB関連はちょっとヘビー。細かいことは1年くらい前にmixiで書いたのでそちらを参照してもらうとして、ようはセグメントレジスタをいじって、特殊なアドレスにスレッド情報を格納しておく必要があるって事。ここまでいくと、wineコマンドをスクラッチから書くのとたいしてかわらない手間だという事がわかる。

そうすると、いっそのことexecでwineを叩けばいいじゃんって話になって、結局は必要なDLLを読むEXEを作ってあげて、forkした先の子供でwineをexec。あとは親子でプロセス間通信をして用を足す、って流れに。とりあえずこの方向でやる場合、pipeとmmapで通信できる事は確認してあります。

サンプルコードを書くとこんな感じ。どっちかの方法というよりかは組み合わせて通信するんだろうな。pipeで同期化も兼ねてコマンドを指示。データはmmapの共有メモリにて、って。pipeだとデータの管理が大変だし、共有メモリのポーリングは辛そう。

他にも自作のWinBeを使うって方法もあります。現在、DarwinとLinuxにも移植してあって、勝手しったる〜ってやつで。こっちだとWin32とLinux両方の実行環境を1つのプロセス内に混在させる事ができるんですが・・・如何せん、ひとりで作ったものなので、wineに比べると圧倒的に互換性が低いのが難点です。それでもcodecやら、リンクしたいなと思う類いの物なら大抵動いてくれるので、それなりに重宝します。


-------- winmain.cpp --------

#include <windows.h>
#include <stdio.h>

int WINAPI
WinMain
(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HANDLE hFile = CreateFile("shared_file",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (INVALID_HANDLE_VALUE != hFile) MessageBoxA(NULL, "CreateFile: success", "TEST", MB_OK);
HANDLE hShare = CreateFileMapping(hFile,
NULL,
PAGE_READWRITE,
0,
1024,
"shared_memory");
if (INVALID_HANDLE_VALUE != hShare) MessageBoxA(NULL, "CreateFileMapping: success", "TEST", MB_OK);
char *sharedBuffer;
sharedBuffer = (char *)MapViewOfFile(hShare,
FILE_MAP_WRITE,
0,
0,
1024);
if (NULL != sharedBuffer) MessageBoxA(NULL, "MapViewOfFile: success", "TEST", MB_OK);

snprintf(sharedBuffer, 1024, "this message is send via mmap");
MessageBoxA(NULL, "Write to Memory Mapped File", "TEST", MB_OK);

DWORD dwWrote;
BOOL rc = WriteFile(GetStdHandle(STD_OUTPUT_HANDLE),
"hello from exe",
15,
&dwWrote,
NULL);
if (rc) MessageBoxA(NULL, "WriteFile to STDOUT: success", "TEST", MB_OK);
else MessageBoxA(NULL, "WriteFile to STDOUT: failed", "TEST", MB_OK);

char readBuffer[256];
DWORD dwRead = 0;
rc = ReadFile(GetStdHandle(STD_INPUT_HANDLE),
readBuffer,
255,
&dwRead,
NULL);
readBuffer[dwRead] = 0;
char message[512];
snprintf(message, 512, "ReadFile from STDIN: %s", readBuffer);
MessageBoxA(NULL, message, "TEST", MB_OK);

return 0;
}


-------- main.cpp --------

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/mman.h>

int
main
(int argc, char **argv)
{
int elf2exe_pipefd[2];
int exe2elf_pipefd[2];

if (-1 == pipe(elf2exe_pipefd)) perror("pipe: elf2exe");
if (-1 == pipe(exe2elf_pipefd)) perror("pipe: exe2elf");

pid_t cpid = fork();
if (-1 == cpid) perror("fork");

if (cpid == 0) {
// child (exe)
close(STDIN_FILENO);
close(STDOUT_FILENO);
if (-1 == dup2(elf2exe_pipefd[0], STDIN_FILENO)) perror("dup2: elf2exe");
if (-1 == dup2(exe2elf_pipefd[1], STDOUT_FILENO)) perror("dup2: exe2elf");
close(elf2exe_pipefd[0]);
close(elf2exe_pipefd[1]);
close(exe2elf_pipefd[0]);
close(exe2elf_pipefd[1]);

char *exec_argv[] = {
"wine",
"test.exe",
NULL,
};
execvp(exec_argv[0], exec_argv);
// no return
} else {
// parent (elf)
close(elf2exe_pipefd[0]);
close(exe2elf_pipefd[1]);
int fd_rd = exe2elf_pipefd[0];
int fd_wr = elf2exe_pipefd[1];

char buf[256];
ssize_t size = read(fd_rd, buf, 255);
if (-1 == size) perror("read");
buf[size] = 0;
printf("read from exe: %s\n", buf);
write(fd_wr, "hello from elf", 14);

int fd_map = open("shared_file", O_RDWR);
if (-1 == fd_map) perror("open");
char *shared = (char *)mmap(NULL, 1024,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd_map, 0);
if (NULL == shared) perror("mmap");
puts(shared);
fflush(stdout);
wait(NULL);
puts("parent process exit");
}
return 0;
}

2009年1月3日土曜日

Subversionでクライアントが固まる障害

年末から、Subversionでクライアントがupdate中にハングアップしたり、redMine側のレポジトリブラウザを開くとredMine自体が応答しなくなる、といった症状に悩まされていました。

色々調べてみたんですが、Subversionのデータベースが壊れた場合の方法しか出てこない。うちもこれなのかなぁ・・・と思ってレポジトリを直接覗いて調べていたら・・・

% ls -hl .../db/revs/
:
:
:
-rw-r--r-- 1 apache apache 1.4G 12月 23 04:49 5665
:
:
:

なぬ? 1.4Gの差分ってなんだよー・・・orz
ってことで、原因は大量ファイルの一括commitでした。さらに調べると、この時commitされたファイルのうち1つが650MBちかくある。・・・こいつが原因か!! むぅ・・・もう少し考えてcommitして欲しいなぁorz よくよく見るとcommitしてあるファイルを日にち付けてtar.gzで固めただけっぽいし。バージョン管理してるんだから、そういうの不要じゃん。

原因に気づき、update中のクライアントをtcpdumpで追いかけてやると、確かにハング中もHTTPで大量データのやりとりを続けてたorz 仕方ないので「でかすぎて有害なので消すよ」とコメントを付けてtrunkからremoveしました。これでredMineのレポジトリブラウザも生き返るかなぁ・・・。途中経過を内部で持ってたらアウトかもなぁ。最初のアクセスに凄く時間がかかった記憶があるので、内部で差分を変換してデータベース化してそうな予感なんだけど・・・。

2009年1月2日金曜日

お金をかけずにSystemVerilogを使ってみる

設計に関して言えば、まだまだ他のツールチェインとの相互運用の面で問題が起きやすく、手放しに喜ぶ事のできないSystemVerilogですが、検証に関して言えば逆にVerilogなんかじゃやってられません。会社で使う分にはライセンスに困らないので普通にIUSとかQuestaを使えば良いのだけど、個人で使うとなると・・・わりと選択肢がないです。

フリーなVerilog処理系で有名なのはIcurus Verilog。ウェブなんかではSystemVerilogにも対応してるとか書いてるページもあるんですが、それは嘘。引数の互換性を持たせるためにVerilogのバージョンを指定できるようになってますが、パーザは引数を見ずに、全てのモードで同じ動作をします。

という事で商用の評価版として重宝しているのがModelSim。ModelSim Xilinx Edition-III Starterが制限付きながらも利用できます。Windows版しか利用できないのが残念ですが・・・。あくまでインストールしたコンソール端末だけで利用せよ、との意図で、リモートデスクトップからログインしているとライセンス違反で起動できません。そうなると、Cygwinを入れてsshdを立てておき、sloginしてコマンドを叩く、って使い方をするしかありません。もちろんちょっとした試験にはこれで十分なんですが、波形を表示しようとするとちょっと面倒。wlfをvcdに変換してscpしたり、gtkwaveを使ったり。あるいはVNCを使うのもありですが。。。もう少しなんとかならんかなぁ、って事でLinux+wineにインストールしてみました。って事で、以下書きメモです。

  1. Configure Wineのアプリケーション設定でWindowsのバージョンを「Windows 98」に設定。
  2. Configure Wineのドライブ設定でCドライブの詳細設定を選びシリアルを適当に設定(8桁の[0-9A-F]です)。
  3. インストーラから標準インストール(mxe_3_6.3c/setup.exeを使いました)。
  4. ライセンス申請
    • インストーラからの流れでうまくいかない場合、Modeltech_xe_starter/lic_request.txtの6行目に書かれているURLを叩けばライセンス申請に入れる
    • lic_request.txtで最後から2番目の行に「Disk Serial Number」の行がなければシリアルの取得に失敗してるので、上記のURLのQueryにも「ds=xxxxxxxx」(xxxxxxxxは2で設定したシリアル番号)を追加してやる必要あり。
こんな感じでうまく動いてそうです。あとはvlib.exe、vlog.exe、vsim.exeあたりに

--- /usr/local/bin/vlib ---
#!/bin/sh
exec wine $HOME/.wine/drive_c/Modeltech_xe_starter/win32xoem/vlog.exe $*
------------
こんな感じのラッパーを用意してやれば、ほぼLinux版のつもりで使えそうです。とりあえずovm-2.0.1のexample/tlm/tlm_fifoが動くのは確認しました。

そんなこんなで、近々OVMあたりも紹介したいなぁ、と思ってます。

追記(2009/1/4):バージョン設定についてはmodelsim.exe、vsim.exe、vish.exeあたりを個別に行っておけば、Default Settingsは変えずにOKっぽいです。