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).
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 natty
et notez que, puisque /sys
appartient à root, vous devez utiliser Sudo
pour 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 Makefile
NAME _ (placez-le simplement dans le même répertoire que testmod.c
, puis exécutez make
dans 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 testmod
name__. 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é name
name__, qui contient le nom du pilote, sur lequel nous pouvons filtrer. Pour créer un filtre, il suffit de echo
la 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 Sudo
name__, nous devons encapsuler la redirection entière echo
en tant que commande d'argument d'un Sudo
name __- ed bash
nom__. 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 module
name__.
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é (insmod
name__) ou non chargé (rmmod
name__), pas lorsque nous lisons cat
name__. 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 trace
name__); mais vous devriez plutôt relire le trace
après chaque opération. Après le premier insmod
name__, nous obtiendrions le même résultat de cat
name __- avec trace
et trace_pipe
; Cependant, après le rmmod
name__, la lecture du fichier trace
donnerait:
<...>-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, insmod
avait 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 tee
name__) une sortie en cours d'exécution de trace_pipe
dans ce cas. Notez également que pour effacer/réinitialiser/effacer le fichier trace
name__, 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 trace
est 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 grep
et 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 testmod
ne 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 pulseaudio
dès que le périphérique USB a été branché - la procédure semble donc fonctionner pour de tels cas d'utilisation.
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.
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.
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
$
Vous pouvez essayer lsof
ou fuser
.
essayez kgdb et définissez un point d'arrêt sur votre module