OSSのベンチマークツールとベンチマークレポート
拡張によるカスタマイズからMySQLとの連係まで
先月の注目記事をまとめてチェック!
Linuxで音楽を楽しむ
最新オープンソースOSの実力は?

パッチのあるべき姿

2005年07月28日 11:47AM
  • あとで読む
  • あとで読む
  • 印刷向け表示
Gentooパッケージのメンテナ(保守担当者)として私がいつも心がけていることの1つは、パッチを上流開発者に送ることである。上流でパッチを適用してもらえば、パッケージの将来バージョンからそのパッチを取り除け、パッケージの保守にかかる手間も省ける。

他のディストリビューションやパッケージでも同様にしてもらえれば、と思うが、残念ながら必ずしもそうはなっていない。Debian、Fedora Core、SUSEなどのLinuxディストリビューションもそうだし、FreeBSDのポート、DarwinPort、Finkなども同様である。以下は、私から開発者への提案である。これを取り入れてもらえば、開発者自身にとっても、コードに触れるその他の人々にとっても、作業がずっと容易になる。

まだテストしていないプラットフォームでソフトウェア問題が起こっても(未テストの理由は、使っているディストリビューションが異なる、環境が異なる、オペレーティングシステムが異なる、ハードウェアプラットフォームが異なる、などさまざま)、それが上流開発者の耳に入らなければ、開発者はいまのままですべてが順調と思い込み、問題の解決方法を考えることもなく、結果的に将来のバージョンでさらに多くの問題を抱え込むことになりかねない。発生した問題を上流開発者に知らせ、バグを記録しておくことは、オープンソースプロジェクトに協力する早道の1つである。問題を報告することなら、技術知識のないユーザにもできる。

しかし、バグを修正できるだけの技術を持っている人は、ぜひ上流開発者にパッチを送るようにしてほしい。ただ、上流ではすべてのパッチを無条件に適用できるわけではないから、短期的には問題の修正が難しくなることもあるだろう。パッチを作るときにエラーが入り込むこともある。上流で簡単に取り除ければよいが、ときにはひどいパッチを含んだパッケージングシステムが出回ることにもなる(Gentooでも起こりうる)。

パッチを書くときにまず覚えておくべきことは、このパッチが現環境とは異なる環境で使用されるかもしれない、ということである。ここで言う「環境」には、オペレーティングシステムとそのバージョン、ディストリビューション(オペレーティングシステムに複数のディストリビューションがあるとき)、使用ドライバ(何種類もあるとき)、ライブラリやツールのバージョンなど、プログラムの動作に影響しうるあらゆる要素が含まれる。パッチの作成に取りかかっている開発者は、意識するしないにかかわらず、とかく、現環境が「正しい」環境であり、誰もがこの環境に統一すべきであるという前提に立ちがちである。だが、百歩譲って現環境が「正しい環境」であるとしても、パッチは「壊れた」環境にも適用されるものであり、十分な一般性を持っていなければならない。

GNUのAutotoolsを使用すれば、さまざまな環境に自動適応するC/C++プログラムが書ける。確かに多くの問題を抱えていて、実際に使っているユーザの大多数に憎まれているツールではあるが、複数プラットフォーム用の適応的ビルドシステムを扱うには、現在のところこれが最良だろう。ヘッダ、ライブラリ、関数、その他の有無を検査する機能を持っている。惜しむらくは、学習のきわめて難しいツールであることで、ここではその使い方やAutotools M4言語によるマクロの書き方を詳しく説明するつもりはないが、ざっと見るだけは見ておこう。

