システム奮闘記:その53
プログラムで使う静的ライブラリ、共有ライブラリ C言語(libc,gcc)
(2006年9月30日に掲載)
はじめに
よくプログラム開発をする時に「ライブラリ」という言葉を目にする。
プログラムだけでなく、新しいソフトなどをインストールする際も
「ライブラリ依存の問題」という言葉が出てくる場合もある。
ところで、一体、ライブラリとは何なのか。実は、2005年まで・・・
全くわかっていませんでした (^^;;
その上、勤務先でプログラムを書いたりするにも関わらず、
2006年になるまで、プログラムの開発効率を上げるための
独自のライブラリを作った事がありませんでした (^^;;;;;
というわけで、ライブラリとは何かについて書く事にしました。
ライブラリとの出会い
「ライブラリ」という名称は学生時代から耳にしていた。
1995年、大学3回生の時、研究室配属が決まった。
教授から「君はパソコンが得意やろ」と言われて、シミュレーションや
データ解析を行う事になったのだが、研究室にあるプログラムはFORTRANだった。
だが、私はFORTRANは大嫌いな言語だった。
1994年、大学2回生の時にFORTRANの講習があったのだが、用語がわからない、
言語開発ツールの使い方がわからない、エラーの意味がわからないなどがあり
その上、取得単位も2単位というのも手伝って
FORTRANは人間のやるもんじゃねー!
勝手に宣言して「絶対に、触るもんか」と心に固く決めていたのだった。
このトラウマは2006年になっても消えていない (^^;;
私が所属していたのは物理学科だった。
物理の数値計算を行うプログラムはFORTRANが主流だった。
if文、for文など簡単なC言語が使える私だったので、
C言語の切り替わる事がないのかと思ったのだが、教授や院生に
FORTRANは過去のライブラリが豊富だから、FORTRANがなくなる事はない
と言われた事がある。
それが「ライブラリ」の存在を初めて耳にした時だと思う。
(さすがに10年くらい前の記憶なので、あやふやかもしれないが)
だが、この時点では「ライブラリは何か」を知る事はなかったし、
それ以降も長い間、知る事なく過ごしてきた。
月日が経って2002年にライブラリと再会
状況が変化したのは2002年だった。
ライブラリとは何かを知らないため、トンチンカンな質問を
MLに投げた事がある。その例を紹介します。
PostgreSQLをインストールした後、PostgreSQLにアクセスする際の
言語としてC言語にするか、PHP言語にするか考えた。
この時、PHP言語は全く知識なく、C言語はif文、for文程度だったが、
まだ、知っている方だと思い、シーラカンス本の丸写しで
以下のC言語のソースを作ってみた。
投稿した内容 |
#include <stdio.h>
#include <postgres_fe.h>
#include <libpq-fe.h>
int main(void)
{
char *pghost ="", *pgport ="" , *dbName = "test" ;
PGconn *con;
con = PQsetdb(pghost,pgport,NULL,NULL,dbName);
}
|
そして、gccでコンパイルを行ったのだが
エラーが出てもうた (TT)
だった。
エラーの内容 |
gcc access.c -L/usr/local/pgsql/lib -I/usr/local/pgsql/include
/tmp/ccuntItK.o: In function `main':
/tmp/ccuntItK.o(.text+0x30): undefined reference to `PQsetdbLogin'
collect2: ld returned 1 exit status
|
なぜ、この時、エラーが出たのか、その当時はわからなかった。
そこで、Project-BLUEのMLに助けを求めた。
投稿したら、すぐに馬場さんからお返事を頂いた。
馬場さんからのお返事 |
>gcc access.c -L/usr/local/pgsql/lib -I/usr/local/pgsql/include
>
>/tmp/ccuntItK.o: In function `main':
>/tmp/ccuntItK.o(.text+0x30): undefined reference to `PQsetdbLogin'
>collect2: ld returned 1 exit status
>
>
> PQsetdbLogin の関数が見れないみたいです。
ええと、ライブラリのパスは指定してあっても、ライブラリの指定が
ありません。いま手元に pgsql が入ったマシンがないので、ライブラリ
の名前がわからないのですが、もしそれが、仮に、libabc.a とか libabc.so
だったとすると、コンパイル時には、
gcc access.c -L/usr/local/pgsql/lib -I/usr/local/pgsql/include -labc
のように、-labc をつけないといけないのでは。
|
続いて、わかとのさんから、お返事を頂いた。
わかとのさんからのお返事 |
わかとのっす。
> なぜ、ダメなのか、もし、よろしければ、
> 教えていただけませんでしょうか?
> ソースと、症状を下に書きました。
エラー自体は、「最後のリンク時点で解決できないシンボルがある」
というものですね。
馬場さんも書いてますが、これは PostgreSQL のクライアント向けの
ライブラリが -l 等で指定されてないってことです。
手元の環境でコンパイルだけチェックしてみましたが、
> gcc access.c -L/usr/local/pgsql/lib -I/usr/local/pgsql/include
は、
gcc access.c -L/usr/local/pgsql/lib -I/usr/local/pgsql/include -lpq
とかしてやるのが正しいかと。
|
馬場さん、わかとのさんのアドバイスで、PostgreSQLへのアクセスの際、
コンパイル時に「 -lpg 」というオプションを付ける必要がある事がわかった。
ふと思った。
postgreSQLのライブラリが「libpg」なので、「-lpg」をつける。
学生時代、UNIXでC言語で三角関数や対数関数などを使ったプログラムを
ccやgccでコンパイルを行う際に、「-lm」オプションをつける事を
教えてもらった事がある。
学生時代は、なぜオプションを付けるのか知らなかった。
なので、この時、少し調べてみたら、次の事がわかった。
数学の計算式を使う際に取り込むライブラリとしてlibm.a がある。
そのライブラリを取り込むのに「-lm」オプションが必要だという事がわかった。
(注意) |
この時、静的ライブラリと共有ライブラリを知らなかったため、
ライブラリファイルの拡張子は「a」と思い込んでしまった。
そのため、数学計算のライブラリは libm.a と思ったのだった。
|
ここまでの内容を読まれた方で、なんでヘッダーやライブラリの場所へ
パスを通しているのに、ライブラリの事がわかっていないのか
不思議に思われる方がおられると思います。
その理由は以下の通りです。
なぜ、パスを通す話を知っていたのか? |
gcc access.c -L/usr/local/pgsql/lib -I/usr/local/pgsql/include
/tmp/ccuntItK.o: In function `main':
/tmp/ccuntItK.o(.text+0x30): undefined reference to `PQsetdbLogin'
collect2: ld returned 1 exit status
|
青い部分は、ライブラリのあるディレクトリへのパスの指定。
赤い部分は、ヘッダーファイルのあるディレクトリへのパスの指定。
ライブラリが何なのかが全くわかっていなかったのに、
なぜ、パスを通すオプションを知っていたのか。
実は、学生時代に、UNIX上でソフトのインストールする際に、
makeでコンパイルしていた経験があるからだった。
もちろん、READMEなどの内容の丸写しだったが、READMEなどを読んでいる間に
コンパイルを行う際の「-L」オプションがライブラリへのパスで、
「-I」オプションがヘッダーファイルへのパスだという事を覚えた。
4年前なのでハッキリとした事は覚えていないため、推測になるが
最初、コンパイルをした時、オプションなしでコンパイルをしたが
エラーが出たので、なんとなく直感で、PostgreSQLのヘッダーと
ライブラリのディレクトリにパスを通せば、コンパイルが通ると
思ったため、オプションをつけたのだと思う。
|
だが、この時点では、まだライブラリが何かは全くわかっていなかった。
「プログラムをコンパイルする時に必要な物」という認識でしかなかった。
そのため、トンチンカンな疑問が生まれた。その疑問は何なのか。
それは、なんで数値計算のプログラムに、<math.h>のヘッダーファイルを
取り込む部分があるのに、その上、ライブラリを取り込む必要があるのか
謎に思えたのだった。
そんな事を思いついたのは、当時、以下のように考えたからだ。
「数値計算に関する部分はヘッダーファイルで定義されているから、
それ以外に取り込む物はないはず。なのでライブラリを取り込む理由が
わからない」だった。
今にして思えば、凄い誤解なのだが、当時は、<math.h>の
ヘッダーファイルを見て、ヘッダーファイルに数値計算に関する関数の定義が
されていると思い込んでいた。
実際に、<math.h>には、数値計算のためのマクロ定義があるだけに
関数などの定義などもヘッダーファイルで行っていると思い込んだ。
math.h ファイルの一部抜粋 |
/* Some useful constants. */
#if defined __USE_BSD || defined __USE_XOPEN
# define M_E 2.7182818284590452354 /* e */
# define M_LOG2E 1.4426950408889634074 /* log_2 e */
|
なので、PostgreSQLの話を書いた「システム奮闘記:その7」の初版では、
以下の文章を平気で書いていたのだった
この時、湧いてきた疑問 |
math.hのヘッダーファイルで数値計算の関数を定義しているから
ヘッダーファイルを #include で取り込めば良いのに、
なぜ、ライブラリまで取り込むのか、わからない。
という事は、一体、ライブラリって何なのか。
|
しかし、この時も「ライブラリとは何か」を調べる事なかった。
調べるだけの知識もなかった (^^;;
そして、2005年まで封印される事になった。
ところで、C言語にするか、PHP言語にするかの選択ですが、
この時、色々な方から「PHPの方を覚えた方が早い」というご意見を頂き、
PHP言語を選びました。
よくやくC言語のライブラリ(libc)を学ぶ
2005年1月、10年がかりでC言語のポイントを理解した。
詳しくは「システム奮闘記:その36」(C言語のポインタ)をご覧ください。
そこから、本格的に、C言語の勉強が動きだす。
なにせ、10年もの間、足踏を強いられてきたために「失われた10年」を
何が何でも取り戻さないといけない。
そこで勉強に使ったのが次の本だ。
「Linuxプログラミング」(葛西重夫訳:ソフトバンク出版)
通称、「目隠し本」と呼ばれる本なのだが、改訂版が出たため
表紙の部分が人の目隠しの絵でなくなった。
この本の最初の部分にライブラリの説明が書いてあった。
本に書いてあるライブラリの説明 |
ライブラリとは、再利用を目的として作成されたコンパイル済みの
関数の集合の事です。
|
そして、ライブラリには2種類ある事が書かれている。
静的ライブラリと共有ライブラリだ。もちろん・・・
2種類ある事なんぞ知らないもーん (^^)
そうなのです。
事務員なので、ライブラリが2種類ある事を知らなくても良いのだ!
さて本を見ると、ライブラリファイルの名前をつける際に、
以下のような形で、ファイル名の付け方の決まりがある。
ライブラリファイル名と名前の決まり |
|
三角関数などの数値計算のライブラリのファイル名が libm.a なので
「m」がライブラリの名前部分になるという。
なので、gccでコンパイルの際、「-l」のオプションで、
ライブラリを取り込む場合、「-l」の後ろにライブラリを指定するので
「-lm」となる。
もちろん、パスが通っている事が大事だ。
取り込みたいライブラリがあるディレクトリにパスが通っていない場合は
「-L/(ディレクトリ名)」をする必要がある。
|
おぼろげながら、ライブラリが何なのか見えてきた。
さて、ライブラリには2種類ある事を知った。
「静的ライブラリ」と「共有ライブラリ」の2種類だ。
まずは本の通りに「静的ライブラリ」から見ていく事にした。
内容は「ライブラリとは何か」を静的ライブラリの話を通して説明している。
さて、2つの関数を含む独自のライブラリを作る例題を触ってみる事にした。
fred.c |
#include <stdio.h>
void fred(int arg)
{
printf("fred: you passed %d\n", arg);
}
|
bill.c |
#include <stdio.h>
void bill(char *arg)
{
printf("bill: you passed %s\n", arg);
}
|
main()関数のない短い2つのプログラムだが、これをコンパイルして
オブジェクトファイルを作成する。
オブジェクトファイルを作る様子 |
suga@jitaku[~/pro/lib]% ls
bill.c fred.c
suga@jitaku[~/pro/lib]% gcc -c bill.c fred.c
suga@jitaku[~/pro/lib]% ls -l
合計 16
-rw-r--r-- 1 suga users 83 9月 19日 22:21 bill.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 bill.o
-rw-r--r-- 1 suga users 81 9月 19日 22:21 fred.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 fred.o
|
赤い部分のように、gccに「-c」のオプションをつけると、
オブジェクトファイルが作成できる。
青い部分は、生成されたオブジェクトファイルだ。
|
次にヘッダーファイルと本体のプログラムを用意する。
ヘッダーファイル ( lib.h ) |
void bill(char *);
void fred(int);
|
本体のプログラム ( program.c ) |
#include "lib.h"
int main()
{
bill("Hello World");
exit(0);
}
|
そして、 program.c ファイルからオブジェクトファイルを生成する。
オブジェクトファイルを作る様子 |
suga@jitaku[~/pro/lib]% ls
bill.c bill.o fred.c fred.o lib.h program.c
suga@jitaku[~/pro/lib]% gcc -c program.c
suga@jitaku[~/pro/lib]% ls -l
合計 16
-rw-r--r-- 1 suga users 83 9月 19日 22:21 bill.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 bill.o
-rw-r--r-- 1 suga users 81 9月 19日 22:21 fred.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 fred.o
-rw-r--r-- 1 suga users 81 9月 19日 22:21 program.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 program.o
|
赤い部分のように、gccに「-c」のオプションをつけると、
オブジェクトファイルが作成できる。
青い部分は、生成されたオブジェクトファイルだ。
|
そして、gccに「-o」オプションを使って、オブジェクトファイルを
合体させて、実行ファイルを作成する事ができる。
実行ファイルを作る様子 |
suga@jitaku[~/pro/lib]% ls
bill.c bill.o fred.c fred.o lib.h program.c program.o
suga@jitaku[~/pro/lib]% gcc -o program.o bill.o
suga@jitaku[~/pro/lib]% ls -l
合計 16
-rw-r--r-- 1 suga users 83 9月 19日 22:21 bill.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 bill.o
-rw-r--r-- 1 suga users 81 9月 19日 22:21 fred.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 fred.o
-rw-r--r-- 1 suga users 35 9月 19日 23:25 lib.h
-rw-r--r-- 1 suga users 764 9月 19日 22:25 program*
-rw-r--r-- 1 suga users 81 9月 19日 22:21 program.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 program.o
|
赤い部分のように、gccに「-o」のオプションをつけると、
オブジェクトファイル同士が合体し、実行ファイルを作る事ができる。
青い部分は、生成された実行ファイルだ。
|
ここまでの流れだと、個々にコンパイルをして、オブジェクトを作成し、
その後、合体させただけに思える。
だふぁ、fred.c と bill.c の関数を合体させたオブジェクトファイルを
ar コマンドで、ひとまとめにする事ができるという。
ar コマンドでオブジェクトファイル同士の合体させる様子 |
suga@jitaku[~/pro/lib]% ls
bill.c bill.o fred.c fred.o lib.h program.c program.o
suga@jitaku[~/pro/lib]% ar crv lib.a bill.o fred.o
a - bill.o
a - fred.o
suga@jitaku[~/pro/lib]% ls -l
合計 40
-rw-r--r-- 1 suga users 83 9月 19日 22:21 bill.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 bill.o
-rw-r--r-- 1 suga users 81 9月 19日 22:21 fred.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 fred.o
-rw-r--r-- 1 suga users 35 9月 20日 23:25 lib.a
-rw-r--r-- 1 suga users 35 9月 19日 23:25 lib.h
-rw-r--r-- 1 suga users 81 9月 19日 22:21 program.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 program.o
|
青い部分は、オブジェクトファイル fred.o と bill.o を合体させる部分。
赤い部分は、生成された合体ファイルだ。
|
上の赤い部分で指しているファイルの事を
ライブラリと呼ぶ!
と本に書いてあった。
だが、これではピンとこない。
本に従って、生成した lib.a のライブラリファイルを使って
コンパイルを行う。
本体の program.c のオブジェクトファイルがあるので
program.o と lib.a を合体させて実行ファイルを生成する。
ar コマンドでオブジェクトファイル同士の合体させる様子 |
suga@jitaku[~/pro/lib]% ls
bill.c bill.o fred.c fred.o lib.a lib.h program.c program.o
suga@jitaku[~/pro/lib]% gcc -o program program.o lib.a
suga@jitaku[~/pro/lib]% ls -l
合計 40
-rw-r--r-- 1 suga users 83 9月 19日 22:21 bill.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 bill.o
-rw-r--r-- 1 suga users 81 9月 19日 22:21 fred.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 fred.o
-rw-r--r-- 1 suga users 35 9月 20日 23:25 lib.a
-rw-r--r-- 1 suga users 35 9月 19日 23:25 lib.h
-rwxr-xr-x 1 suga users 4468 9月 21日 22:20 program*
-rw-r--r-- 1 suga users 81 9月 19日 22:21 program.c
-rw-r--r-- 1 suga users 764 9月 19日 22:21 program.o
|
赤い部分は、program.o と lib.a を合体させて実行ファイルを生成する部分。
青い部分は、生成された実行ファイル。
|
少しづつ見えてきた。
lib.a は fred.c と bill.c で定義した独自関数をひとまとめにしている。
要するに、1つの関数に、1つのオブジェクトファイルにしていたら、
関数ごとにオブジェクトファイルを用意しないといけなくなる。
なので、ひとまとめにすると便利だ。
その時、ライブラリを作成する長所を、次のように考えた。
ライブラリの長所を次のように考えた |
例えば、三角関数や指数関数などの数学の計算を行う関数は
関数ごとに、1個、1個、バラバラにオブジェクトファイルがあると
取り込む際に手間になるのだが、ライブラリという
ファイルパッケージにしてしまえば、コンパイルの時、便利だ。
|
なんとなくライブラリが何かがわかった感じだったが、
この時は、単にオブジェクトファイルの集合としか捉えていなかった。
ところで、実行ファイルが、どの関数で構成されているのかを見る
コマンドがあるという。nmコマンドだ。
さきほど作成した program の実行ファイルで見てみる事にした。
nm コマンドで実行ファイルを構成している関数を見る |
suga@jitaku[~/pro/lib]% nm program
08049484 D _DYNAMIC
08049560 D _GLOBAL_OFFSET_TABLE_
0804844c R _IO_stdin_used
w _Jv_RegisterClasses
08049550 d __CTOR_END__
(途中省略)
080482ac T _start
08048388 T bill
080482d0 t call_gmon_start
0804957c b completed.1
08049474 W data_start
U exit@@GLIBC_2.0
08048330 t frame_dummy
0804835c T main
0804947c d p.0
U printf@@GLIBC_2.0
suga@jitaku[~/pro/lib]%
|
青い部分は、独自に作成した bill()関数だ。
lib.a のライブラリは bill()関数と、fred()関数で構成されているが、
program.c のコンパイルの際、program.c は bill()しか使っていないので、
必要なbill()関数だけを取り込んでいる。
この事から、ライブラリを取り込む場合、本体のプログラムで必要な関数だけを
取り込むようになっている。
|
本を読み進めると、もう1つの「共有ライブラリ」の話になる。
最初に、静的ライブラリの欠点について書いていた。
まずは、静的ライブラリの欠点を読んで、次のように思った。
静的ライブラリの場合、コンパイルの際、実行ファイルの中に
ライブラリが取り込まれる。
図にすると以下のような感じになる。
静的ライブラリの場合、実行ファイルの中に取り込む |
|
そのため、同じライブラリを複数のプログラムが使う場合、
個々のプログラムが、同じライブラリを取り込む事になる
図にすると以下のようになる。
個々のプログラムが同じライブラリを持つ場合 |
|
個々のプログラムが同じライブラリを持つため、
ライブラリの容量 × プログラムの数 だけディスク容量を食う事になる。
もし、ライブラリが大きい場合、より多くのディスクを食うため、
もったいない。
しかも、この問題が起こった時代は、今のように何十ギガ単位で
ディスクが使える時代と違い、1Mぐらいでも貴重で、
かつ、高価な時代なだけに、切実な問題と言える。
|
そこでディスク容量の節約のために作られたのが「共有ライブラリ」だという。
共有ライブラリの場合、実行ファイルの中には取り込まない |
|
上図のように、共有ライブラリを使うと、コンパイルの際に
実行ファイルの中にライブラリが取り込まれる事はない。
実行ファイルと、ライブラリが分離している状態だ。
分離する事の長所を図にすると以下の通りになる、
共有ライブラリの長所 |
|
個々のプログラムは、ライブラリを取り込まない。
ライブラリは、複数のプログラムが共有して使うため、
ライブラリは1ヶ所に置いておけば良いのだ
その分、静的ライブラリと違い、ディスクの節約につながるのだ
|
本当にディスクの節約になるのだろうか。
この時は、調べる術を知らなかった。その前に・・・
gccでコンパイルの際、どっちのライブラリを使っているかすら
知らなかったのらー!!
という事で、この時点では「へぇ〜」と思った程度だった。
ここまで読まれた方の中で「ディスクだけでなく、メモリの節約もあるぞ」
という声があるかもしれません。
この時、メモリの節約の話が出ていましたが、何故、メモリの節約になるのか
わかりませーん!! (^^)
だった。
この時、2点、疑問ができた。
疑問 |
(1) |
本当に、共有ライブラリはディスクの節約になるのか |
(2) |
何故、共有ライブラリはメモリの節約になるのか |
この疑問が解決できるのは、だいぶ後になってからだった。
標準ライブラリの存在を知る
その後、ちょこちょことC言語などを触ったりしていくにつれ、
ライブラリが何なのかが、わかってくる。
要するに、C言語のライブラリとは、C言語で使われている関数を
定義しているファイルなのだ。
printf()関数やfgets()関数など、基本的な関数はライブラリがなくても
コンパイルができると思っていたのだが、実は、標準ライブラリを
取り込んでいる事を知った。
標準ライブラリを活用している様子 |
|
そして、普通ならライブラリを取り込む際に、「-l(ライブラリ名)」という
オプションをつけるのだが、標準ライブラリは例外でオプションを
付ける必要がない事を知った。
なので、printf()関数やfgets()関数などの入出力程度のプログラムなら
gccにライブラリを取り込むオプションが不要だというわけだ。
この話は全く気がつかなかっただけに、新しい発見だった!
Linuxの場合、glibcという標準ライブラリを使っている。
標準ライブラリにある関数は、Linuxが提供する関数(システムコール)を使い、
関数を定義している。
今まで、C言語の関数は、独自関数を除けば、コンパイラのgccが全て
アセンブラに変換してくれると思い込んでいた。
そのため、ライブラリに関数が定義されているとは想像もしなかった。
回り道をしながら2つの疑問の謎解き開始!
さて、目隠し本を読み続けるが挫折する。
そこで「急がば回れ」で、基礎体力をつけようと考えて、
OSやCPUの勉強を始めた。
2005年8月に、以下の本を読む事になった。
「はじめて読む8086」(蒲地輝尚 著、村瀬康治 監修:アスキー出版)
この時、初めてプログラムを実行する際、メモリ上に実行ファイルを
読む込む事を知った!
プログラム実行する前にメモリに読み込まれる |
|
486CPUのマシンや、Pentiumマシンの場合は、仮想メモリの概念が出てきて
実際のメモリの中に全部格納するわけではないのだが、
ここでは触れない事にします。
|
だが、この時点では、まだ課題(2)と結びつけて考えなかった。
というよりも、8086CPUを理解するのに必死で、すっかり課題(2)の事を
忘れていたのだった (^^;;
ところで、疑問(1)、(2)だけでなく、そもそも「ライブラリとは何か」の
疑問も残っていた。
そんな状態を引きずりながらも、2005年10月、次の本を読んだ。
「C言語 入門書の次に読む本」(坂井 弘亮:技術評論社)
この本はC言語でのプログラム開発の手法について
「再利用可能」とか「テスト・デバッグの短縮」と書いてあったも
意味がわからへんなぁ。
だった。
この時、ライブラリが便利な理由を知識として得たのだが、
実体験がないだけに、いまいちピンと来ない。
それからズルズル1年近く過ごすのだった。
本の知識だけでは、なかなか実感としてライブラリが何なのかが
湧いてこない。
だが、仕事でC言語でプログラム開発をする機会がない私には、
実感として湧いて来る機会がないのだ。
だが、2006年7月、ひょんな事から、ハッキリとライブラリが
何なのかが見えたのだった。
それは、C言語ではなく、PHP言語での事だった。
今まで社内で構築していたシステムだが、PHP3でプログラムをしていた。
それをPHP4に乗り換えるついでに、ソースの全面書き換えを行う事にした。
PHPのソース。今までは独自の関数を、ほとんど作る事もなく、
if文、for文などで処理を書いていた。
だが、同じソースの中で、長々と処理を書いていたのでは、
読みずらいソースになってしまう。
そこでユーザー定義関数の部分は別ファイルに記述しておいて
本体ファイルの方で require()を使って取り込めば、ソースがすっきりする。
ユーザー定義関数の部分を別ファイルに記述する |
|
ユーザー定義関数の部分を別ファイルに記述すれば、
本来のプログラムの部分がすっきりする。
この方法は、Pukiwkiなどのプログラムを見て、
「こりゃ便利だ!」と思い、採用しました。
Pukiwikiに関しては「システム奮闘記:その52」の
「Pukiwikiを使ってCMS導入」をご覧ください。
あと、処理を関数化する利点としては、関数なしで記述すると、
変数名の衝突がないように変数名を考えなくてはならない。
衝突があった場合、誤作動を起こす原因になるからだ。
だが、関数の中で変数を宣言すると、ローカル変数なので、
関数の外の変数名と衝突が避けられる。
変数名を色々考える煩わしさから解放される。
|
ふと思った。
ユーザー定義関数を記述したファイルだが、別のプログラムでも使えば
わざわざ個々のプログラムで処理を書かなくても良くなる。
いくつかの複数のPHPのプログラムで、共通した処理もある。
しかも、今後、PHPのプログラムを書く時に、同じ処理が必要な場合は
そのファイルを活用する事ができる。
というわけで、以下のように複数のプログラムで、ユーザー定義関数を
記述したファイルを共有する事にした。
ユーザー定義関数のファイルを複数のプログラムで共有化 |
|
ユーザー定義関数の部分を別ファイルにする事によって、
他のプログラムでも利用可能になる。
ユーザー定義関数のファイルの共有化というわけだ。
|
ふと気づいた。
これってライブラリやん!
例え、ユーザー定義関数が1つしか記述していないファイルであっても、
関数の集合ファイルといっても間違いないはず。
ここでハッキリ見えたのは、ライブラリの活用による開発効率のアップだった。
つまり、一度、独自関数を作ってしまえば、他のプログラムにも使える。
わざわざ、同じ記述や似たような記述を、プログラムの度に
行う必要もない。
その上、最初に独自関数を作った際、テストやデバックを行っているので
他のプログラムに活用する際は、その部分のテストやデバッグの手間がなくなる。
その上、独自関数の中身を改良した場合の変更が楽になる。
ライブラリ化をすれば変更が楽になる! |
|
ユーザー定義関数に変更がある場合、ライブラリ化をしておくと
変更するのがライブラリファイルだけになり、変更が楽になる。
もし、個々のファイルに同じユーザー定義関数を埋め込んでいて
変更が必要になった場合は、個々に変更が必要な上、
変更漏れが起こる可能性がある。
|
これだとプログラムの管理や修正が楽になる。
ところで、プログラム開発の効率アップを、C言語で置き換えた考えてみた。
三角関数など数学計算に使うプログラムを複数作る場合だ。
数学のライブラリを使ってプログラムを作る場合 |
|
数学計算のライブラリは、libm.a か libm.so のどちらかを使う。
片方が静的ライブラリで、もう一方は共有ライブラリだ。
どっちを使えば良いのかは、後述しています。
|
上図のように、ライブラリを使えば、わざわざ三角関数の計算を行う
sin()、cos()関数を定義を行わなくても良い利点がある。
しかも、作成者がsin()、cos()関数のテストやデバッグを行っているため、
わざわざ関数の動作検証を行わなくても良い利点もある。
ふと学生時代に、教授や院生から言われた言葉を思い出す。
FORTRANは過去のライブラリが豊富だから、FORTRANがなくなる事はない
FORTRANの数値計算の処理部分は、過去に作られ長年に渡り改良さて
今に至っている。わざわざ独自で処理部分を作らなくても
過去の蓄積を利用すれば良いし、もし、独自で作成するとなれば、
テストやデバッグに追われ、本来の数値計算の目的が行えなくなる。
同様に、何気なく使っているC言語の関数だが、それらの関数は先人が開発して
テスト・デバッグをした物を使っているため、使う方としては
独自に関数の作成を行う必要もなければ、テスト・デバッグを行う必要性もない。
今にして先人のありがたみを感じる今日この頃。
本当は断言するのは良くないのだが・・・ |
ライブラリがあるお陰で、テスト・デバッグが不要と書きました。
この事について「glibcはバグがあるぞ」とか「確認は必要」との
声があると思います。
ここでは、あくまでもライブラリがキチンとテスト・デバッグ済み
という前提で、ライブラリの利点を紹介しています。
|
ライブラリを活用する長所・短所が見えてきた。
なので
これはネタに使えると思った私
早速、ライブラリの話をネタにするため、「詳解LINUXカーネル」や
「Linuxプログラミング」(目隠し本)を開いてみる。
すると、残っていた2つの疑問も解決できた。
疑問 |
(1) |
本当に、共有ライブラリはディスクの節約になるのか |
(2) |
何故、共有ライブラリはメモリの節約になるのか |
まずは、(1)の疑問からみていく事にします。
静的ライブラリは、実行ファイルにライブラリを抱えこむが、
共有ライブラリは、実行ファイルには取り込まない。
静的ライブラリと共有ライブラリだが、比較したくても、
どう比較して良いのやら、わからない。
そんな中、「詳解LINUXカーネル」には、近代的なUNIXでは
共有ライブラリを使う。
だが、gccのコンパイルの際に「-static」を付けると、
共有ライブラリではなく、静的ライブラリを使うと書いている。
「どういう事かいな?」と思いつつ、ライブラリのあるディレクトリ
/usr/lib を見てみる。
ライブラリのあるディレクトリ /usr/lib (Plamo Linux4.0の場合) |
suga@jitaku[/usr/lib]% ls
libc.a libimlib-xpm.so* libttf.so.2@
libc.so libisapnp.a libttf.so.2.2.0*
libc_nonshared.a libisc.a libungif.a
|
赤い部分が標準ライブラリの静的ライブラリ版
青い部分が標準ライブラリの共有ライブラリ版
|
同じライブラリでも、静的と共有の両方を用意している事なんぞ
知らなかったのらー!!
どうやらライブラリは静的と共有の2つあって、普段は共有ライブラリを使い
「-static」オプションを付けると、静的ライブラリが使われるようだ。
それを見てみるため、次のソースをコンパイルしてみた。
ソース ( test1.c ) |
#include <stdio.h>
int main(void)
{
printf("Hello\n");
return(0);
}
|
まずは普通に gcc でコンパイルしてみる。
この場合、共有ライブラリを取り込む形になる。
すると、ファイルの大きさは以下のようになった。
普通に gcc でコンパイルした結果 |
suga@jitaku[~/pro/lib2]% gcc test.c -o test
suga@jitaku[~/pro/lib2]% ls -l
合計 12
-rwxr-xr-x 1 suga users 4253 9月 24日 12:04 test*
-rw-r--r-- 1 suga users 77 9月 24日 12:02 test.c
suga@jitaku[~/pro/lib2]%
|
できた実行ファイルの大きさは 4253バイトだった。約 4K バイト。
次に静的ライブラリを取り込むように、gccに「-static」オプションをつける。
すると、以下の通りになった。
gcc に「-static」をつけてコンパイルした結果 |
suga@jitaku[~/pro/lib2]% gcc test.c -o test -static
suga@jitaku[~/pro/lib2]% ls -l
合計 432
-rwxr-xr-x 1 suga users 431305 9月 24日 12:08 test*
-rw-r--r-- 1 suga users 77 9月 24日 12:02 test.c
suga@jitaku[~/pro/lib2]%
|
なんと 431305バイト、約431Kバイトだった!
共有ライブラリを使う時よりも、100倍もデカいファイルになっている。
エグイ! エグイのらー!
これを見れば、共有ライブラリがディスクの節約になるのは、一目瞭然だ。
特に、今の時代みたいに何十ギガ単位でハードディスクが安く買えたら良いが
共有ライブラリができた時代を考えたら、数メガでも高価な時代。
何が何でもディスクの節約が迫られた状況が垣間見える
さて、疑問(2)のメモリの節約の話をします。
この課題が出来た時点では、実行ファイルがメモリ上に読み込まれる事は
知らなかったが、その後、知る事になる。
486系のCPUの場合、以下のような感じで、実行ファイルが
メモリ上に読み込まれる。
実行ファイルがメモリ上に読み込まれる様子 |
|
上図は、あくまでも論理メモリで見た場合なのだ。
複数のプロセスが動いているOSでは、物理メモリで考えると、わかりやすい。
もし、静的ライブラリを使った複数のプログラムが同時に稼働していると
物理メモリ上の様子は以下のようになる。
物理メモリ上の様子 |
|
静的ライブラリは、実行ファイルの中に取り込まれる。
そして、実行ファイルが稼働する際、そのファイルはメモリ上に
読み込まれるため、上図のように重複してライブラリが取り込まれる。
メモリをライブラリのために占有してしまう事になる。
しかし、同じ共有ライブラリを使ったプログラムを実行させると
以下のようになる。
物理メモリ上の様子
(共有ライブラリを使ったプログラムの場合) |
|
左は静的ライブラリの場合で、右は共有ライブラリの場合だ。
共有ライブラリもメモリ上に読み込まれるのだが、1ヶ所に置かれるため、
重複してメモリを占有する事はない。
そのためメモリの節約になる。
|
さきほどの静的ライブラリと共有ライブラリでコンパイルした時の
ファイルの大きさの違いを考えると、重要な意味を持つ。
昔は、ハードディスクも高かったが、メモリも高価だった。
それに、1Mのメモリを搭載させるだめでも、何十万や何百万の時代もあった。
今のように、十何万のパソコンで1ギガのメモリ搭載している時代とは
大きく違うのだ。
そういう事を考えると、静的ライブラリを使うと
エグイほどリソースを食うのだ!
静的ライブラリは、昔は、非常に金食い虫だったのだ。
そんな背景があるからこそ、gccでコンパイルする時は、
共有ライブラリを使う事がわかった。
もう1つ共有ライブラリを使う利点がある。
それはライブラリを改良した時だ。
静的ライブラリの場合、コンパイル時に、実行ファイルに取り込まれる。
そのため、ライブラリを改良して、それを使っているプログラムに
反映させる場合は、該当のプログラムを、1個づつコンパイルを行う必要がある。
静的ライブラリの場合 |
|
上図のように、ライブラリを改良して、それを使う場合、
個々のプログラムのコンパイルを行って、新しいライブラリを
取り込む必要があるため、手間が発生する。
プログラムが多い場合は、コンパイルをし忘れる事も起こり得る。
|
だが、共有ライブラリの場合は、実行ファイルに取り込まれないため
ライブラリのあるディレクトリで、ライブラリの交換だけ行えば済む。
共有ライブラリの場合 |
|
共有ライブラリには、ライブラリを改良した場合に、
ライブラリを交換するだけで済む利点がある。
これは大きな利点だ!
静的ライブラリの存在意義と共有ライブラリの問題点
だが、ここで疑問が残った。
共有ライブラリを使えば、静的ライブラリは不要になっても、
おかしくないのに、なぜ、静的ライブラリが残っているのかだった。
実は、共有ライブラリにも欠点があるのだ。
それは共有ライブラリの長所が、短所になるという物だ。
先ほど、ライブラリが新しくなっても、共有ライブラリの場合、
ライブラリを置き換えるだけで良いと書きました。
この長所が、厄介や短所を生む原因なのだ。それは
ライブラリ依存の問題
なのだ。
もし、ライブラリの中に定義している関数の仕様を変更したらどうなるか。
今までのライブラリの中の関数との整合性がなくなり、使えなくなる
問題が発生する。
共有ライブラリの欠点 |
|
ライブラリの中の関数の仕様変更は、色々ありますが、
関数の引数の数が変わったりすると、前のライブラリと
新しいライブラリの関数との間で整合性がなくなります。
|
前のライブラリの関数と、新しいライブラリの関数の整合性がないために
ソフトの不具合が生じたりするのだ。
厄介なのは、実行ファイルだけでなく、共有ライブラリ自身が、
他の共有ライブラリの関数を利用している場合も多々ある。
つまり、共有ライブラリが他の共有ライブラリに依存しているのだ。
共有ライブラリが、他の共有ライブラリに依存 |
|
さて、実行ファイルや共有ライブラリが、その共有ライブラリに
依存しているかを見るコマンドがある。
ldd コマンドだ。
そこで、以下のソースの実行ファイルで試してみる。
プログラム ( test2.c ) |
#include <stdio.h>
#include <math.h>
int main(void)
{
double a ;
a = sin(3.14159/2.0);
printf("a = %1.5f \n",a);
return(0);
}
|
sin()関数があるので、数学のライブラリを取り込む必要がある上、
printf()関数という標準ライブラリも使っている。
少なくとも、2つの共有ライブラリを使っている事が、この時点でわかる。
さて、コンパイルをしてプログラムを実行させる。
プログラムの実行と依存しているライブラリ |
suga@jitaku[~/pro/lib2]% gcc test2.c -o test2 -lm
suga@jitaku[~/pro/lib2]% ./test2
a = 1.00000
suga@jitaku[~/pro/lib2]% ldd ./test2
libm.so.6 => /lib/libm.so.6 (0x4001b000)
libc.so.6 => /lib/libc.so.6 (0x4003e000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
suga@jitaku[~/pro/lib2]%
|
上の結果、予想通り、実行ファイルが、数学のライブラリ(libm.so)と
標準ライブラリ(libc.so)に依存している事がわかる。
他にも ld-linux.so ライブラリにも依存している。
lddコマンドは、共有ライブラリが、どの共有ライブラリに、
依存しているのか見る事もできる。
共有ライブラリが、他の共有ライブラリに依存している様子 |
suga@jitaku[/usr/lib]% ldd libpam.so
libc.so.6 => /lib/libc.so.6 (0x40012000)
libdl.so.2 => /lib/libdl.so.2 (0x4012b000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000)
|
PAMに関するライブラリを見てみたら、3つのライブラリに依存している事が
上の結果からわかる。
|
共有ライブラリの中には、複数の共有ライブラリに依存している物があるのだ。
図にすると以下の感じだ。
共有ライブラリが、他の複数の共有ライブラリに依存 |
|
上図では、共有ライブラリが、複数のライブラリに依存している事を
示したのだが、数個の依存なら、まだ良い方かもしれない。
ApacheとPHPを連動させて動かすには、PHPのモジュール(共有ライブラリ)を
Apacheが取り込む形になっている。
そこで、PHPのモジュールそのものが、どの共有ライブラリに依存しているのか
見てみる事にした。
PHPのモジュールの依存関係 |
[suga@server libexec]# ls
httpd.exp libphp4.so
[suga@server libexec]# ldd libphp4.so
libcrypt.so.1 => /lib/libcrypt.so.1 (0xb7c53000)
libpq.so.3 => /usr/local/pgsql/lib/libpq.so.3 (0xb7c41000)
libpdf.so.2 => /usr/local/lib/libpdf.so.2 (0xb7b8b000)
libz.so.1 => /usr/lib/libz.so.1 (0xb7b7d000)
libtiff.so.3 => /usr/lib/libtiff.so.3 (0xb7b3a000)
libpng.so.2 => /usr/lib/libpng.so.2 (0xb7b19000)
libjpeg.so.62 => /usr/lib/libjpeg.so.62 (0xb7afb000)
libresolv.so.2 => /lib/libresolv.so.2 (0xb7ae9000)
libm.so.6 => /lib/i686/libm.so.6 (0xb7ac7000)
libdl.so.2 => /lib/libdl.so.2 (0xb7ac4000)
libnsl.so.1 => /lib/libnsl.so.1 (0xb7aaf000)
libc.so.6 => /lib/i686/libc.so.6 (0x42000000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000)
[suga@server libexec]#
|
多くのライブラリに依存している事がわかる。
もし、どこかのライブラリが新しくなり、前のライブラリとの
整合性がなくなった場合、間違いなく、このPHPのモジュールは
不具合を起こしたり、場合によっては、動かなくなる。
共有ライブラリが、他の複数の共有ライブラリに依存している事が
あるのがわかった。
場合によっては、以下の図のように依存関係が複雑に絡む場合もある。
共有ライブラリが、他の共有ライブラリに複雑に依存 |
|
上の図のように複雑な場合、どこか1ヶ所、整合性が保てなくなると
不具合が生じたりする。
ライブラリの更新の際に、どのライブラリが更新されたのかを
把握していないと、不具合が生じた時に、原因の究明に時間がかかったり、
謎になったりすると思う。
そう考えると
安易なライブラリの更新は危険!
と言える。
特に、標準ライブラリに至っては、全てのライブラリが依存している。
そのため、下手に標準ライブラリ(glibc)のバージョンアップをすると
全てのライブラリに影響が出るため
安易にバージョンアップはできないのらー!!
なぜ、静的ライブラリが現役なのか。
それは、静的ライブラリの場合は、既にプログラムの中にライブラリが
取り込まれているため、取り込まれたライブラリを使い続ける。
そのため、ライブラリ依存の問題が起こらない。
なので、静的ライブラリが存続している理由の1つには
ライブラリ依存の問題が起こらないプログラムにするためもある
なるほど!
これで静的ライブラリの存在意義がわかった!
だが、Linux(UNIX系)の場合、複雑といっても、クモの巣のような
ライブラリの依存関係があるわけではない。
大雑把に書くと、以下の図のような感じで、標準ライブラリや
システムコールを土台にして、共有ライブラリや、ソフトが乗っている感じだ。
Linuxの場合のライブラリとソフトの関係 |
|
Apache、Postfix、BINDなどのLinux上で動くプログラムは
システムコールと標準ライブラリを呼び出して動いている。
もちろん、いくつかのライブラリを使っている。
だが、Apacheのバージョンを上げたからといって、
利用しているライブラリや、glibcの中身や仕様を変更する事はないし、
ましてや、システムコールの中身や仕様が変わるわけもない。
そのため、あるソフトがバージョンを上げても、
そのソフトが提供しているライブラリを使っているソフトを除けば
他のソフトには不具合が生じる問題が起こらない。
|
こんなわけで、Linux上でApacheのバージョンを上げたからといって、
別に、PostfixやPostgreSQLに不具合が生じるわけではない。
だが、Windowsの場合は、ソフトをバージョンアップすると
Linux(UNIX系)と違って、共有ライブラリが引き起こす厄介な問題が
発生する事もある。
この問題の事を
コンポーネント指向の問題
と呼んでいる。
Windowsでいう共有ライブラリのファイルは、DLLファイルにあたる。
静的ライブラリは、LIBファイルにあたる。
Linux(UNIX系)のシステムと同様に、DLLファイルも、他のDLLファイルに
依存しているのだが、Windowsの方が根深い問題になっている。
Linux(UNIX系)の場合は、土台がシステムコールと標準ライブラリ(libc)が
土台になっている。
だが、Windowsの場合は土台がなく、DLLファイルの集合体で構成されている。
その上、クモの巣のような依存関係にある。
Windowsの場合のコンポーネントとソフトの関係 |
|
クモの巣のように複雑に依存関係が入り組んでいるため、
どこか1ヶ所が変わると、全く関係ないと思われるソフトに
不具合が生じたりする。
その上、ソフトをバージョンアップすると、土台になっている
コンポーネントのバージョンまで変わる。
そのコンポーネントに依存している、他のコンポーネントや
ソフトに影響が出てくる。
|
|
Microsoftですら、どの組み合わせが良くで、どれが悪いのか
全く把握できていないため「テストは念入りに」という始末だ。
WindowsUpdateの際は、念入りにテストする必要があると言われている。
|
非常に厄介な問題だ。
私自身、Windows2000Proの入ったノートパソコンがSP2だったのを
SP4をいれた所、今までインストールしていたソフトに不具合が出たり
急にカウントダウンの画面が出てきて、勝手に電源が切れたりする問題が
発生した事があり、うんざりした事があった。
ところで、Linux上にソフトやライブラリのインストールを行う際
Windowsと同様に、ライブラリ依存関係で問題がありそうな場合が出てくる。
RPMでの場合は、エラーを出してインストールできないようになっている。
具体例として、RedHat7.3のマシンに、php-4.2.2-17.i386.rpm
をインストールする場合だ。
エラーの内容 |
[root@server]# rpm -ihv php-4.2.2-17.i386.rpm
エラー: 依存性の欠如:
httpd-mmn = 20020628は php-4.2.2-17 に必要とされています
libc.so.6(GLIBC_2.3) は php-4.2.2-17 に必要とされています
libcrypto.so.4 は php-4.2.2-17 に必要とされています
libdb-4.0.so は php-4.2.2-17 に必要とされています
libgcc_s.so.1 は php-4.2.2-17 に必要とされています
libpng12.so.0 は php-4.2.2-17 に必要とされています
libssl.so.4 は php-4.2.2-17 に必要とされています
libstdc++.so.5 は php-4.2.2-17 に必要とされています
|
初めてRPMを使って、この手のエラーが出た時は、エラーの意味すらわからず
なんでやねん (TT)
と思ったのだが、今となっては不具合を未然に防ぐありがたい存在だ。
RPMの場合は、用途によっては、そのソフトやライブラリが
不必要なライブラリに依存している場合もある。
不要なライブラリに依存しないようにするには、ソースコンパイルが一番だ!
configureのオプションを使って、不要な機能を省けば、不要なライブラリに
依存する問題は起こらなくなる。
この事からソフトのバージョンアップを考える上では、Windowsよりも
Linux(UNIX系)の方が安心して行えるという事が言える。
上の事から、MS嫌いの私は「お決まり文句」として
うふふ、Windowsの弱点を突いたぞ ( ̄∀ ̄)
と言ってみる。
余談!
ある日の事、何気なくlddコマンドを使って、意味もなく
「libgdk_imlib.so」ライブラリが依存している先のライブラリを見てみた。
すると以下の結果が出てきた。
結果の内容 |
[suga@server]% ldd libgdk_imlib.so
libSM.so.6 => /usr/X11R6/lib/libSM.so.6 (0xb7f67000)
libICE.so.6 => /usr/X11R6/lib/libICE.so.6 (0xb7f4f000)
libXext.so.6 => /usr/X11R6/lib/libXext.so.6 (0xb7f42000)
libglib-1.2.so.0 => /usr/lib/libglib-1.2.so.0 (0xb7f1e000)
libc.so.6 => /lib/i686/libc.so.6 (0x42000000)
libX11.so.6 => /usr/X11R6/lib/libX11.so.6 (0xb7e49000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000)
libdl.so.2 => /lib/libdl.so.2 (0xb7e46000)
|
上の結果を見て
SMライブラリって何やねん!
と思った。
くだらん連想をするのが得意な私は、どんどん怪しい方向へ想像を膨らます。
このライブラリを使えば、網タイツを履いたお姉さんが出てきて
女王様とお呼びよ!
と叫ぶプログラムが動くのだろうか。
それとも、以下の図のようなペンギンが出てくるのかも
こんなペンギンが登場するかも |
|
ロウソクと鞭を持ち、網タイツを履いたペンギンがお決まり文句の
「女王様とお呼びよ!」と叫ぶシーンを画面に出すための
関数群を揃えているのではと妄想してしまう。
|
うーん、こんな話を書いたため、システム奮闘記が18禁のサイトになったら
ギャグだなぁ・・・。
まとめ
ライブラリとは何か。
長年、耳にはしていたため言葉としては知っていたが、
実際には、どういう物かはわかっていませんでした。
C言語の関数は、コンパイラのgccが全てアセンブラに変換してくれると
思い込んでいたため、ライブラリを取り込む理由などが、わかっていませんでした。
しかし、関数は過去から集積された資産である事を知ると、
先人のお陰で便利な関数が使えるのだなぁと思うようになりました。
それと同時に、いかに独自ライブラリを活用したら、プログラム開発が
楽になるのかを考えるようになりました。
この話が新人プログラマーにとって、お役に立てれば幸いです。
次章:「hdparmコマンドの使い方」読む
前章:「PukiwikiでCMS。ホームページ更新も楽々」読む
目次:Linux、オープンソースで「システム奮闘記」に戻る