Listes chaînées en Kernel Windows

Ce cours dédiée au développement de drivers en kernel mode sans Framework

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 124
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Listes chaînées en Kernel Windows

Post by Hydraxx »

Listes chaînées en Kernel Windows

Les listes chaînées sont utilisées partout dans le noyau Windows pour organiser des objets, des requêtes, des processus, des éléments de file d'attente ou des structures internes.

Dans les drivers WDM, on rencontre principalement :
  • Code: Select all

    LIST_ENTRY
    pour les listes doublement chaînées ;
  • Code: Select all

    SINGLE_LIST_ENTRY
    pour les listes simplement chaînées ;
  • Code: Select all

    CONTAINING_RECORD
    pour retrouver la structure complète à partir du champ de liaison ;
  • les variantes interlocked lorsque plusieurs contextes accèdent à la même liste ;
  • les lookaside lists, qui sont des caches d'allocations et non de simples listes chaînées.
Le point essentiel à comprendre est que le noyau n'impose pas de stocker directement un pointeur vers chaque structure utilisateur. Le plus souvent, le champ de liaison est intégré directement dans la structure.

1. Principe d'une structure contenant un lien

Exemple :

Code: Select all

typedef struct _MY_ENTRY
{
    LIST_ENTRY Link;
    LONG Value;

} MY_ENTRY, *PMY_ENTRY;
Ici :

Code: Select all

MY_ENTRY
contient un champ :

Code: Select all

LIST_ENTRY Link;
Le noyau manipule donc principalement l'adresse de :

Code: Select all

entry->Link
et non directement celle de la structure complète.

2. LIST_ENTRY

La structure :

Code: Select all

LIST_ENTRY
représente un maillon de liste doublement chaînée.

Conceptuellement, elle contient deux pointeurs :

Code: Select all

Flink
Blink
avec :
On peut représenter une liste ainsi :

Code: Select all

A <-> B <-> C
Mais dans le noyau Windows, les listes `LIST_ENTRY` sont généralement circulaires.

3. Liste doublement chaînée circulaire

Une liste typique ressemble à :

Code: Select all

       +-------------------------+
       |                         |
       v                         |
Head <-> A <-> B <-> C <--------+
La tête fait partie de la structure de chaînage.

Elle n'est pas nécessairement un véritable objet métier.

Sa fonction est de représenter le début et la fin de la liste.

4. Initialiser une liste

On utilise :

Code: Select all

InitializeListHead
Exemple :

Code: Select all

LIST_ENTRY Head;

InitializeListHead(&Head);
Après initialisation d'une liste vide :

Code: Select all

Head.Flink == &Head
Head.Blink == &Head
La tête se référence donc elle-même.

5. Tester si une liste est vide

L'API prévue est :

Code: Select all

IsListEmpty
Exemple :

Code: Select all

if (IsListEmpty(&Head))
{
    KdPrint(("Liste vide\n"));
}
On pourrait tester manuellement :

Code: Select all

Head.Flink == &Head
mais `IsListEmpty` rend l'intention beaucoup plus claire.

6. Insérer en tête

Pour insérer un élément juste après la tête :

Code: Select all

InsertHeadList
Exemple :

Code: Select all

InsertHeadList(
    &Head,
    &entry->Link
);
Si la liste contient :

Code: Select all

A -> B -> C
l'insertion de `X` en tête donne :

Code: Select all

X -> A -> B -> C
7. Insérer en fin

Pour insérer à la fin :

Code: Select all

InsertTailList
Exemple :

Code: Select all

InsertTailList(
    &Head,
    &entry->Link
);
Si la liste contient :

Code: Select all

A -> B -> C
elle devient :

Code: Select all

A -> B -> C -> X
8. Retirer le premier élément

On utilise :

Code: Select all

RemoveHeadList
Exemple :

Code: Select all

PLIST_ENTRY first =
    RemoveHeadList(&Head);
Cette fonction retourne un pointeur vers le `LIST_ENTRY` retiré.

Il faut ensuite retrouver la structure contenant ce champ.

9. Retirer le dernier élément

On utilise :

Code: Select all

RemoveTailList
Exemple :

Code: Select all

PLIST_ENTRY last =
    RemoveTailList(&Head);
Là encore, on obtient le champ `LIST_ENTRY`, pas directement la structure complète.

10. Retirer un élément précis

Lorsqu'on connaît l'élément :

Code: Select all

