FreeBSD 5.4のconfig(8)を読んだ。config(8)は組込むドライバやオプション等を記述したカーネル設定ファイルを読み、それに従ってコンパイルに必要なファイルを生成するプログラムである。
config(8)を実行しカーネルをコンパイルする手順は次の例のようになる。現在のFreeBSDではmake buildkernelが推奨手順であるが、その中でもこの手順は行われている。
# cd /sys/i386/conf # config MYKERNEL Kernel build directory is ../compile/MYKERNEL Don't forget to do a ``make depend'' # cd ../compile/MYKERNEL # make depend # make
生成するファイルの雛形(Makefile.arch)や、生成・コンパイルするファイルの情報(files, files.arch、指定可能なオプションのリスト(options, options.arch)等は/sys/confにおかれている。config(8)はファイル生成時にこれらの情報を参照する。
ソースファイルの構成
config(8)は小さなプログラムで、4つの.cファイル、2つのヘッダファイル、加えてlang.lとconfig.yから成る。
config(8)の作者はカーネル設定ファイルのためのわかりやすい文法を設計した。lang.lとconfig.yはそのための字句解析器・構文解析器のソースコードである。このような問題に特化した簡明な文法の採用は一般に推奨される戦略である。とりわけlexやyaccによる解析器の記述は確立された手法で、理解や保守も易しい。FreeBSDのソースツリー中の*.lファイルの数は39、*.yは62を数える。
config.hはこのプログラムで使われるデータ構造とグローバル変数を定義している。configvers.hはバージョンを定義しており、config(8)プログラムと処理すファイルのバージョンが整合するか確認するために用いられる。
4つのCソースファイルのうち、main.cは全体のドライバとなるメイン関数及び他の3つから共通に使われる関数の定義であり、他のmkmakefile.c, mkheaders.c, mkoptions.cはそれぞれ名前の示すファイルを生成するモジュールである。
処理の流れ
mainではまずグローバル変数(特にdtab, fntab, cputype, ftab)を初期化した後、解析器(yyparse)を呼びカーネル設定ファイルを読む。その後ファイルを生成する処理options, makefile, headers, configfileを順に行う。
カーネル設定ファイルの読み込み
スキャナが入力ストリームからトークンを単位として切り出し、それを受けてパーザが文法に従って処理を行う。
スキャナはdevice, optionsなどのキーワードに対応するトークン(DEVICE, OPTIONSなど)、英数字から成る名前(ID)、数(NUMBER)を返す。文字列から数値を得るにはsscanfが使われる。
シンプルではあるが、書き易さの為の工夫も入れられている。device foo2という行をDEVICE ID("foo") NUMBER(2)と認識するための初期状態の切り替えがそれである(IDは英数字から成る為に本来なら"foo2"という名前として切り出される。その場合、device foo 0のような不自然に見えるわかち書きが必要となる)。
反面、おおらかな部分もある。例えば文字列。manにはA `"' character may be inserted into a quoted string by using the sequence `\"'.
とあるが、そんな実装はされていない。"abc"はabcに、\"abc=def\"は"abc=def"になる。しかしそれだけである。プログラミング言語ではない故、誰も困っていなければ厳密さは求められないのだろう。
パーサではfiles, options, deviceという命令に応じ、fntab, opt, mkopt, dtabという4つのリストに項目を追加していく(関数newfile, newopt, newdev)。include命令に対してはflex(GNUによるlexの実装)の独自機能で別ファイルのインクルードを実現している。
| 変数名 | 命令 | 説明 |
|---|---|---|
| fntab | files | ソースファイルの情報をリストしたファイルの追加指定。 |
| dtab | device | デバイスのリスト。名前と数を保持。 |
| opt | options, device | オプション(Cのマクロとして出力される)のリスト。 |
| mkopt | makeoptions, config | Makefileに変数として出力されるオプションのリスト。 |
余談だが行末だけでなくセミコロンでも文は終端する。また既出のデバイスやオプションなどを取り消すnodevice, nooptionsといった命令がある。私は今回ソースコードを読んで初めて知った。
出力
ファイルの出力はoptions, makefile, headers, configfileの4つの関数で行われる。
optionsはoptions命令で指定されたオプションを定義するヘッダファイルopt_*.hを出力する。
makefileは、files, files.archなどを読み込んだ後、Makefile, hints.c, env.cを生成する。ここで読み込んだ情報は次のheadersでも使われる。
headersではfilesでcountというフラグの立てられたものについて、"デバイス名.h"というヘッダファイルを生成する。かつてはコンパイル時にカーネルに組込むデバイスには#define NDEV 1のようにデバイス数を値とするマクロを定義し、デバイスのソースファイルの方でコードを#ifdef NDEV ... #endif で囲むという手法をとっていた。その名残りである。
configfileは、カーネル設定ファイルの内容を文字列としてカーネルに持たせるためのファイルconfig.cを生成する。
これらの中でも/sys/confに置かれるfilesなどのファイルを読み込んでいるのだが、面白いことにどれも手書きで済ませている。微妙な力の抜き加減である。
一方、ヘッダファイルを生成するときはわざわざ既に書かれている内容を読み込み、反映させようとするものでファイルの内容が変化するか調べるということを繰り返している。makeのときの再コンパイルを減らすためであろう。ご苦労なことである。
データと処理の流れを表すダイアグラム
楕円はデータ。平行四辺形は処理。矩形はファイル。
