LoginSignup
7
5

More than 3 years have passed since last update.

Kernel Hackerしか興味がないBootconfig Kernel APIの話

Posted at

Bootconfig Kernel API

前々日の記事の続きとして、Extra Boot Configurationのカーネル内APIを紹介します。個々のAPIの詳細については、Linuxカーネルのドキュメントのほうが詳しいしので、そちらを参照してください。

これまでのkernel option API

これまでのKernel Boot Option APIはコマンドラインに渡されたオプションに対して、キーワードが一致すれば呼び出されるハンドラを定義するか、予め変数と型名を指定するインタフェースでした。

__setup()マクロ

__setup()マクロはレガシーAPIで、指定したキーワードがカーネルコマンドラインのオプションに出てきた場合、指定したハンドラが呼び出されます。
ハンドラはchar *を引数として受け取り、正常に処理したら!0を返します。ただ、キーワードマッチングが雑なので、前方一致したら呼び出される場合(例えば"param"というキーワードは"parameter"にも当てはまります)があり、もしハンドラ自身がこれはマッチしないと判断したら、0を返すことになっています。ハンドラに渡される引数は、マッチしたオプション文字列からマッチした文字列を引いたものなので、オプションが値を取る場合には"param="と書く("="までマッチするので"parameter"にはマッチしなくなる)ことで、自分でマッチしているかどうかを判断することを省略することが多いです。

module_param系マクロ

こちらはカーネルドライバ(モジュール)のパラメタとしてカーネルコマンドライン引数とmodprobe/insmodの引数を共通化し、sysfs以下から参照できるように作られたAPIです。同一ソースコード内で設定されたモジュール名を参照するので、カーネルコマンドラインでは必ず"モジュール名."から始まるオプションになります。
またパラメタの型とストア先の変数を指定する形になっているので、独自にハンドラを書く必要がありません。(コールバック関数を与えるマクロもあります)

Bootconfig API

Bootconfig APIは、initcallハンドラなどの__init関数内部から自発的に呼び出す形式になっています。これはカーネルコマンドラインとの使い分けを意識しており、コマンドラインオプションは構造化されていない設定を行うのに向いていますが、Bootconfigでは構造化された(いくつかの設定がひとまとまりになった)オプションの設定を行うのに特化しています。

Bootconfig APIの定義

Bootconfig APIは、lib/bootconfig.cinclude/linux/bootconfig.hで定義されています。KernelDoc形式のドキュメントが付いていて、make htmldocsなどを実行すればドキュメント化されます。

Bootconfigのデータ構造

Bootconfigは読み込んだ設定ファイルをパースして、ツリー構造を使って情報を保持しています。
例えば以下のような設定はもツリーで表されます。

key.word = Value
key.word2.subkey
key2.word3 = Value1, Value2

これはこのようになります。
bconftree.png

Bootconfig APIは、このツリー構造から、例えば"key.word"を指定して"Value"を得ることだけでなく、"key"を指定するとその下に"word"と"word2"があることを確認したり、 全体で"key.word"、 "key.word2.subkey"、 "key2.word3" というオプションが存在することを確認したりといった使い方を想定しています。

xbc_nodeデータ構造体

Bootconfigのデータ構造は、xbc_nodeというツリー構造のノードを表す構造体で出来ています。これは非常に単純な双方向ツリーノードになっていて、parent, child, sibling, dataの4つのフィールドを持っています。ただし、それぞれ配列のインデックス値やデータオフセット値しか入っていないので、内容にアクセスするには専用の関数を使います。Bootconfig APIでは基本的にこのxbc_nodeを返すか、あるいは値そのものを返すので、xbc_node_get_data()というノードから値を取得する関数さえおさえればなんとかなります。

Bootconfigのデータ

前述のxbc_nodeはデータを保持していません。bootconfigはユーザから渡された設定ファイルのデータそのものをパースし、xbc_nodeはそのオフセットだけを保持しています。このため、ハンドラに渡される時にコピーされるカーネルコマンドラインのデータと違い、データをパースする場合には自前でコピーする必要があります。(xbc_node_get_data()const char *を返すのはこれが理由です)

Bootconfig APIの注意点

