Поделиться через


Синхронизация и уведомления в сетевых драйверах

Каждый раз, когда два потока выполнения совместно используют ресурсы, к которым можно получить доступ одновременно, на однопроцессорном компьютере или на симметричном многопроцессорном компьютере (SMP), их необходимо синхронизировать. Например, на однопроцессорном компьютере, если одна функция драйвера обращается к общему ресурсу и прерывается другой функцией, которая выполняется на более высоком уровне IRQL, например ISR, общий ресурс должен быть защищен, чтобы предотвратить состояние гонки, в результате которых ресурс остается в неопределенном состоянии. На компьютере SMP два потока могут одновременно работать на разных процессорах и пытаться изменить одни и те же данные. Такие доступы должны быть синхронизированы.

NDIS предоставляет блокировки спина, которые можно использовать для синхронизации доступа к общим ресурсам между потоками, которые выполняются в одном IRQL. Когда два потока, которые совместно используют ресурс, выполняются в разных irQL, NDIS предоставляет механизм для временного повышения IRQL нижнего кода IRQL, чтобы доступ к общему ресурсу можно было сериализовать.

Если поток зависит от вхождения события за пределами потока, поток зависит от уведомления. Например, драйверу может потребоваться уведомить о прохождении некоторого периода времени, чтобы он мог проверка свое устройство. Или драйвер сетевого интерфейса карта (NIC) может потребоваться периодически выполнять такие операции, как опрос. Таймеры предоставляют такой механизм.

События предоставляют механизм, который два потока выполнения могут использовать для синхронизации операций. Например, драйвер мини-порта может проверить прерывание на сетевом адаптере, записав на устройство. Драйвер должен дождаться прерывания, чтобы уведомить драйвер о том, что операция прошла успешно. События можно использовать для синхронизации операции между потоком, ожидающим завершения прерывания, и потоком, обрабатывающим прерывание.

Эти механизмы NDIS описаны в следующих подразделах этого раздела.

Спин-блокировки

Спин-блокировка предоставляет механизм синхронизации для защиты ресурсов, совместно используемых потоками режима ядра, работающими на IRQL > PASSIVE_LEVEL на однопроцессорном или многопроцессорном компьютере. Спин-блокировка обрабатывает синхронизацию между различными потоками выполнения, которые параллельно выполняются на компьютере SMP. Поток получает спин-блокировку перед доступом к защищенным ресурсам. Блокировка спина не позволяет использовать ресурс любому потоку, кроме того, который удерживает спиновую блокировку. На компьютере SMP поток, ожидающий спин-блокировки, циклически пытается получить спин-блокировку, пока не будет освобожден потоком, удерживающим блокировку.

Еще одной характеристикой спин-блокировок является связанный IRQL. Попытка получить спин-блокировку временно вызывает IRQL запрашивающего потока в IRQL, связанный с спин-блокировкой. Это предотвращает вытеснение выполняющегося потока всеми более низкими потоками IRQL на одном процессоре. Потоки на одном и том же процессоре, работающие на более высоком IRQL, могут вытеснить выполняющийся поток, но эти потоки не могут получить спин-блокировку, так как у них меньше IRQL. Таким образом, после того как поток получил спин-блокировку, никакие другие потоки не смогут получить спин-блокировку, пока она не будет освобождена. Правильно написанный сетевой драйвер позволяет свести к минимуму время, затрачиваемого на блокировку спина.

Обычно блокировка спина используется для защиты очереди. Например, функция отправки драйвера минипорта MiniportSendNetBufferLists может ставить в очередь пакеты, переданные ей драйвером протокола. Так как другие функции драйвера также используют эту очередь, MiniportSendNetBufferLists должна защищать очередь с помощью спин-блокировки, чтобы только один поток за раз может управлять ссылками или содержимым. MiniportSendNetBufferLists получает спин-блокировку, добавляет пакет в очередь, а затем освобождает спин-блокировку. Использование spin lock гарантирует, что поток, удерживающий блокировку, является единственным потоком, изменяющим связи очереди, пока пакет безопасно добавляется в очередь. Когда драйвер мини-порта принимает пакеты из очереди, такой доступ защищается той же блокировкой спина. При выполнении инструкций, которые изменяют голову очереди или любое из полей связи, составляющих очередь, драйвер должен защитить очередь с помощью спин-блокировки.

Драйвер должен позаботиться о том, чтобы не перезащитить очередь. Например, драйвер может выполнять некоторые операции (например, заполнять поле, содержащее длину) в поле , зарезервированном сетевым драйвером пакета, прежде чем он помещает пакет в очередь. Драйвер может сделать это за пределами области кода, защищенной спин-блокировкой, но должен сделать это перед постановкой пакета в очередь. После того как пакет находится в очереди, а выполняющийся поток спустит блокировку спина, драйвер должен предположить, что другие потоки могут немедленно выпустить пакет.

