Kbuild とモジュール

このページの目次

.ko の生成後でも、読み込みは失敗する可能性があります。署名が有効でも、ABI の互換性が保証されるわけではありません。モジュールが本当に適合しているかどうかを説明するには、⁠構成、ビルド情報、読み込みチェック、リソース解放 をそれぞれ確認する必要があります。

確認目標参照箇所
機能の選択と生成物の構成Kconfig、Kbuild
再現可能なビルドとターゲットの一致外部モジュールプロジェクト、ビルド情報
実行と信頼の境界読み込み、シンボルエクスポート、署名
構成、ビルド、読み込みは3つの異なるチェック境界です Kconfig と .config 機能を選択し、構成依存関係を満たす 1 Kbuild と modpost コンパイルとリンク、モジュールのシンボルなどをチェック 2 カーネルモジュールローダー ターゲットの互換性、シンボル、署名をチェックし、初期化を呼び出す 3 .ko 構築は任意 kernel への load 保証ではなく、load 成功も driver 動作の証明ではない。

Kconfig:依存関係はテキストメニューの飾りではない

bool は y/n を表し、tristate はさらに m を許可します。前提にはモジュールサポートなどの構成が含まれます。depends on はオプションの利用可能性を制約し、select は逆方向にもう一つのシンボルを強制します。これにより、後者の本来の依存関係チェックを回避する可能性があるため、select を汎用的な依存関係宣言として扱うことはできません。Kconfig

.config は選択結果であり、ソースコードやカーネルバージョンを変更した後に olddefconfig などで更新できますが、デフォルト値の変更はレビューする必要があります。defconfig はアーキテクチャに依存し、現在のマシンのすべてのハードウェアに適していることが保証されているわけではありません。

依存関係が m の場合、消費者が y を維持できない理由

通常の tristate 依存関係については、n < m < y として上限を理解できます。CONSUMER が PROVIDER の機能を直接呼び出し、依存関係が depends on PROVIDER と書かれている場合、PROVIDER が m のとき、CONSUMER の最高値は m までしか上がりません。ビルトインコードが、実行時に読み込まれる必要があるモジュールに依存することはできません。

独立した実験ディレクトリに以下の Kconfig を保存します。これには、逆方向強制が依存関係をどのように突破するかを観察するための、意図的に誤った select 関係も含まれています。

mainmenu "Dependency example"
config MODULES
    bool "Enable modules"
    modules
    default y
config PROVIDER
    tristate "Provider"
config CONSUMER
    tristate "Consumer"
    depends on PROVIDER
config HARDWARE
    bool "Required hardware support"
config DRIVER
    tristate "Driver"
    depends on HARDWARE
config FORCE_DRIVER
    bool "Force driver"
    select DRIVER
seed でのリクエスト評価後の主要な結果理由
PROVIDER=m、CONSUMER=yCONSUMER=m前方依存関係が上限を m に引き下げる
PROVIDER=n、CONSUMER=yCONSUMER=nプロバイダが無効化されているため、消費者は有効にできない
PROVIDER=y、CONSUMER=yCONSUMER=y依存関係がビルトインを許可する
HARDWARE=n、FORCE_DRIVER=yDRIVER=y、かつ unmet dependencies が報告されるselect により下限が強制され、DRIVER の依存関係が満たされていない

例えば、以下の内容を seed.config として保存し、ビルド済みカーネルソースツリーの scripts/kconfig/conf を使用して評価します。出力ディレクトリと KCONFIG_CONFIG はこの実験ディレクトリに留め、使用中のカーネル構成を変更しないようにしてください。

CONFIG_MODULES=y
CONFIG_PROVIDER=m
CONFIG_CONSUMER=y
KCONF=/path/to/kernel/scripts/kconfig/conf
KCONFIG_CONFIG=out.config "$KCONF" --defconfig=seed.config Kconfig

4行目は推奨される構成ではなく、なぜ誤った関係が危険なのかを示すものです。修正時には、構成が実際の依存関係を表現するようにするか、複数の呼び出し元が選択できる依存関係のない内部ヘルパーを個別にモデル化してください。select を imply に置き換えても、ビルトイン/モジュールのリンク関係の設計を代替できるわけではありません。Kconfig の依存関係と3値式

依存上限と要求 tristate を独立に変更します。結果は依存で制限され、項目の要求が依存を自動有効化するわけではありません。

図を準備しています
y を希望しても m/n に制限されるのはなぜ?