Bootconfigは起動時のオプションを提供する機能であるため、全てのデータ構造と関数に__initdata__initが付いています。つまり、カーネルの初期化後は削除されるので注意が必要です。また、APIを呼び出して良いのは__initをつけた関数からだけです。
今後モジュールなどから参照したいというユースケースが増えれば__initは消えるかも知れません。

Bootconfig APIの実例

今の所唯一の使用例であるBoot-time tracingのコードをベースに使い方の実例を説明します。

設定名前空間の限定

kernel/trace/trace_boot.c
static int __init trace_boot_init(void)
{
        struct xbc_node *trace_node;
        struct trace_array *tr;

        trace_node = xbc_find_node("ftrace");
        if (!trace_node)
                return 0;

        ...

        /* Global trace array is also one instance */
        trace_boot_init_one_instance(tr, trace_node);
        trace_boot_init_instances(trace_node);

        return 0;
}
/*
 * Start tracing at the end of core-initcall, so that it starts tracing
 * from the beginning of postcore_initcall.
 */
core_initcall_sync(trace_boot_init);

まず注目したいのは、initcall関数としてtrace_boot_init()を実装しているところです。オプションのハンドラではないのでカーネル起動時に呼び出す初期化関数の一つにする必要があります。
この例では、最初に自分の扱う設定名の名前空間として"ftrace"を使うので、"ftrace"をキーとしたノードをxbc_find_node("ftrace")で探しています。これ以降はこのノードを起点としてAPIを叩くことで、その他の設定を気にしなくてよくなります。

キーに含まれるデータの取得

trace_boot_init_instances()ではBootconfigに特徴的な処理を見ることが出来ます。

kernel/trace/trace_boot.c
static void __init
trace_boot_init_instances(struct xbc_node *node)
{
        struct xbc_node *inode;
        struct trace_array *tr;
        const char *p;

        node = xbc_node_find_child(node, "instance");
        if (!node)
                return;

        xbc_node_for_each_child(node, inode) {
                p = xbc_node_get_data(inode);
                if (!p || *p == '\0')
                        continue;

                tr = trace_array_get_by_name(p);
                if (!tr) {
                        pr_err("Failed to get trace instance %s\n", p);
                        continue;
                }
                trace_boot_init_one_instance(tr, inode);
                trace_array_put(tr);
        }
}

まずnode = xbc_node_find_child(node, "instance")によって、"ftrace.instance"を表すノードを取得します。

Bootconfigでは、Boot-time tracingのオプションにあるように、設定キーの一部に「新規作成したいインスタンス名」や「新規作成したいイベント名」を指定することが出来ます。これらの名前はユーザが設定するまでは分かりませんから、__setup()マクロなどでは処理できません。また、仮に出来ても、ハンドラ自身でパターンをパースしなければなりません。

一方、Bootconfigでは、xbc_node_for_each_child(node, inode)を使うことで「特定のキーの直下にあるキーワードの一覧」を列挙できます。これにより自由な名前をキーに組み込むことが可能になります。

キーに対応する値の取得

あるキーに対応する値を取得するにはxbc_node_find_value()を使います。
この関数には、ルートノード、検索するキーワード、それに値ノードへのポインタのアドレスを渡します。単純に値を取るだけであれば最後の引数はNULLにできます。また、特定のノードをルートノードにせず、Bootconfig全体から探すのであれば、ルートノードをNULLにするか、xbc_find_value()関数を使います。

kernel/trace/trace_boot.c
static void __init
trace_boot_enable_tracer(struct trace_array *tr, struct xbc_node *node)
{
        const char *p;

        trace_boot_set_ftrace_filter(tr, node);

        p = xbc_node_find_value(node, "tracer", NULL);
        if (p && *p != '\0') {
                if (tracing_set_tracer(tr, p) < 0)
                        pr_err("Failed to set given tracer: %s\n", p);
        }
...
}

ここでは関数に引数として渡されたインスタンスを表すノード("ftrace.instance.foo"など)の下から、"tracer"というキーに対応する値を取得しています。

配列値の取得

あるキーに対応する配列(値)を取得するには、xbc_node_for_each_array_value()を使います。
このマクロはルートノード、検索するキーワード、配列ノードのポインタ、配列値へのポインタ、の4つの引数を渡します。これにより、配列を持つであろうキーワードをルートノード以下から検索し、その配列の要素を列挙することが出来ます。

