ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja uk en ja ja ja el ja en ja ja ja uk ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja en ja ja en ja ja vi ja sv eu ja en en hu ja ja en en pl ja ja jbo hbs_lat en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja
そしてこの例では、どちらも同じファイルであるlibwebp.so.6.0.2を指しています。
通常のアプリケーションは、バージョンありの方(libwebp.so.6)を用いて動作します。 一方でバージョンなしの方(libwebp.so)は、開発用に用います。
開発段階ではバージョンを意識せずに済む方が便利です。-lwebpというようにライブラリー名を指定するだけでリンクできるからです。バージョンを意識する場合は/usr/lib/x86_64-linux-gnu/libwebp.so.6や/usr/lib/x86_64-linux-gnu/libwebp.so.6.0.2といったように目的の共有ライブラリーのパスを指定しないといけません。 一方で共有ライブラリーを使うアプリケーションにとっては、このようにメジャーバージョン毎にシンボリックリンクを分けることがとても重要になってきます。
共有ライブラリーのシンボリックリンクは、メジャーバージョン毎に共存できる
前章の例を見ると、メジャーバージョンがバージョンありのシンボリックリンクの名前(libwebp.so.6の6の部分)に含まれていることが分かります。
メジャーバージョンが変わらなければ、シンボリックリンクの名前は同じままです。 一方で、メジャーバージョンが変わると、シンボリックリンクの名前も変わります。
これはつまり、共有ライブラリーのシンボリックリンクは、メジャーバージョン毎に共存できるということです。
アプリケーションにとって、共有ライブラリーのメジャーバージョンは他のバージョンとは異なる意味を持っているのです。
アプリケーションバイナリインターフェース(ABI)の互換性を管理する
以上の仕組みは、アプリケーションバイナリインターフェース(ABI)に利用されます。
API(アプリケーションプログラミングインターフェース)が、ソースコードレベルでのインターフェースを指すのに対し、 ABIはバイナリレベルでのインターフェースを指します。
このような共有ライブラリーの構成により、ABIの互換性を上手く管理することができます。
ABIの互換性と共有ライブラリーのメジャーバージョン
アプリケーションバイナリインターフェース(ABI)とは、バイナリレベルでのインターフェースのことです。
ABIの互換性について、具体例で考えてみます。
次のようなシンプルなライブラリーを作ります。
/* hello1.h */ void hello(const char *name);
/* hello1.c */ #include   #include "hello1.h" void hello(const char *name) { printf("Hello %s!\n", name); }
これを共有ライブラリーとしてビルド1し、シンボリックリンクを作成します。
$ cc -shared -olibhello.so.1 -Wl,-soname,libhello.so.1 hello1.c $ ln -s libhello.so.1 libhello.so # 次のような共有ライブラリーの構成になります libhello.so -> libhello.so.1* libhello.so.1*
この共有ライブラリーを用いるアプリケーションを作ります。
/* app1.c */ #include "hello1.h" int main(void) { hello("Me"); return 0; }
ビルドして実行します。 開発用のシンボリックリンクとしてバージョンなしのlibhello.soを用意してあるので、-lhelloで指定できて便利ですね。
$ cc -oapp1 -Wl,-rpath=$PWD app1.c -L. -lhello $ ./app1 Hello Me!
無事Hello Me!と表示されました。
次に、このライブラリーを機能拡張してみます。ファイル名はhello2にしておきます。
/* hello2.h */ /* 引数の数が増えているのでABIが違う!(APIも違う) */ void hello(const char *name, const char *message);
/* hello2.c */ #include   #include "hello2.h" void hello(const char *name, const char *message) { printf("Hello %s! %s\n", name, message); }
引数の数が増えているので、ABIとAPIの双方とも異なっています。 ここで、機能拡張したhello2ライブラリーを、先ほどと同じ名前の共有ライブラリーにしてみましょう。
# ABIが違うけど共有ライブラリーの名前は同じ(libhello.so.1)にしちゃえ! $ cc -shared -olibhello.so.1 -Wl,-soname,libhello.so.1 hello2.c
この状態で先ほどのapp1を、ビルドし直さずにそのまま実行してみます。
$ ./app1 Hello Me! _�
このように出力にゴミが混じってしまいます。 関数が削除されたり名前が変わったりした場合などは、クラッシュすることもありえます。
さてここで重要なのは、アプリケーション(app1)はビルドし直していない、ということです。 共有ライブラリーの場合、アプリケーションの実行時に実装が読み込まれます。 そのため、共有ライブラリーが更新されただけで、アプリケーション側が動かなくなることがありえるのです。
このように、アプリケーションのバイナリは変化していませんが、共有ライブラリーのバイナリが変化することで、 アプリケーションが動かなくことがあります。 これがABIの互換性を失ってしまうということです。
この問題を回避するために、機能拡張したhello2を別の共有ライブラリーにしてhello1と共存させてみましょう。
$ cc -shared -olibhello.so.1 -Wl,-soname,libhello.so.1 hello1.c $ cc -shared -olibhello.so.2 -Wl,-soname,libhello.so.2 hello2.c # 次のような共有ライブラリーの構成になります libhello.so -> libhello.so.1* libhello.so.1* libhello.so.2*
hello2で動作するアプリケーションapp2を作成します。
/* app2.c: hello2用のアプリケーション */ #include "hello2.h" int main(void) { hello("Me", "Yay!"); return 0; }
app2をビルドして、app1とそれぞれ動作を確認します。
$ cc -oapp2 -Wl,-rpath=$PWD app2.c libhello.so.2 $ ./app1 # ビルドし直していない!libhello.so.1をhello1.cベースに戻しただけ。 Hello Me! $ ./app2 Hello Me! Yay!
app1とapp2の双方とも正常に動作します。
特にapp1の方はビルドし直していないのに、動作が直っています。 ABIの互換性が回復したからです。
このようにABIの互換性を失う場合は共有ライブラリーを分けて共存させることで、互換性を失い動かなくなることを防げます。 新しいバージョンのlibhello.so.2がインストールされても、アプリケーション側がlibhello.so.1を利用し続ければ、 意図せず互換性を失い動かなくなってしまうことはありません。 最新の共有ライブラリーで動作したい別のアプリケーションがあった場合は、libhello.so.2の方を利用できます。
上例では簡単のためlibhello.so.1とlibhello.so.2は実ファイルでしたが、実際にはこれらもシンボリックリンクとし、 libhello.so.1.5.0とlibhello.so.2.0.1のようなフルバージョンの入った共有ライブラリーにそれぞれリンクさせます。
このため一般的に、アップデートによりABIの互換性を失う場合は共有ライブラリーのメジャーバージョンを上げ、互換性を保てる場合はメジャーバージョンを変えません。 マイナーバージョンなどについては好みで変更します。 例えば、どのバージョンの共有ライブラリーがインストールされているのかを特定できるようにするために使えます2。
本記事では、GNU/Linuxの共有ライブラリーのバージョンとアプリケーションバイナリインターフェースについて紹介しました。
共有ライブラリーのメジャーバージョンは他の桁のバージョンと比べて特別な意味を持っており、ABIの互換性を失う場合に上げる必要がある、ということを知って頂けたら幸いです。
クリアコードではこのように業務で学んだことも含めて成果を公開することを重視しています。 業務の成果を公開する職場で働きたい人はクリアコードの採用情報をぜひご覧ください。
gccではなくccコマンドを用います。ccコマンドはシステムのC Compilerです。 GNU/Linuxならccコマンドはgccであることが多いですが、FreeBSDやmacOSならclang等になります。 gccコマンドは通常はGCCがないと動かないので、GCCでコンパイルすることが必須の場合に使う必要があります。 そうでないならば、ccコマンドで十分です。 ↩
参考: http://archive.linux.or.jp/JF/JFdocs/Program-Library-HOWTO/shared-libraries.html ↩
現在クリアコードではソフトウェア開発者を募集しています! フリーソフトウェア開発での開発の仕方をベースに、成果や知識を公開し共有しながら業務に携われます。
Fluentd v1.15.1リリース -- プラグイン共通の排他機能の追加
Red Hat Enterprise Linux系OSの種類やバージョンを見分ける方法
Apache Arrow
milter manager
ククログの記事はCC BY-SA 4.0とGFDLのデュアルライセンスで自由に利用できます。
クリアコードはプログラミングが楽しいソフトウェア開発者を1名募集しています。
クリアコードは「クリアコードをいい感じにする人」を1名募集しています。
クリアコードはフリーソフトウェア開発で培った技術力を提供しています。特にMozilla製品(Mozilla FirefoxとMozilla Thunderbird)とRubyとGroonga(全文検索)に関連した開発を得意としています。