如果我加载内核模块并列出已加载的模块lsmod
,我可以得到模块的"使用计数"(其他模块的数量与模块的引用).有没有办法弄清楚什么是使用模块?
问题是我正在开发的模块坚持它的使用计数为1,因此我不能用rmmod
它来卸载它,但它的"by"列是空的.这意味着每次我想重新编译和重新加载模块时,我都必须重启机器(或者,至少,我无法找出任何其他方法来卸载它).
实际上,似乎有一种方法可以列出声称模块/驱动程序的进程 - 但是,我没有看到它被广告(在Linux内核文档之外),所以我会在这里记下我的注释:
首先,非常感谢@haggai_e的回答; 指向函数的指针try_module_get
和try_module_put
负责管理使用计数(refcount)的指针是允许我追踪程序的关键.
在网上进一步观察,我不知何故偶然发现Linux-Kernel Archive:[PATCH 1/2]跟踪:减少模块跟踪点的开销 ; 最后指向内核中存在的工具,称为(我猜)"跟踪"; 此文档位于Documentation/trace - Linux内核源代码树目录中.特别是,两个文件解释了跟踪工具,events.txt和ftrace.txt.
但是,在运行的Linux系统上还有一个简短的"跟踪迷你HOWTO" /sys/kernel/debug/tracing/README
(另见我真的厌倦了人们说没有文档...); 请注意,在内核源代码树中,此文件实际上是由文件kernel/trace/trace.c生成的.我已经在Ubuntu上测试了这个natty
,并注意,因为/sys
root由root拥有,你必须使用它sudo
来读取这个文件,就像在sudo cat
或
sudo less /sys/kernel/debug/tracing/README
......这几乎所有其他操作/sys
都将在这里描述.
首先,这里是一个简单的最小模块/驱动程序代码(我从引用的资源中放在一起),它只是创建一个/proc/testmod-sample
文件节点,它返回字符串"This is 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
#include
#include
#include // 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
驱动程序获取的全部内容- 仅当驱动程序加载(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
此处.但是,一个问题是这个文件不是"管道"(所以你不应该tail -f
在这个trace
文件上运行); 但相反,你应该trace
在每次操作后重新阅读.在第一个之后insmod
,我们将从cat
-ing 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
......即使它是"满员".
最后,请注意,如果我们没有实现过滤器,我们将获得正在运行的系统上的所有模块调用的日志 - 这将记录任何调用(也是后台)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和生成它所需的处理时间).
在查看此内容时,我偶然发现了Ftrace PDF调试Linux内核,它引用了一个工具trace-cmd,它几乎与上面类似 - 但是通过一个更简单的命令行界面.还有一个trace-cmd
名为KernelShark的"前端阅读器"GUI ; 这两个也都在Debian/Ubuntu存储库中sudo apt-get install trace-cmd kernelshark
.这些工具可以替代上述程序.
最后,我只是注意到,虽然上面的testmod
示例并没有真正显示在多个声明的上下文中使用,但我使用相同的跟踪过程来发现我正在编码的USB模块,pulseaudio
一旦被重复声明USB设备已插入 - 因此该过程似乎适用于此类用例.
它在Linux内核模块编程指南中说,模块的使用计数由函数try_module_get
和控制try_module_put
.也许您可以找到为模块调用这些函数的位置.