kernel/trace/trace_boot.c
static void __init
trace_boot_enable_events(struct trace_array *tr, struct xbc_node *node)
{
        struct xbc_node *anode;
        char buf[MAX_BUF_LEN];
        const char *p;

        xbc_node_for_each_array_value(node, "events", anode, p) {
                if (strlcpy(buf, p, ARRAY_SIZE(buf)) >= ARRAY_SIZE(buf)) {
                        pr_err("String is too long: %s\n", p);
                        continue;
                }

                if (ftrace_set_clr_event(tr, buf, 1) < 0)
                        pr_err("Failed to enable event: %s\n", p);
        }
}

この事例では、nodeとしてインスタンスに対応するノードを渡されたとき、"events"というキーワードのノードを検索し、存在すればその配列上でループを行います。(ftrace.instance.foo.eventsなど)
キーワードに対応するノードがなかったり、対応するノードが値のノードを持っていない場合などは何もせずに終了します。

ところで、ループの中でいきなり取得した値(p)をstrlcpy()でバッファにコピーしているのが分かるでしょうか。これはftrace_set_clr_event()が指定された文字列を変更しながらパースするため、const char *pをそのまま渡せないからです。

Bootconfig全体に含まれるオプションを列挙する

最後に、Bootconfig APIを使うもう一つの事例であるprocfsインタフェースの関数を使って、Bootconfig全体に含まれるキーバリューリストを取得する方法を説明します。

fs/proc/bootconfig.c
/* Return the needed total length if @size is 0 */
static int __init copy_xbc_key_value_list(char *dst, size_t size)
{
        struct xbc_node *leaf, *vnode;
        char *key, *end = dst + size;
        const char *val;
        char q;
        int ret = 0;

        key = kzalloc(XBC_KEYLEN_MAX, GFP_KERNEL);

        xbc_for_each_key_value(leaf, val) {
                ret = xbc_node_compose_key(leaf, key, XBC_KEYLEN_MAX);
                if (ret < 0)
                        break;
                ret = snprintf(dst, rest(dst, end), "%s = ", key);
                if (ret < 0)
                        break;
                dst += ret;
                vnode = xbc_node_get_child(leaf);
                if (vnode) {
                        xbc_array_for_each_value(vnode, val) {
                                if (strchr(val, '"'))
                                        q = '\'';
                                else
                                        q = '"';
                                ret = snprintf(dst, rest(dst, end), "%c%s%c%s",
                                        q, val, q, vnode->next ? ", " : "\n");
                                if (ret < 0)
                                        goto out;
                                dst += ret;
                        }
                } else {
                        ret = snprintf(dst, rest(dst, end), "\"\"\n");
                        if (ret < 0)
                                break;
                        dst += ret;
                }
        }
out:
        kfree(key);

        return ret < 0 ? ret : dst - (end - size);
}

xbc_for_each_key_value()マクロを使うと、Bootconfigに含まれる全てのキー(leaf: キーを表す一番深いノード)と値(val)のペアを取得できます。ただしノードからキーワードの文字列と、配列値へアクセスするには、それぞれ専用の関数を呼び出す必要があります。

キーワードの復元

先に説明したように、Bootconfigのパラメタ名を表すキーワードは"."で分割されて複数のノードになってしまっています。xbc_node_compose_key()関数は特定のノードから、元のキーワードを指定したバッファに復元します。

配列の取得と値なしオプションの検出

xbc_for_each_key_value()で列挙される値は、キーに配列が指定されていた場合、配列の先頭の値になります。配列全体を取得するにはxbc_array_for_each_value()マクロで配列の要素を列挙します。
xbc_array_for_each_value()マクロは配列の先頭のノードが必要なので、leafノードの子ノード(=値ノード)を与えます。
ちなみにleafノードに子ノードがない場合は、値を取らないキーワード(オプション)です。

まとめ

Bootonfig APIの説明は以上になります。
ここでは簡単に、これまでのカーネルコマンドラインオプションのAPIと、BootconfigのAPIの違いや、Bootconfig APIの内部構造と、実例を使った使用方法の解説を行いました。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5