it-swarm.com.ru

Есть ли способ выяснить, что использует модуль ядра Linux?

Если я загружаю модуль ядра и перечисляю загруженные модули с помощью lsmod, я могу получить "счетчик использования" модуля (количество других модулей со ссылкой на модуль). Есть ли способ выяснить, что использует модуль, хотя?

Проблема в том, что модуль, который я разрабатываю, настаивает на том, что его счетчик использования равен 1, и поэтому я не могу использовать rmmod для его выгрузки, но его столбец "by" пуст. Это означает, что каждый раз, когда я хочу перекомпилировать и перезагружать модуль, мне приходится перезагружать машину (или, по крайней мере, я не могу найти какой-либо другой способ выгрузить его).

68
mipadi

На самом деле, кажется, есть способ перечислить процессы, которые требуют модуль/драйвер - однако, я не видел его рекламируемого (за пределами документации ядра Linux), поэтому я буду записывать свои заметки здесь:

Прежде всего, большое спасибо за ответ @ haggai_e ; указатель на функции try_module_get и try_module_put как ответственные за управление счетом использования (refcount) был ключом, который позволил мне отследить процедуру.

Заглядывая дальше в онлайн, я каким-то образом наткнулся на сообщение Архив ядра Linux: трассировка [PATCH 1/2]: сокращение накладных расходов на точки трассировки модуля ; который в конце концов указал на объект, присутствующий в ядре, известный как (я думаю) "отслеживание"; документация для этого находится в каталоге Documentation/trace - дерево исходников ядра Linux . В частности, два файла объясняют средство трассировки, events.txt и ftrace.txt .

Но в /sys/kernel/debug/tracing/README также есть краткое "мини-руководство по отслеживанию" в работающей системе Linux (см. Также я действительно очень устал от людей, которые говорят, что документации нет… ); обратите внимание, что в исходном дереве ядра этот файл фактически генерируется файлом kernel/trace/trace.c . Я проверил это в Ubuntu natty и обратите внимание, что, поскольку /sys принадлежит root, вы должны использовать Sudo для чтения этого файла, как в Sudo cat или

Sudo less /sys/kernel/debug/tracing/README

... и это касается почти всех других операций в /sys, которые будут описаны здесь.


Прежде всего, вот простой минимальный код модуля/драйвера (который я собрал из упомянутых ресурсов), который просто создает файловый узел /proc/testmod-sample, который возвращает строку "Это 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_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- и 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 уже давно завершено, и поэтому он больше не существует в списке процессов - и, следовательно, не может быть найден через записанный идентификатор процесса (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

... который добавляет немало накладных расходов (как в количестве данных журнала, так и в времени обработки, необходимом для его генерации).


При поиске я наткнулся на отладка ядра Linux с помощью Ftrace PDF , что относится к инструменту trace-cmd , который в значительной степени делает то же, что и выше, - но через более простой интерфейс командной строки. Для trace-cmd также имеется графический интерфейс "читателя внешнего интерфейса", называемый KernelShark ; оба они также находятся в репозиториях Debian/Ubuntu через Sudo apt-get install trace-cmd kernelshark. Эти инструменты могут быть альтернативой процедуре, описанной выше.

Наконец, я просто хотел бы отметить, что, хотя приведенный выше пример testmod на самом деле не показывает использование в контексте нескольких заявок, я использовал одну и ту же процедуру трассировки, чтобы обнаружить, что кодируемый USB-модуль неоднократно заявлялся pulseaudio как как только USB-устройство было подключено - похоже, процедура работает для таких случаев использования.

47
sdaau

В Руководстве по программированию модуля ядра Linux говорится, что счетчик использования модуля контролируется функциями try_module_get и try_module_put. Возможно, вы сможете найти, где эти функции вызываются для вашего модуля.

7
haggai_e

Все, что вы получите, это список модулей, которые зависят от других модулей (столбец Used by в lsmod). Вы не можете написать программу, чтобы сказать, почему модуль был загружен, если он все еще нужен для чего-либо, или что может сломаться, если вы выгружаете его и все, что от него зависит.

4
Norman Ramsey

Если вы используете 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
$
3
JonahB 9L

Вы можете попробовать lsof или fuser.

1
jedihawk

попробуйте kgdb и установите точку останова для вашего модуля

0
river