RemoveEntryList(
    &entry->Link
);
Cela reconnecte les éléments précédent et suivant.

L'élément n'est pas libéré automatiquement.

Retirer une entrée de la liste et libérer sa mémoire sont deux opérations différentes.

11. CONTAINING_RECORD

Après avoir récupéré un :

Code: Select all

PLIST_ENTRY
il faut souvent retrouver la structure contenant ce champ.

Windows fournit :

Code: Select all

CONTAINING_RECORD
Exemple :

Code: Select all

PMY_ENTRY entry =
    CONTAINING_RECORD(
        current,
        MY_ENTRY,
        Link
    );
Le principe est :

Code: Select all

adresse du champ Link
        ↓
calcul de l'offset
        ↓
adresse de MY_ENTRY
12. Pourquoi CONTAINING_RECORD est important

Dans de nombreuses structures kernel, un objet contient directement un ou plusieurs `LIST_ENTRY`.

On ne stocke donc pas forcément :

Code: Select all

PMY_ENTRY Next;
mais plutôt :

Code: Select all

LIST_ENTRY Link;
C'est cette approche qui permet au même objet d'être intégré facilement dans les mécanismes génériques de listes du noyau.

13. Parcourir une liste vers l'avant

Le parcours standard est :

Code: Select all

PLIST_ENTRY current =
    Head.Flink;

while (current != &Head)
{
    PMY_ENTRY entry =
        CONTAINING_RECORD(
            current,
            MY_ENTRY,
            Link
        );

    KdPrint((
        "Value = %ld\n",
        entry->Value
    ));

    current = current->Flink;
}
Le point important est la condition :

Code: Select all

current != &Head
et non :

Code: Select all

current != nullptr
car une liste `LIST_ENTRY` est circulaire.

14. Parcourir une liste vers l'arrière

On peut également commencer par :

Code: Select all

Head.Blink
Puis avancer avec :

Code: Select all

current = current->Blink;
Exemple :

Code: Select all

PLIST_ENTRY current =
    Head.Blink;

while (current != &Head)
{
    PMY_ENTRY entry =
        CONTAINING_RECORD(
            current,
            MY_ENTRY,
            Link
        );

    KdPrint((
        "Value = %ld\n",
        entry->Value
    ));

    current = current->Blink;
}
15. Exemple complet avec allocation

Structure :

Code: Select all

typedef struct _MY_ENTRY
{
    LIST_ENTRY Link;
    LONG Value;

} MY_ENTRY, *PMY_ENTRY;
Initialisation :

Code: Select all

LIST_ENTRY Head;

InitializeListHead(&Head);
Allocation :

Code: Select all

PMY_ENTRY entry =
    (PMY_ENTRY)ExAllocatePool2(
        POOL_FLAG_NON_PAGED,
        sizeof(MY_ENTRY),
        'rtnE'
    );

if (entry == nullptr)
{
    return STATUS_INSUFFICIENT_RESOURCES;
}
Initialisation :

Code: Select all

RtlZeroMemory(
    entry,
    sizeof(MY_ENTRY)
);

entry->Value = 42;
Insertion :

Code: Select all

InsertTailList(
    &Head,
    &entry->Link
);
16. Plusieurs LIST_ENTRY dans la même structure

Une structure peut appartenir à plusieurs listes différentes.

Exemple :

Code: Select all

typedef struct _MY_OBJECT
{
    LIST_ENTRY GlobalLink;
    LIST_ENTRY DeviceLink;

    ULONG Id;

} MY_OBJECT, *PMY_OBJECT;
Le même objet peut alors être présent dans :
  • une liste globale ;
  • une liste spécifique à un périphérique.
Il faut utiliser le bon membre avec `CONTAINING_RECORD`.

Exemple :

Code: Select all

PMY_OBJECT obj =
    CONTAINING_RECORD(
        current,
        MY_OBJECT,
        DeviceLink
    );
17. LIST_ENTRY dans les structures internes Windows

Le noyau Windows utilise énormément ce modèle.

Conceptuellement, une structure interne peut contenir :

Code: Select all

LIST_ENTRY SomeLinks;
puis être reliée à d'autres objets du même type.

Un exemple classique étudié dans les structures de processus est :

Code: Select all

ActiveProcessLinks
dans les structures associées aux processus.

Cela explique pourquoi la maîtrise de `LIST_ENTRY` et `CONTAINING_RECORD` est importante pour comprendre les structures kernel.

18. SINGLE_LIST_ENTRY

Windows fournit également :

