Synchronisierung und Benachrichtigung in Netzwerktreibern

Wenn zwei Ausführungsthreads Ressourcen gemeinsam nutzen, auf die gleichzeitig zugegriffen werden kann, entweder auf einem Uniprozessorcomputer oder auf einem symmetrischen Multiprozessorcomputer (SMP), müssen sie synchronisiert werden. Wenn beispielsweise eine Treiberfunktion auf einem Uniprozessorcomputer auf eine freigegebene Ressource zugreift und von einer anderen Funktion unterbrochen wird, die in einem höheren IRQL ausgeführt wird, z. B. einer ISR, muss die freigegebene Ressource geschützt werden, um Racebedingungen zu verhindern, die die Ressource in einem unbestimmten Zustand zurücklassen. Auf einem SMP-Computer können zwei Threads gleichzeitig auf verschiedenen Prozessoren ausgeführt werden und versuchen, dieselben Daten zu ändern. Solche Zugriffe müssen synchronisiert werden.

NDIS bietet Drehsperren, mit denen Sie den Zugriff auf freigegebene Ressourcen zwischen Threads synchronisieren können, die mit demselben IRQL ausgeführt werden. Wenn zwei Threads, die eine Ressource gemeinsam mit verschiedenen IRQLs nutzen, ausgeführt werden, stellt NDIS einen Mechanismus zum vorübergehenden Auslösen der IRQL des niedrigeren IRQL-Codes bereit, sodass der Zugriff auf die freigegebene Ressource serialisiert werden kann.

Wenn ein Thread vom Auftreten eines Ereignisses außerhalb des Threads abhängt, ist der Thread auf Benachrichtigungen angewiesen. Beispielsweise muss ein Treiber benachrichtigt werden, wenn ein Zeitraum verstrichen ist, damit er sein Gerät überprüfen kann. Oder ein Netzwerkschnittstellentreiber Karte (NIC) muss möglicherweise einen regelmäßigen Vorgang ausführen, z. B. Die Abfrage. Timer bieten einen solchen Mechanismus.

Ereignisse bieten einen Mechanismus, den zwei Ausführungsthreads verwenden können, um Vorgänge zu synchronisieren. Beispielsweise kann ein Miniporttreiber den Interrupt auf einer NIC testen, indem er auf das Gerät schreibt. Der Treiber muss auf einen Interrupt warten, um den Treiber darüber zu informieren, dass der Vorgang erfolgreich war. Sie können Ereignisse verwenden, um einen Vorgang zwischen dem Thread zu synchronisieren, der auf den Abschluss des Interrupts wartet, und dem Thread, der den Interrupt verarbeitet.

In den folgenden Unterabschnitten in diesem Thema werden diese NDIS-Mechanismen beschrieben.

Drehsperren

Eine Spinsperre bietet einen Synchronisierungsmechanismus zum Schutz von Ressourcen, die von Kernelmodusthreads gemeinsam genutzt werden, die am IRQL > PASSIVE_LEVEL entweder auf einem Uniprozessor- oder einem Multiprozessorcomputer ausgeführt werden. Eine Spinsperre übernimmt die Synchronisierung zwischen verschiedenen Ausführungsthreads, die gleichzeitig auf einem SMP-Computer ausgeführt werden. Ein Thread erhält vor dem Zugriff auf geschützte Ressourcen eine Spinsperre. Die Spin-Sperre behält jeden Thread, aber der, der die Spinsperre hält, davon ab, die Ressource zu verwenden. Auf einem SMP-Computer ein Thread, der auf die Spin-Lock-Schleifen wartet, die versucht, die Spinsperre zu erhalten, bis sie von dem Thread freigegeben wird, der die Sperre enthält.

