有没有办法弄清楚什么是使用 Linux 内核模块?

如果我加载一个内核模块并用 lsmod列出加载的模块,我可以得到该模块的“使用计数”(包含对该模块的引用的其他模块的数量)。但是,有没有一种方法可以确定 什么是否使用了模块?

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

64326 次浏览

你可以试试 lsof或者 fuser

您获得的只是一个列表,其中列出了哪些模块依赖于哪些其他模块(lmod 中的 Used by列)。您不能编写一个程序来说明为什么要加载该模块,是否仍然需要该模块,或者如果您卸载该模块可能会发生什么以及依赖于该模块的所有事情。

它在 内核模块编程指南上说,模块的使用计数由函数 try_module_getmodule_put控制。也许您可以找到为您的模块调用这些函数的位置。

更多信息: https://www.kernel.org/doc/htmldocs/kernel-hacking/routines-module-use-counters.html

实际上,似乎有一种方法可以列出声称拥有模块/驱动程序的进程——然而,我还没有看到它的广告(Linux 内核文档之外) ,所以我将在这里记下我的笔记:

首先,非常感谢 @ haggai _ e的回答; 指向函数 try_module_gettry_module_put的指针(作为负责管理使用计数的指针)是允许我跟踪过程的关键。

在线进一步查找,我偶然发现了后面的 Linux-Kernel Archive: [ PATCH 1/2]跟踪: 减少模块跟踪点的开销; 它最终指向了内核中的一个工具,称为(我猜想)“跟踪”; 这个工具的文档在目录 文档/跟踪-Linux 内核源代码树中。特别是,有两个文件解释了跟踪工具 大事件Ftrace.txt

但是,在 /sys/kernel/debug/tracing/README中运行的 Linux 系统上也有一个简短的“跟踪 mini-HOWTO”(也参见 我真的受够了人们说没有文件..。) ; 请注意,在内核源代码树中,这个文件实际上是由文件 Kernel/trace/trace.c生成的。我已经在 Ubuntu natty上测试过了,并且注意到,由于 /sys属于 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 <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_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

现在,我们应该首先制作一个过滤器,它将对 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的字段,其中包含驱动程序名称,我们可以对其进行筛选。要创建一个过滤器,我们只需将过滤器字符串 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读取时,重新计数才会改变。因此,我们可以简单地用 CTRL + C在终端中断从 trace_pipe的读取,并完全停止跟踪:

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

在这里,请注意大多数示例提到读取文件 /sys/kernel/debug/tracing/trace而不是像这里一样读取 trace_pipe。但是,一个问题是这个文件不应该被“管道化”(因此您不应该在这个 trace文件上运行 tail -f) ; 相反,您应该在每次操作之后重新读取 trace。在第一个 insmod之后,我们将从 cat获得相同的输出——同时处理 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已经退出很长时间了,所以它不再存在于进程列表中——因此当时无法通过记录的进程 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

... 这会增加相当多的开销(包括日志数据量和生成日志所需的处理时间)。


在查找这些信息的时候,我偶然发现了 通过 Ftrace PDF 调试 Linux 内核,它指的是一个工具 Trace-cmd,它的功能和上面差不多——但是通过一个更简单的命令行界面。还有一个用于 trace-cmd的“前端阅读器”图形用户界面,称为 KernelShark,这两个图形用户界面都通过 sudo apt-get install trace-cmd kernelshark存储在 Debian/Ubuntu 存储库中。这些工具可以替代上述过程。

最后,我要提醒一下,虽然上面的 testmod示例并没有在多个声明的情况下使用,但是我使用了相同的跟踪过程来发现我正在编码的一个 USB 模块,在 USB 设备插入时就被 pulseaudio重复声明了——所以这个过程似乎对这样的用例起作用了。

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

对于那些急于想知道为什么不能重新装载模块的人来说,我可以通过

  • 使用“ modinfo”获取当前使用的模块的路径
  • 正在发送
  • 将要加载的新模块复制到它所在的路径
  • 输入“ modprochDRIVER _ NAME. ko”。

尝试 kgdb 并将断点设置为模块