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

Posted

技术标签:

【中文标题】有没有办法弄清楚啥是使用 Linux 内核模块?【英文标题】:Is there a way to figure out what is using a Linux kernel module?有没有办法弄清楚什么是使用 Linux 内核模块? 【发布时间】:2010-10-01 17:31:43 【问题描述】:

如果我加载内核模块并使用lsmod 列出加载的模块,我可以获得模块的“使用计数”(引用该模块的其他模块的数量)。不过,有没有办法弄清楚 什么 正在使用模块?

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

【问题讨论】:

“什么”用什么术语?什么代码?什么模块?什么用户?什么程序?虽然我有点感觉这与编程无关:) 非常有趣 嗯,它编程相关的,因为我问是因为我正在编写一个内核模块。 请澄清问题以显示您要解决的编程问题。 问题对我来说很清楚,诺曼:他如何找出是什么让 rmmod 无法移除他的实验模块?如何避免每次编译新版本都需要重启? 【参考方案1】:

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

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

在网上进一步寻找这个,我偶然发现了Linux-Kernel Archive: [PATCH 1/2] tracing: Reduce overhead of module tracepoints的帖子;最终指向内核中存在的一个工具,称为(我猜)“跟踪”;这方面的文档在目录Documentation/trace - Linux kernel source tree 中。特别是,两个文件解释了跟踪工具,events.txt 和 ftrace.txt。

但是,在/sys/kernel/debug/tracing/README 中运行的Linux 系统上还有一个简短的“跟踪迷你HOWTO”(另见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 文件节点,它返回字符串“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) 找到当时 - 因此我们得到一个空白的&lt;...&gt; 作为进程名称。因此,在这种情况下,最好记录(通过teetrace_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

...即使它是“满的”。

最后,请注意,如果我们没有实现过滤器,我们将获得运行系统上所有模块调用的日志 - 这将记录任何调用(也包括后台)到@987654400 @ 等,因为那些使用 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

...这增加了相当多的开销(在日志数据量和生成它所需的处理时间方面)。


在查找此内容时,我偶然发现了Debugging Linux Kernel by Ftrace PDF,它指的是一个工具trace-cmd,它与上面的功能非常相似——但通过一个更简单的命令行界面。 trace-cmd 还有一个名为KernelShark 的“前端阅读器”GUI;这两个也通过sudo apt-get install trace-cmd kernelshark 在 Debian/Ubuntu 存储库中。这些工具可以替代上述过程。

最后,我要注意的是,虽然上面的 testmod 示例并没有真正显示在多个声明的上下文中的用途,但我使用相同的跟踪过程发现我正在编码的 USB 模块是插入 USB 设备后,pulseaudio 反复声明 - 因此该过程似乎适用于此类用例。

【讨论】:

感谢@RichardHansen 的评论 - 问题是“有没有办法弄清楚 什么 正在使用模块”;并且您可以在模块跟踪中看到例如rmmod-21354tr-6232(进程名称 - 进程ID)是执行module_put 的那些,即更改模块的引用计数 - 即那些进程正在“使用”该模块;所以我认为它完全回答了 OP 的要求......干杯! “我真的很累”链接失效了【参考方案2】:

它在Linux Kernel Module Programming Guide 上说,模块的使用计数由函数try_module_getmodule_put 控制。也许您可以找到为您的模块调用这些函数的位置。

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

【讨论】:

【参考方案3】:

您所得到的只是哪些模块依赖于哪些其他模块的列表(lsmod 中的Used by 列)。您无法编写程序来说明模块为何被加载、是否仍然需要它,或者如果您卸载它以及所有依赖它的东西可能会破坏什么。

【讨论】:

【参考方案4】:

你可以试试lsoffuser

【讨论】:

我究竟该如何使用 lsof 呢? 这不是一个通用的解决方案,但如果驱动程序在/dev 中创建了一个设备,它可以通过lsof 列出。【参考方案5】:

如果您在没有 --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
$

【讨论】:

嗯,这通常是不正确的:我的机器上有:$ lsmod | grep snd snd_seq 47263 1 snd_timer 19130 1 snd_seq snd_seq_device 5100 1 snd_seq ... ;所以 snd_seq 被某物声明(引用计数为 1),但无法说出原因,因为它后面的列是空的,因此没有其他模块专门声明它(但如果一个 ftraces 从一开始就已经内核启动过程,我猜可以找出来)。 这仅适用于 lsmod 也在“使用者”列中显示某些内容的情况; rmmod 在显示依赖项方面没有比 lsmod 更多的逻辑。【参考方案6】:

尝试 kgdb 并为您的模块设置断点

【讨论】:

【参考方案7】:

对于任何急于弄清楚为什么他们无法重新加载模块的人,我能够通过

解决这个问题 使用“modinfo”获取当前使用模块的路径 rm -rfing 将我要加载的新模块复制到它所在的路径 输入“modprobe DRIVER_NAME.ko”。

【讨论】:

这个答案实际上并没有回答所提出的问题。

以上是关于有没有办法弄清楚啥是使用 Linux 内核模块?的主要内容,如果未能解决你的问题,请参考以下文章

Linux内核——通过模块动态添加系统调用

windows任务管理器中查看选项中的“显示内核时间”是啥意思?啥是“内核时间”?

如何在 Linux 内核模块中找到合适的 DWARF 符号作为地址?

如何确定内核堆栈大小

linux中啥是块设备和字符设备?

有没有办法检查 PHP 范围内的可用端口?