Salut l'équipe
Aujourd’hui on attaque un sujet central du développement système Windows : le kernel Windows.
C’est une étape importante, parce qu’à partir de maintenant on ne parle plus seulement d’API user-mode, de processus, de threads ou de synchronisation classique. On parle de la partie du système qui contrôle la machine entière, coordonne les requêtes, arbitre l’accès aux ressources critiques et exécute le code le plus privilégié.
Et surtout, ici, les erreurs n’ont pas du tout les mêmes conséquences.
En user mode :
- une erreur peut faire planter ton programme
- une mauvaise lecture mémoire peut provoquer une exception
- Windows peut souvent contenir les dégâts
- un mauvais pointeur peut corrompre le système
- une erreur logique peut bloquer une pile de drivers
- une faute de synchronisation peut provoquer un BSOD
- une mauvaise gestion d’IRP peut rendre un device inutilisable
Le but n’est pas juste de “survoler le kernel”, mais de construire une vraie base mentale solide.
1) C’est quoi le kernel
Le kernel, ou noyau, est la partie de Windows qui exécute les opérations les plus sensibles et les plus privilégiées du système.
C’est lui qui :
- gère la mémoire virtuelle
- ordonnance les threads
- arbitre l’accès au CPU
- pilote les entrées / sorties
- fait le lien entre les applications et le matériel
- applique une partie importante des règles de sécurité
- coordonne les drivers
- gère les interruptions matérielles
On peut le voir comme un chef d’orchestre :
- les applications demandent des services
- le matériel génère des événements
- les drivers traitent les requêtes
- le kernel arbitre le tout
2) Pourquoi le kernel existe
Sur une machine moderne, plusieurs programmes tournent en même temps. Ils veulent tous :
- utiliser de la mémoire
- créer des threads
- accéder au disque
- envoyer des données réseau
- ouvrir des handles
- lire des fichiers
- dialoguer avec des devices
Chaque programme pourrait :
- écraser la mémoire d’un autre
- parler directement au matériel sans contrôle
- monopoliser le CPU
- faire planter toute la machine au moindre bug
- les ressources sont partagées
- les accès sont contrôlés
- les transitions critiques sont centralisées
- le matériel est exposé via des abstractions cohérentes
C’est la séparation la plus importante à comprendre.
Windows distingue principalement deux grands niveaux d’exécution :
- user mode
- kernel mode
Le user mode est l’espace dans lequel tournent les programmes classiques :
- applications console
- applications GUI
- services user-mode
- outils d’administration
- la majorité des logiciels du système
- l’accès mémoire est limité à l’espace du processus
- certaines instructions CPU privilégiées sont interdites
- l’accès direct au matériel n’est pas autorisé
- le programme doit passer par les API du système
Si une application plante, Windows peut généralement :
- arrêter le processus
- lever une exception
- préserver le reste du système
Le kernel mode est l’espace privilégié.
Le code qui s’exécute ici a un niveau d’autorité beaucoup plus élevé. Il peut :
- accéder à la mémoire du noyau
- interagir avec des structures système critiques
- gérer les devices
- recevoir des interruptions matérielles
- exécuter du code avec très peu de garde-fous
En kernel mode, une erreur ne menace pas seulement ton programme. Elle menace l’intégrité du système entier.
3.3) Conséquence directe
La différence pratique est énorme :
- en user mode, tu développes une application
- en kernel mode, tu participes au fonctionnement du système
4) Comment une application atteint le kernel
Quand tu codes en Win32, tu utilises souvent des API comme :
Code: Select all
CreateFileCode: Select all
ReadFileCode: Select all
WriteFileCode: Select all
CreateProcessCode: Select all
VirtualAlloc
Les API Win32 ne sont pas “le kernel”. Elles sont une abstraction user-mode.
4.1) Win32 = couche d’abstraction
L’API Win32 est la couche que les développeurs utilisent le plus souvent.
Elle est pratique, documentée, relativement stable, et masque beaucoup de détails internes.
Par exemple :
Code: Select all
CreateFile(...)
En réalité, ce n’est qu’une façade de plus haut niveau.
4.2) Native API = interface plus proche du noyau
Sous Win32, on trouve la Native API, exposée notamment via
Code: Select all
ntdll.dllPar exemple, derrière un appel Win32 comme
Code: Select all
CreateFileCode: Select all
NtCreateFile(...)
- passage par une transition système
- entrée dans le noyau
- traitement par les composants internes de Windows
Code: Select all
Application
↓
Win32 API
↓
Native API (ntdll)
↓
System call
↓
Kernel
Parce que beaucoup de développeurs s’arrêtent à Win32.
En développement kernel, tu dois comprendre que Win32 n’est qu’une surcouche pratique. Le vrai cœur du traitement se trouve plus bas.
Donc oui, on peut dire que :
- Win32 = abstraction user-mode
- Native API = interface plus proche du noyau
La Native API n’est pas “le kernel lui-même” : elle est surtout le pont entre le monde user-mode et les services du noyau.
5) L’idée centrale : toute I/O devient une requête système
L’un des points les plus importants du kernel Windows est la gestion des entrées / sorties.
Quand une application lit un fichier, envoie une commande à un device ou communique avec un driver, elle ne parle pas directement au matériel.
Le système transforme cette demande en un objet de travail standardisé.
Cet objet, dans le modèle Windows, c’est l’IRP.
6) IRP — I/O Request Packet
L’IRP est une structure centrale dans l’architecture d’I/O de Windows.
Tu peux voir un IRP comme la représentation d’une requête d’entrée / sortie.
Exemples de requêtes :
- ouvrir un device
- lire des données
- écrire
- envoyer un IOCTL
- fermer un handle
- exécuter certaines opérations PnP ou power
Parce qu’il sert de support commun entre plusieurs couches du système.
Le kernel peut ainsi :
- créer une requête de façon standardisée
- la faire passer à une pile de drivers
- laisser chaque driver ajouter son traitement
- centraliser la complétion et le retour d’état
Un IRP contient en substance :
- l’état de la requête
- des informations de statut
- les paramètres de l’opération
- éventuellement des buffers
- des informations pour la pile de drivers
Et c’est ça qu’il faut déjà comprendre.
Le kernel ne dit pas juste “lis ce fichier”. Il fabrique une requête structurée qui traverse un chemin précis.
6.3) Règle mentale fondamentale
Toute I/O sérieuse dans Windows se matérialise sous forme de requête. Dans le modèle driver classique, cette requête est souvent portée par un IRP.
7) De l’application au hardware : le vrai pipeline
Prenons un exemple simple : une application appelle
Code: Select all
ReadFileLe déroulement mental correct ressemble à ça :
Code: Select all
Application
↓
Win32 API
↓
Native API
↓
System call
↓
I/O Manager
↓
Création / préparation d’une requête
↓
IRP
↓
Driver stack
↓
Hardware
Code: Select all
Hardware
↓
Interrupt / notification
↓
Driver
↓
Complétion de la requête
↓
Retour vers le noyau
↓
Retour vers user mode
- les applications demandent
- le noyau orchestre
- les drivers participent au traitement
- le matériel exécute
- le résultat remonte ensuite dans l’autre sens
Un driver est un composant logiciel qui s’exécute en kernel mode et qui participe au traitement des requêtes système, souvent en lien avec un device ou une pile d’I/O.
Mais il faut éviter une définition trop simpliste.
Un driver ne sert pas uniquement à “piloter une carte”.
Dans Windows, un driver peut :
- contrôler un périphérique physique
- filtrer des requêtes
- représenter un device logique
- participer à une pile logicielle complexe
- gérer des opérations PnP, power et I/O
C’est faux dans la majorité des cas.
Le bon modèle mental est celui-ci :
Code: Select all
Un driver est surtout un ensemble de callbacks enregistrés dans le système.
- il n’a pas de classique
Code: Select all
main - il n’impose pas son propre flow général
- il est appelé par le kernel quand un événement pertinent survient
- une requête I/O arrive
- un device est détecté
- un device est retiré
- une demande d’alimentation change
- une interruption matérielle se produit
Quand on dit qu’un driver est passif, on ne veut pas dire qu’il ne fait rien.
On veut dire que :
- le système décide quand il est appelé
- le contexte d’appel ne lui appartient pas
- son exécution est déclenchée par des événements externes
Le driver ne contrôle pas le rythme du système. Il répond au rythme imposé par le système.
C’est une différence énorme avec une application classique.
9) “The system is in charge”
C’est probablement l’idée la plus importante de l’introduction au kernel.
En user mode, tu écris souvent du code qui suit un flux relativement clair :
- initialisation
- boucle
- appels
- traitement
- sortie
- le système charge le driver
- le système appelle tes routines
- le système t’envoie des requêtes
- le système peut t’appeler depuis différents contextes
- le système attend que tu respectes ses règles
Le système est toujours maître du timing et du cadre d’exécution.
Si tu développes en oubliant ça, tu vas écrire du code dangereux.
10) La pile de drivers
Dans Windows, un driver n’est généralement pas seul.
Les requêtes passent souvent à travers plusieurs drivers superposés. On appelle ça une driver stack.
10.1) Les trois grandes catégories à connaître au début
Même si la réalité peut être plus riche, il faut déjà connaître ces trois rôles majeurs.
Function driver
C’est le driver principal qui implémente la logique essentielle pour un device.
Filter driver
Il s’insère dans la pile pour observer, modifier, surveiller, bloquer ou ajuster certaines requêtes.
Bus driver
Il gère un bus matériel ou logique, comme USB ou PCI, et participe à la découverte des devices.
10.2) Représentation mentale simple
Code: Select all
Filter driver
↓
Function driver
↓
Bus driver
↓
Hardware
Parce qu’un débutant imagine souvent :
Code: Select all
app → mon driver → matériel
Code: Select all
app → I/O Manager → plusieurs couches de drivers → matériel
11) Le rôle du I/O Manager
Le I/O Manager est un composant central du noyau dans la gestion des requêtes d’entrée / sortie.
C’est lui qui participe à :
- la création et la préparation des IRP
- le routage vers les bons objets de device
- l’orchestration du passage dans la pile
- la coordination de la complétion
12) Comment une application parle à un driver
Le mécanisme classique passe par :
- un device object exposé par le driver
- souvent un symbolic link accessible depuis user mode
- un handle ouvert avec
Code: Select all
CreateFile - une commande envoyée via
Code: Select all
DeviceIoControl
Code: Select all
Application (.exe)
↓
CreateFile("\\.\MonDriver")
↓
DeviceIoControl(...)
↓
I/O Manager
↓
IRP_MJ_DEVICE_CONTROL
↓
Driver
Ça montre que :
- le driver n’est pas une DLL classique
- on ne l’appelle pas comme une fonction normale
- on dialogue avec lui via le mécanisme d’I/O du système
Code: Select all
Chargement du driver
↓
Initialisation (DriverEntry)
↓
Éventuelle association à des devices
↓
Réception de requêtes
↓
Traitement I/O / PnP / Power / Interruptions
↓
Nettoyage / déchargement
Ton code peut être appelé :
- depuis des threads différents
- dans des contextes variés
- parfois à des niveaux d’exécution différents
- parfois en concurrence avec d’autres chemins d’exécution
Ce que ça veut dire :
- tu ne dois pas présumer que c’est “ton thread”
- tu ne peux pas bloquer n’importe comment
- tu ne peux pas toucher n’importe quelle structure sans synchronisation
- tu dois connaître les contraintes du contexte d’appel
Quand un périphérique veut signaler un événement important, il peut déclencher une interruption.
ISR — Interrupt Service Routine
L’ISR est appelée très rapidement en réponse à une interruption.
Elle doit rester courte et faire le minimum.
DPC — Deferred Procedure Call
Le DPC permet de repousser une partie du travail à un moment plus approprié.
Schéma mental :
Code: Select all
Hardware
↓
Interrupt
↓
ISR
↓
DPC
↓
Traitement ultérieur / complétion
16) IRQL — notion critique
Sans encore entrer dans tout le détail, retiens ceci :
- toutes les portions de code kernel ne s’exécutent pas dans les mêmes conditions
- selon le niveau courant, certaines opérations sont autorisées ou interdites
- plus le niveau est élevé, plus les contraintes sont fortes
En kernel mode, le “où” et le “quand” d’exécution comptent autant que le “quoi”.
17) Synchronisation en kernel
Les bugs de synchronisation en kernel sont particulièrement dangereux.
Exemples de conséquences :
- corruption mémoire
- état incohérent du driver
- IRP bloqués
- deadlocks
- BSOD
Une erreur classique en kernel peut être bien plus grave qu’en user mode.
Exemples :
- déréférencer un pointeur invalide
- oublier de compléter une requête
- renvoyer un mauvais statut
- mal gérer un buffer
- oublier une synchronisation
- faire un travail lourd dans une routine sensible
- casser la cohérence de la pile de drivers
- se figer
- produire un bugcheck
- perdre l’accès à un device
- devenir instable de façon intermittente
- Win32 n’est pas le fond du système
- la Native API est plus proche du noyau
- le noyau orchestre les requêtes
- l’IRP est une unité centrale de travail
- les drivers sont événementiels et passifs
- les drivers travaillent en pile
- le contexte d’exécution est une contrainte majeure
- les erreurs de logique sont structurellement plus graves
Code: Select all
Application
↓
Win32 API
↓
Native API
↓
System call
↓
Kernel
↓
I/O Manager
↓
IRP
↓
Driver stack
↓
Hardware
↓
Interrupt / traitement de retour
↓
Complétion
↓
Retour vers l’application
- le système est en contrôle
- le driver est passif
- les requêtes traversent une pile
- le contexte d’exécution est critique
- une erreur kernel peut compromettre toute la machine
Ce qu’il faut retenir de cette introduction, c’est surtout ceci :
- Win32 est une abstraction, pas le fond réel du système
- la Native API sert de pont vers le noyau
- les requêtes I/O sont structurées et routées par le système
- l’IRP est un élément central du modèle driver
- un driver est un ensemble de routines appelées par le kernel
- les drivers vivent dans une pile, pas en isolation
- le contexte d’exécution et les contraintes système sont fondamentaux
- la moindre erreur peut avoir des conséquences critiques
Résumé ultra condensé
Le kernel contrôle les ressources critiques du système.
Win32 est une abstraction user-mode.
La Native API sert de pont vers le noyau.
Les I/O deviennent des requêtes structurées, souvent portées par des IRP.
Ces requêtes traversent une pile de drivers.
Les drivers sont passifs, événementiels, et appelés par le système.
Le contexte d’exécution, l’IRQL, la synchronisation et la rigueur sont essentiels.
En kernel mode, une erreur peut compromettre toute la machine.