Code: Select all

SINGLE_LIST_ENTRY
pour les listes simplement chaînées.

Conceptuellement :

Code: Select all

Head -> A -> B -> C -> NULL
Contrairement à `LIST_ENTRY`, il n'existe pas de pointeur vers l'élément précédent.

19. Structure avec SINGLE_LIST_ENTRY

Exemple :

Code: Select all

typedef struct _MY_SINGLE_ENTRY
{
    SINGLE_LIST_ENTRY Link;
    LONG Value;

} MY_SINGLE_ENTRY, *PMY_SINGLE_ENTRY;
La structure conserve un lien vers l'élément suivant.

20. Initialisation d'une singly-linked list

Une liste simple peut être initialisée avec :

Code: Select all

SINGLE_LIST_ENTRY Head = {};
ou explicitement :

Code: Select all

Head.Next = nullptr;
Une liste vide se termine donc par :

Code: Select all

NULL
et non par un retour circulaire vers sa tête.

21. PushEntryList

Pour ajouter en tête :

Code: Select all

PushEntryList(
    &Head,
    &entry->Link
);
Le nouvel élément devient immédiatement le premier.

Cette opération correspond naturellement à une structure de type pile.

22. PopEntryList

Pour retirer le premier élément :

Code: Select all

PSINGLE_LIST_ENTRY link =
    PopEntryList(&Head);
Si la liste est vide, le résultat peut être :

Code: Select all

nullptr
Il faut donc vérifier :

Code: Select all

if (link != nullptr)
{
    ...
}
23. Parcours d'une singly-linked list

Exemple :

Code: Select all

PSINGLE_LIST_ENTRY current =
    Head.Next;

while (current != nullptr)
{
    PMY_SINGLE_ENTRY entry =
        CONTAINING_RECORD(
            current,
            MY_SINGLE_ENTRY,
            Link
        );

    KdPrint((
        "Value = %ld\n",
        entry->Value
    ));

    current = current->Next;
}
24. LIST_ENTRY vs SINGLE_LIST_ENTRY

`LIST_ENTRY` :
  • doublement chaînée ;
  • navigation avant et arrière ;
  • suppression d'un élément plus flexible ;
  • deux pointeurs par entrée ;
  • généralement circulaire.
`SINGLE_LIST_ENTRY` :
  • simplement chaînée ;
  • navigation uniquement vers l'avant ;
  • moins de mémoire utilisée ;
  • très adaptée aux piles simples ;
  • fin avec `NULL`.
25. Concurrence et listes

Les opérations classiques sur `LIST_ENTRY` ne rendent pas automatiquement une liste thread-safe.

Exemple problématique :

Code: Select all

Thread A:
InsertTailList(...)

Thread B:
RemoveHeadList(...)
Si les deux opérations modifient les liens en même temps, la liste peut être corrompue.

Une liste partagée doit donc être protégée par une primitive appropriée.

26. Corruption de Flink et Blink

Une liste doublement chaînée dépend de la cohérence de :

Code: Select all

Flink
Blink
Si un lien est incorrect, les symptômes peuvent inclure :
  • boucle infinie ;
  • accès mémoire invalide ;
  • liste impossible à parcourir ;
  • corruption d'autres structures ;
  • bug check.
27. Variantes interlocked

Windows fournit également des opérations interlocked pour certains scénarios.

On rencontre notamment :

Code: Select all

ExInterlockedInsertHeadList
ExInterlockedInsertTailList
ExInterlockedRemoveHeadList
Ces fonctions permettent d'effectuer certaines opérations de manière atomique en utilisant un spinlock fourni.

Exemple conceptuel :

Code: Select all

KSPIN_LOCK Lock;

KeInitializeSpinLock(&Lock);
Puis :

Code: Select all

ExInterlockedInsertTailList(
    &Head,
    &entry->Link,
    &Lock
);
28. Ne pas mélanger les stratégies de synchronisation

Si une liste est protégée par un spinlock explicite autour d'une série d'opérations :

Code: Select all

KeAcquireSpinLock(...);

// plusieurs opérations sur la liste

KeReleaseSpinLock(...);
il ne faut pas utiliser arbitrairement des variantes interlocked sur la même structure sans comprendre la stratégie globale.

Une liste doit avoir une politique de synchronisation claire et cohérente.

29. Listes et IRQL

`LIST_ENTRY` n'impose pas à lui seul un IRQL particulier.

Mais le contexte autour de la liste peut en imposer un.