depends on は上限を設けます。BUS && NET に依存する DRIVER は依存が m の時に内建 y にはできません。select は別の意味で通常依存の代替ではありません。

Kbuild:オブジェクトの集合と複合モジュール

obj-$(CONFIG_MYDRV) += mydrv.o
mydrv-y := main.o helper.o

構成が y の場合、ビルトインオブジェクトのパスに追加され、m の場合はモジュールがビルドされ、無効化されている場合は対応する集合に追加されません。mydrv-y はその複合オブジェクトを構成するファイルを指定します。mydrv.o を、同名の mydrv.c と無関係な2つの異なるターゲットとして扱わないでください。

構成が y であっても、最終的にリンクできることが自動的に保証されるわけではありません。構成が m であっても、依存元がビルトイン方式でそれを呼び出せることが保証されるわけではありません。Kconfig の関係とリンク依存関係は共同で設計する必要があります。

完全な外部モジュールプロジェクト

ディレクトリには以下の3つのファイルが含まれています。この例では、読み込みとアンロードのみを記録し、デバイスを管理しません。

hello.c:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>

static int greeting_id = 42;
module_param(greeting_id, int, 0444);
MODULE_PARM_DESC(greeting_id, "Identifier printed on initialization");

static int __init hello_init(void)
{
    pr_info("kb_hello: id=%d\n", greeting_id);
    return 0;
}
static void __exit hello_exit(void)
{
    pr_info("kb_hello: removed\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Knowledge base build example");

Kbuild:

obj-m := kb_hello.o
kb_hello-y := hello.o

Makefile(レシピ行はタブで始まる):

KDIR ?= /lib/modules/$(shell uname -r)/build
MODULE_DIR := $(CURDIR)

.PHONY: all clean
all:
	$(MAKE) -C $(KDIR) M=$(MODULE_DIR) modules
clean:
	$(MAKE) -C $(KDIR) M=$(MODULE_DIR) clean

このディレクトリで make を実行すると kb_hello.ko が得られ、make KDIR=/path/to/prepared/kernel によってターゲットのビルドディレクトリを指定することもできます。$(MAKE) を使用して再帰ビルドのパラメータを保持し、Kbuild はオブジェクトを個別に列挙し、通常のラッパーターゲットとカーネルルールが混同されないようにします。外部モジュール

ヘッダーファイルだけでは不十分:ターゲットのビルド情報と一致させる必要がある

外部モジュールには、ターゲットカーネルの構成、生成されたヘッダーファイル、および必要なビルド成果物が必要です。modules_prepare は、MODVERSIONS を有効にするために必要な完全な Module.symvers を生成しません。対応するビルドからそれが必要であり、ヘッダーファイルの準備だけですべてのシンボルバージョン情報が含まれると考えるのは誤りです。

modpost はエクスポートされたシンボルとモジュールのメタ情報を処理します。未解決のシンボル、名前空間、およびバージョンの問題は、ここでおよび読み込み段階でそれぞれ調査する必要があります。メインライン Linux は、カーネルバージョン間で安定した内部モジュール ABI を保証するものではありません。安定した API に関する説明

2つの外部モジュール:シンボルが既知だからといって、消費者にリンクされているわけではない

以下の2番目のプロジェクトは、モジュール間の依存関係のみを示すものであり、hello プロジェクトとは異なるディレクトリに配置されます。provider は関数をエクスポートし、consumer は初期化時にそれを呼び出します。まず2つのモジュールを統一してビルドし、modpost が両方のエクスポート情報を同時に確認できるようにします。

kb_api.h:

#ifndef KB_API_H
#define KB_API_H
unsigned int kb_step(unsigned int value);
#endif

kb_provider.c:

#include <linux/module.h>
#include "kb_api.h"

unsigned int kb_step(unsigned int value)
{
    return value + 1U;
}
EXPORT_SYMBOL_GPL(kb_step);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Knowledge base symbol provider");

kb_consumer.c:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include "kb_api.h"

static int __init kb_consumer_init(void)
{
    return kb_step(41U) == 42U ? 0 : -EINVAL;
}
static void __exit kb_consumer_exit(void) {}
module_init(kb_consumer_init);
module_exit(kb_consumer_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Knowledge base symbol consumer");

Kbuild:

obj-m := kb_provider.o kb_consumer.o

前の Makefile のラッパールールを再利用し、準備済みの KDIR を指定してビルドできます。完了後、モジュールを読み込まずにチェックできます。

modinfo -F depends ./kb_consumer.ko
nm -u ./kb_consumer.ko

depends には kb_provider が含まれているはずです。未定義シンボルのリストには依然として kb_step が表示されます。これは矛盾していません。consumer の .ko は provider の実体を静的にコピーしているわけではなく、シンボルはカーネルの読み込み時に解決されます。modpost はそれがどのモジュールから来るかを知っており、依存関係のメタ情報を生成します。

宣言・export 記録・実行時実装は別の証拠 C compile kb_step の宣言 modpost symbol 提供者を記録 runtime load provider symbol 解決 未定義参照は残せる。依存情報は別に記録する KBUILD_EXTRA_SYMBOLS は情報提供で、provider 実装の copy ではない。

2つのモジュールを独立したビルドディレクトリに分割し、consumer に宣言ヘッダーファイルのみを提供した場合、C コンパイルは成功する可能性がありますが、modpost は kb_step のエクスポート記録を欠いているため、続行を拒否します。まず provider をビルドし、次に consumer のビルドに KBUILD_EXTRA_SYMBOLS=/absolute/path/to/provider/Module.symvers を渡すことで、ビルド段階に必要なシンボル情報を補完できます。2つのプロジェクトは互いに互換性のあるターゲットカーネルに対してビルドする必要があります。

このパラメータは、provider をインストールしたり読み込んだりするものではなく、ヘッダーファイルの不一致、古い symvers、または ABI の不一致を正しい依存関係に変えることもできません。ランタイムでは、provider が存在し、最初に解決されている必要があります。モジュールインデックスが正しくインストールされていれば、modprobe は依存関係に基づいて検索および読み込みを行います。insmod を使用して consumer ファイルのみを指定した場合、ビルド時に symvers を渡したからといって、provider が自動的に見つかるわけではありません。外部モジュールのシンボルと Module.symvers

読み込みツールとライフサイクル

ツール依存関係の処理
insmod指定されたファイルを読み込むだけで、依存関係を自動的に検索しない
modprobeモジュールインデックスと構成に基づいて依存関係、エイリアスを処理する
modprobe -r使用されなくなった依存関係の削除を試みるが、すべてがアンロードされる保証はない

ツールを選択する前に、「このファイルを読み込む」と「インストール済みモジュールの関係に基づいて読み込む」を区別してください。

モジュールの読み込みには、リロケーション、シンボル解決、チェック、および init 呼び込みが含まれます。init の失敗時には、取得したリソースをクリーンアップする必要があります。エグジットパスでは、新しいリクエストの停止、作業のキャンセル、参照とコールバックの終了待機を行う必要があり、単に pr_info を1行書くだけでは、実際のドライバの完全なアンロードロジックとしては不十分です。

まず modinfo ./kb_hello.ko を使用して、名前、パラメータ、および vermagic をチェックできます。インストールと読み込みはシステムに影響を与えるため、通常は一致するカーネルのテスト VM で行う必要があります。make install の起動項目と initramfs の動作は、アーキテクチャおよびディストリビューションのフックに依存しており、Kbuild が一度に完了するすべての手順を保証するものではありません。

エクスポートされたシンボルは恒久的な公共 API ではない

EXPORT_SYMBOL はシンボルを適用可能なモジュールに公開し、EXPORT_SYMBOL_GPL はモジュールのライセンス宣言に制限を課します。シンボル名前空間は、対応するインポートを要求する可能性があります。エクスポートされるということは、解決可能であることを意味しますが、任意のコンテキストでの呼び出しが安全であることや、将来のバージョンで不変であることが約束されるわけではありません。

モジュール署名:検証、自動署名、強制ポリシーは分離される

構成またはポリシー担当する事項
CONFIG_MODULE_SIG署名検証メカニズムを提供する
CONFIG_MODULE_SIG_ALLビルドおよびインストールにおける自動署名に関与する
CONFIG_MODULE_SIG_FORCE または強制ポリシー許容される署名が必須かどうかを決定する

SIG=y は、「すべてのモジュールが自動的に署名され、一致しないモジュールがすべて拒否される」ことを意味しません。署名ドキュメント

ランタイムポリシー、信頼できるキー、Secure Boot の統合は、カーネルおよびディストリビューションの構成の影響を受けます。署名検証が成功しても、モジュールが現在のカーネル ABI と一致していること、または動作が正しいことが保証されるわけではありません。署名はモジュールコンテンツの後に付加され、署名後に修正または strip すると無効になる可能性があります。署名は正しい生成物処理段階に配置する必要があります。

ポリシーのさらなるテストについては、デバッグとテスト を参照してください。