PARTIE 1 : FONDAMENTAUX
1.1 Architecture Linux : Espace noyau vs espace utilisateur
Système Linux
├── Espace utilisateur (User Space)
│ ├── Applications (bash, gcc, etc.)
│ ├── Bibliothèques (libc, etc.)
│ └── Appels système (syscalls)
│
└── Espace noyau (Kernel Space)
├── Modules noyau
├── Gestionnaire de mémoire
├── Planificateur (scheduler)
├── Pilotes de périphériques
└── Système de fichiers
Différences clés :
| Aspect | Espace utilisateur | Espace noyau |
|---|---|---|
| Accès mémoire | Mémoire virtuelle limitée | Accès complet à toute la mémoire |
| Privilèges | Restrictions sévères | Privilèges maximum |
| Stabilité | Processus isolé | Panne = crash système |
| Débogage | Outils classiques (gdb) | Débogage complexe |
| API | Bibliothèques standards (libc) | API noyau spécifiques |
1.2 Qu’est-ce qu’un module noyau ?
Définition :
Un module noyau est un morceau de code compilé qui peut être chargé et déchargé dynamiquement dans le noyau Linux, sans nécessiter de redémarrage.
Avantages :
- Modularité : Extension des fonctionnalités sans recompiler le noyau
- Développement : Test plus facile qu’un patch noyau complet
- Maintenance : Mise à jour sans redémarrage du système
- Spécialisation : Chargement uniquement des fonctionnalités nécessaires
Limitations importantes :
- ❌ Pas de bibliothèque C standard (pas de printf, malloc, etc.)
- ❌ Pas de protection mémoire (erreur = panne système)
- ❌ Pas de floating point (sauf avec précautions)
- ❌ Stack limitée (4Ko-8Ko typiquement)
1.3 Différences avec la programmation applicative
Exemple comparatif :
// PROGRAMMATION APPLICATIVE (espace utilisateur)
#include <stdio.h>
#include <stdlib.h>
int main() {
char *buffer = malloc(100); // malloc standard
printf("Hello World!\n"); // printf standard
free(buffer);
return 0;
}
// PROGRAMMATION NOYAU (espace noyau)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
static int __init mon_module_init(void) {
char *buffer = kmalloc(100, GFP_KERNEL); // kmalloc du noyau
printk(KERN_INFO "Hello Kernel!\n"); // printk du noyau
kfree(buffer);
return 0;
}
Points critiques :
- Gestion mémoire :
kmalloc/kfreeau lieu demalloc/free - Affichage :
printkau lieu deprintf - Entrée/sortie : Pas de fonctions standards (fopen, fread, etc.)
- Concurrence : Gestion explicite nécessaire (mutex, spinlocks)
PARTIE 2 : ENVIRONNEMENT DE DÉVELOPPEMENT
2.1 Installation des outils sur Debian 12
Commandes d’installation :
# Mettre à jour le système sudo apt update && sudo apt upgrade -y # Installer les outils de développement sudo apt install -y build-essential linux-headers-$(uname -r) sudo apt install -y libelf-dev libssl-dev flex bison sudo apt install -y git gdb dmesg # Vérifier l'installation uname -r # Doit afficher la version du noyau (ex: 6.1.0-10-amd64) ls /usr/src/linux-headers-$(uname -r) # Doit exister
2.2 Structure d’un projet de module
Arborescence recommandée :
mon_module/
├── Makefile
├── src/
│ ├── module_principal.c
│ └── module_secondaire.c
├── include/
│ └── definitions.h
└── scripts/
└── test_module.sh
AVERTISSEMENT IMPORTANT :
⚠️ TOUJOURS tester les modules dans une machine virtuelle
⚠️ NE JAMAIS développer sur votre machine principale
⚠️ Une erreur dans un module peut crasher tout le système
Premier module : Hello World
Créons notre premier module pour valider l’environnement :
hello_world.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
// Macro de licence (OBLIGATOIRE)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Votre Nom");
MODULE_DESCRIPTION("Mon premier module noyau");
MODULE_VERSION("1.0");
// Fonction d'initialisation
static int __init hello_init(void)
{
// printk pour l'affichage dans les logs noyau
// KERN_INFO est le niveau de log
printk(KERN_INFO "Hello World! Module charge avec succes.\n");
return 0; // 0 = succès
}
// Fonction de nettoyage
static void __exit hello_exit(void)
{
printk(KERN_INFO "Au revoir! Module decharge.\n");
}
// Déclaration des points d'entrée
module_init(hello_init);
module_exit(hello_exit);
Makefile correspondant :
makefile
# Spécifier la version du noyau
KVERSION = $(shell uname -r)
# Nom du module
MODULE_NAME = hello_world
obj-m += $(MODULE_NAME).o
# Règle de compilation
all:
make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules
# Règle de nettoyage
clean:
make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean
# Règle d'installation
install:
sudo insmod $(MODULE_NAME).ko
# Règle de désinstallation
uninstall:
sudo rmmod $(MODULE_NAME)
# Règle de test
test: all install
dmesg | tail -5
sleep 2
make uninstall
dmesg | tail -5
Commandes de test :
# Compiler le module
make
# Charger le module
sudo insmod hello_world.ko
# Vérifier qu'il est chargé
lsmod | grep hello_world
# Voir les logs
dmesg | tail -5
# Décharger le module
sudo rmmod hello_world
# Vérifier les messages de sortie
dmesg | tail -5
Explications ligne par ligne :
#include <linux/module.h>: Définitions de base pour les modules#include <linux/kernel.h>: Fonctions noyau comme printk#include <linux/init.h>: Macros __init et __exitMODULE_LICENSE("GPL"): OBLIGATOIRE – spécifie la licencestatic int __init: Fonction exécutée au chargement__init: Le code peut être libéré après initialisationprintk(KERN_INFO "..."): Équivalent noyau de printfmodule_init/exit: Points d’entrée/sortie du module
PARTIE 3 : ANATOMIE D’UN MODULE
3.1 Structure de base complète
Voici un module plus complet qui montre tous les éléments essentiels :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h> // Pour kmalloc/kfree
#include <linux/errno.h> // Codes d'erreur
// ============================================================================
// SECTION : METADONNEES DU MODULE
// ============================================================================
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Votre Nom <email@example.com>");
MODULE_DESCRIPTION("Module demonstrant l'anatomie complete d'un module noyau");
MODULE_VERSION("1.0");
// Parametres du module (peuvent etre passes a l'insertion)
static char *parametre_texte = "valeur_par_defaut";
static int parametre_numerique = 42;
module_param(parametre_texte, charp, 0644);
MODULE_PARM_DESC(parametre_texte, "Un parametre texte modifiable");
module_param(parametre_numerique, int, 0644);
MODULE_PARM_DESC(parametre_numerique, "Un parametre numerique");
// ============================================================================
// SECTION : VARIABLES GLOBALES ET ETAT
// ============================================================================
static struct kmem_cache *mon_cache = NULL;
static void *allocation_persistante = NULL;
static int compteur_operations = 0;
// ============================================================================
// SECTION : FONCTION D'INITIALISATION
// ============================================================================
static int __init mon_module_init(void)
{
int resultat = 0;
printk(KERN_INFO "=== DEBUT INITIALISATION MODULE ===\n");
printk(KERN_INFO "Parametres: texte='%s', numerique=%d\n",
parametre_texte, parametre_numerique);
// 1. Allocation de memoire
allocation_persistante = kmalloc(1024, GFP_KERNEL);
if (!allocation_persistante) {
printk(KERN_ERR "ECHEC: Impossible d'allouer 1024 octets\n");
resultat = -ENOMEM;
goto erreur_allocation;
}
printk(KERN_INFO "SUCCES: Memoire allouee (1024 octets)\n");
// 2. Creation d'un cache memoire
mon_cache = kmem_cache_create("mon_cache_noyau",
256, 0, SLAB_HWCACHE_ALIGN, NULL);
if (!mon_cache) {
printk(KERN_ERR "ECHEC: Creation du cache memoire\n");
resultat = -ENOMEM;
goto erreur_cache;
}
printk(KERN_INFO "SUCCES: Cache memoire cree\n");
// 3. Initialisation des compteurs
compteur_operations = 0;
printk(KERN_INFO "=== INITIALISATION TERMINEE AVEC SUCCES ===\n");
return 0;
// ============================================================================
// SECTION : GESTION DES ERREURS (PATTERN goto)
// ============================================================================
erreur_cache:
kfree(allocation_persistante);
erreur_allocation:
printk(KERN_ERR "=== INITIALISATION ECHOUEE (code: %d) ===\n", resultat);
return resultat;
}
// ============================================================================
// SECTION : FONCTION DE NETTOYAGE
// ============================================================================
static void __exit mon_module_exit(void)
{
printk(KERN_INFO "=== DEBUT NETTOYAGE MODULE ===\n");
// 1. Destruction du cache memoire
if (mon_cache) {
kmem_cache_destroy(mon_cache);
printk(KERN_INFO "Cache memoire detruit\n");
}
// 2. Liberation de la memoire
if (allocation_persistante) {
kfree(allocation_persistante);
printk(KERN_INFO "Memoire liberee\n");
}
// 3. Affichage des statistiques
printk(KERN_INFO "Operations effectuees: %d\n", compteur_operations);
printk(KERN_INFO "=== NETTOYAGE TERMINEE ===\n");
}
// ============================================================================
// SECTION : POINTS D'ENTREE PRINCIPAUX
// ============================================================================
module_init(mon_module_init);
module_exit(mon_module_exit);
3.2 Explications détaillées
Les macros essentielles :
MODULE_LICENSE() – CRITIQUE :
MODULE_LICENSE("GPL"); // Licence GPL
// MODULE_LICENSE("GPL v2"); // GPL version 2
// MODULE_LICENSE("Dual BSD/GPL"); // Double licence
// MODULE_LICENSE("Proprietary"); // Licence propriétaire (limitations)
Sans MODULE_LICENSE(), le module marquera le noyau comme « tainted » !
module_param() : Permet de passer des paramètres au module :
static int ma_variable = 10; module_param(ma_variable, int, 0644); // Types: bool, charp, int, long, short, uint, ulong, ushort // Permissions: 0644 (rw-r--r--), 0444 (r--r--r--), etc.
Gestion d’erreurs avec pattern goto :
Pourquoi utiliser goto en noyau ?
static int __init init(void)
{
res = alloc_A();
if (!res) goto err_A;
res = alloc_B();
if (!res) goto err_B;
res = alloc_C();
if (!res) goto err_C;
return 0; // Succès
err_C:
free_B();
err_B:
free_A();
err_A:
return -ENOMEM;
}
Ce pattern est PRÉFÉRÉ car :
- Plus lisible que des if/else imbriqués
- Garantit le bon ordre de nettoyage
- Standard dans le code noyau Linux
3.3 Module avec meilleures pratiques
advanced_module.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/errno.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Expert Kernel");
MODULE_DESCRIPTION("Module avec gestion d'erreur robuste");
#define DEVICE_NAME "mon_device"
#define BUFFER_SIZE 4096
static struct {
void *buffer_principal;
void *buffer_secondaire;
int devices_crees;
bool initialisation_terminee;
} etat_global = {
.buffer_principal = NULL,
.buffer_secondaire = NULL,
.devices_crees = 0,
.initialisation_terminee = false
};
// Fonction de nettoyage intermédiaire
static void nettoyage_partiel(void)
{
printk(KERN_INFO "Nettoyage des ressources partielles...\n");
if (etat_global.buffer_secondaire) {
kfree(etat_global.buffer_secondaire);
etat_global.buffer_secondaire = NULL;
printk(KERN_INFO "Buffer secondaire libere\n");
}
if (etat_global.buffer_principal) {
kfree(etat_global.buffer_principal);
etat_global.buffer_principal = NULL;
printk(KERN_INFO "Buffer principal libere\n");
}
}
static int __init mon_module_init(void)
{
int ret = 0;
printk(KERN_INFO "Initialisation du module avance...\n");
// Étape 1: Allocation buffer principal
etat_global.buffer_principal = kmalloc(BUFFER_SIZE, GFP_KERNEL);
if (!etat_global.buffer_principal) {
printk(KERN_ERR "Echec allocation buffer principal\n");
ret = -ENOMEM;
goto echec_init;
}
printk(KERN_INFO "Buffer principal alloue (%d octets)\n", BUFFER_SIZE);
// Étape 2: Allocation buffer secondaire
etat_global.buffer_secondaire = kzalloc(BUFFER_SIZE / 2, GFP_KERNEL);
if (!etat_global.buffer_secondaire) {
printk(KERN_ERR "Echec allocation buffer secondaire\n");
ret = -ENOMEM;
goto echec_init;
}
printk(KERN_INFO "Buffer secondaire alloue et initialise a zero\n");
// Étape 3: Simulation creation device
// Normalement: register_chrdev, device_create, etc.
etat_global.devices_crees = 1;
printk(KERN_INFO "Device '%s' cree\n", DEVICE_NAME);
// Marquer l'initialisation comme reussie
etat_global.initialisation_terminee = true;
printk(KERN_INFO "=== MODULE CHARGE AVEC SUCCES ===\n");
return 0;
echec_init:
nettoyage_partiel();
printk(KERN_ERR "=== ECHEC CHARGEMENT MODULE (code: %d) ===\n", ret);
return ret;
}
static void __exit mon_module_exit(void)
{
printk(KERN_INFO "Dechargement du module...\n");
if (!etat_global.initialisation_terminee) {
printk(KERN_WARNING "Module jamais initialise completement\n");
nettoyage_partiel();
return;
}
// Simulation suppression device
if (etat_global.devices_crees > 0) {
printk(KERN_INFO "Device '%s' supprime\n", DEVICE_NAME);
etat_global.devices_crees = 0;
}
// Liberation memoire
nettoyage_partiel();
printk(KERN_INFO "=== MODULE DECHARGE AVEC SUCCES ===\n");
}
module_init(mon_module_init);
module_exit(mon_module_exit);
3.4 Makefile avancé
Makefile :
# Configuration
KVERSION := $(shell uname -r)
KDIR := /lib/modules/$(KVERSION)/build
PWD := $(shell pwd)
# Modules a compiler
obj-m += hello_world.o
obj-m += module_anatomy.o
obj-m += advanced_module.o
# Flags de compilation
ccflags-y := -DDEBUG -Wall -Wextra
# Cible par defaut
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
# Cible de debogage
debug: ccflags-y += -DDEBUG -g
debug: all
# Nettoyage
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
# Installation et test
test-module: all
sudo dmesg -C
sudo insmod hello_world.ko
dmesg | tail -3
sudo rmmod hello_world
dmesg | tail -3
test-advanced: all
sudo dmesg -C
sudo insmod advanced_module.ko
dmesg
sudo rmmod advanced_module
dmesg
# Aide
help:
@echo "Cibles disponibles:"
@echo " all - Compiler tous les modules"
@echo " debug - Compiler avec symboles de debogage"
@echo " clean - Nettoyer les fichiers generes"
@echo " test-module - Tester le module hello_world"
@echo " test-advanced - Tester le module avance"
.PHONY: all clean debug test-module test-advanced help
3.5 Commandes de test avancées
Script de test complet :
#!/bin/bash # test_complet.sh echo "=== TEST COMPLET DES MODULES ===" # Compilation echo "1. Compilation..." make clean make # Test parametres echo "2. Test avec parametres..." sudo insmod module_anatomy.ko parametre_texte="test_valeur" parametre_numerique=123 dmesg | tail -5 sudo rmmod module_anatomy echo "3. Test valeurs par defaut..." sudo insmod module_anatomy.ko dmesg | tail -5 sudo rmmod module_anatomy echo "4. Test module avance..." sudo insmod advanced_module.ko dmesg | tail -10 sudo rmmod advanced_module dmesg | tail -5 echo "=== TESTS TERMINES ==="
Commandes utiles pour l’inspection :
# Voir les modules charges lsmod # Voir les parametres d'un module (si charges) sudo modinfo hello_world.ko # Surveiller les logs noyau en temps reel sudo dmesg -w # Voir l'utilisation memoire des modules cat /proc/modules # Checker le statut 'tainted' cat /proc/sys/kernel/tainted
Points clés à retenir :
- Toujours implémenter module_init() et module_exit()
- MODULE_LICENSE() est OBLIGATOIRE
- Utiliser le pattern goto pour la gestion d’erreurs
- Nettoyer TOUTES les ressources dans la fonction exit
- printk() pour le débogage, pas de printf()
- Tester systématiquement les échecs d’allocation
AVERTISSEMENT : Ces modules sont éducatifs. En production, ajoutez :
- Vérifications de sécurité supplémentaires
- Gestion de la concurrence (mutex)
- Support de la configuration via sysfs/procfs
- Gestion d’énergie (suspend/resume)
PARTIE 4 : BIBLIOTHÈQUES ET API DU NOYAU
4.1 Headers principaux – Révision étendue
#include <linux/module.h> // Définitions de base des modules #include <linux/kernel.h> // Fonctions noyau (printk, etc.) #include <linux/init.h> // Macros __init, __exit #include <linux/slab.h> // Allocation mémoire (kmalloc, kfree) #include <linux/fs.h> // Système de fichiers #include <linux/errno.h> // Codes d'erreur #include <linux/device.h> // Infrastructure device #include <linux/cdev.h> // Périphériques caractères #include <linux/uaccess.h> // copy_to_user, copy_from_user #include <linux/io.h> // Accès mémoire mappé (ioremap, iounmap) #include <linux/interrupt.h> // Gestion d'interruptions #include <linux/gpio.h> // API GPIO - IMPORTANT POUR NOUS #include <linux/of.h> // Device Tree #include <linux/of_gpio.h> // GPIO via Device Tree #include <linux/delay.h> // Délais (msleep, udelay)
4.2 API Mémoire – Approfondissement
Exemple complet d’allocations mémoire :
#include <linux/slab.h>
#include <linux/vmalloc.h>
static void demo_memoire(void)
{
void *kmalloc_ptr = NULL;
void *kzalloc_ptr = NULL;
void *vmalloc_ptr = NULL;
int *tableau = NULL;
// 1. kmalloc - mémoire physique contiguë (rapide)
kmalloc_ptr = kmalloc(1024, GFP_KERNEL);
if (!kmalloc_ptr) {
printk(KERN_ERR "kmalloc a échoué\n");
return;
}
printk(KERN_INFO "kmalloc réussi: %p\n", kmalloc_ptr);
// 2. kzalloc - kmalloc + initialisation à zéro
kzalloc_ptr = kzalloc(512, GFP_KERNEL);
if (!kzalloc_ptr) {
printk(KERN_ERR "kzalloc a échoué\n");
goto erreur;
}
printk(KERN_INFO "kzalloc réussi: %p (initialisé à 0)\n", kzalloc_ptr);
// 3. Tableau avec kmalloc_array
tableau = kmalloc_array(100, sizeof(int), GFP_KERNEL);
if (!tableau) {
printk(KERN_ERR "kmalloc_array a échoué\n");
goto erreur;
}
printk(KERN_INFO "Tableau de 100 int alloué: %p\n", tableau);
// 4. vmalloc - mémoire virtuelle (peut être non contiguë en physique)
vmalloc_ptr = vmalloc(8192); // 8KB
if (!vmalloc_ptr) {
printk(KERN_ERR "vmalloc a échoué\n");
goto erreur;
}
printk(KERN_INFO "vmalloc réussi: %p\n", vmalloc_ptr);
// Utilisation...
memset(kmalloc_ptr, 0xAA, 1024);
memset(tableau, 0, 100 * sizeof(int));
erreur:
// Nettoyage dans l'ordre INVERSE
if (vmalloc_ptr) vfree(vmalloc_ptr);
if (tableau) kfree(tableau);
if (kzalloc_ptr) kfree(kzalloc_ptr);
if (kmalloc_ptr) kfree(kmalloc_ptr);
}
4.4 API GPIO – PARTIE CRITIQUE
4.4.1 Concepts GPIO de base
Définitions :
- GPIO : General Purpose Input/Output
- Direction : INPUT (lecture) ou OUTPUT (écriture)
- Valeur : HIGH (1) ou LOW (0)
- Actif : ACTIVE_HIGH ou ACTIVE_LOW
4.4.2 Module GPIO complet avec LED
gpio_led.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/errno.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Expert GPIO");
MODULE_DESCRIPTION("Controle LED via GPIO avec gestion d'erreurs robuste");
// ============================================================================
// CONFIGURATION GPIO - À ADAPTER À VOTRE MATÉRIEL !
// ============================================================================
/*
* IMPORTANT: Choisir les GPIO selon votre carte:
* - Raspberry Pi: GPIO4=broche7, GPIO17=broche11, GPIO18=broche12
* - BeagleBone: GPIO_20=brocheP9.41, GPIO_60=brocheP9.12
* - Carte DIY: Vérifier la documentation
*/
// Par défaut: GPIO 4 (broche 7 sur Raspberry Pi)
static int gpio_led = 4;
module_param(gpio_led, int, 0644);
MODULE_PARM_DESC(gpio_led, "Numero GPIO pour la LED (defaut: 4)");
static int gpio_bouton = 17; // GPIO pour bouton (optionnel)
module_param(gpio_bouton, int, 0644);
MODULE_PARM_DESC(gpio_bouton, "Numero GPIO pour bouton (defaut: 17)");
// Etat global
static struct {
bool gpio_led_reserve;
bool gpio_bouton_reserve;
bool module_actif;
} etat_gpio = {
.gpio_led_reserve = false,
.gpio_bouton_reserve = false,
.module_actif = false
};
// ============================================================================
// FONCTIONS GPIO
// ============================================================================
static int configurer_gpio_led(void)
{
int ret = 0;
printk(KERN_INFO "Configuration GPIO %d pour LED...\n", gpio_led);
// 1. Réserver le GPIO
ret = gpio_request(gpio_led, "led_module");
if (ret) {
printk(KERN_ERR "Echec reservation GPIO %d (code: %d)\n", gpio_led, ret);
return ret;
}
etat_gpio.gpio_led_reserve = true;
printk(KERN_INFO "GPIO %d reserve avec succes\n", gpio_led);
// 2. Configurer en sortie
ret = gpio_direction_output(gpio_led, 0); // 0 = LED éteinte au début
if (ret) {
printk(KERN_ERR "Echec configuration sortie GPIO %d\n", gpio_led);
return ret;
}
printk(KERN_INFO "GPIO %d configure en sortie (valeur initiale: LOW)\n", gpio_led);
return 0;
}
static int configurer_gpio_bouton(void)
{
int ret = 0;
printk(KERN_INFO "Configuration GPIO %d pour bouton...\n", gpio_bouton);
// 1. Réserver le GPIO
ret = gpio_request(gpio_bouton, "bouton_module");
if (ret) {
printk(KERN_WARNING "Echec reservation bouton GPIO %d (inutilisable)\n", gpio_bouton);
return ret; // Non critique pour ce demo
}
etat_gpio.gpio_bouton_reserve = true;
// 2. Configurer en entrée
ret = gpio_direction_input(gpio_bouton);
if (ret) {
printk(KERN_WARNING "Echec configuration entree GPIO %d\n", gpio_bouton);
return ret;
}
printk(KERN_INFO "GPIO %d configure en entree\n", gpio_bouton);
return 0;
}
static void liberer_gpios(void)
{
if (etat_gpio.gpio_led_reserve) {
gpio_set_value(gpio_led, 0); // Eteindre LED
gpio_free(gpio_led);
etat_gpio.gpio_led_reserve = false;
printk(KERN_INFO "GPIO LED %d libere\n", gpio_led);
}
if (etat_gpio.gpio_bouton_reserve) {
gpio_free(gpio_bouton);
etat_gpio.gpio_bouton_reserve = false;
printk(KERN_INFO "GPIO bouton %d libere\n", gpio_bouton);
}
}
// ============================================================================
// DEMONSTRATION LED
// ============================================================================
static void demonstration_led(void)
{
int i;
printk(KERN_INFO "=== DEMONSTRATION LED GPIO %d ===\n", gpio_led);
// Clignotement rapide
for (i = 0; i < 6; i++) {
gpio_set_value(gpio_led, 1); // Allumer
printk(KERN_INFO "LED ON\n");
msleep(200); // 200ms - NE PAS utiliser usleep en noyau!
gpio_set_value(gpio_led, 0); // Eteindre
printk(KERN_INFO "LED OFF\n");
msleep(200);
}
// Allumer en continu
gpio_set_value(gpio_led, 1);
printk(KERN_INFO "LED allumee en continu\n");
}
static void lire_bouton(void)
{
int valeur;
if (!etat_gpio.gpio_bouton_reserve) {
return; // Bouton non configuré
}
valeur = gpio_get_value(gpio_bouton);
printk(KERN_INFO "Bouton GPIO %d: %s\n",
gpio_bouton, valeur ? "HIGH" : "LOW");
}
// ============================================================================
// INITIALISATION ET NETTOYAGE
// ============================================================================
static int __init gpio_module_init(void)
{
int ret;
printk(KERN_INFO "=== INITIALISATION MODULE GPIO ===\n");
// 1. Configuration LED (obligatoire)
ret = configurer_gpio_led();
if (ret) {
goto erreur_init;
}
// 2. Configuration bouton (optionnelle)
configurer_gpio_bouton(); // On ignore les erreurs
// 3. Démonstration
demonstration_led();
lire_bouton();
etat_gpio.module_actif = true;
printk(KERN_INFO "=== MODULE GPIO ACTIF ===\n");
printk(KERN_INFO "LED sur GPIO: %d\n", gpio_led);
if (etat_gpio.gpio_bouton_reserve) {
printk(KERN_INFO "Bouton sur GPIO: %d\n", gpio_bouton);
}
return 0;
erreur_init:
liberer_gpios();
printk(KERN_ERR "=== ECHEC INITIALISATION GPIO ===\n");
return ret;
}
static void __exit gpio_module_exit(void)
{
printk(KERN_INFO "=== NETTOYAGE MODULE GPIO ===\n");
if (etat_gpio.module_actif) {
// Eteindre la LED avant de sortir
if (etat_gpio.gpio_led_reserve) {
gpio_set_value(gpio_led, 0);
printk(KERN_INFO "LED eteinte\n");
}
liberer_gpios();
}
printk(KERN_INFO "=== MODULE GPIO DESACTIVE ===\n");
}
module_init(gpio_module_init);
module_exit(gpio_module_exit);
4.5 API Timers – Pour exécution périodique
gpio_timer.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/timer.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("LED clignotante avec timer noyau");
static int gpio_led = 4;
static int intervalle_ms = 500; // 500ms par défaut
module_param(intervalle_ms, int, 0644);
static struct timer_list mon_timer;
static bool etat_led = false; // false=OFF, true=ON
// Fonction appelée par le timer
static void timer_callback(struct timer_list *t)
{
// Changer l'état de la LED
etat_led = !etat_led;
gpio_set_value(gpio_led, etat_led ? 1 : 0);
printk(KERN_INFO "Timer: LED %s\n", etat_led ? "ON" : "OFF");
// Reprogrammer le timer
mod_timer(&mon_timer, jiffies + msecs_to_jiffies(intervalle_ms));
}
static int __init timer_module_init(void)
{
int ret;
printk(KERN_INFO "Initialisation timer LED...\n");
// Configuration GPIO
ret = gpio_request(gpio_led, "timer_led");
if (ret) {
printk(KERN_ERR "Echec GPIO %d\n", gpio_led);
return ret;
}
gpio_direction_output(gpio_led, 0);
// Initialisation du timer
timer_setup(&mon_timer, timer_callback, 0);
// Premier déclenchement
mod_timer(&mon_timer, jiffies + msecs_to_jiffies(intervalle_ms));
printk(KERN_INFO "Timer LED actif (intervalle: %d ms)\n", intervalle_ms);
return 0;
}
static void __exit timer_module_exit(void)
{
// Arrêter le timer
del_timer_sync(&mon_timer);
// Eteindre LED et libérer GPIO
gpio_set_value(gpio_led, 0);
gpio_free(gpio_led);
printk(KERN_INFO "Timer LED desactive\n");
}
module_init(timer_module_init);
module_exit(timer_module_exit);
4.6 Makefile pour modules GPIO
Makefile :
KVERSION := $(shell uname -r)
KDIR := /lib/modules/$(KVERSION)/build
PWD := $(shell pwd)
obj-m += gpio_led.o
obj-m += gpio_timer.o
ccflags-y := -Wall -Wextra
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
# Test GPIO LED
test-gpio: all
sudo dmesg -C
sudo insmod gpio_led.ko gpio_led=4 gpio_bouton=17
dmesg
sleep 3
sudo rmmod gpio_led
dmesg
# Test timer LED
test-timer: all
sudo dmesg -C
sudo insmod gpio_timer.ko intervalle_ms=300
dmesg | head -10
sleep 5
sudo rmmod gpio_timer
dmesg | tail -5
.PHONY: all clean test-gpio test-timer
4.7 Commandes de test GPIO
Script de test pratique :
#!/bin/bash # test_gpio.sh echo "=== TEST MODULES GPIO ===" # Vérifier les GPIO disponibles echo "1. GPIO disponibles:" ls /sys/class/gpio/ 2>/dev/null || echo "Interface GPIO non disponible" # Compilation echo "2. Compilation modules..." make # Test module LED basique echo "3. Test module LED..." sudo insmod gpio_led.ko gpio_led=4 sleep 2 sudo rmmod gpio_led # Test avec paramètres différents echo "4. Test avec GPIO personnalisé..." sudo insmod gpio_led.ko gpio_led=17 sleep 2 sudo rmmod gpio_led # Test timer echo "5. Test timer LED..." sudo insmod gpio_timer.ko intervalle_ms=200 sleep 3 sudo rmmod gpio_timer echo "=== TESTS GPIO TERMINES ==="
PARTIE 5 : EXEMPLES PROGRESSIFS
5.1 Module « Hello World » amélioré
hello_advanced.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/slab.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Votre Nom");
MODULE_DESCRIPTION("Hello World avance avec informations systeme");
MODULE_VERSION("2.0");
static int nombre_affichages = 3;
module_param(nombre_affichages, int, 0644);
MODULE_PARM_DESC(nombre_affichages, "Nombre de fois ou afficher le message");
static char *message_personnalise = "Bonjour le noyau!";
module_param(message_personnalise, charp, 0644);
MODULE_PARM_DESC(message_personnalise, "Message personnalise a afficher");
static void afficher_infos_systeme(void)
{
printk(KERN_INFO "=== INFORMATIONS SYSTÈME ===\n");
printk(KERN_INFO "Version noyau: %s\n", UTS_RELEASE);
printk(KERN_INFO "Architecture: %s\n", CONFIG_ARCH);
printk(KERN_INFO "Nombre processeurs: %d\n", num_online_cpus());
}
static int __init hello_advanced_init(void)
{
int i;
printk(KERN_INFO "=== INITIALISATION HELLO ADVANCED ===\n");
// Afficher les informations système
afficher_infos_systeme();
// Afficher les paramètres
printk(KERN_INFO "Parametres: affichages=%d, message='%s'\n",
nombre_affichages, message_personnalise);
// Boucle d'affichage
for (i = 0; i < nombre_affichages; i++) {
printk(KERN_INFO "[%d/%d] %s\n",
i + 1, nombre_affichages, message_personnalise);
// Petite pause si ce n'est pas le dernier
if (i < nombre_affichages - 1) {
msleep(1000); // 1 seconde
}
}
// Allocation de test
void *test_buffer = kmalloc(256, GFP_KERNEL);
if (test_buffer) {
printk(KERN_INFO "Test allocation memoire: OK (%p)\n", test_buffer);
kfree(test_buffer);
}
printk(KERN_INFO "=== MODULE HELLO ADVANCE CHARGE ===\n");
return 0;
}
static void __exit hello_advanced_exit(void)
{
printk(KERN_INFO "=== DECHARGEMENT HELLO ADVANCED ===\n");
// Message d'au revoir
int i;
for (i = 0; i < 2; i++) {
printk(KERN_INFO "Au revoir! (%d)\n", i + 1);
if (i == 0) msleep(500);
}
printk(KERN_INFO "=== MODULE HELLO ADVANCE DECHARGE ===\n");
}
module_init(hello_advanced_init);
module_exit(hello_advanced_exit);
5.2 Module de journalisation dans un fichier
kernel_logger.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/time.h>
#include <linux/version.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Expert Logger");
MODULE_DESCRIPTION("Journalisation noyau vers fichier");
// Configuration
#define LOG_FILENAME "/var/log/kernel_module.log"
static char *fichier_log = LOG_FILENAME;
module_param(fichier_log, charp, 0644);
MODULE_PARM_DESC(fichier_log, "Chemin du fichier de log");
static int max_lignes = 10;
module_param(max_lignes, int, 0644);
MODULE_PARM_DESC(max_lignes, "Nombre maximum de lignes a journaliser");
// Etat global
static struct {
int compteur_lignes;
bool initialise;
} etat_logger = {
.compteur_lignes = 0,
.initialise = false
};
// Fonction pour obtenir le timestamp
static void obtenir_timestamp(char *buffer, size_t taille)
{
struct timespec64 now;
ktime_get_real_ts64(&now);
snprintf(buffer, taille, "[%lld.%09ld]",
(long long)now.tv_sec, now.tv_nsec);
}
// Fonction pour ecrire dans le fichier
static int ecrire_dans_fichier(const char *message)
{
struct file *fichier;
loff_t pos = 0;
int ret;
char *buffer_complet;
char timestamp[50];
// Allouer un buffer pour le message complet
buffer_complet = kmalloc(1024, GFP_KERNEL);
if (!buffer_complet) {
printk(KERN_ERR "Erreur allocation memoire pour log\n");
return -ENOMEM;
}
// Obtenir le timestamp
obtenir_timestamp(timestamp, sizeof(timestamp));
// Formater le message complet
snprintf(buffer_complet, 1024, "%s %s\n", timestamp, message);
// Ouvrir le fichier en mode append
fichier = filp_open(fichier_log, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (IS_ERR(fichier)) {
printk(KERN_ERR "Erreur ouverture fichier %s: %ld\n",
fichier_log, PTR_ERR(fichier));
kfree(buffer_complet);
return PTR_ERR(fichier);
}
// Ecrire dans le fichier
ret = kernel_write(fichier, buffer_complet, strlen(buffer_complet), &pos);
if (ret < 0) {
printk(KERN_ERR "Erreur ecriture fichier: %d\n", ret);
} else {
etat_logger.compteur_lignes++;
printk(KERN_INFO "Log ecrit: %s", buffer_complet);
}
// Fermer le fichier
filp_close(fichier, NULL);
kfree(buffer_complet);
return ret;
}
// Fonction pour lire le fichier de log
static void lire_fichier_log(void)
{
struct file *fichier;
char *buffer;
loff_t pos = 0;
int ret;
buffer = kmalloc(4096, GFP_KERNEL);
if (!buffer) {
printk(KERN_ERR "Erreur allocation pour lecture\n");
return;
}
// Ouvrir en lecture
fichier = filp_open(fichier_log, O_RDONLY, 0);
if (IS_ERR(fichier)) {
printk(KERN_WARNING "Impossible de lire le fichier log\n");
kfree(buffer);
return;
}
// Lire le contenu
ret = kernel_read(fichier, buffer, 4095, &pos);
if (ret > 0) {
buffer[ret] = '\0';
printk(KERN_INFO "=== CONTENU DU FICHIER LOG ===\n%s\n", buffer);
}
filp_close(fichier, NULL);
kfree(buffer);
}
static int __init kernel_logger_init(void)
{
int i;
printk(KERN_INFO "=== INITIALISATION JOURNALISEUR NOYAU ===\n");
// Message de bienvenue dans le log fichier
ecrire_dans_fichier("=== DEBUT JOURNALISATION MODULE NOYAU ===");
// Journaliser quelques evenements de test
for (i = 0; i < max_lignes && i < 5; i++) {
char message[100];
snprintf(message, sizeof(message),
"Evenement test %d du module noyau", i + 1);
ecrire_dans_fichier(message);
msleep(100);
}
// Lire et afficher le contenu actuel
lire_fichier_log();
etat_logger.initialise = true;
printk(KERN_INFO "Journaliseur actif: %s (%d lignes ecrites)\n",
fichier_log, etat_logger.compteur_lignes);
return 0;
}
static void __exit kernel_logger_exit(void)
{
printk(KERN_INFO "=== ARRET JOURNALISEUR NOYAU ===\n");
if (etat_logger.initialise) {
ecrire_dans_fichier("=== FIN JOURNALISATION MODULE NOYAU ===");
printk(KERN_INFO "Total lignes journalisees: %d\n",
etat_logger.compteur_lignes);
printk(KERN_INFO "Fichier log disponible: %s\n", fichier_log);
}
}
module_init(kernel_logger_init);
module_exit(kernel_logger_exit);
5.3 Module GPIO complet pour contrôle LED
led_controller.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/cdev.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Controleur LED");
MODULE_DESCRIPTION("Controle LED GPIO avec interface utilisateur");
// Configuration GPIO
static int gpio_led = 4;
module_param(gpio_led, int, 0644);
MODULE_PARM_DESC(gpio_led, "GPIO pour la LED");
// Variables globales
static struct {
bool led_allumee;
bool gpio_reserve;
int compteur_operations;
struct class *classe;
struct device *device;
dev_t numero_device;
} etat_led = {
.led_allumee = false,
.gpio_reserve = false,
.compteur_operations = 0
};
// Fonctions de controle LED
static void allumer_led(void)
{
if (!etat_led.gpio_reserve) return;
gpio_set_value(gpio_led, 1);
etat_led.led_allumee = true;
etat_led.compteur_operations++;
printk(KERN_INFO "LED allumee (GPIO %d)\n", gpio_led);
}
static void eteindre_led(void)
{
if (!etat_led.gpio_reserve) return;
gpio_set_value(gpio_led, 0);
etat_led.led_allumee = false;
etat_led.compteur_operations++;
printk(KERN_INFO "LED eteinte (GPIO %d)\n", gpio_led);
}
static void basculer_led(void)
{
if (etat_led.led_allumee) {
eteindre_led();
} else {
allumer_led();
}
}
static void clignoter_led(int repetitions, int delais_ms)
{
int i;
printk(KERN_INFO "Debut clignotement: %d repetitions, %d ms\n",
repetitions, delais_ms);
for (i = 0; i < repetitions; i++) {
allumer_led();
msleep(delais_ms);
eteindre_led();
if (i < repetitions - 1) {
msleep(delais_ms);
}
}
printk(KERN_INFO "Fin clignotement\n");
}
// Demonstration automatique
static void demonstration_led(void)
{
printk(KERN_INFO "=== DEMONSTRATION LED CONTROLEUR ===\n");
// Sequence 1: Clignotement rapide
printk(KERN_INFO "Sequence 1: Clignotement rapide\n");
clignoter_led(5, 200);
msleep(500);
// Sequence 2: Clignotement lent
printk(KERN_INFO "Sequence 2: Clignotement lent\n");
clignoter_led(3, 500);
msleep(500);
// Sequence 3: Allumage progressif
printk(KERN_INFO "Sequence 3: Pulse\n");
int i;
for (i = 0; i < 3; i++) {
allumer_led();
msleep(100);
eteindre_led();
msleep(50);
allumer_led();
msleep(300);
eteindre_led();
msleep(200);
}
// Final: Allumer
allumer_led();
printk(KERN_INFO "=== FIN DEMONSTRATION ===\n");
}
// Initialisation GPIO
static int initialiser_gpio(void)
{
int ret;
printk(KERN_INFO "Initialisation GPIO %d pour LED...\n", gpio_led);
// Verifier si le GPIO est valide
if (!gpio_is_valid(gpio_led)) {
printk(KERN_ERR "GPIO %d invalide\n", gpio_led);
return -EINVAL;
}
// Reserver le GPIO
ret = gpio_request(gpio_led, "led_controller");
if (ret) {
printk(KERN_ERR "Erreur reservation GPIO %d: %d\n", gpio_led, ret);
return ret;
}
etat_led.gpio_reserve = true;
// Configurer en sortie
ret = gpio_direction_output(gpio_led, 0);
if (ret) {
printk(KERN_ERR "Erreur configuration sortie GPIO %d: %d\n", gpio_led, ret);
gpio_free(gpio_led);
etat_led.gpio_reserve = false;
return ret;
}
// Initialiser eteinte
gpio_set_value(gpio_led, 0);
etat_led.led_allumee = false;
printk(KERN_INFO "GPIO %d initialise avec succes\n", gpio_led);
return 0;
}
static void __init led_controller_init(void)
{
int ret;
printk(KERN_INFO "=== INITIALISATION LED CONTROLEUR ===\n");
// Initialiser GPIO
ret = initialiser_gpio();
if (ret) {
printk(KERN_ERR "Echec initialisation GPIO\n");
return;
}
// Lancer la demonstration
demonstration_led();
printk(KERN_INFO "=== LED CONTROLEUR ACTIF ===\n");
printk(KERN_INFO "GPIO: %d, Etat: %s, Operations: %d\n",
gpio_led,
etat_led.led_allumee ? "ALLUMEE" : "ETEINTE",
etat_led.compteur_operations);
}
static void __exit led_controller_exit(void)
{
printk(KERN_INFO "=== ARRET LED CONTROLEUR ===\n");
// Eteindre la LED
if (etat_led.led_allumee) {
eteindre_led();
}
// Liberer GPIO
if (etat_led.gpio_reserve) {
gpio_free(gpio_led);
printk(KERN_INFO "GPIO %d libere\n", gpio_led);
}
// Afficher les statistiques
printk(KERN_INFO "Statistiques finales:\n");
printk(KERN_INFO "- Total operations: %d\n", etat_led.compteur_operations);
printk(KERN_INFO "- Dernier etat: %s\n",
etat_led.led_allumee ? "ALLUMEE" : "ETEINTE");
printk(KERN_INFO "=== LED CONTROLEUR DESACTIVE ===\n");
}
module_init(led_controller_init);
module_exit(led_controller_exit);
5.4 Module avec timer pour exécution périodique
periodic_timer.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/gpio.h>
#include <linux/slab.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Timer Expert");
MODULE_DESCRIPTION("Module avec timer periodique et multiple fonctionnalites");
// Configuration
static int intervalle_timer_ms = 1000;
module_param(intervalle_timer_ms, int, 0644);
MODULE_PARM_DESC(intervalle_timer_ms, "Intervalle timer en millisecondes");
static int gpio_led = 4;
module_param(gpio_led, int, 0644);
static int duree_execution_sec = 30;
module_param(duree_execution_sec, int, 0644);
MODULE_PARM_DESC(duree_execution_sec, "Duree d'execution en secondes");
// Etat global
static struct {
struct timer_list mon_timer;
unsigned long compteur_ticks;
bool timer_actif;
bool gpio_configure;
bool led_etat;
struct timespec64 debut;
} etat_timer = {
.compteur_ticks = 0,
.timer_actif = false,
.gpio_configure = false,
.led_etat = false
};
// Fonction pour obtenir le temps ecoule
static long obtenir_temps_ecoule_ms(void)
{
struct timespec64 maintenant, ecoule;
ktime_get_real_ts64(&maintenant);
ecoule = timespec64_sub(maintenant, etat_timer.debut);
return timespec64_to_ms(&ecoule);
}
// Callback du timer
static void callback_timer(struct timer_list *t)
{
long temps_ecoule = obtenir_temps_ecoule_ms();
etat_timer.compteur_ticks++;
// Basculer LED si GPIO configure
if (etat_timer.gpio_configure) {
etat_timer.led_etat = !etat_timer.led_etat;
gpio_set_value(gpio_led, etat_timer.led_etat ? 1 : 0);
}
// Journaliser periodiquement
if (etat_timer.compteur_ticks % 10 == 0) {
printk(KERN_INFO "Timer tick %lu - Temps ecoule: %ld ms\n",
etat_timer.compteur_ticks, temps_ecoule);
} else {
printk(KERN_DEBUG "Tick %lu - LED: %s\n",
etat_timer.compteur_ticks,
etat_timer.led_etat ? "ON" : "OFF");
}
// Verifier la duree d'execution
if (duree_execution_sec > 0 &&
temps_ecoule >= duree_execution_sec * 1000) {
printk(KERN_INFO "Duree d'execution atteinte (%d sec)\n",
duree_execution_sec);
return; // Ne pas reprogrammer
}
// Reprogrammer le timer
if (etat_timer.timer_actif) {
mod_timer(&etat_timer.mon_timer,
jiffies + msecs_to_jiffies(intervalle_timer_ms));
}
}
// Configuration GPIO optionnelle
static int configurer_gpio_led(void)
{
int ret;
if (!gpio_is_valid(gpio_led)) {
printk(KERN_WARNING "GPIO %d invalide - LED desactivee\n", gpio_led);
return -EINVAL;
}
ret = gpio_request(gpio_led, "periodic_timer");
if (ret) {
printk(KERN_WARNING "Erreur GPIO %d - LED desactivee: %d\n", gpio_led, ret);
return ret;
}
ret = gpio_direction_output(gpio_led, 0);
if (ret) {
printk(KERN_WARNING "Erreur configuration GPIO %d: %d\n", gpio_led, ret);
gpio_free(gpio_led);
return ret;
}
etat_timer.gpio_configure = true;
etat_timer.led_etat = false;
printk(KERN_INFO "GPIO %d configure pour LED\n", gpio_led);
return 0;
}
static int __init periodic_timer_init(void)
{
printk(KERN_INFO "=== INITIALISATION TIMER PERIODIQUE ===\n");
// Enregistrer le temps de debut
ktime_get_real_ts64(&etat_timer.debut);
// Configuration GPIO (optionnelle)
configurer_gpio_led();
// Initialiser le timer
timer_setup(&etat_timer.mon_timer, callback_timer, 0);
// Demarrer le timer
etat_timer.timer_actif = true;
mod_timer(&etat_timer.mon_timer,
jiffies + msecs_to_jiffies(intervalle_timer_ms));
printk(KERN_INFO "Timer periodique active:\n");
printk(KERN_INFO "- Intervalle: %d ms\n", intervalle_timer_ms);
printk(KERN_INFO "- Duree max: %d sec\n", duree_execution_sec);
printk(KERN_INFO "- LED GPIO: %d (%s)\n", gpio_led,
etat_timer.gpio_configure ? "actif" : "inactif");
return 0;
}
static void __exit periodic_timer_exit(void)
{
printk(KERN_INFO "=== ARRET TIMER PERIODIQUE ===\n");
// Arreter le timer
etat_timer.timer_actif = false;
del_timer_sync(&etat_timer.mon_timer);
// Eteindre et liberer GPIO
if (etat_timer.gpio_configure) {
gpio_set_value(gpio_led, 0);
gpio_free(gpio_led);
printk(KERN_INFO "GPIO %d libere\n", gpio_led);
}
// Afficher les statistiques finales
printk(KERN_INFO "Statistiques finales:\n");
printk(KERN_INFO "- Total ticks: %lu\n", etat_timer.compteur_ticks);
printk(KERN_INFO "- Temps total: %ld ms\n", obtenir_temps_ecoule_ms());
printk(KERN_INFO "- Ticks par seconde: %lu\n",
etat_timer.compteur_ticks * 1000 / (obtenir_temps_ecoule_ms() ?: 1));
printk(KERN_INFO "=== TIMER PERIODIQUE DESACTIVE ===\n");
}
module_init(periodic_timer_init);
module_exit(periodic_timer_exit);
5.5 Makefile pour tous les exemples
Makefile :
KVERSION := $(shell uname -r)
KDIR := /lib/modules/$(KVERSION)/build
PWD := $(shell pwd)
# Tous les modules de la partie 5
obj-m += hello_advanced.o
obj-m += kernel_logger.o
obj-m += led_controller.o
obj-m += periodic_timer.o
ccflags-y := -Wall -Wextra
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
# Tests individuels
test-hello:
sudo dmesg -C
sudo insmod hello_advanced.ko nombre_affichages=2 message_personnalise="Test reussi!"
dmesg
sudo rmmod hello_advanced
dmesg
test-logger:
sudo dmesg -C
sudo insmod kernel_logger.ko max_lignes=5 fichier_log="/tmp/kernel_test.log"
dmesg
sudo rmmod kernel_logger
dmesg
cat /tmp/kernel_test.log 2>/dev/null || echo "Fichier log non trouve"
test-led:
sudo dmesg -C
sudo insmod led_controller.ko gpio_led=4
dmesg
sudo rmmod led_controller
dmesg
test-timer:
sudo dmesg -C
sudo insmod periodic_timer.ko intervalle_timer_ms=500 duree_execution_sec=10
dmesg | head -15
sleep 12
sudo rmmod periodic_timer
dmesg | tail -10
# Test complet
test-all: test-hello test-logger test-led test-timer
.PHONY: all clean test-hello test-logger test-led test-timer test-all
5.6 Script de démonstration complet
demo_complete.sh :
#!/bin/bash echo "=== DEMONSTRATION COMPLETE PARTIE 5 ===" # Compilation echo "1. Compilation des modules..." make # Test Hello World avancé echo "2. Test Hello World avancé..." sudo insmod hello_advanced.ko nombre_affichages=3 message_personnalise="Salut Kernel!" sleep 2 sudo rmmod hello_advanced dmesg | tail -10 # Test Logger echo "3. Test Journalisation..." sudo insmod kernel_logger.ko max_lignes=8 fichier_log="/tmp/demo_kernel.log" sleep 1 sudo rmmod kernel_logger echo "--- Contenu du log ---" cat /tmp/demo_kernel.log 2>/dev/null || echo "Fichier non trouvé" # Test LED Controller echo "4. Test Contrôleur LED..." sudo insmod led_controller.ko gpio_led=17 sleep 3 sudo rmmod led_controller dmesg | tail -5 # Test Timer périodique echo "5. Test Timer Périodique..." sudo insmod periodic_timer.ko intervalle_timer_ms=300 duree_execution_sec=8 sleep 10 sudo rmmod periodic_timer dmesg | grep "Timer\|Tick" | tail -10 echo "=== DEMONSTRATION TERMINEE ==="
PARTIE 6 : BONNES PRATIQUES
6.1 Gestion des erreurs et nettoyage des ressources
6.1.1 Pattern de gestion d’erreurs robuste
error_handling.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Expert Bonnes Pratiques");
MODULE_DESCRIPTION("Demonstration gestion d'erreurs robuste");
#define DEVICE_NAME "safe_device"
#define MAX_DEVICES 2
#define BUFFER_SIZE 1024
// Structure pour l'etat complet du module
struct module_state {
struct cdev cdev;
dev_t dev_num;
struct class *device_class;
struct device *device;
void *buffer_principal;
void *buffer_secondaire;
int gpio_led;
bool ressources_allouees[6]; // Bitmap des ressources
enum {
RESSOURCE_CDEV = 0,
RESSOURCE_CLASS,
RESSOURCE_DEVICE,
RESSOURCE_BUFFER1,
RESSOURCE_BUFFER2,
RESSOURCE_GPIO
};
};
static struct module_state module_etat;
// Fonctions de gestion des ressources
static int allouer_buffer_principal(void)
{
module_etat.buffer_principal = kzalloc(BUFFER_SIZE, GFP_KERNEL);
if (!module_etat.buffer_principal) {
printk(KERN_ERR "Echec allocation buffer principal\n");
return -ENOMEM;
}
module_etat.ressources_allouees[RESSOURCE_BUFFER1] = true;
printk(KERN_INFO "Buffer principal alloue: %p\n", module_etat.buffer_principal);
return 0;
}
static int allouer_buffer_secondaire(void)
{
module_etat.buffer_secondaire = kmalloc(BUFFER_SIZE / 2, GFP_KERNEL);
if (!module_etat.buffer_secondaire) {
printk(KERN_ERR "Echec allocation buffer secondaire\n");
return -ENOMEM;
}
module_etat.ressources_allouees[RESSOURCE_BUFFER2] = true;
printk(KERN_INFO "Buffer secondaire alloue: %p\n", module_etat.buffer_secondaire);
return 0;
}
static int configurer_gpio(void)
{
int ret;
module_etat.gpio_led = 4; // GPIO par defaut
if (!gpio_is_valid(module_etat.gpio_led)) {
printk(KERN_ERR "GPIO %d invalide\n", module_etat.gpio_led);
return -EINVAL;
}
ret = gpio_request(module_etat.gpio_led, "safe_module");
if (ret) {
printk(KERN_ERR "Echec reservation GPIO %d: %d\n", module_etat.gpio_led, ret);
return ret;
}
ret = gpio_direction_output(module_etat.gpio_led, 0);
if (ret) {
printk(KERN_ERR "Echec configuration GPIO %d: %d\n", module_etat.gpio_led, ret);
gpio_free(module_etat.gpio_led);
return ret;
}
module_etat.ressources_allouees[RESSOURCE_GPIO] = true;
printk(KERN_INFO "GPIO %d configure avec succes\n", module_etat.gpio_led);
return 0;
}
static int creer_device(void)
{
int ret;
// Allocation numero device
ret = alloc_chrdev_region(&module_etat.dev_num, 0, MAX_DEVICES, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ERR "Echec allocation numero device: %d\n", ret);
return ret;
}
module_etat.ressources_allouees[RESSOURCE_CDEV] = true;
printk(KERN_INFO "Numero device alloue: %d\n", MAJOR(module_etat.dev_num));
// Creation classe
module_etat.device_class = class_create(DEVICE_NAME);
if (IS_ERR(module_etat.device_class)) {
printk(KERN_ERR "Echec creation classe device\n");
return PTR_ERR(module_etat.device_class);
}
module_etat.ressources_allouees[RESSOURCE_CLASS] = true;
// Creation device
module_etat.device = device_create(module_etat.device_class, NULL,
module_etat.dev_num, NULL, DEVICE_NAME);
if (IS_ERR(module_etat.device)) {
printk(KERN_ERR "Echec creation device\n");
return PTR_ERR(module_etat.device);
}
module_etat.ressources_allouees[RESSOURCE_DEVICE] = true;
printk(KERN_INFO "Device %s cree avec succes\n", DEVICE_NAME);
return 0;
}
// Fonction de nettoyage complet
static void nettoyer_ressources(void)
{
printk(KERN_INFO "Nettoyage des ressources...\n");
// ORDRE INVERSE de l'allocation
if (module_etat.ressources_allouees[RESSOURCE_DEVICE]) {
device_destroy(module_etat.device_class, module_etat.dev_num);
module_etat.ressources_allouees[RESSOURCE_DEVICE] = false;
printk(KERN_INFO "Device detruit\n");
}
if (module_etat.ressources_allouees[RESSOURCE_CLASS]) {
class_destroy(module_etat.device_class);
module_etat.ressources_allouees[RESSOURCE_CLASS] = false;
printk(KERN_INFO "Classe detruite\n");
}
if (module_etat.ressources_allouees[RESSOURCE_CDEV]) {
unregister_chrdev_region(module_etat.dev_num, MAX_DEVICES);
module_etat.ressources_allouees[RESSOURCE_CDEV] = false;
printk(KERN_INFO Region device liberee\n");
}
if (module_etat.ressources_allouees[RESSOURCE_GPIO]) {
gpio_set_value(module_etat.gpio_led, 0);
gpio_free(module_etat.gpio_led);
module_etat.ressources_allouees[RESSOURCE_GPIO] = false;
printk(KERN_INFO "GPIO libere\n");
}
if (module_etat.ressources_allouees[RESSOURCE_BUFFER2]) {
kfree(module_etat.buffer_secondaire);
module_etat.buffer_secondaire = NULL;
module_etat.ressources_allouees[RESSOURCE_BUFFER2] = false;
printk(KERN_INFO "Buffer secondaire libere\n");
}
if (module_etat.ressources_allouees[RESSOURCE_BUFFER1]) {
kfree(module_etat.buffer_principal);
module_etat.buffer_principal = NULL;
module_etat.ressources_allouees[RESSOURCE_BUFFER1] = false;
printk(KERN_INFO "Buffer principal libere\n");
}
}
static int __init safe_module_init(void)
{
int ret;
printk(KERN_INFO "=== INITIALISATION MODULE SECURISE ===\n");
// Initialiser le bitmap de ressources
memset(&module_etat.ressources_allouees, 0, sizeof(module_etat.ressources_allouees));
// Sequence d'initialisation avec gestion d'erreur
ret = allouer_buffer_principal();
if (ret) goto erreur;
ret = allouer_buffer_secondaire();
if (ret) goto erreur;
ret = configurer_gpio();
if (ret) goto erreur;
ret = creer_device();
if (ret) goto erreur;
// Simulation d'utilisation
memset(module_etat.buffer_principal, 0xAA, BUFFER_SIZE);
gpio_set_value(module_etat.gpio_led, 1);
printk(KERN_INFO "=== MODULE SECURISE CHARGE AVEC SUCCES ===\n");
return 0;
erreur:
printk(KERN_ERR "Erreur lors de l'initialisation: %d\n", ret);
nettoyer_ressources();
return ret;
}
static void __exit safe_module_exit(void)
{
printk(KERN_INFO "=== DECHARGEMENT MODULE SECURISE ===\n");
nettoyer_ressources();
printk(KERN_INFO "=== MODULE SECURISE DECHARGE ===\n");
}
module_init(safe_module_init);
module_exit(safe_module_exit);
6.2 Sécurité et stabilité
6.2.1 Vérifications de sécurité critiques
security_checks.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/bug.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Demonstration des verifications de securite");
#define MAX_USER_INPUT 256
static void demonstrations_securite(void)
{
char *buffer = NULL;
int *tableau = NULL;
size_t taille;
printk(KERN_INFO "=== DEMONSTRATIONS SECURITE ===\n");
// 1. Verification des allocations
buffer = kmalloc(MAX_USER_INPUT, GFP_KERNEL);
if (!buffer) {
printk(KERN_ERR "Echec allocation memoire - arret securise\n");
return;
}
// 2. Initialisation de la memoire
memset(buffer, 0, MAX_USER_INPUT);
// 3. Verification des bornes
taille = MAX_USER_INPUT - 1;
if (taille > MAX_USER_INPUT) {
printk(KERN_WARNING "Taille invalide, ajustement automatique\n");
taille = MAX_USER_INPUT - 1;
}
// 4. Verification des pointeurs
if (!buffer) {
printk(KERN_ERR "Pointeur nul detecte\n");
return;
}
// 5. Utilisation de BUG_ON pour les conditions critiques
BUG_ON(!buffer);
// 6. Verification de saturation arithmetique
int grande_taille = 1000;
int petite_taille = 10;
if (grande_taille + petite_taille < grande_taille) {
printk(KERN_ERR "Saturation arithmetique detectee!\n");
kfree(buffer);
return;
}
// 7. Allocation avec verification de taille
tableau = kmalloc_array(1000, sizeof(int), GFP_KERNEL);
if (!tableau) {
printk(KERN_ERR "Echec allocation tableau\n");
kfree(buffer);
return;
}
printk(KERN_INFO "Toutes les verifications de securite passees\n");
// Nettoyage
kfree(tableau);
kfree(buffer);
}
// Fonction pour simuler le traitement de donnees utilisateur
static int traiter_donnees_utilisateur(const char __user *data, size_t taille)
{
char *buffer_kernel;
int ret = 0;
// Verification de base
if (!data) {
printk(KERN_ERR "Donnees utilisateur nulles\n");
return -EINVAL;
}
if (taille > MAX_USER_INPUT) {
printk(KERN_ERR "Taille des donnees trop importante: %zu\n", taille);
return -EFBIG;
}
if (taille == 0) {
printk(KERN_WARNING "Taille des donnees nulle\n");
return -EINVAL;
}
// Allocation
buffer_kernel = kmalloc(taille + 1, GFP_KERNEL);
if (!buffer_kernel) {
printk(KERN_ERR "Echec allocation pour donnees utilisateur\n");
return -ENOMEM;
}
// Copie securisee depuis l'espace utilisateur
if (copy_from_user(buffer_kernel, data, taille)) {
printk(KERN_ERR "Echec copie depuis espace utilisateur\n");
kfree(buffer_kernel);
return -EFAULT;
}
// Null-terminator pour securite
buffer_kernel[taille] = '\0';
printk(KERN_INFO "Donnees utilisateur traitees: %s\n", buffer_kernel);
kfree(buffer_kernel);
return ret;
}
static int __init security_module_init(void)
{
printk(KERN_INFO "=== INITIALISATION MODULE SECURITE ===\n");
demonstrations_securite();
// Test de traitement de donnees (simulation)
char test_data[] = "Donnees test securisees";
traiter_donnees_utilisateur((const char __user *)test_data, strlen(test_data));
printk(KERN_INFO "=== MODULE SECURITE ACTIF ===\n");
return 0;
}
static void __exit security_module_exit(void)
{
printk(KERN_INFO "=== DECHARGEMENT MODULE SECURITE ===\n");
}
module_init(security_module_init);
module_exit(security_module_exit);
6.3 Débogage avec dmesg et printk
6.3.1 Module de débogage avancé
debug_module.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/version.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Module de debogage avance");
// Niveaux de debug configurables
static int niveau_debug = 3; // 0=aucun, 1=erreurs, 2=avertissements, 3=info, 4=debug
module_param(niveau_debug, int, 0644);
MODULE_PARM_DESC(niveau_debug, "Niveau de debug (0-4)");
// Macros de debogage
#define DEBUG_ERR(fmt, ...) \
if (niveau_debug >= 1) \
printk(KERN_ERR "[ERR] %s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define DEBUG_WARN(fmt, ...) \
if (niveau_debug >= 2) \
printk(KERN_WARNING "[WARN] %s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define DEBUG_INFO(fmt, ...) \
if (niveau_debug >= 3) \
printk(KERN_INFO "[INFO] %s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
#define DEBUG_DBG(fmt, ...) \
if (niveau_debug >= 4) \
printk(KERN_DEBUG "[DBG] %s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
// Fonction pour dumper les informations systeme
static void dump_infos_systeme(void)
{
DEBUG_INFO("=== INFORMATIONS SYSTEME ===\n");
DEBUG_INFO("Version noyau: %s\n", UTS_RELEASE);
DEBUG_INFO "Compilation: %s\n", LINUX_COMPILE_BY "@" LINUX_COMPILE_HOST);
DEBUG_INFO("Date compilation: %s %s\n", LINUX_COMPILE_DATE, UTS_VERSION);
// Informations sur la memoire
struct sysinfo si;
si_meminfo(&si);
DEBUG_INFO("Memoire totale: %lu MB\n", si.totalram / 1024 / 1024);
DEBUG_INFO("Memoire libre: %lu MB\n", si.freeram / 1024 / 1024);
}
// Simulation d'operations avec differents niveaux de log
static void simulation_operations(void)
{
int i;
DEBUG_INFO("Debut simulation d'operations\n");
for (i = 0; i < 5; i++) {
DEBUG_DBG("Iteration %d/5\n", i + 1);
switch (i) {
case 0:
DEBUG_INFO("Operation 1: Initialisation\n");
break;
case 1:
DEBUG_WARN("Operation 2: Avertissement test\n");
break;
case 2:
DEBUG_ERR("Operation 3: Erreur simulation\n");
break;
case 3:
DEBUG_INFO("Operation 4: Retour a la normale\n");
break;
case 4:
DEBUG_DBG("Operation 5: Debug detaille\n");
break;
}
// Simulation delai
if (niveau_debug >= 4) {
DEBUG_DBG("Pause 100ms\n");
}
msleep(100);
}
DEBUG_INFO("Fin simulation d'operations\n");
}
// Fonction pour tester les differents niveaux de printk
static void test_niveaux_printk(void)
{
DEBUG_INFO("=== TEST NIVEAUX PRINTK ===\n");
printk(KERN_EMERG "Niveau 0: EMERGENCE - Systeme inutilisable\n");
printk(KERN_ALERT "Niveau 1: ALERTE - Action immediate requise\n");
printk(KERN_CRIT "Niveau 2: CRITIQUE - Conditions critiques\n");
printk(KERN_ERR "Niveau 3: ERREUR - Conditions d'erreur\n");
printk(KERN_WARNING "Niveau 4: AVERTISSEMENT - Conditions d'avertissement\n");
printk(KERN_NOTICE "Niveau 5: NOTICE - Conditions normales mais significatives\n");
printk(KERN_INFO "Niveau 6: INFORMATION - Messages informatifs\n");
printk(KERN_DEBUG "Niveau 7: DEBUG - Messages de debug\n");
DEBUG_INFO("=== FIN TEST PRINTK ===\n");
}
static int __init debug_module_init(void)
{
printk(KERN_INFO "=== INITIALISATION MODULE DEBUG ===\n");
DEBUG_INFO("Niveau de debug configure: %d\n", niveau_debug);
dump_infos_systeme();
test_niveaux_printk();
simulation_operations();
DEBUG_INFO("=== MODULE DEBUG ACTIF ===\n");
return 0;
}
static void __exit debug_module_exit(void)
{
DEBUG_INFO("=== DECHARGEMENT MODULE DEBUG ===\n");
DEBUG_INFO("Nettoyage des ressources de debug\n");
DEBUG_DBG("Dernier message de debug\n");
printk(KERN_INFO "=== MODULE DEBUG DECHARGE ===\n");
}
module_init(debug_module_init);
module_exit(debug_module_exit);
6.4 Compilation avec Makefile avancé
Makefile professionnel :
# Configuration de compilation
KVERSION := $(shell uname -r)
KDIR := /lib/modules/$(KVERSION)/build
PWD := $(shell pwd)
# Modules a compiler
obj-m += error_handling.o
obj-m += security_checks.o
obj-m += debug_module.o
# Flags de compilation
ccflags-y := -Wall -Wextra -Wno-unused-parameter
ccflags-y += -DDEBUG
# Cibles de compilation
all:
@echo "Compilation des modules noyau..."
$(MAKE) -C $(KDIR) M=$(PWD) modules
# Compilation avec debug etat
debug: ccflags-y += -g -DDEBUG_VERBOSE
debug: all
# Compilation release (optimisations)
release: ccflags-y := -O2
release: all
# Nettoyage
clean:
@echo "Nettoyage des fichiers generes..."
$(MAKE) -C $(KDIR) M=$(PWD) clean
rm -f *.o *.mod.c Module.symvers modules.order
rm -f .*.cmd *.mod *.ko
# Installation des modules
install: all
@echo "Installation des modules..."
sudo insmod error_handling.ko || true
sudo insmod security_checks.ko || true
sudo insmod debug_module.ko niveau_debug=4 || true
# Desinstallation
uninstall:
@echo "Desinstallation des modules..."
sudo rmmod debug_module 2>/dev/null || true
sudo rmmod security_checks 2>/dev/null || true
sudo rmmod error_handling 2>/dev/null || true
# Test automatique
test: all uninstall
@echo "=== LANCEMENT DES TESTS ==="
@echo "1. Test gestion d'erreurs..."
sudo dmesg -C
sudo insmod error_handling.ko
sudo rmmod error_handling
dmesg | tail -10
@echo "2. Test securite..."
sudo dmesg -C
sudo insmod security_checks.ko
sudo rmmod security_checks
dmesg | tail -10
@echo "3. Test debug..."
sudo dmesg -C
sudo insmod debug_module.ko niveau_debug=4
sudo rmmod debug_module
dmesg | tail -15
# Verification de la qualite du code
check:
@echo "Verification de la qualite du code..."
@echo "Checking coding style..."
@-checkpatch.pl --no-tree --file *.c 2>/dev/null || echo "Install checkpatch.pl for style checking"
# Aide
help:
@echo "Cibles disponibles:"
@echo " all - Compilation standard"
@echo " debug - Compilation avec symboles debug"
@echo " release - Compilation optimisee"
@echo " clean - Nettoyage complet"
@echo " install - Installation des modules"
@echo " uninstall - Desinstallation des modules"
@echo " test - Tests automatiques"
@echo " check - Verification qualite code"
.PHONY: all debug release clean install uninstall test check help
6.5 Script de monitoring et débogage
monitor_system.sh :
#!/bin/bash
# Script de monitoring pour le developpement de modules noyau
echo "=== MONITORING SYSTEME NOYAU ==="
# Fonction pour afficher les informations des modules
show_module_info() {
echo "--- Modules charges ---"
lsmod | head -10
echo
}
# Fonction pour surveiller les messages du noyau
monitor_dmesg() {
echo "--- Derniers messages noyau ---"
dmesg | tail -20
echo
}
# Fonction pour verifier l'etat systeme
check_system_health() {
echo "--- Sante du systeme ---"
echo "Uptime: $(cat /proc/uptime | cut -d' ' -f1) seconds"
echo "Load average: $(cat /proc/loadavg)"
echo "Memoire libre: $(grep MemFree /proc/meminfo | awk '{print $2}') kB"
echo
}
# Fonction pour surveiller un module specifique
monitor_module() {
local module=$1
echo "--- Surveillance module: $module ---"
if lsmod | grep -q "$module"; then
echo "✓ Module $module est charge"
# Afficher les messages relatifs au module
dmesg | grep "$module" | tail -5
else
echo "✗ Module $module n'est pas charge"
fi
echo
}
# Execution des fonctions
show_module_info
monitor_dmesg
check_system_health
# Surveillance des modules de test
monitor_module "error_handling"
monitor_module "security_checks"
monitor_module "debug_module"
echo "=== MONITORING TERMINE ==="
6.6 Commandes de débogage essentielles
Commandes à connaître :
# Surveillance en temps reel sudo dmesg -w # Nettoyage des logs sudo dmesg -C # Filtrage des messages dmesg | grep -i error dmesg | grep -i warning dmesg | tail -f | grep "votre_module" # Informations systeme cat /proc/modules cat /proc/kmsg cat /proc/kallsyms | grep votre_fonction # Debug avance echo 8 > /proc/sys/kernel/printk # Augmente verbosite sudo strace -p <pid> sudo ltrace -p <pid> # Monitoring performance sudo perf record -g -p <pid> sudo perf report
Points clés des bonnes pratiques :
- Gestion d’erreurs : Toujours utiliser le pattern goto pour le nettoyage
- Sécurité : Vérifier toutes les entrées, utiliser BUG_ON pour les invariants
- Débogage : Niveaux de log appropriés, macros de debug
- Compilation : Makefile robuste avec différentes configurations
- Monitoring : Scripts pour surveiller l’état du système
PARTIE 7 : CONCEPTS AVANCÉS
7.1 Interruptions et gestionnaires d’IRQ
7.1.1 Module avec gestion d’interruption GPIO
irq_handler.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/atomic.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Expert IRQ");
MODULE_DESCRIPTION("Gestionnaire d'interruption pour bouton GPIO");
// Configuration - ADAPTER À VOTRE MATÉRIEL !
static int gpio_bouton = 17; // GPIO pour le bouton (entrée)
static int gpio_led = 4; // GPIO pour la LED (sortie)
static int irq_number; // Numéro d'IRQ assigné
module_param(gpio_bouton, int, 0644);
module_param(gpio_led, int, 0644);
// État global avec protection
static struct {
atomic_t compteur_pressions; // Compteur atomique
bool bouton_appuye; // État actuel
spinlock_t lock_etat; // Verrou pour l'état
struct timer_list timer_anti_rebond;
} etat_irq = {
.compteur_pressions = ATOMIC_INIT(0),
.bouton_appuye = false,
};
// Fonction de timer pour anti-rebond
static void timer_anti_rebond_callback(struct timer_list *t)
{
unsigned long flags;
int valeur_bouton;
// Lire l'état actuel du bouton
valeur_bouton = gpio_get_value(gpio_bouton);
spin_lock_irqsave(&etat_irq.lock_etat, flags);
if (valeur_bouton && !etat_irq.bouton_appuye) {
// Front montant détecté - bouton pressé
etat_irq.bouton_appuye = true;
atomic_inc(&etat_irq.compteur_pressions);
// Basculer la LED
gpio_set_value(gpio_led, !gpio_get_value(gpio_led));
printk(KERN_INFO "Bouton PRESSE (compteur: %d)\n",
atomic_read(&etat_irq.compteur_pressions));
}
else if (!valeur_bouton && etat_irq.bouton_appuye) {
// Front descendant - bouton relâché
etat_irq.bouton_appuye = false;
printk(KERN_INFO "Bouton RELACHE\n");
}
spin_unlock_irqrestore(&etat_irq.lock_etat, flags);
}
// Gestionnaire d'interruption
static irqreturn_t gestionnaire_bouton(int irq, void *dev_id)
{
// Programmer le timer anti-rebond (délai de 50ms)
mod_timer(&etat_irq.timer_anti_rebond, jiffies + msecs_to_jiffies(50));
return IRQ_HANDLED;
}
// Configuration des GPIO et IRQ
static int configurer_gpio_irq(void)
{
int ret;
printk(KERN_INFO "Configuration GPIO bouton: %d, LED: %d\n",
gpio_bouton, gpio_led);
// Vérification des GPIO
if (!gpio_is_valid(gpio_bouton) || !gpio_is_valid(gpio_led)) {
printk(KERN_ERR "GPIO invalide\n");
return -EINVAL;
}
// Configuration LED (sortie)
ret = gpio_request(gpio_led, "irq_led");
if (ret) {
printk(KERN_ERR "Erreur GPIO LED %d: %d\n", gpio_led, ret);
return ret;
}
gpio_direction_output(gpio_led, 0);
// Configuration bouton (entrée)
ret = gpio_request(gpio_bouton, "irq_bouton");
if (ret) {
printk(KERN_ERR "Erreur GPIO bouton %d: %d\n", gpio_bouton, ret);
gpio_free(gpio_led);
return ret;
}
gpio_direction_input(gpio_bouton);
// Obtenir le numéro d'IRQ pour le GPIO
irq_number = gpio_to_irq(gpio_bouton);
if (irq_number < 0) {
printk(KERN_ERR "Impossible d'obtenir IRQ pour GPIO %d: %d\n",
gpio_bouton, irq_number);
gpio_free(gpio_bouton);
gpio_free(gpio_led);
return irq_number;
}
printk(KERN_INFO "GPIO %d mappe vers IRQ %d\n", gpio_bouton, irq_number);
return 0;
}
// Enregistrement du gestionnaire d'interruption
static int enregistrer_irq_handler(void)
{
int ret;
// Initialiser le spinlock
spin_lock_init(&etat_irq.lock_etat);
// Initialiser le timer anti-rebond
timer_setup(&etat_irq.timer_anti_rebond, timer_anti_rebond_callback, 0);
// Enregistrer le gestionnaire d'IRQ
// IRQF_TRIGGER_RISING : déclenche sur front montant
// IRQF_TRIGGER_FALLING : déclenche sur front descendant
ret = request_irq(irq_number, gestionnaire_bouton,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"bouton_gpio", NULL);
if (ret) {
printk(KERN_ERR "Echec enregistrement IRQ %d: %d\n", irq_number, ret);
return ret;
}
printk(KERN_INFO "Gestionnaire IRQ enregistre avec succes\n");
return 0;
}
static int __init irq_module_init(void)
{
int ret;
printk(KERN_INFO "=== INITIALISATION GESTIONNAIRE IRQ ===\n");
// Configuration matérielle
ret = configurer_gpio_irq();
if (ret) {
goto erreur;
}
// Enregistrement IRQ
ret = enregistrer_irq_handler();
if (ret) {
goto erreur_cleanup_gpio;
}
printk(KERN_INFO "=== GESTIONNAIRE IRQ ACTIF ===\n");
printk(KERN_INFO "Appuyez sur le bouton GPIO %d pour tester\n", gpio_bouton);
printk(KERN_INFO "LED sur GPIO %d basculera a chaque pression\n", gpio_led);
return 0;
erreur_cleanup_gpio:
gpio_free(gpio_bouton);
gpio_free(gpio_led);
erreur:
printk(KERN_ERR "=== ECHEC INITIALISATION IRQ ===\n");
return ret;
}
static void __exit irq_module_exit(void)
{
printk(KERN_INFO "=== DECHARGEMENT GESTIONNAIRE IRQ ===\n");
// Libérer l'IRQ
free_irq(irq_number, NULL);
// Arrêter le timer
del_timer_sync(&etat_irq.timer_anti_rebond);
// Libérer les GPIO
gpio_free(gpio_bouton);
gpio_free(gpio_led);
// Afficher les statistiques finales
printk(KERN_INFO "Statistiques finales:\n");
printk(KERN_INFO "- Pressions totales: %d\n",
atomic_read(&etat_irq.compteur_pressions));
printk(KERN_INFO "=== GESTIONNAIRE IRQ DESACTIVE ===\n");
}
module_init(irq_module_init);
module_exit(irq_module_exit);
7.2 Systèmes de fichiers virtuels
7.2.1 Interface procfs pour le module
procfs_interface.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/jiffies.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Expert VFS");
MODULE_DESCRIPTION("Interface procfs pour module noyau");
#define PROC_DIR "mon_module"
#define PROC_FILE_STAT "statistiques"
#define PROC_FILE_CONFIG "configuration"
static struct proc_dir_entry *proc_dir = NULL;
static struct proc_dir_entry *proc_stat = NULL;
static struct proc_dir_entry *proc_config = NULL;
// Structure pour les statistiques
static struct {
atomic_t lectures;
atomic_t ecritures;
unsigned long demarrage;
char derniere_commande[100];
} stats = {
.lectures = ATOMIC_INIT(0),
.ecritures = ATOMIC_INIT(0),
.demarrage = 0,
.derniere_commande = ""
};
// Callback pour lecture du fichier statistiques
static int proc_stat_show(struct seq_file *m, void *v)
{
unsigned long uptime = jiffies - stats.demarrage;
atomic_inc(&stats.lectures);
seq_printf(m, "=== STATISTIQUES MODULE ===\n");
seq_printf(m, "Uptime: %lu secondes\n", uptime / HZ);
seq_printf(m, "Lectures procfs: %d\n", atomic_read(&stats.lectures));
seq_printf(m, "Ecritures procfs: %d\n", atomic_read(&stats.ecritures));
seq_printf(m, "Derniere commande: %s\n", stats.derniere_commande);
seq_printf(m, "Timestamp: %lu\n", jiffies);
return 0;
}
// Callback pour ouverture du fichier statistiques
static int proc_stat_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_stat_show, NULL);
}
// Callback pour écriture dans le fichier configuration
static ssize_t proc_config_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
char *commande;
int ret;
atomic_inc(&stats.ecritures);
// Allouer un buffer pour la commande
commande = kmalloc(count + 1, GFP_KERNEL);
if (!commande) {
return -ENOMEM;
}
// Copier depuis l'espace utilisateur
if (copy_from_user(commande, buffer, count)) {
kfree(commande);
return -EFAULT;
}
commande[count] = '\0';
// Supprimer le saut de ligne si présent
if (commande[count - 1] == '\n') {
commande[count - 1] = '\0';
}
// Traiter la commande
printk(KERN_INFO "Commande recue via procfs: %s\n", commande);
// Sauvegarder la dernière commande
strncpy(stats.derniere_commande, commande, sizeof(stats.derniere_commande) - 1);
stats.derniere_commande[sizeof(stats.derniere_commande) - 1] = '\0';
kfree(commande);
return count;
}
// Callback pour lecture du fichier configuration
static int proc_config_show(struct seq_file *m, void *v)
{
atomic_inc(&stats.lectures);
seq_printf(m, "=== CONFIGURATION MODULE ===\n");
seq_printf(m, "Interface procfs active\n");
seq_printf(m, "Repertoire: /proc/%s\n", PROC_DIR);
seq_printf(m, "Fichiers disponibles:\n");
seq_printf(m, " - %s (lecture seule)\n", PROC_FILE_STAT);
seq_printf(m, " - %s (lecture/ecriture)\n", PROC_FILE_CONFIG);
seq_printf(m, "Ecrire une commande dans %s pour la tester\n", PROC_FILE_CONFIG);
return 0;
}
static int proc_config_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_config_show, NULL);
}
// Operations pour les fichiers procfs
static const struct proc_ops proc_stat_fops = {
.proc_open = proc_stat_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
static const struct proc_ops proc_config_fops = {
.proc_open = proc_config_open,
.proc_read = seq_read,
.proc_write = proc_config_write,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
// Création de l'interface procfs
static int creer_interface_procfs(void)
{
// Créer le répertoire
proc_dir = proc_mkdir(PROC_DIR, NULL);
if (!proc_dir) {
printk(KERN_ERR "Echec creation repertoire /proc/%s\n", PROC_DIR);
return -ENOMEM;
}
// Créer le fichier statistiques (lecture seule)
proc_stat = proc_create(PROC_FILE_STAT, 0444, proc_dir, &proc_stat_fops);
if (!proc_stat) {
printk(KERN_ERR "Echec creation fichier %s\n", PROC_FILE_STAT);
remove_proc_entry(PROC_DIR, NULL);
return -ENOMEM;
}
// Créer le fichier configuration (lecture/écriture)
proc_config = proc_create(PROC_FILE_CONFIG, 0644, proc_dir, &proc_config_fops);
if (!proc_config) {
printk(KERN_ERR "Echec creation fichier %s\n", PROC_FILE_CONFIG);
remove_proc_entry(PROC_FILE_STAT, proc_dir);
remove_proc_entry(PROC_DIR, NULL);
return -ENOMEM;
}
printk(KERN_INFO "Interface procfs creee: /proc/%s/\n", PROC_DIR);
return 0;
}
static int __init procfs_module_init(void)
{
int ret;
printk(KERN_INFO "=== INITIALISATION MODULE PROCFS ===\n");
// Initialiser les statistiques
stats.demarrage = jiffies;
strcpy(stats.derniere_commande, "Aucune");
// Créer l'interface procfs
ret = creer_interface_procfs();
if (ret) {
goto erreur;
}
printk(KERN_INFO "=== MODULE PROCFS ACTIF ===\n");
printk(KERN_INFO "Interface disponible dans /proc/%s/\n", PROC_DIR);
printk(KERN_INFO " - cat /proc/%s/%s pour les statistiques\n",
PROC_DIR, PROC_FILE_STAT);
printk(KERN_INFO " - echo 'commande' > /proc/%s/%s pour tester\n",
PROC_DIR, PROC_FILE_CONFIG);
return 0;
erreur:
printk(KERN_ERR "=== ECHEC INITIALISATION PROCFS ===\n");
return ret;
}
static void __exit procfs_module_exit(void)
{
printk(KERN_INFO "=== DECHARGEMENT MODULE PROCFS ===\n");
// Nettoyer l'interface procfs
if (proc_config) {
remove_proc_entry(PROC_FILE_CONFIG, proc_dir);
}
if (proc_stat) {
remove_proc_entry(PROC_FILE_STAT, proc_dir);
}
if (proc_dir) {
remove_proc_entry(PROC_DIR, NULL);
}
// Afficher les statistiques finales
printk(KERN_INFO "Statistiques finales procfs:\n");
printk(KERN_INFO "- Lectures: %d\n", atomic_read(&stats.lectures));
printk(KERN_INFO "- Ecritures: %d\n", atomic_read(&stats.ecritures));
printk(KERN_INFO "=== MODULE PROCFS DESACTIVE ===\n");
}
module_init(procfs_module_init);
module_exit(procfs_module_exit);
7.3 Périphériques caractères
7.3.1 Driver de périphérique caractère complet
char_device.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/mutex.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Expert Char Device");
MODULE_DESCRIPTION("Pilote de peripherique caractere complet");
#define DEVICE_NAME "mon_device"
#define CLASS_NAME "mon_module"
#define BUFFER_SIZE 4096
// Structure principale du device
struct char_device_data {
struct cdev cdev;
struct class *device_class;
struct device *device;
dev_t dev_num;
char *buffer;
size_t buffer_usage;
struct mutex lock; // Mutex pour la synchronisation
atomic_t open_count;
};
static struct char_device_data dev_data;
// Fonctions file_operations
static int device_open(struct inode *inode, struct file *file)
{
atomic_inc(&dev_data.open_count);
printk(KERN_INFO "Device ouvert (ouvertures: %d)\n",
atomic_read(&dev_data.open_count));
return 0;
}
static int device_release(struct inode *inode, struct file *file)
{
atomic_dec(&dev_data.open_count);
printk(KERN_INFO "Device ferme (ouvertures restantes: %d)\n",
atomic_read(&dev_data.open_count));
return 0;
}
static ssize_t device_read(struct file *filp, char __user *buffer,
size_t length, loff_t *offset)
{
ssize_t bytes_read = 0;
// Verrouiller le mutex
if (mutex_lock_interruptible(&dev_data.lock)) {
return -ERESTARTSYS;
}
// Verifier si on a depasse la fin des donnees
if (*offset >= dev_data.buffer_usage) {
mutex_unlock(&dev_data.lock);
return 0; // EOF
}
// Calculer le nombre d'octets a lire
if (*offset + length > dev_data.buffer_usage) {
bytes_read = dev_data.buffer_usage - *offset;
} else {
bytes_read = length;
}
// Copier vers l'espace utilisateur
if (copy_to_user(buffer, dev_data.buffer + *offset, bytes_read)) {
mutex_unlock(&dev_data.lock);
return -EFAULT;
}
// Mettre a jour l'offset
*offset += bytes_read;
mutex_unlock(&dev_data.lock);
printk(KERN_DEBUG "Lecture de %zd octets (offset: %lld)\n",
bytes_read, *offset);
return bytes_read;
}
static ssize_t device_write(struct file *filp, const char __user *buffer,
size_t length, loff_t *offset)
{
ssize_t bytes_written = 0;
// Verrouiller le mutex
if (mutex_lock_interruptible(&dev_data.lock)) {
return -ERESTARTSYS;
}
// Verifier si on depasse la taille du buffer
if (*offset >= BUFFER_SIZE) {
mutex_unlock(&dev_data.lock);
return -ENOSPC;
}
// Calculer le nombre d'octets a ecrire
if (*offset + length > BUFFER_SIZE) {
bytes_written = BUFFER_SIZE - *offset;
} else {
bytes_written = length;
}
// Copier depuis l'espace utilisateur
if (copy_from_user(dev_data.buffer + *offset, buffer, bytes_written)) {
mutex_unlock(&dev_data.lock);
return -EFAULT;
}
// Mettre a jour l'usage et l'offset
if (*offset + bytes_written > dev_data.buffer_usage) {
dev_data.buffer_usage = *offset + bytes_written;
}
*offset += bytes_written;
mutex_unlock(&dev_data.lock);
printk(KERN_DEBUG "Ecriture de %zd octets (usage total: %zu)\n",
bytes_written, dev_data.buffer_usage);
return bytes_written;
}
static loff_t device_llseek(struct file *filp, loff_t offset, int whence)
{
loff_t newpos;
if (mutex_lock_interruptible(&dev_data.lock)) {
return -ERESTARTSYS;
}
switch (whence) {
case 0: /* SEEK_SET */
newpos = offset;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + offset;
break;
case 2: /* SEEK_END */
newpos = dev_data.buffer_usage + offset;
break;
default: /* Error */
mutex_unlock(&dev_data.lock);
return -EINVAL;
}
// Validation de la nouvelle position
if (newpos < 0 || newpos > BUFFER_SIZE) {
mutex_unlock(&dev_data.lock);
return -EINVAL;
}
filp->f_pos = newpos;
mutex_unlock(&dev_data.lock);
return newpos;
}
// Structure file_operations
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write,
.llseek = device_llseek,
};
// Initialisation du device
static int initialiser_device(void)
{
int ret;
printk(KERN_INFO "Initialisation du peripherique caractere...\n");
// Allocation du numero de device
ret = alloc_chrdev_region(&dev_data.dev_num, 0, 1, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ERR "Echec allocation numero device\n");
return ret;
}
// Initialisation du cdev
cdev_init(&dev_data.cdev, &fops);
dev_data.cdev.owner = THIS_MODULE;
// Ajout du cdev au systeme
ret = cdev_add(&dev_data.cdev, dev_data.dev_num, 1);
if (ret) {
printk(KERN_ERR "Echec ajout cdev\n");
unregister_chrdev_region(dev_data.dev_num, 1);
return ret;
}
// Creation de la classe
dev_data.device_class = class_create(CLASS_NAME);
if (IS_ERR(dev_data.device_class)) {
printk(KERN_ERR "Echec creation classe\n");
cdev_del(&dev_data.cdev);
unregister_chrdev_region(dev_data.dev_num, 1);
return PTR_ERR(dev_data.device_class);
}
// Creation du device
dev_data.device = device_create(dev_data.device_class, NULL,
dev_data.dev_num, NULL, DEVICE_NAME);
if (IS_ERR(dev_data.device)) {
printk(KERN_ERR "Echec creation device\n");
class_destroy(dev_data.device_class);
cdev_del(&dev_data.cdev);
unregister_chrdev_region(dev_data.dev_num, 1);
return PTR_ERR(dev_data.device);
}
// Allocation du buffer
dev_data.buffer = kzalloc(BUFFER_SIZE, GFP_KERNEL);
if (!dev_data.buffer) {
printk(KERN_ERR "Echec allocation buffer\n");
device_destroy(dev_data.device_class, dev_data.dev_num);
class_destroy(dev_data.device_class);
cdev_del(&dev_data.cdev);
unregister_chrdev_region(dev_data.dev_num, 1);
return -ENOMEM;
}
// Initialisation des donnees
dev_data.buffer_usage = 0;
mutex_init(&dev_data.lock);
atomic_set(&dev_data.open_count, 0);
// Message d'accueil dans le buffer
strncpy(dev_data.buffer, "Bienvenue dans le pilote de peripherique caractere!\n",
BUFFER_SIZE - 1);
dev_data.buffer_usage = strlen(dev_data.buffer);
printk(KERN_INFO "Peripherique caractere initialise: %s (majeur: %d)\n",
DEVICE_NAME, MAJOR(dev_data.dev_num));
return 0;
}
static int __init char_device_init(void)
{
int ret;
printk(KERN_INFO "=== INITIALISATION PERIPHERIQUE CARACTERE ===\n");
ret = initialiser_device();
if (ret) {
goto erreur;
}
printk(KERN_INFO "=== PERIPHERIQUE CARACTERE ACTIF ===\n");
printk(KERN_INFO "Device cree: /dev/%s\n", DEVICE_NAME);
printk(KERN_INFO "Testez avec: echo 'test' > /dev/%s\n", DEVICE_NAME);
printk(KERN_INFO "Puis: cat /dev/%s\n", DEVICE_NAME);
return 0;
erreur:
printk(KERN_ERR "=== ECHEC INITIALISATION PERIPHERIQUE ===\n");
return ret;
}
static void __exit char_device_exit(void)
{
printk(KERN_INFO "=== DECHARGEMENT PERIPHERIQUE CARACTERE ===\n");
// Nettoyage dans l'ordre inverse
if (dev_data.buffer) {
kfree(dev_data.buffer);
}
if (dev_data.device) {
device_destroy(dev_data.device_class, dev_data.dev_num);
}
if (dev_data.device_class) {
class_destroy(dev_data.device_class);
}
if (dev_data.cdev.ops) {
cdev_del(&dev_data.cdev);
}
if (dev_data.dev_num) {
unregister_chrdev_region(dev_data.dev_num, 1);
}
printk(KERN_INFO "=== PERIPHERIQUE CARACTERE DESACTIVE ===\n");
}
module_init(char_device_init);
module_exit(char_device_exit);
7.4 Synchronisation : mutex, spinlocks
7.4.1 Module de démonstration de synchronisation
synchronisation.c :
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Expert Synchronisation");
MODULE_DESCRIPTION("Demonstration mutex, spinlocks et atomic");
#define NUM_THREADS 3
// Ressource partagee
static struct {
int compteur_standard;
atomic_t compteur_atomique;
struct mutex verrou_mutex;
spinlock_t verrou_spin;
bool execution;
} shared_data = {
.compteur_standard = 0,
.compteur_atomique = ATOMIC_INIT(0),
.execution = true
};
// Structure pour les threads
static struct task_struct *threads[NUM_THREADS];
// Fonction thread avec mutex
static int thread_mutex_func(void *data)
{
int thread_id = (long)data;
int i;
printk(KERN_INFO "Thread mutex %d demarre\n", thread_id);
for (i = 0; i < 100 && shared_data.execution; i++) {
// Section critique protegee par mutex
mutex_lock(&shared_data.verrou_mutex);
// Operation non atomique
shared_data.compteur_standard++;
printk(KERN_DEBUG "Thread %d: compteur = %d\n",
thread_id, shared_data.compteur_standard);
mutex_unlock(&shared_data.verrou_mutex);
// Pause courte
usleep_range(1000, 5000);
}
printk(KERN_INFO "Thread mutex %d termine\n", thread_id);
return 0;
}
// Fonction thread avec spinlock
static int thread_spinlock_func(void *data)
{
int thread_id = (long)data;
int i;
unsigned long flags;
printk(KERN_INFO "Thread spinlock %d demarre\n", thread_id);
for (i = 0; i < 100 && shared_data.execution; i++) {
// Section critique protegee par spinlock
spin_lock_irqsave(&shared_data.verrou_spin, flags);
// Operation non atomique
shared_data.compteur_standard++;
printk(KERN_DEBUG "Thread %d (spin): compteur = %d\n",
thread_id, shared_data.compteur_standard);
spin_unlock_irqrestore(&shared_data.verrou_spin, flags);
// Pause courte
usleep_range(1000, 5000);
}
printk(KERN_INFO "Thread spinlock %d termine\n", thread_id);
return 0;
}
// Fonction thread avec operations atomiques
static int thread_atomic_func(void *data)
{
int thread_id = (long)data;
int i;
printk(KERN_INFO "Thread atomic %d demarre\n", thread_id);
for (i = 0; i < 200 && shared_data.execution; i++) {
// Operation atomique - pas besoin de verrou
atomic_inc(&shared_data.compteur_atomique);
printk(KERN_DEBUG "Thread %d (atomic): compteur = %d\n",
thread_id, atomic_read(&shared_data.compteur_atomique));
// Pause courte
usleep_range(500, 2000);
}
printk(KERN_INFO "Thread atomic %d termine\n", thread_id);
return 0;
}
// Lancement des threads
static int lancer_threads(void)
{
int i;
printk(KERN_INFO "Lancement de %d threads...\n", NUM_THREADS);
// Thread 1: Mutex
threads[0] = kthread_run(thread_mutex_func, (void *)1, "thread_mutex");
if (IS_ERR(threads[0])) {
printk(KERN_ERR "Echec creation thread mutex\n");
return PTR_ERR(threads[0]);
}
// Thread 2: Spinlock
threads[1] = kthread_run(thread_spinlock_func, (void *)2, "thread_spinlock");
if (IS_ERR(threads[1])) {
printk(KERN_ERR "Echec creation thread spinlock\n");
kthread_stop(threads[0]);
return PTR_ERR(threads[1]);
}
// Thread 3: Atomic
threads[2] = kthread_run(thread_atomic_func, (void *)3, "thread_atomic");
if (IS_ERR(threads[2])) {
printk(KERN_ERR "Echec creation thread atomic\n");
kthread_stop(threads[0]);
kthread_stop(threads[1]);
return PTR_ERR(threads[2]);
}
return 0;
}
static int __init sync_module_init(void)
{
int ret;
int i;
printk(KERN_INFO "=== INITIALISATION SYNCHRONISATION ===\n");
// Initialisation des mecanismes de synchronisation
mutex_init(&shared_data.verrou_mutex);
spin_lock_init(&shared_data.verrou_spin);
// Lancement des threads
ret = lancer_threads();
if (ret) {
goto erreur;
}
// Attendre un peu pour que les threads travaillent
printk(KERN_INFO "Les threads travaillent pendant 5 secondes...\n");
msleep(5000);
// Arreter l'execution
shared_data.execution = false;
// Attendre la fin des threads
for (i = 0; i < NUM_THREADS; i++) {
if (!IS_ERR(threads[i])) {
kthread_stop(threads[i]);
}
}
// Afficher les resultats finaux
printk(KERN_INFO "=== RESULTATS SYNCHRONISATION ===\n");
printk(KERN_INFO "Compteur standard (mutex/spinlock): %d\n",
shared_data.compteur_standard);
printk(KERN_INFO "Compteur atomique: %d\n",
atomic_read(&shared_data.compteur_atomique));
printk(KERN_INFO "=== MODULE SYNCHRONISATION ACTIF ===\n");
return 0;
erreur:
printk(KERN_ERR "=== ECHEC INITIALISATION SYNCHRONISATION ===\n");
return ret;
}
static void __exit sync_module_exit(void)
{
int i;
printk(KERN_INFO "=== DECHARGEMENT SYNCHRONISATION ===\n");
// S'assurer que tous les threads sont arretes
shared_data.execution = false;
for (i = 0; i < NUM_THREADS; i++) {
if (!IS_ERR(threads[i])) {
kthread_stop(threads[i]);
}
}
// Detruire les mutex
mutex_destroy(&shared_data.verrou_mutex);
printk(KERN_INFO "=== MODULE SYNCHRONISATION DESACTIVE ===\n");
}
module_init(sync_module_init);
module_exit(sync_module_exit);
7.5 Makefile pour les concepts avancés
Makefile :
# Configuration
KVERSION := $(shell uname -r)
KDIR := /lib/modules/$(KVERSION)/build
PWD := $(shell pwd)
# Modules avances
obj-m += irq_handler.o
obj-m += procfs_interface.o
obj-m += char_device.o
obj-m += synchronisation.o
# Flags
ccflags-y := -Wall -Wextra -Wno-unused-parameter
ccflags-y += -DDEBUG
all:
@echo "Compilation des modules avances..."
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
# Tests specifiques
test-irq: all
sudo dmesg -C
sudo insmod irq_handler.ko gpio_bouton=17 gpio_led=4
dmesg
# Tester en appuyant sur le bouton physique
sleep 10
sudo rmmod irq_handler
dmesg
test-procfs: all
sudo dmesg -C
sudo insmod procfs_interface.ko
dmesg
cat /proc/mon_module/statistiques
echo "test_command" > /proc/mon_module/configuration
cat /proc/mon_module/statistiques
sudo rmmod procfs_interface
dmesg
test-char: all
sudo dmesg -C
sudo insmod char_device.ko
dmesg
sudo mknod /dev/mon_device c $(shell cat /proc/devices | grep mon_device | cut -d' ' -f1) 0
echo "Hello Device Driver!" > /dev/mon_device
cat /dev/mon_device
sudo rmmod char_device
dmesg
test-sync: all
sudo dmesg -C
sudo insmod synchronisation.ko
dmesg
sudo rmmod synchronisation
dmesg
# Test complet
test-all: test-irq test-procfs test-char test-sync
.PHONY: all clean test-irq test-procfs test-char test-sync test-all