Par exemple :
  • une liste protégée par spinlock peut être manipulée à `DISPATCH_LEVEL` ;
  • ses éléments doivent alors résider en mémoire nonpageable ;
  • une liste en mémoire pageable ne doit pas être touchée à un IRQL incompatible.
30. Listes et pool kernel

Un pattern fréquent est :

Code: Select all

ExAllocatePool2
        ↓
initialiser l'objet
        ↓
InsertTailList
        ↓
utiliser l'objet
        ↓
RemoveEntryList
        ↓
ExFreePool
La liste n'est qu'un mécanisme d'organisation.

Elle ne gère pas automatiquement la durée de vie mémoire des objets.

31. Ownership d'un élément

Il faut toujours savoir :
  • qui possède l'objet lorsqu'il est dans la liste ;
  • qui peut le retirer ;
  • qui le libère ;
  • si un autre thread peut encore utiliser son adresse.
Exemple :

Code: Select all

RemoveEntryList(&entry->Link);
ExFreePool(entry);
est correct uniquement si aucun autre code ne détient encore une référence valide vers `entry`.

32. Supprimer pendant un parcours

Une erreur fréquente est :

Code: Select all

RemoveEntryList(current);
ExFreePool(entry);

current = current->Flink;
Après `ExFreePool`, `current` pointe vers de la mémoire libérée.

Il ne faut donc plus utiliser :

Code: Select all

current->Flink
33. Parcours sûr avec suppression

Il faut sauvegarder le prochain élément avant la suppression :

Code: Select all

PLIST_ENTRY current =
    Head.Flink;

while (current != &Head)
{
    PLIST_ENTRY next =
        current->Flink;

    PMY_ENTRY entry =
        CONTAINING_RECORD(
            current,
            MY_ENTRY,
            Link
        );

    RemoveEntryList(current);

    ExFreePool(entry);

    current = next;
}
34. Recherche d'un élément

Exemple :

Code: Select all

PMY_ENTRY FindValue(
    PLIST_ENTRY Head,
    LONG Value
)
{
    PLIST_ENTRY current =
        Head->Flink;

    while (current != Head)
    {
        PMY_ENTRY entry =
            CONTAINING_RECORD(
                current,
                MY_ENTRY,
                Link
            );

        if (entry->Value == Value)
        {
            return entry;
        }

        current = current->Flink;
    }

    return nullptr;
}
35. Listes dans une DEVICE_EXTENSION

Une `DEVICE_EXTENSION` peut contenir une tête de liste :

Code: Select all

