有没有办法找出什么是使用Linux内核模块?

如果我加载一个内核模块,并用lsmod列出加载的模块,我可以得到模块的“使用计数”(其他模块的数量与模块的引用)。 有没有办法找出什么是使用模块,但?

问题是我正在开发的模块坚持使用count为1,因此我不能使用rmmod来卸载它,但是它的“by”列是空的。 这意味着每次我想重新编译和重新加载模块,我都必须重新启动机器(或者,至less,我不能找出任何其他方式来卸载它)。

实际上,似乎有一种方法可以列出声称模块/驱动程序的进程 – 但是,我没有看到它公布的内容(Linux内核文档之外),所以我会在这里记下我的注释:

首先,非常感谢@haggai_e的回答。 指向函数try_module_gettry_module_put作为负责pipe理使用计数(refcount)的指针是允许我追踪程序的关键。

进一步寻找这个在线,我不知何故偶然发现了Linux内核归档:[PATCH 1/2]跟踪:减less模块跟踪点的开销 ; 最后指出了内核中存在的一个设施,称为(我猜)“追踪”; 此文档位于Documentation / trace – Linux内核源代码树目录中 。 特别是有两个文件解释了跟踪工具events.txt和ftrace.txt 。

但是,在/sys/kernel/debug/tracing/README ,在运行的Linux系统上也有一个简短的“跟踪mini-HOWTO”(另见我真的很厌烦别人说没有文档 )。 请注意,在内核源码树中,该文件实际上是由文件kernel / trace / trace.c生成的 。 我已经在Ubuntu natty上testing过了,注意由于/sys是root的,所以你必须使用sudo来读取这个文件,就像sudo cat或者

 sudo less /sys/kernel/debug/tracing/README 

…和/sys下的所有其他操作几乎相同,这里将对此进行描述。


首先,这里是一个简单的最小模块/驱动程序代码(我把它们放在引用的资源中),它简单地创build一个/proc/testmod-sample文件节点,它返回string“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 <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构build(只要把它放在与testmod.c相同的目录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 

当这个模块/驱动程序被构build时,输出是一个内核对象文件testmod.ko


此时,我们可以准备与try_module_gettry_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 

现在,我们应该首先制作一个filter,它将对module_getmodule_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的字段,它包含了驱动程序的名字,我们可以过滤它。 要创buildfilter,我们只需将filterstringecho显到相应的文件中:

 sudo bash -c "echo name == testmod > /sys/kernel/debug/tracing/events/module/filter" 

这里首先要注意的是,由于我们必须调用sudo ,所以我们必须将整个echoredirect作为一个sudo bash的参数命令来包装。 其次,请注意,由于我们写入“父” module/filter ,而不是特定的事件(这将是module/module_put/filter等),这个filter将被应用于所有列为module目录“子”的事件。

最后,我们启用跟踪模块:

 sudo bash -c "echo 1 > /sys/kernel/debug/tracing/events/module/enable" 

从这一点上,我们可以读取跟踪日志文件; 对于我来说,阅读阻塞的“pipe道”版本的跟踪文件工作 – 像这样:

 sudo cat /sys/kernel/debug/tracing/trace_pipe | tee tracelog.txt 

在这一点上,我们不会看到日志中的任何东西 – 所以现在是加载(并利用和删除)驱动程序的时间(在正在读取trace_pipe的不同terminal中):

 $ sudo insmod ./testmod.ko $ cat /proc/testmod-sample This is testmod. $ sudo rmmod testmod 

如果我们回到正在读取trace_pipe的terminal,我们应该看到如下内容:

 # 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 )时, testmod改变,而不是当我们读取cat 。 所以我们可以简单地在该terminal中用CTRL + C中断trace_pipe的读操作; 并完全停止追踪:

 sudo bash -c "echo 0 > /sys/kernel/debug/tracing/tracing_enabled" 

在这里,请注意,大多数示例指的是读取文件/sys/kernel/debug/tracing/trace而不是trace_pipe 。 然而,一个问题是,这个文件并不意味着“pipe道”(所以你不应该在这个trace文件上运行一个tail -f )。 而是应该在每次操作后重新读取trace 。 在第一次insmod ,我们将从cat -ing tracetrace_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已经退出了很长时间,所以在进程列表中不再存在 – 因此在当时不能通过logging的进程ID(PID)find – 因此我们得到一个空白的<...>作为进程名称。 因此,在这种情况下, trace_pipetrace_pipelogin(通过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 

即使是“满”。

最后,请注意,如果我们没有实现一个filter,我们将获得所有正在运行的系统上的所有模块调用的日志 – 这将logging任何调用(也是背景)为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 PDFdebuggingLinux内核 ,它引用了一个工具trace-cmd ,它几乎和上面类似,但通过一个更简单的命令行界面。 trace-cmd还有一个称为KernelShark的“前端阅读器”GUI; 这两个都是通过sudo apt-get install trace-cmd kernelshark在Debian / Ubuntu存储库中。 这些工具可以替代上述程序。

最后,我只是注意到,虽然上面的testmod示例并没有真正显示在多个声明的上下文中使用,但是我使用了相同的跟踪过程来发现我正在编码的USB模块被pulseaudio重复声明为USB设备插入后,程序似乎适用于这种使用情况。

它在“ Linux内核模块编程指南”中说, 模块的使用计数由函数try_module_gettry_module_put 。 也许你可以find为你的模块调用这些函数的地方。

你所得到的是哪个模块依赖于哪个模块(lsmod中的Used by列)的列表。 你不能编写一个程序来告诉模块为什么被加载,如果它仍然是需要的,或者如果你卸载了它,所有依赖它的东西都会被破坏。

如果你使用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 $ 

你可以试试lsoffuser

尝试kgdb并设置断点到你的模块

对于任何人拼命想弄清楚为什么他们不能重新加载模块,我能够通过解决这个问题

  • 使用“modinfo”获取当前使用模块的path
  • rm -rfing它
  • 复制新模块我想加载到它所在的path
  • 键入“modprobe DRIVER_NAME.ko”。