Ein weiteres Merkmal von Drehsperren ist die zugehörige IRQL. Beim versuchten Erwerb einer Spin-Sperre wird der IRQL des anfordernden Threads vorübergehend auf den IRQL-Wert der Spinsperre ausgelöst. Dadurch wird verhindert, dass alle niedrigeren IRQL-Threads auf demselben Prozessor dem ausführenden Thread vorenthalten. Threads auf demselben Prozessor, die auf einem höheren IRQL ausgeführt werden, können dem ausführenden Thread vorenthalten, aber diese Threads können die Spinsperre nicht abrufen, da sie einen niedrigeren IRQL aufweist. Nachdem ein Thread eine Spinsperre erworben hat, kann daher keine anderen Threads die Spinsperre abrufen, bis sie freigegeben wurde. Ein gut geschriebener Netzwerktreiber minimiert die Zeit, die eine Spinsperre hält.

Eine typische Verwendung für eine Drehsperre besteht darin, eine Warteschlange zu schützen. Beispielsweise kann die Miniporttreiber-Sendefunktion MiniportSendNetBufferLists Warteschlangenpakete erhalten, die von einem Protokolltreiber an sie übergeben werden. Da diese Warteschlange auch von anderen Treiberfunktionen verwendet wird, muss MiniportSendNetBufferLists die Warteschlange mit einer Spinsperre schützen, sodass jeweils nur ein Thread die Links oder Inhalte bearbeiten kann. MiniportSendNetBufferLists ruft die Spinsperre ab, fügt das Paket der Warteschlange hinzu und gibt dann die Spinsperre frei. Die Verwendung einer Spinsperre stellt sicher, dass der Thread, der die Sperre enthält, der einzige Thread ist, der die Warteschlangenlinks ändert, während das Paket der Warteschlange sicher hinzugefügt wird. Wenn der Miniporttreiber die Pakete aus der Warteschlange entfernt, wird ein solcher Zugriff durch dieselbe Spinsperre geschützt. Beim Ausführen von Anweisungen, die den Kopf der Warteschlange oder eines der Linkfelder ändern, aus denen die Warteschlange besteht, muss der Treiber die Warteschlange mit einer Spinsperre schützen.

Ein Treiber muss darauf achten, eine Warteschlange nicht zu überschützen. Der Treiber kann beispielsweise einige Vorgänge (z. B. das Ausfüllen eines Felds mit der Länge) im reservierten Feld des Netzwerktreibers eines Pakets ausführen, bevor er das Paket in die Warteschlange stellt. Der Treiber kann dies außerhalb der Coderegion tun, die durch die Spin-Sperre geschützt ist, muss dies jedoch vor dem Anstehen des Pakets tun. Nachdem sich das Paket in der Warteschlange befindet und der ausgeführte Thread die Spin-Sperre loslässt, muss der Treiber davon ausgehen, dass andere Threads das Paket sofort dequenieren können.

Vermeiden von Problemen mit der Drehsperre

Um einen möglichen Deadlock zu vermeiden, sollte ein NDIS-Treiber alle NDIS-Spinsperren freigeben, bevor eine andere NDIS-Funktion als eine NdisXxxSpinlock-Funktion aufgerufen wird. Wenn ein NDIS-Treiber diese Anforderung nicht erfüllt, kann ein Deadlock wie folgt auftreten:

  1. Thread 1, der die NDIS-Spinsperre A enthält, ruft eine NdisXxx-Funktion auf, die versucht, die NDIS-Spinsperre B durch Aufrufen der NdisAcquireSpinLock-Funktion abzurufen.

  2. Thread 2, der die NDIS-Spinsperre B enthält, ruft eine NdisXxx-Funktion auf, die versucht, die NDIS-Spinsperre A durch Aufrufen der NdisAcquireSpinLock-Funktion abzurufen.

  3. Thread 1 und Thread 2, die jeweils darauf warten, dass der andere seine Spin-Sperre losgibt, werden Deadlocks.

Microsoft Windows-Betriebssysteme hindern einen Netzwerktreiber nicht daran, mehr als eine Drehsperre gleichzeitig zu halten. Wenn jedoch ein Abschnitt des Treibers versucht, die Spinsperre A zu erhalten, während spin lock B gedrückt wird, und ein anderer Abschnitt versucht, die Spinsperre B zu erhalten, während spin lock A gehalten wird, wird deadlock-Ergebnisse. Wenn mehr als eine Spin-Sperre erworben wird, sollte ein Treiber deadlock vermeiden, indem er eine Reihenfolge des Erwerbes erzwingt. Das heißt, wenn ein Treiber die Erlangung der Drehsperre A vor Spin Lock B erzwingt, tritt die oben beschriebene Situation nicht ein.

