web-dev-qa-db-fra.com

Existe-t-il un moyen de savoir ce qui utilise un module de noyau Linux?

Si je charge un module du noyau et que je liste les modules chargés avec lsmod, je peux obtenir le "nombre d'utilisations" du module (nombre d'autres modules avec une référence au module). Y at-il un moyen de comprendre quoi utilise un module, cependant?

Le problème est qu'un module que je développe insiste sur le fait que son nombre d'utilisations est égal à 1 et que je ne peux donc pas utiliser rmmod pour le décharger, mais que sa colonne "by" est vide. Cela signifie que chaque fois que je veux recompiler et recharger le module, je dois redémarrer la machine (ou, du moins, je ne peux trouver aucun autre moyen de le décharger).

64
mipadi

En fait, il semble y avoir un moyen de lister les processus qui réclament un module/pilote - cependant, je ne l'ai pas vu annoncé (en dehors de la documentation du noyau Linux), je vais donc noter mes notes ici:

Tout d’abord, merci beaucoup pour la réponse de @ haggai_e ; le pointeur sur les fonctions try_module_get et try_module_put en tant que responsables de la gestion du nombre d'utilisations (refcount) était la clé qui m'a permis de suivre la procédure.

En cherchant plus loin en ligne, je suis tombé sur le message Archive du noyau Linux: trace [PATCH 1/2]: Réduit le temps système des points de trace du module ; qui a finalement indiqué une installation présente dans le noyau, connue sous le nom de "traçage"; la documentation correspondante se trouve dans le répertoire Documentation/trace - Arborescence des sources du noyau Linux . En particulier, deux fichiers expliquent la fonction de trace, events.txt et ftrace.txt .

Mais, il existe également un court "mini-HOWTO de traçage" sur un système Linux en cours d'exécution dans /sys/kernel/debug/tracing/README (voir aussi j'en ai vraiment marre des gens qui disent qu'il n'y a pas de documentation… ); notez que dans l'arborescence des sources du noyau, ce fichier est en fait généré par le fichier kernel/trace/trace.c . J'ai testé cela sur Ubuntu nattyet notez que, puisque /sys appartient à root, vous devez utiliser Sudopour lire ce fichier, comme dans Sudo cat ou

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

... et cela s'applique à peu près à toutes les autres opérations sous /sys qui seront décrites ici.


Tout d’abord, voici un code module/pilote minimal simple (que j’ai rassemblé à partir des ressources mentionnées), qui crée simplement un noeud de fichier /proc/testmod-sample, qui renvoie la chaîne "This is testmod". quand il est lu c'est 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");

Ce module peut être construit avec les éléments suivants MakefileNAME _ (placez-le simplement dans le même répertoire que testmod.c, puis exécutez makedans ce même répertoire):

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

Lorsque ce module/pilote est créé, la sortie est un fichier d’objet du noyau, testmod.ko.


À ce stade, nous pouvons préparer le suivi des événements liés à try_module_get et try_module_put; ceux-ci sont dans /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

Notez que sur mon système, le traçage est activé par défaut:

$ Sudo cat /sys/kernel/debug/tracing/tracing_enabled
1

... cependant, le traçage du module (spécifiquement) n'est pas:

$ Sudo cat /sys/kernel/debug/tracing/events/module/enable
0

Maintenant, nous devrions d’abord créer un filtre qui réagira sur les événements module_get, module_put etc, mais uniquement pour le module testmodname__. Pour ce faire, nous devons d’abord vérifier le format de l’événement:

$ 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

Nous pouvons voir ici qu’il existe un champ appelé namename__, qui contient le nom du pilote, sur lequel nous pouvons filtrer. Pour créer un filtre, il suffit de echola chaîne de filtrage dans le fichier correspondant:

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

Ici, notons tout d'abord que, puisque nous devons appeler Sudoname__, nous devons encapsuler la redirection entière echoen tant que commande d'argument d'un Sudoname __- ed bashnom__. Deuxièmement, notez que puisque nous avons écrit au "parent" module/filter, et non aux événements spécifiques (ce qui serait module/module_put/filter etc), ce filtre sera appliqué à tous les événements répertoriés comme "enfants" du répertoire modulename__.

Enfin, nous activons le traçage pour le module:

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

À partir de ce moment, nous pouvons lire le fichier journal de trace. pour moi, la lecture de la version bloquée du fichier de trace a fonctionné - comme ceci:

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