Предотвращение проблем со спин-блокировкой

Чтобы избежать возможной взаимоблокировки, драйвер NDIS должен освободить все блокировки спина NDIS перед вызовом функции NDIS, отличной от функции NdisXxxSpinlock . Если драйвер NDIS не соответствует этому требованию, взаимоблокировка может возникнуть следующим образом:

  1. Поток 1, содержащий спиновую блокировку NDIS A, вызывает функцию NdisXxx , которая пытается получить спин-блокировку NDIS B путем вызова функции NdisAcquireSpinLock .

  2. Поток 2, содержащий спин-блокировку NDIS B, вызывает функцию NdisXxx , которая пытается получить спин-блокировку NDIS A путем вызова функции NdisAcquireSpinLock .

  3. Потоки 1 и 2, каждый из которых ожидает, чтобы другой спустит свою спин-блокировку, становятся взаимоблокировками.

Операционные системы Microsoft Windows не запрещают сетевому драйверу одновременное удержание нескольких спин-блокировок. Однако если одна часть драйвера пытается получить спин-блокировку A, удерживая спин-блокировку B, а другая часть пытается получить спин-блокировку B с удержанием спин-блокировки A, взаимоблокировка приводит к взаимоблокировкам. Если он получает несколько спин-блокировок, драйвер должен избежать взаимоблокировки, применяя порядок приобретения. То есть, если драйвер принудительно получает спин-блокировку A перед спин-блокировкой B, описанная выше ситуация не возникнет.

При получении блокировки спина irQL вызывается DISPATCH_LEVEL и сохраняется старый код IRQL в спин-блокировке. При освобождении блокировки спина в irQL устанавливается значение, хранящееся в спин-блокировке. Так как NDIS иногда входит в драйверы в PASSIVE_LEVEL, проблемы могут возникать при использовании следующей последовательности кода:

NdisAcquireSpinLock(A);
NdisAcquireSpinLock(B);
NdisReleaseSpinLock(A);
NdisReleaseSpinLock(B);

Драйвер не должен обращаться к блокировкам спина в этой последовательности по следующим причинам:

  • Между освобождением спин-блокировки A и освобождением спин-блокировки B код выполняется в PASSIVE_LEVEL, а не DISPATCH_LEVEL и может быть подвергнут неправильному прерыванию.

  • После освобождения спин-блокировки B код выполняется в DISPATCH_LEVEL что может привести к сбою вызывающего объекта гораздо позже с IRQL_NOT_LESS_OR_EQUAL stop-ошибкой.

Использование спин-блокировок влияет на производительность, и, как правило, драйвер не должен использовать много спиновых блокировок. Иногда функции, которые обычно отличаются (например, функции отправки и получения), имеют незначительные перекрытия, для которых можно использовать две спин-блокировки. Использование нескольких блокировок спина может быть полезным компромиссом, чтобы позволить двум функциям работать независимо на отдельных процессорах.

Таймеры

Таймеры используются для опроса или операций тайм-аута. Драйвер создает таймер и связывает функцию с таймером. Связанная функция вызывается по истечении периода, указанного в таймере. Таймеры могут быть одноразовыми или периодическими. После установки периодического таймера он будет продолжать срабатать по истечении каждого периода, пока не будет явно очищен. При каждом срабатывании таймера с одним выстрелом необходимо сбрасывать.

Таймеры создаются и инициализируются путем вызова NdisAllocateTimerObject и задаются путем вызова NdisSetTimerObject. Если используется непериодический таймер, его необходимо сбросить, вызвав NdisSetTimerObject. Таймер очищается путем вызова NdisCancelTimerObject.

События

События используются для синхронизации операций между двумя потоками выполнения. Событие выделяется драйвером и инициализируется путем вызова NdisInitializeEvent. Поток, выполняющийся в IRQL = PASSIVE_LEVEL вызывает NdisWaitEvent , чтобы перейти в состояние ожидания. Когда поток драйвера ожидает события, он указывает максимальное время ожидания, а также событие, которое будет ожидаться. Ожидание потока выполняется, когда вызывается событие NdisSetEvent , что приводит к сигналу о событии, или когда истекает указанный максимальный интервал времени ожидания, в зависимости от того, что произойдет раньше.

Как правило, событие задается взаимодействующим потоком, который вызывает NdisSetEvent. События не подписываются при их создании и должны быть заданы, чтобы сигнализировать об ожидающих потоках. События остаются сигналами до вызова NdisResetEvent .

Поддержка многопроцессорных обработчиков в сетевых драйверах