Durch das Erwerben einer Drehsperre wird der IRQL auf DISPATCH_LEVEL und speichert den alten IRQL in der Spinsperre. Durch Das Loslassen der Drehsperre wird der IRQL auf den wert festgelegt, der in der Spinsperre gespeichert ist. Da NDIS manchmal Treiber an PASSIVE_LEVEL eingibt, können Probleme mit der folgenden Codesequenz auftreten:

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

Ein Treiber sollte aus den folgenden Gründen nicht auf Drehsperren in dieser Sequenz zugreifen:

  • Zwischen dem Freigeben von Spin lock A und dem Freigeben von Spin Lock B wird der Code bei PASSIVE_LEVEL statt DISPATCH_LEVEL ausgeführt und unterliegt einer unangemessenen Unterbrechung.

  • Nach dem Freigeben von Spin Lock B wird der Code auf DISPATCH_LEVEL ausgeführt, was dazu führen kann, dass der Aufrufer viel später einen Fehler mit einem IRQL_NOT_LESS_OR_EQUAL Stoppfehler verursacht.

Die Verwendung von Drehsperren wirkt sich auf die Leistung aus, und im Allgemeinen sollte ein Treiber nicht viele Drehsperren verwenden. Gelegentlich weisen Funktionen, die in der Regel unterschiedlich sind (z. B. Sende- und Empfangsfunktionen), geringfügige Überlappungen auf, für die zwei Drehsperren verwendet werden können. Die Verwendung von mehr als einer Spinsperre kann ein lohnender Kompromiss sein, damit die beiden Funktionen unabhängig auf separaten Prozessoren arbeiten können.

Timer

Timer werden für Abruf- oder Timeoutvorgänge verwendet. Ein Treiber erstellt einen Timer und ordnet dem Timer eine Funktion zu. Die zugeordnete Funktion wird aufgerufen, wenn der im Timer angegebene Zeitraum abläuft. Timer können one-shot oder periodisch sein. Sobald ein periodischer Timer festgelegt ist, wird er nach Ablauf jedes Zeitraums weiterhin ausgelöst, bis er explizit gelöscht wird. Ein One-Shot-Timer muss jedes Mal zurückgesetzt werden, wenn er ausgelöst wird.

Timer werden erstellt und initialisiert, indem NdisAllocateTimerObject aufgerufen und durch Aufrufen von NdisSetTimerObject festgelegt wird. Wenn ein nichtperiodischer Timer verwendet wird, muss er durch Aufrufen von NdisSetTimerObject zurückgesetzt werden. Ein Timer wird durch Aufrufen von NdisCancelTimerObject gelöscht.

Ereignisse

Ereignisse werden verwendet, um Vorgänge zwischen zwei Ausführungsthreads zu synchronisieren. Ein Ereignis wird von einem Treiber zugeordnet und durch aufrufen von NdisInitializeEvent initialisiert. Ein Thread, der unter IRQL = PASSIVE_LEVEL ausgeführt wird , ruft NdisWaitEvent auf, um sich in einen Wartezustand zu versetzen. Wenn ein Treiberthread auf ein Ereignis wartet, gibt er eine maximale Wartezeit sowie das Ereignis an, für das gewartet werden soll. Die Wartezeit des Threads ist erfüllt, wenn NdisSetEvent aufgerufen wird, wodurch das Ereignis signalisiert wird, oder wenn das angegebene maximale Wartezeitintervall abläuft, je nachdem, was zuerst auftritt.

In der Regel wird das Ereignis von einem kooperierenden Thread festgelegt, der NdisSetEvent aufruft. Ereignisse werden beim Erstellen nicht signalisiert und müssen festgelegt werden, um Wartethreads zu signalisieren. Ereignisse bleiben so lange signalisiert, bis NdisResetEvent aufgerufen wird.

Multiprozessorunterstützung in Netzwerktreibern