カーネルモジュールをロードし、ロードされたモジュールをlsmod
でリストすると、モジュールの「使用カウント」(モジュールへの参照を持つ他のモジュールの数)を取得できます。 whatがモジュールを使用していることを把握する方法はありますか?
問題は、私が開発しているモジュールはその使用回数が1であると主張しているため、rmmod
を使用してアンロードできませんが、その「by」列は空です。つまり、モジュールを再コンパイルして再ロードするたびに、マシンを再起動する必要があります(または、少なくとも、他の方法でアンロードすることはできません)。
実際には、モジュール/ドライバーを要求するプロセスをリストする方法があるようです-しかし、私はそれを宣伝していません(Linuxカーネルのドキュメント以外)ので、ここにメモを書き留めます:
まず、 @ haggai_e の回答に感謝します。使用カウント(refcount)の管理を担当する関数としての関数try_module_get
およびtry_module_put
へのポインターは、プロシージャを追跡できるようにするキーでした。
このオンラインをさらに探して、私は何らかの形で投稿を偶然見つけました Linux-Kernel Archive:[PATCH 1/2]トレース:モジュールトレースポイントのオーバーヘッドを減らす ;これは最終的に、カーネルに存在する「トレース」と呼ばれる機能を指していました。このドキュメントは、ディレクトリ Documentation/trace-Linux kernel source tree にあります。特に、トレース機能を説明する2つのファイル events.txt と ftrace.txt があります。
しかし、/sys/kernel/debug/tracing/README
で実行中のLinuxシステムには、短い「トレースmini-HOWTO」もあります( ドキュメントがないと言っている人には本当にうんざりしています... )。カーネルソースツリーでは、このファイルは実際にはファイル kernel/trace/trace.c によって生成されることに注意してください。 Ubuntu natty
でこれをテストしましたが、/sys
はrootが所有しているため、Sudo cat
のようにSudo
を使用してこのファイルを読み取る必要があります。
Sudo less /sys/kernel/debug/tracing/README
...それは、ここで説明する/sys
の下の他のほとんどすべての操作に当てはまります。
まず、これは単純な最小限のモジュール/ドライバーコードです(参照されたリソースからまとめました)。これは、文字列「This is testmod」を返す/proc/testmod-sample
ファイルノードを作成するだけです。読まれているとき;これはtestmod.c
:
/*
https://github.com/spotify/linux/blob/master/samples/tracepoints/tracepoint-sample.c
https://www.linux.com/learn/linux-training/37985-the-kernel-newbie-corner-kernel-debugging-using-proc-qsequenceq-files-part-1
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h> // for sequence files
struct proc_dir_entry *pentry_sample;
char *defaultOutput = "This is testmod.";
static int my_show(struct seq_file *m, void *v)
{
seq_printf(m, "%s\n", defaultOutput);
return 0;
}
static int my_open(struct inode *inode, struct file *file)
{
return single_open(file, my_show, NULL);
}
static const struct file_operations mark_ops = {
.owner = THIS_MODULE,
.open = my_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init sample_init(void)
{
printk(KERN_ALERT "sample init\n");
pentry_sample = proc_create(
"testmod-sample", 0444, NULL, &mark_ops);
if (!pentry_sample)
return -EPERM;
return 0;
}
static void __exit sample_exit(void)
{
printk(KERN_ALERT "sample exit\n");
remove_proc_entry("testmod-sample", NULL);
}
module_init(sample_init);
module_exit(sample_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mathieu Desnoyers et al.");
MODULE_DESCRIPTION("based on Tracepoint sample");
このモジュールは、次のMakefile
でビルドできます(testmod.c
と同じディレクトリに配置し、同じ場所でmake
を実行します)ディレクトリ):
CONFIG_MODULE_FORCE_UNLOAD=y
# for oprofile
DEBUG_INFO=y
EXTRA_CFLAGS=-g -O0
obj-m += testmod.o
# mind the tab characters needed at start here:
all:
make -C /lib/modules/$(Shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(Shell uname -r)/build M=$(PWD) clean
このモジュール/ドライバーがビルドされると、出力はカーネルオブジェクトファイルtestmod.ko
になります。
この時点で、try_module_get
およびtry_module_put
;に関連するイベントトレースを準備できます。それらは/sys/kernel/debug/tracing/events/module
にあります:
$ Sudo ls /sys/kernel/debug/tracing/events/module
enable filter module_free module_get module_load module_put module_request
私のシステムでは、トレースがデフォルトで有効になっていることに注意してください。
$ Sudo cat /sys/kernel/debug/tracing/tracing_enabled
1
...ただし、モジュールトレース(具体的には)は次のとおりではありません。
$ Sudo cat /sys/kernel/debug/tracing/events/module/enable
0
ここで、module_get
、module_put
などのイベントに反応するフィルターを最初に作成する必要がありますが、これはtestmod
モジュールに対してのみです。そのためには、まずイベントの形式を確認する必要があります。
$ Sudo cat /sys/kernel/debug/tracing/events/module/module_put/format
name: module_put
ID: 312
format:
...
field:__data_loc char[] name; offset:20; size:4; signed:1;
print fmt: "%s call_site=%pf refcnt=%d", __get_str(name), (void *)REC->ip, REC->refcnt
ここで、name
と呼ばれるフィールドがあり、これにフィルターをかけることができるドライバー名を保持していることがわかります。フィルターを作成するには、フィルター変数をecho
単純に対応するファイルに入れます。
Sudo bash -c "echo name == testmod > /sys/kernel/debug/tracing/events/module/filter"
ここでは、最初にSudo
を呼び出す必要があるため、echo
リダイレクト全体をSudo
- ed bash
の引数コマンドとしてラップする必要があることに注意してください。第二に、特定のイベント(module/filter
など)ではなく「親」module/module_put/filter
に書き込んだため、このフィルターはmodule
ディレクトリの「子」としてリストされたすべてのイベントに適用されることに注意してください。 。
最後に、モジュールのトレースを有効にします。
Sudo bash -c "echo 1 > /sys/kernel/debug/tracing/events/module/enable"
この時点から、トレースログファイルを読み取ることができます。私にとっては、トレースファイルのブロックされた「パイプされた」バージョンを読んだ-このように:
Sudo cat /sys/kernel/debug/tracing/trace_pipe | tee tracelog.txt
この時点では、ログには何も表示されません。そのため、(trace_pipe
が読み取られている場所とは異なるターミナルで)ドライバーをロード(および利用、削除)します。
$ Sudo insmod ./testmod.ko
$ cat /proc/testmod-sample
This is testmod.
$ Sudo rmmod testmod
trace_pipe
が読み取られているターミナルに戻ると、次のようなものが表示されます。
# tracer: nop
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
insmod-21137 [001] 28038.101509: module_load: testmod
insmod-21137 [001] 28038.103904: module_put: testmod call_site=sys_init_module refcnt=2
rmmod-21354 [000] 28080.244448: module_free: testmod
testmod
ドライバーに対して取得することはほとんどすべてです。refcountは、insmod
を読み取るときではなく、ドライバーがロード(rmmod
)またはアンロード(cat
)されたときにのみ変更されます。したがって、次のようにtrace_pipe
からの読み取りを単純に中断できます。 CTRL+C その端末で;トレースを完全に停止するには:
Sudo bash -c "echo 0 > /sys/kernel/debug/tracing/tracing_enabled"
ここで、ほとんどの例は、ここのように/sys/kernel/debug/tracing/trace
の代わりにtrace_pipe
ファイルを読み取ることに言及していることに注意してください。ただし、1つの問題は、このファイルが「パイプ」されることを意図していないことです(したがって、このtrace
ファイルでtail -f
を実行しないでください)。しかし、代わりに、各操作の後にtrace
を再読み取りする必要があります。最初のinsmod
の後、cat
から同じ出力を取得し、trace
とtrace_pipe
の両方を取得します。ただし、rmmod
の後に、trace
ファイルを読み取ると、次のようになります。
<...>-21137 [001] 28038.101509: module_load: testmod
<...>-21137 [001] 28038.103904: module_put: testmod call_site=sys_init_module refcnt=2
rmmod-21354 [000] 28080.244448: module_free: testmod
...つまり、この時点でinsmod
はすでに長い時間終了しているため、プロセスリストにはもう存在しません。したがって、記録されたプロセスID(PID)で見つけることができません。プロセス名として空の<...>
を取得します。したがって、この場合、trace_pipe
からの実行中の出力を(tee
を介して)ログに記録することをお勧めします。また、trace
ファイルをクリア/リセット/消去するには、単に0を書き込むだけです:
Sudo bash -c "echo 0 > /sys/kernel/debug/tracing/trace"
これが直観に反すると思われる場合は、trace
が特殊ファイルであり、常にファイルサイズがゼロであると常に報告することに注意してください。
$ Sudo ls -la /sys/kernel/debug/tracing/trace
-rw-r--r-- 1 root root 0 2013-03-19 06:39 /sys/kernel/debug/tracing/trace
...「フル」であっても。
最後に、フィルターを実装しなかった場合、実行中のシステムでallモジュール呼び出しのログを取得することに注意してください。 grep
など、binfmt_misc
モジュールを使用するもの:
...
tr-6232 [001] 25149.815373: module_put: binfmt_misc call_site=search_binary_handler refcnt=133194
..
grep-6231 [001] 25149.816923: module_put: binfmt_misc call_site=search_binary_handler refcnt=133196
..
cut-6233 [000] 25149.817842: module_put: binfmt_misc call_site=search_binary_handler refcnt=129669
..
Sudo-6234 [001] 25150.289519: module_put: binfmt_misc call_site=search_binary_handler refcnt=133198
..
tail-6235 [000] 25150.316002: module_put: binfmt_misc call_site=search_binary_handler refcnt=129671
...これにより、かなりのオーバーヘッドが追加されます(ログデータの量と、生成に必要な処理時間の両方で)。
これを見ながら、私は Ftrace PDFによるLinuxカーネルのデバッグ につまずきました。これはツールを指します trace-cmd 。これは上記とほとんど同じですが、より簡単なコマンドラインインターフェイス。 KernelShark ;と呼ばれるtrace-cmd
の「フロントエンドリーダー」GUIもあります。これらは両方ともSudo apt-get install trace-cmd kernelshark
を介してDebian/Ubuntuリポジトリにもあります。これらのツールは、上記の手順の代わりになる可能性があります。
最後に、上記のtestmod
の例は複数のクレームのコンテキストでの使用を実際に示していませんが、コーディングしているUSBモジュールがpulseaudio
によって繰り返しクレームされていることを発見するために同じトレース手順を使用しましたUSBデバイスが差し込まれたらすぐに-そのような手順はそのようなユースケースで機能するようです。
Linux Kernel Module Programming Guide には、モジュールの使用回数は関数try_module_get
およびtry_module_put
。おそらく、これらの関数がモジュールで呼び出される場所を見つけることができます。
取得できるのは、どのモジュールが他のどのモジュールに依存しているかのリストです(Used by
列(lsmod)。モジュールがなぜロードされたのか、それがまだ何かのために必要であるのか、それをアンロードするとそれに依存するすべてのものが壊れるのかを伝えるプログラムを書くことはできません。
--forceオプションなしでrmmodを使用すると、モジュールを使用しているものがわかります。例:
$ lsmod | grep firewire
firewire_ohci 24695 0
firewire_core 50151 1 firewire_ohci
crc_itu_t 1717 1 firewire_core
$ Sudo modprobe -r firewire-core
FATAL: Module firewire_core is in use.
$ Sudo rmmod firewire_core
ERROR: Module firewire_core is in use by firewire_ohci
$ Sudo modprobe -r firewire-ohci
$ Sudo modprobe -r firewire-core
$ lsmod | grep firewire
$
lsof
またはfuser
を試すことができます。
kgdbを試して、ブレークポイントをモジュールに設定します