始めに
”Linuxカーネルは一つの大きなバイナリで構成されたモノリシックなカーネル(一枚岩カーネル)である。”
この記述は目にタコができるほど様々な記事や資料に書かれています。また、リーナス氏とタネンバウム氏のカーネル設計の議論については
あまりにも有名です。しかしながら、自分を含めた多数の開発者は決してLinuxは単なるモノリシックカーネルだとは思ってはいません。Linux Kernel Module(LKM)を駆使することで、モノリシックカーネルとは対称的なマイクロカーネルの良い点を引き出しています。
マイクロカーネルという設計方法は、タネンバウム氏が開発しているMINIX、GNU HurdやQNXはこれを採用しています。
それぞれの設計方法によってメリットデメリットがありますが、カーネルの設計方法における解説は他の記事におまかせすることにします。
ここではLinuxがマイクロカーネル的な要素を引き出しているLKMにおける協調動作について書いていこうと思います。
この記事の対象者はLinuxモジュールを1回でも作ったことがある人です。(細かい解説は行っていないので)
環境
Linuxディストリビューション: Fedora27 Workstation 64bit
Linuxカーネル: 4.14.13
Cコンパイラ: GCC 7.2.1
期待する結果
今回のゴールは、モジュールを2つロードさせ、片方のモジュールからもう一方のモジュールに定義された関数を呼び出すことです。
これを応用して、様々なことができそうでワクワクします。
実験
以下に適当なカーネルモジュールのソースをおいておきます。
elder.c -> 呼び出される側
little.c -> 呼び出す側
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("Dual BSD/GPL");
static int eld_init(void)
{
printk("<eld> init\n");
return 0;
}
static void eld_exit(void)
{
printk("<eld> exit\n");
}
/*
* little.cによって呼び出される関数
*/
void __eld_public_function(void)
{
printk("<eld> Hi. I'm elder module.");
}
EXPORT_SYMBOL(__eld_public_function);
module_init(eld_init);
module_exit(eld_exit);
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("Dual BSD/GPL");
/*
* 呼び出す関数のプロトタイプ宣言
*/
void __eld_public_function(void);
static int little_init(void)
{
printk("<little> init\n");
// here!!
__eld_public_function();
return 0;
}
static void little_exit(void)
{
printk("<little> exit\n");
}
module_init(little_init);
module_exit(little_exit);
これをmakeしてそれぞれelder.ko、little.koを作ります。
次にモジュールをロードしていきます。
当たり前のことなのですが、呼び出される関数が定義されているelder.koを先にロードします。
$ sudo insmod elder.ko # 先にロード
$ sudo insmod little.ko # 次にロード
littleモジュールの初期化関数で、__eld_public_functionを呼び出しているので、ロードした時点でこの関数は呼び出されています。
早速確認してみましょう。
$ dmesg | grep '<eld>'
[14037.256420] <eld> init
[14054.951514] <eld> Hi. I'm elder module.
この通り、littleモジュールからelderモジュールの関数を呼び出すことができました。
また、lsmodコマンドで情報を見てみます。
$ lsmod | grep 'little'
little 16384 0
elder 16384 1 little
これを見ると、elderは1個のモジュールに依存されており、そのモジュール名がlittleモジュールであるということがわかります。
モジュールをアンロードするときは、依存しているlittle側からアンロードする必要があります。
$ sudo rmmod little # 先にアンロード
$ sudo rmmod elder # 次にアンロード
質素な解説
まず、elderでは自分以外のモジュールから自分が持つ関数を呼び出すことができるようにするため、EXPORT_SYMBOLマクロを使っています。
このマクロを使うことで、他モジュールからアクセス可能なシンボルを生成することができます。今回は、__eld_public_functionに対し、使用しています。
little側では、プロトタイプ宣言した関数を呼び出しているだけです。
終わりに
LKMはカーネルにロードされ、カーネル空間で動作します。したがって、使用できる関数や構造体はLinuxカーネルと同じものになります。つまり、手軽にLinuxカーネルの開発を行えるようなもので、非常に便利な機構です。Linuxカーネルにおけるデバイスドライバや静的にカーネルに組み込まれる必要が無いものは、カーネルモジュールとして開発されることが大半です。これによって、Linuxはモノリシックカーネル的な側面とマイクロカーネル的な側面をうまく両立させているのです。