Bonjour à tous. Dans cet article, je veux partager l'expérience de la création d'un module de noyau Linux simple. Cet article sera utile à ceux qui souhaitent comprendre comment écrire des modules du noyau, mais ne savent pas par où commencer.
J'ai longtemps voulu comprendre ce sujet, mais jusqu'à récemment, je ne savais pas comment l'aborder. Je voulais que le module soit assez simple, mais plus compliqué que le message "Bonjour tout le monde!" sortie dans un fichier journal. Au final, j'ai décidé d'essayer de faire clignoter une LED. Un objectif supplémentaire était de déduire le paramètre responsable de la fréquence de clignotement dans sysfs.
Pour l'expérience, j'ai utilisé la carte Orange Pi One avec Ubuntu Linux à bord (version du noyau 3.4.113). Pour construire le module du noyau, vous avez besoin du compilateur gcc, de l'utilitaire make et des fichiers d'en-tête du noyau. Pour installer les fichiers d'en-tête, exécutez la commande suivante:
sudo apt-get install linux-headers-$(uname -r)
Ensuite, je vais analyser à mon avis les parties les plus intéressantes du module. Afin d'économiser de l'espace, je ne donnerai pas tout le code ici, il est disponible sur
github avec le fichier make.
Dans le module, j'ai utilisé les fichiers d'en-tête:
#include <linux/kernel.h> #include <linux/module.h> #include <linux/gpio.h> #include <linux/hrtimer.h> #include <linux/moduleparam.h>
kernel.h et module.h doivent toujours être activés lors de l'écriture du module du noyau, gpio.h est en fait responsable du travail avec GPIO, hrtimer.h (timer haute résolution) est le fichier d'en-tête du timer, moduleparam.h est nécessaire pour afficher les paramètres dans sysfs.
Afin de ne pas faire briller leurs variables et fonctions dans le noyau du système, toutes doivent être décrites comme statiques. Juste au cas où, je note que le noyau est écrit en C et statique, contrairement à C ++, cela signifie que l'objet n'est disponible qu'à l'intérieur du fichier exécutable.
Le point d'entrée est:
static int blink_module_init(void)
Ici, j'initialise les variables que j'utiliserai à l'avenir, notamment:
gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0);
ktime_set initialise le type de données ktime_t en le définissant sur le nombre de secondes (gpio_blink_interval_s) et de nanosecondes (0) souhaités. À l'avenir, cette variable sera utilisée par le temporisateur.
Voici une demande d'utilisation de GPIO:
err = gpio_request(BLINK_PIN_NR, "blink_led");
Cette fonction retourne 0 en cas de succès, donc à l'avenir je vérifie qu'elle est revenue. Ensuite, la broche sélectionnée doit être définie sur la sortie du signal et spécifier la valeur par défaut.
err = gpio_direction_output(BLINK_PIN_NR, GPIOF_INIT_LOW);
S'il n'y a pas eu d'erreurs, initialisez et démarrez le chronomètre
hrtimer_init(&gpio_blink_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); gpio_blink_timer.function = &gpio_blink_timer_callback; hrtimer_start(&gpio_blink_timer, gpio_timer_interval, HRTIMER_MODE_REL);
La fonction de rappel du minuteur sera gpio_blink_timer_callback. Dans cette fonction, je change la valeur de la broche à l'opposé
gpio_value ^= 0x01; gpio_set_value(BLINK_PIN_NR, gpio_value);
Je demande quand la minuterie devrait fonctionner la prochaine fois
hrtimer_forward_now(&gpio_blink_timer, gpio_timer_interval);
et renvoyez HRTIMER_RESTART.
Voyons maintenant comment afficher une sorte de variable dans sysfs. Pour cela j'utilise une macro
module_param_cb(gpio_blink_interval_s, &kp_ops, &gpio_blink_interval_s, 0660);
Le premier paramètre de cette macro est le nom de fichier dans sysfs. Le second est une structure de données contenant des fonctions de rappel. Le troisième paramètre est un pointeur sur la variable réelle et les quatrièmes autorisations de fichier dans sysfs.
Les fonctions de kp_ops sont appelées lorsque l'utilisateur modifie les valeurs du fichier sysfs ou lit sa valeur. Voici comment je les ai initialisés:
static const struct kernel_param_ops kp_ops = { .set = &set_blink_interval, .get = &get_blink_interval };
Dans ce cas, set est intéressant, car il définit une nouvelle valeur, gpio_timer_interval.
gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0);
Au point de sortie, j'efface toutes les ressources utilisées
static void blink_module_exit(void) { hrtimer_cancel(&gpio_blink_timer); gpio_set_value(BLINK_PIN_NR, 0); gpio_free(BLINK_PIN_NR); printk(KERN_ALERT "Blink module unloaded\n"); }
Les points d'entrée et de sortie doivent être indiqués dans les macros correspondantes
module_init(blink_module_init); module_exit(blink_module_exit);
Il semble décrire tous les points importants. Si les lecteurs ont des questions, je serai heureux d'y répondre dans les commentaires.