16.78MHz -14ページ目

今解き明かされるfglrxの謎

ATiのグラフィックカード用のLinuxプロプリドライバであるfglrx。OpenGL2.0対応となっているが何故か
$ readelf -s /usr/X11R6/lib/libGL.so.1.2 |grep glCreateProgram
1994: 00048930 32 FUNC GLOBAL DEFAULT 10 glCreateProgramObjectARB
3479: 00048930 32 FUNC GLOBAL DEFAULT 10 glCreateProgramObjectARB

プログラマブルシェーダ始めとするOpenGL2.0の主要な関数のシンボルが無い。一部では手抜きなんじゃないかとかウワサされていたが、やっとこの謎が解けた。

fglrxはGLX等を通して関数へのポインタを取得するケースだけを考えて作られており、GLX経由で取得したアドレスは確かに有効な関数を指しているがバイナリ自体に必要なシンボルが存在するとは限らない。少なくとも拡張と標準に同じ機能がある場合標準の方のシンボルは存在しないようだ。リンク時点でシンボルを解決してしまうとかダイナミックロードで呼び出すといったことは出来ない。

glCreateShader : 0xb7f1b79c
glCreateShaderObjectARB : 0xb7f1aa9c
オフセット 0xD00

glCreateProgram : 0xb7f3c7ea
glCreateProgramObjectARB : 0xb7f3baea
オフセット 0xD00

それぞれの関数のアドレスには規則的な位置関係があるのか、と一瞬思ったが、
glUniform1f : 0xb7fc2908
glUniform1fARB : 0xb7fc1b6c
オフセット 0xD9C

そうでもないらしい、ぅーむ。

OpenGL1.1以降の関数は専用の関数からアドレスをもらってくる、というのはWindowsのOpenGLの文化だ。ソースレベルで互換がとれれば十分なケースもあるLinux環境で何故リンカから読めるシンボルをきちんと用意しないのか。多分Windows版をちゃちゃっと書き換えて作ったんだろうなぁコレ

glfwが使えるようになるタイミング

glfwの殆んどの関数はglfwOpenWindowしないと機能しない。glfwInitなんていういかにも準備が整いそうな関数があるがこれを呼び出した直後にglfwExtensionSupportedを呼び出したりするとハマるので注意注意。

...と、自分で何度もこれにハマったので忘れないようにメモしておく。

postfix

OpenGLの関数の末尾の文字には以下のようなルールがある

d GLdoubleを引数に取る
f GLfloatを引数に取る
i GLintを引数に取る
s GLshortを引数に取る
b GLbyteを引数に取る
u 直後の型はunsignedである
v 直前の型はポインタである

例えばglなんとかuivならGLuint*を引数に取るという意味になる
C言語にはオーバーロードが無いため引数の型だけが異なる関数でも名前を変える必要があるわけだが、C++から使うときは出来ればオーバーロードを使いたい
そこでgl.hとglext.hの中にある上記のポストフィックスの付いた関数を全てラップしてオーバーロードしようということを思い付くわけだが関数の数が物凄く多い上にglextに至っては今後まだまだ増えることが予想されるため手作業でやりたくはない。
というわけでperlスクリプトに生成させようと考えた。関数名を取り出して後ろからchopしていって、それがポストフィックスに使われる文字で、かつそれっぽい型が引数に存在すればそれをポストフィックスと判断してハッシュにまとめていく。うん、なんとかなりそうだ。
void glReplacementCodeuiColor4ubVertex3fvSUN(const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *)

と思ったのはこの関数を見付けるまでだった。さすがサンマイクロシステムズ、その掟破りっぷりは他社の追随を許さない(謎

initrdは何処へ行くのか

initrdはLinux環境のルートファイルシステムをちょっと手の込んだ方法でマウントしたいときに使われる。ブートローダーはカーネルといっしょにRAMディスクイメージを展開し、カーネルはその中にある/linuxrcを起動する。linuxrcがexitしたらカーネルは指定されたデバイスにpivot_rootし、initを呼び出す。

このあとinitrdはアンマウントされる、と考えるのが自然だが実際にはそうなっていないことがある。具体的にはlinuxrcがexitした時点でinitrdがビジー状態だとアンマウントしないままずっとメモリ上に残る。巨大なinitrdを使っている場合は特に注意したいところ。procのアンマウントを忘れていたために40MBのinitrdがメモリ上に居座り続けるのはメモリの無駄遣いにも程がある

さて、ここでルートファイルシステムが/usrパーティション/dev/hda1の最上位ディレクトリにループバックファイルシステム/rootfs.imgとして入っているという厄介なケースを考えてみよう
initrdは/dev/hda1を/mnt/hda1にマウントし、losetupで/dev/loop1( loop0はinitrd自身が使用する )に/mnt/hda1/rootfs.imgを割り当て、/proc/sys/kernel/real-root-devに0x0701を書き込んでexitする。カーネルはpivot_rootし、/dev/loop1を/にマウントしてinitを呼び出す。そしてinitから呼び出されたブートスクリプトは/dev/hda1を/usrにマウントしようとするが、出来ない
/dev/hda1はrootfs.imgがマウントされている限りビジー状態なのでinitrdはアンマウントされず適切な場所にマウントし直すことができなくなるわけだ。移せないマウントを移したかのようにみせかけるあまりエレガントではない方法としてバインドマウントが考えられるが、残念ながら今/dev/hda1がマウントされているディレクトリは既にディレクトリツリーに繋がっておらずアクセスする方法がない

あまり知られていないようだがLinuxのドキュメント( Documentation/initrd.txt )にはルートファイルシステムをマウントした後でinitrdのディレクトリにアクセスする方法がきちんと書かれている。linuxカーネルはルートファイルシステムに/initrdという名前のディレクトリがあるとpivot_root時にinitrdをアンマウントせず、そこにマウントする。従って上のようなケースでは/initrdをrootfs.img内に用意しておき、/initrd/mnt/hda1を/usrにバインドマウントすれば良い

ちなみに、initrd内にマウントされたディレクトリが残っている場合だけでなくinitrd内でデーモンを立ち上げた場合等pivot_root時点でプロセスが残ってしまった場合もinitrdはアンマウントされない。pcmciaやfuseをinitrd内で使う場合はinitrdのサイズは可能な限り小さくしよう。

Gnash

iMacが順調にLinuxデスクトップ環境になってくるとブラウザ上にFlashムービーを表示出来るようにしたいという欲求が出てくる。しかしAdobeはPowerPC-Linux向けのプロプリエタリFLASHプラグインを提供していないので、どうしようかと考えてなんとなくオープンソースな実装を探してみた。そしたら

Gnash

あった
gから始まる名前から予想される通り、開発しているはフリーウェア*の総本山、GNUプロジェクト。ここはわりと底辺を支えるソフトウェアを作っているイメージが強かっただけに意外。
Flashバージョン7互換ストリーミングビデオにも対応し、Firefox等のMozillaベースブラウザとKDE Konquerorのためのプラグインが含まれているらしい。知らない間にこんな凄いものが...

バージョンが1.0未満なのがビミョーに不安だが、PowerPC Linux上でFlashを再生するための選択肢は殆んど無いので試しに入れてみることにした。

現在コンパイル中

*フリーウェアはオープンソースの部分集合である点に注意