有没有办法弄清楚正在使用Linux内核模块的原因?


74

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

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


“什么”用什么术语?什么代码?什么模块?什么用户 什么程序?我稍微感觉到这与编程无关:)有趣的是
Johannes Schaub-litb

1
好吧,这是和编程相关的,因为我在问,因为我在写内核模块。
mipadi

请阐明问题以显示您要解决的编程问题。
诺曼·拉姆齐

10
对我来说,这个问题很清楚,诺曼:他如何找出阻止rmmod删除其实验模块的原因?如何避免每次编译新版本时都必须重新启动?
死于Sente

Answers:


50

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

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

在网上进一步寻找,我偶然发现了Linux-Kernel Archive:[PATCH 1/2]跟踪:减少模块跟踪点的开销;最终指向内核中存在的设施,即(我猜)“跟踪”;该文档位于目录Documentation / trace-Linux内核源代码树中。特别是,有两个文件解释了跟踪工具events.txtftrace.txt

但是,在运行Linux的系统上也有一个简短的“跟踪mini-HOWTO” /sys/kernel/debug/tracing/README(另请参见,我真的很讨厌别人说没有文档…);请注意,在内核源代码树中,此文件实际上是由文件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

可以使用以下内容构建此模块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_getmodule_putetc事件做出反应,但仅针对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/filteretc),所以此过滤器将应用于列为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驱动程序获得的所有内容-refcount仅在驱动程序已加载(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-ingtracetrace_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的“前端阅读器” GUI ;这两个都还通过以下方式在Debian / Ubuntu存储库中sudo apt-get install trace-cmd kernelshark。这些工具可以替代上述过程。

最后,我只是要注意,虽然上面的testmod示例并未真正显示出在多个声明的上下文中的用法,但是我使用了相同的跟踪过程来发现我正在编码的USB模块在pulseaudio不久之后就被反复声明了所有权。USB设备已插入-因此该过程似乎适用于此类用例。


5
感谢您的评论@RichardHansen-问题是“是否有办法弄清楚正在使用什么模块”?并且您可以在模块跟踪中看到,例如rmmod-21354tr-6232(进程名称-进程ID)是执行的module_put,即更改模块的引用计数-也就是说,这些进程正在“使用”模块;所以我认为它完全可以满足OP的要求...干杯!
sdaau

“我真的很累”链接已断开
Hi-Angel


4

您所获得的只是一个列表,列出了哪些模块取决于其他模块(Used bylsmod中的列)。您无法编写程序来说明为什么要加载该模块,是否仍需要该模块,或者如果您卸载该模块以及所有依赖该模块的内容,则可能会损坏。


3

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

5
嗯,这不是一般正确的:我有我的机器上: $ lsmod | grep snd snd_seq 47263 1 snd_timer 19130 1 snd_seq snd_seq_device 5100 1 snd_seq ... ; 因此snd_seq被某事物声明(refcount为1),但是却不能说出原因,因为它后面的列为空,因此没有其他模块明确声明它(但是也许ftrace从启动过程开始就已经是内核了,我可以找到答案)。
sdaau 2014年

5
这仅在lsmod在“使用的”列中还显示某些内容的情况下有效;就显示依赖关系而言,rmmod的逻辑比lsmod没有。
dannysauer


0

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


-3

对于渴望找出为什么他们无法重新加载模块的人,我能够通过以下方法解决此问题:

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

该答案实际上并未回答所问的问题。
kelnos
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.