Autotoolsベースのプロジェクトでは、一般に、M4でconfigure.acスクリプトを書く。これは、ホストマシン(ビルドの実行に使われるマシン)で必要とされる検査を書き記したファイルであり、autoconfによってシェルスクリプトに変換される。ほかにmakefile.amファイルも書く。こちらはautomakeによってmakefileのテンプレートに作り替えられる。makefile自体は、検査後にconfigureスクリプトによって作成される。configureスクリプトでは、makefileのための条件を定め、config.hファイルを作成する。このconfig.hファイルにはCプリプロセッサマクロがいくつも含まれていて、C/C++ソースファイルのための条件付きコードを作成するのに使われる(#ifdefsによる)。

たとえば、あるヘッダがUnixシステムで定義されている標準の一部であっても、それが常に存在するという前提に立ってはならない。ライブラリは変化する。システムライブラリも同様であって、いま書いている何かも、将来変化しないという保証はない。システムヘッダや非慣例的ヘッダを使用するときは、そのすべてについて事前に存在の有無を確認する必要がある。この確認作業は、一般に、configure.acでAC_CHECK_HEADERS()を呼び、ホストマシンで生成されたconfig.hによって存在を確かめるという手順で実行できる。ときに、configureの実行中に警告が出され、あるヘッダが「プリプロセスできるが、コンパイルできない」と言われることがある。これは、この時点では警告にすぎないが、将来はエラーになるので、できるだけ早く修正しなければならない。修正作業は、普通、必要ヘッダを必須引数に2つほど追加するだけですむ。この検査により、たとえば、FreeBSDシステムで悪名高いmalloc.hエラーが避けられる(ただ、実際にはmalloc.hを使わないことが望ましい。代わりに、その場所にstdlib.hを使えば問題は起こらない)。

システムヘッダは移植できない。したがって、ほかの方法では得られないカーネルサービスを使用するのでないかぎり、システムヘッダによる情報またはサービスの取得は避けることが望ましい。たとえオペレーティングシステムが同じでも、バージョンが異なるときは、やはりシステムヘッダを避けたほうが移植性は高まる。Linux(のカーネル)バージョンが異なれば、ヘッダ間にも不整合がないとは言えず、不整合があればソフトウェアの動作に支障をきたす。

ライブラリも問題である。glibcは、すべてのLinuxディストリビューションで使用されるライブラリであり(ただし、組み込み用途のディストリビューションは、一般にuclibcまたはdietlibcを使用するので除く)、通常のCライブラリにあるべき基本関数(たとえば、カーネルとの対話に必要な関数)をすべて含んでいるほか、完全なiconv()実装、基本的なgettext実装、長いパラメータの取得に使用されるgetopt_long()関数(プログラムがユーザから与えられた引数の解析にgetoptを使用するときに必要)なども含んでいる。しかし、FreeBSDのシステムライブラリのように、これらを全部は含んでいないものもある。FreeBSDシステムでは、iconv()がGNU libiconvで与えられ、gettextがGNU gettextで与えられる。getopt_long()に至っては、FreeBSDの4.xシリーズかDragonFly BSDにしかない(非Linuxシステムにはあるかもしれない)別のライブラリが必要となる。AutotoolsにはAC_CHECK_LIB()マクロがあって、これでライブラリに特定関数が存在するかどうかを検査し、libiconv、libdl、libintlなどへのリンクが必要かどうかを判断できる。

整数のような基本型のサイズは、ハードウェアプラットフォームごとに異なることがあるし、ポインタサイズも同様である。最近では、デスクトップユーザにとって初めての64ビットハードウェアプラットフォーム、x86_64システムの存在が無視できなくなってきており、それにともなってこの問題自体も大きくなった。x86プラットフォームに慣れたソフトウェア開発者にとって、64ビットの真新しさは問題である。だが、修正は簡単で、数分でできる。変数のサイズが正しいことを確認するための方法としては、sys/types.hヘッダまたはstdint.hヘッダの「標準整数」を使うことが最も重要である。これらは名称変更された整数型であり、名前の形式は(u)intSIZE_tとなる。したがって、8ビット符号付き整数ならint8_t、64ビット符号なし整数ならuint64_tとなる。ポインタのサイズはアーキテクチャによって異なるので、これらの固定長型は使用できない。一般には長整数と同じ長さになるが、その格納に自動的にlongを使用するのでなく、その前にぜひptrdiff_tを使用してみてほしい。ほかにsize_tやoff_tなどの型があるが、これらはどのプラットフォームでも寸法が固定されているので、そのままで使っても安全である。固定長型と「名前付き」型(符号なしのintとlong)を混在させることは絶対に避けなければならないし、整数をprintfやscanfに渡すときは、%コードが正しいことを確認しなければならない。普通は、正しいコードと正しいサイズを得るためのマクロが定義されていて、PRId64が64ビット整数の%d、PRIu32が32ビット整数の%uなどとなっている。

最近、アセンブラコードにも問題が生じてきた。x86_64プロセッサが登場するまで、すべてのハードウェアプラットフォームには独自のアセンブラコードがあって、移植にともなう問題は多くなかった。なすべきことはコードの非アセンブラバージョンを用意することであり、それで、遅くはあっても移植は可能だった。しかし、新しいアーキテクチャでは、x86システムとx86_64システムの間でアセンブラコードを共用できる。とくに、MMX、SSE、3DNow!などの拡張命令セットを使用するマルチメディアアプリケーションで、その傾向が顕著である。アセンブラの構文はたいてい同じだが、レジスタのサイズとそこで実行される命令など、注意すべきことがいくつかある。たとえば、x86_64プロセッサの32ビットレジスタで「基本」命令を実行しようとすると、失敗する。したがって、使用するレジスタを条件のオペランドで選択しなければならない。

ハードウェアの整合性については、とうていここで語り尽くすことはできない。あの条件、この危険を取り上げはじめると、1冊の本が必要となる。非x86アーキテクチャで仕事をしている人なら、最終的な問題の修正方法はすでに周知のことであり、ソフトウェア破損時にはパッチを作成してくれるだろう。

ソフトウェアの整合性に戻ろう。ソフトウェアのクロスコンパイルの可能性は、常に念頭に置いてほしい。Autotoolsベースのプロジェクトをクロスコンパイル環境でビルドするのは、すべての依存関係がクロスコンパイル済みであれば(そして、使用に堪えるターゲットアーキテクチャ用クロスコンパイラがあれば)、一般にさほど難しくない。残念ながら、すでに設計段階で壊れていて、クロスコンパイルでまともに動かないconfigureスクリプトもある。最もよく見るエラーは、目的のアーキテクチャまたはプラットフォームを選択する際に、unameコマンドの出力を用いることである。この出力は現システムについての出力であって、ターゲットプラットフォームについては何も語ってくれない。${host}変数と${target}変数を使うのが正しいやり方である。これらの変数にはCHOSTに似た文字列が含まれていて、ホストシステム(コンパイルの実行に使ったシステム)とターゲットシステム(目的のシステム)を記述している。組構造になった文字列であり、3要素1組のもの(arch-vendor-os)と、4要素1組のもの(arch-vendor-kernel-libc)がある。後者は、オペレーティングシステムに「ネイティブ」libcがない場合のみ用いられる形である。arch部分はハードウェアプラットフォームを指定する部分で、i386、i686、x86_64、ppc、ppc64、sparcなどとなる。vendor部分はハードウェアベンダを指定する(IBM社のメインフレームならibm)。最近ではソフトウェアベンダを指し示すためにも使われるようになっていて、redhat、debian、mandrake、gentooなどの名前を見ることもある(Gentooがこの部分を使用するのは、uclibcシステムとGentoo/*BSDシステムに限られる)が、一般には、x86なら"pc"、その他のアーキテクチャなら"unknown"となる。注意が必要なのは、オペレーティングシステムを指定するos部分である。普通、GNU/Linuxシステムなら"linux-gnu"だが、他のLinuxベースのシステムでは"linux-uclibc"や、ただの"linux"となることもある。また、"freebsdX.Y"のようにして、FreeBSDの特定バージョンを指し示すこともある("freebsd"だけでは有効と見なされない)。どのOSに固有のコードを有効にしたいかを知りたいときは、ここの値を調べればよい。

以上、参考になっただろうか。せっかくパッチを書いても、それが他システムの破壊につながるようでは困る。パッチ作者の注意を喚起するよすがになれば、と思う。開発者がAutotoolsと条件付きコンパイルを正しく併用し、パッチの適用が必要な環境かどうかをまず確認できるようにしてくれれば、上流メンテナはソフトウェアに必要な修正を施す一方で、「共通」環境で何かが変化するたびに初めから同じことをやり直すという無駄を省くことができる。

原文

Diego-"Flameeyes"-Petteno(2005年7月26日(火))
2007年07月01日 07:05PM 更新

前後の記事

  • パッチのあるべき姿