Списки по отдельности и вдвойне связанных
Списки, связанные по отдельности
Операционная система обеспечивает встроенную поддержку списков, которые используют SINGLE_LIST_ENTRY структуры. Отдельно связанный список состоит из заголовка списка и некоторого количества записей списка. (Количество записей списка равно нулю, если список пуст.) Каждая запись списка представлена в виде структуры SINGLE_LIST_ENTRY . Глава списка также представлен в виде SINGLE_LIST_ENTRY структуры.
Каждая структура SINGLE_LIST_ENTRY содержит элемент Next , указывающий на другую SINGLE_LIST_ENTRY структуру. В структуре SINGLE_LIST_ENTRY , представляющей заголовок списка, элемент Next указывает на первую запись в списке или имеет значение NULL, если список пуст. В структуре SINGLE_LIST_ENTRY , представляющей запись в списке, элемент Next указывает на следующую запись списка или имеет значение NULL, если эта запись является последней в списке.
Подпрограммы, которые управляют отдельно связанным списком, принимают указатель на SINGLE_LIST_ENTRY , представляющий голову списка. Они обновляют указатель Next таким образом, чтобы он указывал на первую запись списка после операции.
Предположим, что переменная ListHead является указателем на структуру SINGLE_LIST_ENTRY , представляющую заголовок списка. Драйвер управляет ListHead следующим образом:
Чтобы инициализировать список как пустой, задайте для параметра ListHead-Next>значение NULL.
Чтобы добавить новую запись в список, выделите SINGLE_LIST_ENTRY для представления новой записи, а затем вызовите PushEntryList , чтобы добавить запись в начало списка.
Выведите первую запись из списка с помощью PopEntryList.
У SINGLE_LIST_ENTRY есть только элемент Next. Чтобы сохранить собственные данные в списках, внедрите SINGLE_LIST_ENTRY в качестве члена структуры, описывающей запись списка, как показано ниже.
typedef struct {
// driver-defined members
.
.
.
SINGLE_LIST_ENTRY SingleListEntry;
// other driver-defined members
.
.
.
} XXX_ENTRY;
Чтобы добавить новую запись в список, выделите структуру XXX_ENTRY , а затем передайте указатель на элемент SingleListEntry в PushEntryList. Чтобы преобразовать указатель на SINGLE_LIST_ENTRY обратно в XXX_ENTRY, используйте CONTAINING_RECORD. Ниже приведен пример подпрограмм, которые вставляют и удаляют записи, определяемые драйвером, из отдельно связанного списка.
typedef struct {
PVOID DriverData1;
SINGLE_LIST_ENTRY SingleListEntry;
ULONG DriverData2;
} XXX_ENTRY, *PXXX_ENTRY;
void
PushXxxEntry(PSINGLE_LIST_ENTRY ListHead, PXXX_ENTRY Entry)
{
PushEntryList(ListHead, &(Entry->SingleListEntry));
}
PXXX_ENTRY
PopXxxEntry(PSINGLE_LIST_ENTRY ListHead)
{
PSINGLE_LIST_ENTRY SingleListEntry;
SingleListEntry = PopEntryList(ListHead);
return CONTAINING_RECORD(SingleListEntry, XXX_ENTRY, SingleListEntry);
}
Система также предоставляет атомарные версии операций со списком ExInterlockedPopEntryList и ExInterlockedPushEntryList. Каждый из них принимает дополнительный параметр spin lock. Подпрограмма получает блокировку спина перед обновлением списка, а затем подпрограмма освобождает спин-блокировку после завершения операции. Пока блокировка удерживается, прерывания отключаются. Каждая операция в списке должна использовать одну и ту же блокировку спина, чтобы убедиться, что каждая такая операция в списке синхронизирована с каждой другой операцией. Вы должны использовать спин-блокировку только с этими подпрограммами ExInterlockedXxxList . Не используйте спин-блокировку для других целей. Драйверы могут использовать одну и ту же блокировку для нескольких списков, но такое поведение увеличивает состязание за блокировку, поэтому драйверы должны избегать этого.
Например, подпрограммы ExInterlockedPopEntryList и ExInterlockedPushEntryList могут поддерживать совместное использование отдельно связанного списка потоком драйвера, выполняющимся в IRQL = PASSIVE_LEVEL, и ISR, работающим в DIRQL. Эти подпрограммы отключают прерывания при удержании блокировки спина. Таким образом, поток ISR и драйвера могут безопасно использовать одну и ту же блокировку спина в своих вызовах к этим подпрограммам ExInterlockedXxxList без риска взаимоблокировки.
Не следует смешивать вызовы атомарных и неатомных версий операций списка в одном списке. Если атомарные и неатомные версии выполняются одновременно в одном списке, структура данных может быть повреждена, а компьютер может перестать отвечать на запросы или проверка ошибок (т. е. аварийное завершение). (Вы не можете получить спиновую блокировку при вызове неатомной подпрограммы в качестве альтернативы смешивания вызовов атомарных и неатомных версий операций списка. Использование блокировки спина таким образом не поддерживается и по-прежнему может привести к повреждению списка.)
Система также предоставляет альтернативную реализацию атомарных списков, которые являются более эффективными. Дополнительные сведения см. в разделе Последовательно связанные списки.
Удвоительно связанные списки
Операционная система обеспечивает встроенную поддержку двунавязных списков, использующих LIST_ENTRY структуры. Вдвойне связанный список состоит из заголовка списка и некоторого количества записей списка. (Количество записей списка равно нулю, если список пуст.) Каждая запись списка представлена в виде LIST_ENTRY структуры. Заголовок списка также представлен в виде LIST_ENTRY структуры.
Каждая структура LIST_ENTRY содержит элемент Flink и элемент Blink . Оба элемента являются указателями на LIST_ENTRY структуры.
В структуре LIST_ENTRY , представляющей голову списка, элемент Flink указывает на первую запись в списке, а член Blink указывает на последнюю запись в списке. Если список пуст, Flink и Blink головы списка указывают на сам заголовок списка.
В структуре LIST_ENTRY , представляющей запись в списке, элемент Flink указывает на следующую запись в списке, а элемент Blink указывает на предыдущую запись в списке. (Если запись является последней в списке, Flink указывает на заголовок списка. Аналогичным образом, если запись является первой в списке, Blink указывает на голову списка.)
(Хотя эти правила на первый взгляд могут показаться удивительными, они позволяют выполнять операции вставки и удаления списков без ветвей условного кода.)
Подпрограммы, которые управляют списком с удвояющей связью, принимают указатель на LIST_ENTRY , представляющий голову списка. Эти подпрограммы обновляют элементы Flink и Blink в заголовке списка, чтобы они указывали на первую и последнюю записи в результирующем списке.
Предположим, что переменная ListHead является указателем на структуру LIST_ENTRY , представляющую заголовок списка. Драйвер управляет ListHead следующим образом:
Чтобы инициализировать список как пустой, используйте InitializeListHead, который инициализирует ListHead-Flink> и ListHead-Blink> для указания на ListHead.
Чтобы вставить новую запись в начало списка, выделите LIST_ENTRY для представления новой записи, а затем вызовите InsertHeadList , чтобы вставить запись в начале списка.
Чтобы добавить новую запись в конец списка, выделите LIST_ENTRY для представления новой записи, а затем вызовите InsertTailList , чтобы вставить запись в конец списка.
Чтобы удалить первую запись из списка, используйте RemoveHeadList. При этом возвращается указатель на удаленную запись из списка или на ListHead , если список пуст.
Чтобы удалить последнюю запись из списка, используйте команду RemoveTailList. При этом возвращается указатель на удаленную запись из списка или на ListHead , если список пуст.
Чтобы удалить указанную запись из списка, используйте RemoveEntryList.
Чтобы проверка, чтобы узнать, является ли список пустым, используйте IsListEmpty.
Чтобы добавить список в конце другого списка, используйте AppendTailList.
Сама по себе LIST_ENTRY содержит только элементы Blink и Flink. Чтобы сохранить собственные данные в списках, внедрите LIST_ENTRY в качестве члена структуры, описывающей запись списка, как показано ниже.
typedef struct {
// driver-defined members
.
.
.
LIST_ENTRY ListEntry;
// other driver-defined members.
.
.
.
} XXX_ENTRY;
Чтобы добавить новую запись в список, выделите структуру XXX_ENTRY , а затем передайте указатель на элемент ListEntryв InsertHeadList или InsertTailList. Чтобы преобразовать указатель на LIST_ENTRY обратно в XXX_ENTRY, используйте CONTAINING_RECORD. Пример этого метода с использованием списков, связанных по отдельности, см. в разделе Singly Linked Lists выше.
Система также предоставляет атомарные версии операций со списком ExInterlockedInsertHeadList, ExInterlockedInsertTailList и ExInterlockedRemoveHeadList. (Обратите внимание, что атомарная версия RemoveTailList или RemoveEntryList отсутствует.) Каждая подпрограмма принимает дополнительный параметр спин-блокировки. Подпрограмма получает спиновую блокировку перед обновлением списка, а затем освобождает спиновую блокировку после завершения операции. Пока блокировка удерживается, прерывания отключаются. Каждая операция в списке должна использовать одну и ту же блокировку спина, чтобы обеспечить синхронизацию каждой такой операции в списке. Вы должны использовать спин-блокировку только с этими подпрограммами ExInterlockedXxxList . Не используйте спин-блокировку для других целей. Драйверы могут использовать одну и ту же блокировку для нескольких списков, но такое поведение увеличивает состязание за блокировку, поэтому драйверы должны избегать этого.
Например, подпрограммы ExInterlockedInsertHeadList, ExInterlockedInsertTailList и ExInterlockedRemoveHeadList могут поддерживать совместное использование вдвойне связанного списка потоком драйвера, работающим в IRQL = PASSIVE_LEVEL и ISR, работающим в DIRQL. Эти подпрограммы отключают прерывания при удержании блокировки спина. Таким образом, поток ISR и драйвера могут безопасно использовать одну и ту же блокировку спина в своих вызовах к этим подпрограммам ExInterlockedXxxList без риска взаимоблокировки.
Не следует смешивать вызовы атомарных и неатомных версий операций списка в одном списке. Если атомарные и неатомные версии выполняются одновременно в одном списке, структура данных может быть повреждена, а компьютер может перестать отвечать на запросы или проверка ошибок (т. е. аварийное завершение). (Невозможно получить спиновую блокировку при вызове неатомной подпрограммы, чтобы избежать смешивания вызовов атомарных и неатомных версий операций списка. Использование блокировки спина таким образом не поддерживается и по-прежнему может привести к повреждению списка.)
Последовательно связанные списки
Упорядоченный по отдельности связанный список — это реализация отдельно связанных списков, поддерживающих атомарные операции. Это более эффективно для атомарных операций, чем реализация последовательно связанных списков, описанных в разделе Единые связанные списки.
Структура SLIST_HEADER используется для описания заголовка последовательно связанного списка, а SLIST_ENTRY — для описания записи в списке.
Драйвер управляет списком следующим образом:
Чтобы инициализировать структуру SLIST_HEADER , используйте ExInitializeSListHead.
Чтобы добавить новую запись в список, выделите SLIST_ENTRY для представления новой записи, а затем вызовите ExInterlockedPushEntrySList , чтобы добавить запись в начало списка.
Выведите первую запись из списка с помощью exInterlockedPopEntrySList.
Чтобы полностью очистить список, используйте ExInterlockedFlushSList.
Чтобы определить количество записей в списке, используйте ExQueryDepthSList.
У SLIST_ENTRY есть только элемент Next. Чтобы сохранить собственные данные в списках, внедрите SLIST_ENTRY в качестве члена структуры, описывающей запись списка, как показано ниже.
typedef struct
{
// driver-defined members
.
.
.
SLIST_ENTRY SListEntry;
// other driver-defined members
.
.
.
} XXX_ENTRY;
Чтобы добавить новую запись в список, выделите структуру XXX_ENTRY , а затем передайте указатель на член SListEntry в ExInterlockedPushEntrySList. Чтобы преобразовать указатель на SLIST_ENTRY обратно в XXX_ENTRY, используйте CONTAINING_RECORD. Пример этого метода использования неупорядоченных списков с поперемыкающими ссылками см. в разделе Единые связанные списки.
Предупреждение Для 64-разрядных операционных систем Microsoft Windows SLIST_ENTRY структуры должны быть выровнены по 16 байтам.
Windows XP и более поздние версии Windows предоставляют оптимизированные версии последовательно связанных функций списков, которые недоступны в Windows 2000. Если драйвер использует эти функции и также должен работать с Windows 2000, драйвер должен определить флаг _WIN2K_COMPAT_SLIST_USAGE следующим образом:
#define _WIN2K_COMPAT_SLIST_USAGE
Для процессоров на базе x86 этот флаг заставляет компилятор использовать версии последовательно связанных функций списков, совместимых с Windows 2000.