如果我加载内核模块并列出加载的模块lsmod
,我可以获得模块的“使用计数”(其他模块的数量与模块的引用)。有没有办法找出 使用模块的内容?
问题是我正在开发的模块坚持其使用次数为1,因此我无法使用rmmod
来卸载它,但其“by”列为空。这意味着每次我想重新编译和重新加载模块时,我都必须重新启动机器(或者,至少,我无法找出任何其他方法来卸载它)。
答案 0 :(得分:47)
实际上,似乎有一种方法可以列出声称模块/驱动程序的进程 - 但是,我还没看到它被宣传(在Linux内核文档之外),所以我会记下我的笔记这里:
首先,非常感谢@haggai_e的回答;指向函数try_module_get
和try_module_put
的指针作为负责管理使用计数(refcount)的指针是允许我追踪程序的关键。
在网上寻找更多内容,我不知何故偶然发现了帖子Linux-Kernel Archive: [PATCH 1/2] tracing: Reduce overhead of module tracepoints;最后指向内核中存在的工具,称为(我猜)"跟踪&#34 ;;相关文档位于Documentation/trace - Linux kernel source tree目录中。特别是,两个文件解释了跟踪工具events.txt和ftrace.txt。
但是,还有一个简短的跟踪迷你HOWTO"在/sys/kernel/debug/tracing/README
中正在运行的Linux系统上(另请参阅I'm really really tired of people saying that there's no documentation…);请注意,在内核源代码树中,此文件实际上是由文件kernel/trace/trace.c生成的。我已经在Ubuntu natty
上对此进行了测试,请注意,由于/sys
由root拥有,因此您必须使用sudo
来读取此文件,例如sudo cat
或
sudo less /sys/kernel/debug/tracing/README
...这适用于/sys
下的所有其他操作,这将在此处描述。
首先,这里是一个简单的最小模块/驱动程序代码(我从引用的资源中放在一起),它只是创建一个/proc/testmod-sample
文件节点,它返回字符串"这是testmod "什么时候阅读;这是 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
的参数命令。其次,请注意,因为我们写了&#34;父母&#34; module/filter
,而不是具体事件(可能是module/module_put/filter
等),此过滤器将应用于列为&#34;儿童&#34; module
目录。
最后,我们启用模块跟踪:
sudo bash -c "echo 1 > /sys/kernel/debug/tracing/events/module/enable"
从这一点开始,我们可以读取跟踪日志文件;对我来说,阅读封锁, &#34;管道&#34;跟踪文件的版本工作 - 像这样:
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
驱动程序获取的全部内容 - 仅当驱动程序加载(insmod
)或卸载(rmmod
)时才会更改引用计数,而不是在我们执行时阅读cat
。因此,我们可以简单地在该终端中用 CTRL + C 中断来自trace_pipe
的读取;并完全停止追踪:
sudo bash -c "echo 0 > /sys/kernel/debug/tracing/tracing_enabled"
请注意,大多数示例都是指在此处读取文件/sys/kernel/debug/tracing/trace
而不是trace_pipe
。然而,一个问题是这个文件并不意味着“管道”#34; (所以你不应该在这个tail -f
文件上运行trace
;但是你应该在每次操作后重新阅读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) - 因此我们得到一个空白<...>
作为进程名称。因此,在这种情况下,最好从tee
记录(通过trace_pipe
)运行输出。另请注意,为了清除/重置/擦除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
......即使它已经完全&#34;。
最后,请注意,如果我们没有实现过滤器,我们就会在正在运行的系统上获得所有模块调用的日志 - 这会将任何调用(也是后台)记录到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
...这增加了相当多的开销(在日志数据ammount中,以及生成它所需的处理时间)。
在查看此内容时,我偶然发现了Debugging Linux Kernel by Ftrace PDF,它指的是一个工具trace-cmd,它几乎与上面的相似 - 但是通过一个更简单的命令行界面。还有一个&#34;前端读卡器&#34;名为KernelShark的trace-cmd
的GUI;这两个都通过sudo apt-get install trace-cmd kernelshark
存在于Debian / Ubuntu存储库中。这些工具可以替代上述程序。
最后,我要注意的是,虽然上述testmod
示例并未在多个声明的上下文中显示,但我使用了相同的跟踪程序来发现USB模块一旦插入USB设备,我就会pulseaudio
多次声明我的编码 - 因此该过程似乎适用于此类用例。
答案 1 :(得分:7)
它在Linux Kernel Module Programming Guide上说模块的使用次数由函数try_module_get
和try_module_put
控制。也许您可以找到为您的模块调用这些函数的位置。
答案 2 :(得分:4)
您获得的是一个列表,其中列出了哪些模块取决于哪些其他模块(lsmod中的Used by
列)。你不能写一个程序来说明为什么模块被加载,如果它仍然需要什么,或者如果你卸载它以及依赖它的一切可能会破坏。
答案 3 :(得分:3)
如果你使用rmmod没有--force选项,它会告诉你使用模块的是什么。例如:
$ 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
$
答案 4 :(得分:1)
您可以尝试lsof
或fuser
。
答案 5 :(得分:0)
尝试kgdb并将断点设置为模块
答案 6 :(得分:-2)
对于任何急于弄清楚为什么他们无法重装模块的人,我能够解决这个问题