typedef struct _DEVICE_EXTENSION
{
    LIST_ENTRY Items;
    KSPIN_LOCK Lock;

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
Initialisation :

Code: Select all

InitializeListHead(
    &ext->Items
);

KeInitializeSpinLock(
    &ext->Lock
);
Cela permet d'associer directement une collection d'objets à un périphérique.

36. Nettoyage avant IoDeleteDevice

Avant de supprimer le `DEVICE_OBJECT`, toutes les structures possédées par sa `DEVICE_EXTENSION` doivent être nettoyées.

Exemple conceptuel :

Code: Select all

while (!IsListEmpty(&ext->Items))
{
    PLIST_ENTRY link =
        RemoveHeadList(
            &ext->Items
        );

    PMY_ENTRY entry =
        CONTAINING_RECORD(
            link,
            MY_ENTRY,
            Link
        );

    ExFreePool(entry);
}

IoDeleteDevice(DeviceObject);
37. Linked list et lookaside list : différence

Une erreur conceptuelle fréquente est de confondre :

Code: Select all

LIST_ENTRY
et :

Code: Select all

LOOKASIDE_LIST_EX
Une linked list sert à :

organiser des objets.

Une lookaside list sert à :

réutiliser rapidement des blocs mémoire de taille fixe.

Les deux peuvent être utilisées ensemble.

38. Principe d'une lookaside list

Une lookaside list agit comme un petit cache.

Sans lookaside :

Code: Select all

besoin d'un objet
    ↓
allocation pool
    ↓
utilisation
    ↓
free pool
Avec lookaside :

Code: Select all

besoin d'un objet
    ↓
bloc déjà disponible ?
   / \
 oui non
  |   |
cache pool
  \   /
 utilisation
    ↓
retour au cache
Cela réduit le coût d'allocations répétitives.

39. Anciennes lookaside lists

Les anciens drivers utilisent :

Code: Select all

NPAGED_LOOKASIDE_LIST
PAGED_LOOKASIDE_LIST
avec des fonctions comme :

Code: Select all

ExInitializeNPagedLookasideList
ExInitializePagedLookasideList

ExAllocateFromNPagedLookasideList
ExAllocateFromPagedLookasideList

ExFreeToNPagedLookasideList
ExFreeToPagedLookasideList

ExDeleteNPagedLookasideList
ExDeletePagedLookasideList
Elles sont importantes pour comprendre le code WDM historique.

40. LOOKASIDE_LIST_EX

Les API plus modernes utilisent :

Code: Select all

LOOKASIDE_LIST_EX
Les opérations principales sont :

Code: Select all

ExInitializeLookasideListEx
ExAllocateFromLookasideListEx
ExFreeToLookasideListEx
ExDeleteLookasideListEx
41. Initialisation d'une lookaside moderne

Exemple :

Code: Select all

LOOKASIDE_LIST_EX Lookaside;

NTSTATUS status =
    ExInitializeLookasideListEx(
        &Lookaside,
        nullptr,
        nullptr,
        POOL_FLAG_NON_PAGED,
        0,
        sizeof(MY_ENTRY),
        'rtnE',
        0
    );
Il faut tester :

Code: Select all

if (!NT_SUCCESS(status))
{
    return status;
}
42. Allocation depuis une lookaside

Code: Select all

PMY_ENTRY entry =
    (PMY_ENTRY)
    ExAllocateFromLookasideListEx(
        &Lookaside
    );
Selon la configuration de la lookaside, le système peut :
  • réutiliser un bloc déjà disponible ;
  • effectuer une nouvelle allocation si nécessaire.
43. Rendre un objet à la lookaside

Au lieu d'appeler directement `ExFreePool`, on utilise :

Code: Select all

ExFreeToLookasideListEx(
    &Lookaside,
    entry
);
Le bloc peut alors être conservé pour une future allocation.

44. Détruire une lookaside

Quand elle n'est plus nécessaire :

Code: Select all

ExDeleteLookasideListEx(
    &Lookaside
);
La destruction explicite est importante.

Une lookaside ne doit pas simplement disparaître parce que la structure qui la contient est libérée.

45. Lookaside dans une DEVICE_EXTENSION

Exemple :

Code: Select all

typedef struct _DEVICE_EXTENSION
{
    LOOKASIDE_LIST_EX Lookaside;

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
Au moment de la destruction du périphérique :

Code: Select all

ExDeleteLookasideListEx(
    &ext->Lookaside
);

IoDeleteDevice(
    DeviceObject
);
La lookaside doit être supprimée avant la destruction de la mémoire qui la contient.

46. Quand utiliser une lookaside

Elle est particulièrement adaptée lorsque :
  • les objets ont une taille fixe ;
  • les allocations sont nombreuses ;
  • les objets sont souvent créés puis détruits ;
  • le chemin de code est sensible aux performances.
Elle est moins utile pour une allocation occasionnelle.

47. Listes + lookaside

Un pattern fréquent est :

Code: Select all

ExAllocateFromLookasideListEx
        ↓
initialiser l'objet
        ↓
InsertTailList
        ↓
utiliser
        ↓
RemoveEntryList
        ↓
ExFreeToLookasideListEx
La liste organise l'objet.

La lookaside fournit et recycle sa mémoire.

48. Exemple : structure PROCESS_ENTRY

Code: Select all

typedef struct _PROCESS_ENTRY
{
    LIST_ENTRY Link;
    HANDLE Pid;
    CHAR Name[16];

} PROCESS_ENTRY, *PPROCESS_ENTRY;
La tête :

Code: Select all

LIST_ENTRY ProcessList;

InitializeListHead(
    &ProcessList
);
49. Ajouter un processus

Code: Select all

PPROCESS_ENTRY entry =
    (PPROCESS_ENTRY)
    ExAllocatePool2(
        POOL_FLAG_NON_PAGED,
        sizeof(PROCESS_ENTRY),
        'corP'
    );

if (entry == nullptr)
{
    return STATUS_INSUFFICIENT_RESOURCES;
}

RtlZeroMemory(
    entry,
    sizeof(PROCESS_ENTRY)
);

entry->Pid = Pid;

InsertTailList(
    &ProcessList,
    &entry->Link
);
50. Rechercher par PID

Code: Select all

PPROCESS_ENTRY FindProcess(
    HANDLE Pid
)
{
    PLIST_ENTRY current =
        ProcessList.Flink;

    while (current != &ProcessList)
    {
        PPROCESS_ENTRY entry =
            CONTAINING_RECORD(
                current,
                PROCESS_ENTRY,
                Link
            );

        if (entry->Pid == Pid)
        {
            return entry;
        }

        current = current->Flink;
    }

    return nullptr;
}
51. Supprimer un processus

Code: Select all

PPROCESS_ENTRY entry =
    FindProcess(Pid);

if (entry != nullptr)
{
    RemoveEntryList(
        &entry->Link
    );

    ExFreePool(entry);
}
Dans un vrai driver multithreadé, la recherche et la suppression doivent être correctement synchronisées.

52. Parcours inverse

Code: Select all

PLIST_ENTRY current =
    ProcessList.Blink;

while (current != &ProcessList)
{
    PPROCESS_ENTRY entry =
        CONTAINING_RECORD(
            current,
            PROCESS_ENTRY,
            Link
        );

    KdPrint((
        "PID = %p\n",
        entry->Pid
    ));

    current = current->Blink;
}
53. Erreurs fréquentes
  • Oublier `InitializeListHead`.
  • Tester `current != nullptr` sur une `LIST_ENTRY` circulaire.
  • Utiliser le mauvais champ dans `CONTAINING_RECORD`.
  • Faire `ExFreePool` avant `RemoveEntryList`.
  • Lire `current->Flink` après avoir libéré l'objet correspondant.
  • Modifier la liste depuis plusieurs threads sans synchronisation.
  • Utiliser une structure pageable alors que la liste est manipulée à `DISPATCH_LEVEL`.
  • Confondre une linked list avec une lookaside list.
  • Oublier de détruire une lookaside.
  • Appeler `IoDeleteDevice` avant de détruire une lookaside stockée dans la `DEVICE_EXTENSION`.
54. API à retenir

Pour les listes doublement chaînées :

Code: Select all

LIST_ENTRY
InitializeListHead
IsListEmpty
InsertHeadList
InsertTailList
RemoveHeadList
RemoveTailList
RemoveEntryList
CONTAINING_RECORD
Pour les listes simplement chaînées :

Code: Select all

SINGLE_LIST_ENTRY
PushEntryList
PopEntryList
Pour les variantes synchronisées :

Code: Select all

ExInterlockedInsertHeadList
ExInterlockedInsertTailList
ExInterlockedRemoveHeadList
Pour les lookaside lists modernes :

Code: Select all

LOOKASIDE_LIST_EX
ExInitializeLookasideListEx
ExAllocateFromLookasideListEx
ExFreeToLookasideListEx
ExDeleteLookasideListEx
55. Modèle mental à retenir

Pour une `LIST_ENTRY` :

Code: Select all

structure métier
    |
    +--> LIST_ENTRY Link
             |
             v
        liste circulaire
Pendant un parcours :

Code: Select all

PLIST_ENTRY
    ↓
CONTAINING_RECORD
    ↓
structure réelle
Pour une lookaside :

Code: Select all

taille fixe
    ↓
cache d'allocations
    ↓
allocation rapide
    ↓
réutilisation
56. Résumé

Les listes chaînées kernel reposent surtout sur l'intégration d'un champ de liaison directement dans les structures.

Avec :

Code: Select all

LIST_ENTRY
la liste est généralement doublement chaînée et circulaire.

Le parcours se termine lorsque :

Code: Select all

current == &Head
et non lorsque `current` vaut `NULL`.

`CONTAINING_RECORD` permet de revenir du champ de liaison vers la structure complète.

Avec :

Code: Select all

SINGLE_LIST_ENTRY
la liste est simplement chaînée et se termine par `NULL`.

Les listes ne gèrent pas automatiquement :
  • la mémoire ;
  • l'ownership ;
  • la synchronisation ;
  • la durée de vie des objets.
Enfin, une lookaside list est différente d'une linked list : elle sert de cache pour des objets de taille fixe.

Les API modernes principales sont :

Code: Select all

ExInitializeLookasideListEx
ExAllocateFromLookasideListEx
ExFreeToLookasideListEx
ExDeleteLookasideListEx
La combinaison :

Code: Select all

LIST_ENTRY
+
CONTAINING_RECORD
+
pool/lookaside
+
synchronisation
constitue un pattern fondamental dans de nombreux composants du kernel Windows.

Who is online

Users browsing this forum: No registered users and 0 guests