À ce stade, nous ne verrons rien dans le journal. Il est donc temps de charger (et d'utiliser et de supprimer) le pilote (dans un terminal différent de celui où le code trace_pipe est en cours de lecture):

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

Si nous retournons au terminal où trace_pipe est en cours de lecture, nous devrions voir quelque chose comme:

# 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

C'est à peu près tout ce que nous obtiendrons pour notre pilote testmod- le refcount ne change que lorsque le pilote est chargé (insmodname__) ou non chargé (rmmodname__), pas lorsque nous lisons catname__. Nous pouvons donc simplement interrompre la lecture de trace_pipe avec CTRL+C dans ce terminal; et pour arrêter le traçage complètement:

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

Ici, notez que la plupart des exemples font référence à la lecture du fichier /sys/kernel/debug/tracing/trace au lieu de trace_pipe comme ici. Cependant, un problème est que ce fichier n'est pas censé être "dirigé" (vous ne devez donc pas exécuter un tail -f sur ce fichier tracename__); mais vous devriez plutôt relire le traceaprès chaque opération. Après le premier insmodname__, nous obtiendrions le même résultat de catname __- avec traceet trace_pipe; Cependant, après le rmmodname__, la lecture du fichier tracedonnerait:

   <...>-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

... c'est-à-dire qu'à ce stade, insmodavait déjà été quitté depuis longtemps et qu'il n'existe donc plus dans la liste des processus - et ne peut donc pas être trouvé via l'ID de processus enregistré (PID) à ce moment-là. nous obtenons un <...> vide comme nom de processus. Par conséquent, il est préférable de consigner (via teename__) une sortie en cours d'exécution de trace_pipe dans ce cas. Notez également que pour effacer/réinitialiser/effacer le fichier tracename__, on écrit simplement un 0 dans celui-ci:

Sudo bash -c "echo 0 > /sys/kernel/debug/tracing/trace"

Si cela semble contre-intuitif, notez que traceest un fichier spécial et indiquera toujours une taille de fichier égale à zéro de toute façon:

$ 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

... même si c'est "complet".

Enfin, notez que si nous n’avions pas implémenté de filtre, nous aurions obtenu un journal de tous les appels du module sur le système en cours d'exécution - qui enregistrerait tout appel (également en arrière-plan) dans grepet autres, car ceux-ci utilisent le module 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

... ce qui ajoute un peu de temps système (en termes de quantité de données de journal et de temps de traitement nécessaire pour les générer).


En cherchant cela, je suis tombé sur Débogage du noyau Linux par Ftrace PDF , qui fait référence à un outil trace-cmd , qui fait à peu près la même chose que ci-dessus - mais à travers un interface de ligne de commande plus facile. Il existe également une interface graphique "lecteur frontal" pour trace-cmd appelée KernelShark ; ces deux éléments se trouvent également dans les référentiels Debian/Ubuntu via Sudo apt-get install trace-cmd kernelshark. Ces outils pourraient constituer une alternative à la procédure décrite ci-dessus.

Enfin, je voudrais juste noter que, bien que l'exemple ci-dessus testmodne montre pas vraiment l'utilisation dans le contexte de plusieurs revendications, j'ai utilisé la même procédure de traçage pour découvrir qu'un module USB que je suis en train de coder a été réclamé à plusieurs reprises par pulseaudiodès que le périphérique USB a été branché - la procédure semble donc fonctionner pour de tels cas d'utilisation.

47
sdaau

Il est dit dans le Guide de programmation du module de noyau Linux que le nombre d'utilisations d'un module est contrôlé par les fonctions try_module_get et try_module_put. Peut-être que vous pouvez trouver où ces fonctions sont appelées pour votre module.

6
haggai_e

Tout ce que vous obtenez est une liste des modules qui dépendent de quels autres modules (la colonne Used by dans lsmod). Vous ne pouvez pas écrire de programme pour dire pourquoi le module a été chargé, s'il est toujours nécessaire, ou ce qui pourrait se briser si vous le déchargez et tout ce qui en dépend.

4
Norman Ramsey

Si vous utilisez rmmod SANS l'option --force, il vous dira ce qui utilise un module. Exemple:

$ 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
$
2
JonahB 9L

Vous pouvez essayer lsof ou fuser.

1
jedihawk

essayez kgdb et définissez un point d'arrêt sur votre module

0
river