記事ディレクトリ
カーネルソースコード内の関連ファイル
- /kernel/notifier.c
- /include/linux/notifier.h
1. 通知チェーンの概要
このテキストでは、カーネル ソース コード
4.19.4
に基づいて通知チェーンを構成する特定のデータ構造と API インターフェイスについて説明し、4 つの通知チェーンの具体的なアプリケーション シナリオについて説明し、API インターフェイスを簡単に分析します。
Linux カーネルでは、struct notifier_block
オブザーバー パターンを実装するために使用されるデータ構造です。これにより、カーネルのさまざまな部分が特定のイベントのリスナー (オブザーバー) として自身を登録できるようになります。これらのイベントが発生すると、カーネルは、イベントに適切に応答できるすべての登録済み通知ブロックに通知します。
struct notifier_block
Linux カーネル ヘッダーで定義されinclude/linux/notifier.h
、次の構造を持っています。
struct notifier_block {
int (*notifier_call)(struct notifier_block *nb, unsigned long action, void *data);
struct notifier_block *next;
int priority;
};
-
notifier_call
: このフィールドは、通知イベントの発生時に呼び出されるコールバック関数を指します。コールバック関数の関数シグネチャは次のように定義されますint (*notifier_call)(struct notifier_block *nb, unsigned long action, void *data)
。nb
このパラメータは、通知タイプを含む通知ブロック自体へのポインタでありaction
、data
イベントに関連する追加データへのポインタです。 -
next
: このフィールドは、チェーン内の次のノーティファイア ブロックへのポインタです。Linux カーネルは、登録された通知ブロックのリンク リストを維持するため、リンク リスト全体を横断することができます。 -
priority
: このフィールドは、他の登録済みノーティファイア ブロックに対するこのノーティファイア ブロックの優先度を決定します。複数のブロックが同じイベントに登録すると、カーネルは優先順位の降順でブロックに通知します。より高い優先順位値を持つ通知ブロックは、より低い優先順位値を持つ通知ブロックよりも先に通知されます。
を使用するには、特定のイベント クラスに応じて、struct notifier_block
Linux カーネルによって提供される関数 (例:register_inotifier()
または )を使用してカーネル モジュールを登録できます。register_netdevice_notifier()
一般的に悪用されるstruct notifier_block
イベントには次のようなものがあります。
- ファイルの作成、削除、変更などのファイル システム イベント。
- インターフェイスの有効化または無効化などのネットワーク デバイス イベント。
- ページの割り当てや割り当て解除などのメモリ管理イベント。
これを使用することでstruct notifier_block
、カーネル開発者は、さまざまなコンポーネントが分離された方法でイベントに応答できるようにする、モジュール式で拡張可能なシステムをより適切に設計できます。このパターンは、コードをより適切に整理するのに役立ち、既存のコードに影響を与えることなく、カーネルに新しい機能を簡単に追加できるようになります。
全体の構造を次の図に示します。
2. 通知チェーンの種類
Linux カーネルでは、4 種類の通知チェーンが定義されています。
- (1) アトミックな通知チェーン
次のように定義されます。
アトミック通知チェーンは、カーネル、特にいくつかの基本的な通知メカニズムで広く使用されています。この通知チェーンの処理はアトミックです。つまり、チェーン上の通知の処理を妨げる中断や他の同時操作は発生しません。アトミック通知チェーンのアプリケーション シナリオには、プロセス終了通知、プロセス停止通知、カーネル デバッグおよびトレース イベント通知が含まれます。
- (2) ブロック通知チェーン
次のように定義されます。
ブロッキング通知チェーンは、実行を続行する前に通知チェーン内のすべてのプロセッサが完了するまで待機する必要がある一部のシナリオで使用されます。プロセッサがチェーン上で通知を開始すると、ブロッキング通知チェーンは、すべてのプロセッサがタスクを完了するまで待機してから戻ります。通知チェーンをブロックするためのアプリケーション シナリオには、カーネル モジュールの初期化が含まれます。この場合、あるモジュールは続行する前に、他のモジュールが初期化を完了するのを待つ必要がある場合があります。
- (3) 生(RAW)通知チェーン
次のように定義されます。
生の通知チェーンは、同期メカニズムを持たない特別なタイプの通知チェーンです。これは、通知チェーンの処理中にロックや同期が行われないことを意味し、同時実行性の問題が発生する可能性があります。元の通知チェーンは主に、いくつかの低レベルの基礎となる通知メカニズムに使用され、通常、ユーザーは自分でスレッドの安全性を確保する必要があります。元の通知チェーンのアプリケーション シナリオは比較的少数であり、一部の特定の高パフォーマンス シナリオでのみ使用される場合があります。
- (4) SRCU 通知チェーン
次のように定義されます。
SRCU 通知チェーンは、Linux カーネルの SRCU (Synchronize RCU) メカニズムを通じて実装されます。SRCU 通知チェーンは、通知ハンドラーが削除または解放されたときに競合状態が発生しないようにするための、より高度な同期メカニズムを提供します。これにより、ハンドラーを通知チェーンに安全に追加したり、通知チェーンから削除したりできるようになります。SRCU 通知チェーンのアプリケーション シナリオには、複数のプロセッサがイベントに応答する可能性があり、プロセッサが安全に取り外されたときに同期を保つ必要があるネットワーク デバイス イベント通知が含まれます。
3. 原理解析とAPI
(1) ログアウト通知機能
通知チェーンを使用する前に、対応するタイプの通知チェーンを作成し、登録を使用して登録する必要があります。ソース コードの観点から見ると、各タイプの通知チェーンは登録関数に 1 つずつ対応します。
-
アトミック通知チェーン登録関数:
int atomic_notifier_chain_register(struct atomic_notifier_head *nh,struct notifier_block *nb)
。 -
ブロック通知チェーン登録機能:
int atomic_notifier_chain_register(struct blocking_notifier_head *nh,struct notifier_block *nb)
。 -
独自の通知チェーン登録機能:
int atomic_notifier_chain_register(struct raw_notifier_head *nh,struct notifier_block *nb)
。 -
srcu 通知チェーン登録関数:
int atomic_notifier_chain_register(struct srcu_notifier_head *nh,struct notifier_block *nb)
。
notifier_chain_register()
上記の 4 種類の登録関数は基本的に、コア関数を実装するための関数を呼び出しており、次のように実装されます。
static int notifier_chain_register(struct notifier_block **nl,
struct notifier_block *n)
{
while ((*nl) != NULL) {
if (n->priority > (*nl)->priority)
break;
nl = &((*nl)->next);
}
n->next = *nl;
rcu_assign_pointer(*nl, n);
return 0;
}
上記のコードは優先度に応じてループする動作で、 のn
優先度が の*nl
優先度よりも高ければループを終了し、 の前にn
挿入されます*nl
。通知チェーンを形成します。
(2) ログアウト通知機能
登録機能がある場合は、ログアウト機能に対応します。4 つの通知チェーンのログアウト機能は次のとおりです。
- アトミック通知チェーンログアウト機能:
int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh,struct notifier_block *nb);
- ブロック通知チェーンログアウト機能:
int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh,struct notifier_block *nb);
- 独自の通知チェーンログアウト機能:
int raw_notifier_chain_unregister(struct raw_notifier_head *nh,struct notifier_block *nb);
- srcu 通知チェーンのログアウト関数:
int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh,struct notifier_block *nb);
notifier_chain_unregister()
上記の 4 種類の登録関数は基本的に、コア関数を実装するための関数を呼び出しており、次のように実装されます。
static int notifier_chain_unregister(struct notifier_block **nl,
struct notifier_block *n)
{
while ((*nl) != NULL) {
if ((*nl) == n) {
rcu_assign_pointer(*nl, n->next);
return 0;
}
nl = &((*nl)->next);
}
return -ENOENT;
}
ループ判定ではキャンセル対象を見つけてキャンセルを実行し、リンクリストから削除します。
(3) 通知チェーンの通知
通常、通知チェーンの登録は、カーネルの初期化フェーズ中に各モジュールによって実行されます。特定のイベントが発生すると、カーネルは対応するnotifier_call_chain()
関数を呼び出して、登録されているすべてのモジュールまたはコンポーネントに通知します。このようにして、他のモジュールの存在を明示的に知らなくても、さまざまなモジュールがイベント タイプとパラメーターに基づいてカスタム処理を行うことができます。
4 つの通知チェーンはさまざまな機能に対応します。
- アトミック通知チェーン通知機能:
int atomic_notifier_call_chain(struct atomic_notifier_head *nh,unsigned long val, void *v);
- ブロック通知チェーン通知機能:
int blocking_notifier_call_chain(struct blocking_notifier_head *nh,unsigned long val, void *v);
- 独自の通知チェーン通知機能:
int raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v);
- srcu 通知チェーン通知関数:
int srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v);
notifier_call_chain()
上記の 4 つの関数は、コア関数を実装するために最終的に呼び出されます。コア関数は次のように実装されます。
static int notifier_call_chain(struct notifier_block **nl,
unsigned long val, void *v,
int nr_to_call, int *nr_calls)
{
int ret = NOTIFY_DONE;
struct notifier_block *nb, *next_nb;
nb = rcu_dereference_raw(*nl);
while (nb && nr_to_call) {
next_nb = rcu_dereference_raw(nb->next);
#ifdef CONFIG_DEBUG_NOTIFIERS
if (unlikely(!func_ptr_is_kernel_text(nb->notifier_call))) {
WARN(1, "Invalid notifier called!");
nb = next_nb;
continue;
}
#endif
ret = nb->notifier_call(nb, val, v);
if (nr_calls)
(*nr_calls)++;
if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
break;
nb = next_nb;
nr_to_call--;
}
return ret;
}
- nl: 通知チェーンの先頭へのポインタ。これは、通知チェーンのヘッド ノードへのポインタへのポインタです。
- val: イベントの種類。チェーン自体によって識別される一連のイベント。val はイベント タイプを明確に識別します。
- v: イベントに関する詳細情報を保持するデータ構造へのポインター。
- nr_to_call: 送信された通知の数を記録します。必要がない場合、このフィールドの値は NULL にすることができます
- nr_calls: ノーティファイア呼び出しチェーンは、最後に呼び出されたノーティファイア関数によって返された値を返します。
notifier_chain_unregister()
while ループ構造では以下が呼び出されます。
ret = nb->notifier_call(nb, val, v);
この通知チェーンに登録されているすべての関数は、順番に実行されます。
4. コード例
このセクションでは、アトミック通知チェーンを介したコード例を示します。これは、オブザーバー モードの通信メカニズムを実装するために使用できます。
(1) 通知チェーンを定義する
#include <linux/notifier.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
//定义原子通知链
static ATOMIC_NOTIFIER_HEAD(my_notifier_list);
//通知事件
static int call_notifiers(unsigned long val, void *v)
{
return atomic_notifier_call_chain(&my_notifier_list, val, v);
}
EXPORT_SYMBOL(call_notifiers);
//向通知链注册通知block
static int register_notifier(struct notifier_block *nb)
{
int err;
err = atomic_notifier_chain_register(&my_notifier_list, nb);
if(err)
return err;
}
EXPORT_SYMBOL(register_notifier);
//从通知链中注销通知block
static int unregister_notifier(struct notifier_block *nb)
{
int err;
err = atomic_notifier_chain_unregister(&my_notifier_list, nb);
if(err)
return err;
}
EXPORT_SYMBOL(unregister_notifier);
static int __init myNotifier_init(void)
{
printk("myNotifier init finish\n");
return 0;
}
static void __exit myNotifier_exit(void)
{
printk("myNotifier exit finish\n");
}
module_init(myNotifier_init);
module_exit(myNotifier_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("iriczhao");
(2) オブザーバーモジュールの実装
/**
* 模块1,用于创建通知block,并注册
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/notifier.h>
extern int register_notifier(struct notifier_block *nb);
extern int unregister_notifier(struct notifier_block *nb);
static int notifier_one_call_fn(struct notifier_block *nb,
unsigned long action, void *data)
{
printk(">>this is notifier_one_call_fn\n");
printk("recv action = %d data = %p\n",action,data);
return 0;
}
static int notifier_two_call_fn(struct notifier_block *nb,
unsigned long action, void *data)
{
printk(">>this is notifier_two_call_fn\n");
return 0;
}
/* define a notifier_block */
static struct notifier_block notifier_one = {
.notifier_call = notifier_one_call_fn,
};
static struct notifier_block notifier_two = {
.notifier_call = notifier_two_call_fn,
};
static int __init module_1_init(void)
{
register_notifier(¬ifier_two);
register_notifier(¬ifier_one);
return 0;
}
static void __exit module_1_exit(void)
{
unregister_notifier(¬ifier_two);
unregister_notifier(¬ifier_one);
}
module_init(module_1_init);
module_exit(module_1_exit);
//定义模块相关信息
MODULE_AUTHOR("iriczhao");
MODULE_LICENSE("GPL");
(3) イベント生成モジュール
/*
* 事件通知模块
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/kernel.h>
extern int call_notifiers(unsigned long val, void *v);
static int event_module_init(void)
{
printk("Event module initialized\n");
unsigned long event = 123;
void *data = (void *)0xDEADBEEF;
call_notifiers(event, data);
return 0;
}
static void event_module_exit(void)
{
printk("Event module exiting\n");
}
module_init(event_module_init);
module_exit(event_module_exit);
//定义模块相关信息
MODULE_AUTHOR("iriczhao");
MODULE_LICENSE("GPL");
(4) 出力結果
上記の 3 つのコードをモジュールとして構築し、カーネルにロードします。最初にカスタム通知チェーンをロードし、my_notifier_list
次にmodule_1.ko
2 つのイベント サブスクライバをロードして登録し、最後にmodule_2.ko
通知イベントをロードして、module_1
2 つのパラメータaction
と を送信しdata
てmodule_1
出力します。出力は次のとおりです。