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 :

AspectEspace utilisateurEspace noyau
Accès mémoireMémoire virtuelle limitéeAccès complet à toute la mémoire
PrivilègesRestrictions sévèresPrivilèges maximum
StabilitéProcessus isoléPanne = crash système
DébogageOutils classiques (gdb)Débogage complexe
APIBibliothè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/kfree au lieu de malloc/free
  • Affichage : printk au lieu de printf
  • 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 __exit
  • MODULE_LICENSE("GPL") : OBLIGATOIRE – spécifie la licence
  • static int __init : Fonction exécutée au chargement
  • __init : Le code peut être libéré après initialisation
  • printk(KERN_INFO "...") : Équivalent noyau de printf
  • module_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 :

  1. Toujours implémenter module_init() et module_exit()
  2. MODULE_LICENSE() est OBLIGATOIRE
  3. Utiliser le pattern goto pour la gestion d’erreurs
  4. Nettoyer TOUTES les ressources dans la fonction exit
  5. printk() pour le débogage, pas de printf()
  6. 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 :

  1. Gestion d’erreurs : Toujours utiliser le pattern goto pour le nettoyage
  2. Sécurité : Vérifier toutes les entrées, utiliser BUG_ON pour les invariants
  3. Débogage : Niveaux de log appropriés, macros de debug
  4. Compilation : Makefile robuste avec différentes configurations
  5. 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