Synchronizacja i powiadomienia w sterownikach sieciowych
Za każdym razem, gdy dwa wątki wykonywania współużytkują zasoby, do których można uzyskiwać dostęp w tym samym czasie, w komputerze jednoprocesorowym lub na komputerze wieloprocesorowym symetrycznym (SMP), należy je zsynchronizować. Na przykład na komputerze jednoprocesorowym, jeśli jedna funkcja sterownika uzyskuje dostęp do udostępnionego zasobu i jest przerywana przez inną funkcję uruchamianą z wyższym poziomem IRQL, taką jak ISR, zasób udostępniony musi być chroniony, aby zapobiec warunkom wyścigu, które pozostawiają zasób w nieokreślonym stanie. Na komputerze SMP dwa wątki mogą być uruchomione jednocześnie na różnych procesorach i próbują zmodyfikować te same dane. Takie dostępy muszą być synchronizowane.
Usługa NDIS udostępnia blokady spin, których można użyć do synchronizowania dostępu do udostępnionych zasobów między wątkami uruchomionymi w tym samym środowisku IRQL. Gdy dwa wątki współdzielące zasób działają na różnych poziomach IRQL, usługa NDIS udostępnia mechanizm tymczasowego podnoszenia niższego poziomu IRQL, aby dostęp do współdzielonego zasobu mógł zostać zserializowany.
Gdy wątek zależy od wystąpienia zdarzenia poza wątkiem, wątek opiera się na powiadomieniu. Na przykład kierowca może potrzebować powiadomienia o upływie pewnego czasu, aby mógł sprawdzić swoje urządzenie. Lub sterownik karty sieciowej może być musiał wykonać okresową operację, taką jak sondowanie. Czasomierze zapewniają taki mechanizm.
Zdarzenia zapewniają mechanizm, którego mogą używać dwa wątki wykonywania do synchronizowania operacji. Na przykład sterownik miniportu może przetestować przerwanie na karcie sieciowej, zapisując na urządzeniu. Sterownik musi poczekać na przerwanie, które powiadomi go o pomyślnym wykonaniu operacji. Zdarzenia umożliwiają synchronizowanie operacji między wątkiem oczekującym na zakończenie przerwania i wątkiem obsługującym przerwanie.
W poniższych podsekcjach w tym temacie opisano te mechanizmy NDIS.
Blokady spinowe
spin lock zapewnia mechanizm synchronizujący chroniący zasoby współużytkowane przez wątki w trybie jądra działające na poziomie IRQL > PASSIVE_LEVEL w komputerze jedno- lub wieloprocesorowym. Spinlock obsługuje synchronizację między różnymi wątkami wykonywanymi współbieżnie na komputerze SMP. Wątek uzyskuje blokadę spin przed uzyskaniem dostępu do chronionych zasobów. Blokada spin uniemożliwia dowolnym wątkom, z wyjątkiem tego, który trzyma blokadę spin, korzystanie z zasobu. Na komputerze SMP, wątek, który czeka na pętle blokady spin próbuje uzyskać blokadę spin, dopóki nie zostanie zwolniony przez wątek, który przechowuje blokadę.
Inną cechą blokad spin jest skojarzony IRQL. Podjęcie próby nabycia blokady spinowej tymczasowo podnosi poziom IRQL żądanego wątku do poziomu IRQL związanego z blokadą spinową. Dzięki temu na tym samym procesorze wątki o niższym IRQL nie przerywają wykonywania wątku. Wątki na tym samym procesorze, działające z wyższym poziomem IRQL, mogą przerywać wykonywanie wątku, ale nie mogą uzyskać spinlocku, ponieważ spinlock wymaga niższego poziomu IRQL. W związku z tym, po tym, jak wątek nabył blokadę typu spin, żadne inne wątki nie mogą uzyskać blokady typu spin, dopóki nie zostanie zwolniona. Dobrze napisany sterownik sieciowy minimalizuje czas, przez jaki blokada spinu jest utrzymywana.
Typowym zastosowaniem blokady obrotowej jest ochrona kolejki. Na przykład funkcja wysyłania sterownika miniportu, MiniportSendNetBufferLists, może kolejkować pakiety przekazywane mu przez sterownik protokołu. Ponieważ inne funkcje sterowników używają również tej kolejki, MiniportSendNetBufferLists musi chronić kolejkę za pomocą blokady wirującej, tak aby tylko jeden wątek naraz mógł manipulować łączami lub zawartością. MiniportSendNetBufferLists uzyskuje blokadę obrotową, dodaje pakiet do kolejki, a następnie ją zwalnia. Użycie spinlocka gwarantuje, że wątek trzymający blokadę jest jedynym wątkiem modyfikującym wskaźniki kolejki, podczas gdy pakiet jest bezpiecznie dodawany do kolejki. Gdy sterownik miniportu pobiera pakiety z kolejki, taki dostęp jest chroniony przez tę samą blokadę wirującą. Podczas wykonywania instrukcji modyfikujących nagłówek kolejki lub dowolne pola odnośników tworzących kolejkę, sterownik musi chronić kolejkę za pomocą blokady obrotowej.
Kierowca musi uważać, aby nie nadmiernie chronić sznuru pojazdów. Na przykład sterownik może wykonać pewne operacje (na przykład wypełnienie pola zawierającego długość) w polu zarezerwowanym dla sterownika sieciowego pakietu, zanim ustawi go w kolejce. Sterownik może to zrobić poza regionem kodu chronionym przez blokadę typu spin, ale musi to zrobić przed zakolejkowaniem pakietu. Gdy pakiet znajduje się w kolejce, a aktywny wątek zwalnia blokadę spinlock, sterownik powinien założyć, że inne wątki mogą natychmiast usunąć pakiet z kolejki.
Unikanie problemów ze spinlockami
Aby uniknąć możliwego zakleszczenia, sterownik NDIS powinien zwolnić wszystkie blokady spinlock NDIS przed wywołaniem funkcji NDIS innej niż funkcja NdisXxxSpinlock. Jeśli sterownik NDIS nie spełnia tego wymagania, może wystąpić zakleszczenie w następujący sposób:
Thread 1, który posiada blokadę spin NDIS A, wywołuje funkcję NdisXxx, która próbuje uzyskać blokadę spin NDIS B, wywołując funkcję NdisAcquireSpinLock.
Thread 2, który posiada blokadę spin NDIS B, wywołuje funkcję NdisXxx, która próbuje nabyć blokadę spin NDIS A, wywołując funkcję NdisAcquireSpinLock.
Wątek 1 i wątek 2, które czekają na drugiego, aby zwolnić blokadę spinu, stają się zakleszczone.
Systemy operacyjne Microsoft Windows nie ograniczają sterowników sieciowych w jednoczesnym posiadaniu więcej niż jednej blokady typu spin. Jeśli jednak jedna sekcja kierowcy próbuje uzyskać blokadę typu spin A, gdy trzyma blokadę typu spin B, a inna sekcja próbuje uzyskać blokadę typu spin B, trzymając blokadę typu spin A, dochodzi do zakleszczenia. Jeśli uzyskuje więcej niż jedną blokadę spin, kierowca powinien uniknąć impasu, wymuszając kolejność przejęcia. Oznacza to, że jeśli kierowca wymusza zdobycie blokady obrotu A przed blokadą obrotu B, sytuacja opisana powyżej nie nastąpi.
Uzyskanie blokady obrotowej podnosi IRQL do poziomu DISPATCH_LEVEL i przechowuje stary IRQL w blokadzie obrotowej. Zwolnienie blokady spin ustawia IRQL na wartość przechowywaną w blokadzie spin. Ponieważ NDIS czasami uruchamia sterowniki na poziomie PASSIVE_LEVEL, mogą wystąpić problemy z następującą sekwencją kodu:
NdisAcquireSpinLock(A);
NdisAcquireSpinLock(B);
NdisReleaseSpinLock(A);
NdisReleaseSpinLock(B);
Sterownik nie powinien uzyskać dostępu do spinlocków w tym kontekście z następujących powodów:
Między zwolnieniem blokady spin A i zwolnieniem blokady spin B, kod działa w PASSIVE_LEVEL zamiast DISPATCH_LEVEL i podlega niewłaściwej przerwie.
Po zwolnieniu blokady spin B kod jest uruchamiany w DISPATCH_LEVEL co może spowodować, że obiekt wywołujący zostanie uszkodzony w znacznie późniejszym czasie z błędem zatrzymania IRQL_NOT_LESS_OR_EQUAL.
Korzystanie z blokad spinowych wpływa na wydajność i, ogólnie, sterownik nie powinien używać wielu blokad spinowych. Czasami funkcje, które są zwykle odrębne (na przykład funkcje wysyłania i odbierania), mają niewielkie nakładanie się, dla którego można używać dwóch spinlocków. Użycie więcej niż jednej blokady spin może być opłacalnym kompromisem, aby umożliwić obu funkcjom działanie niezależnie na osobnych procesorach.
Czasomierzy
Czasomierze są używane do sondowania lub wykonywania operacji przekroczenia limitu czasu. Sterownik tworzy czasomierz i kojarzy funkcję z czasomierzem. Skojarzona funkcja jest wywoływana, gdy okres określony w czasomierzu wygaśnie. Czasomierze mogą być jednorazowe lub okresowe. Po ustawieniu czasomierza okresowego, będzie się uruchamiał po wygaśnięciu każdego okresu, dopóki nie zostanie wyczyszczony ręcznie. Czasomierz jednorazowy musi być resetowany za każdym razem, gdy jest uruchamiany.
Czasomierze są tworzone i inicjowane przez wywołanie NdisAllocateTimerObject oraz są ustawiane przez wywołanie NdisSetTimerObject. Gdy używany jest nieperiodyczny czasomierz, musi zostać zresetowany przez wywołanie NdisSetTimerObject. Czasomierz jest czyszczony przez wywołanie NdisCancelTimerObject.
Zdarzenia
Zdarzenia służą do synchronizowania operacji między dwoma wątkami wykonywania. Zdarzenie jest przydzielane przez sterownik i inicjowane przez wywołanie NdisInitializeEvent. Wątek, który działa na poziomie IRQL = PASSIVE_LEVEL, wywołuje NdisWaitEvent, aby przejść w stan oczekiwania. Gdy wątek sterownika czeka na zdarzenie, określa maksymalny czas oczekiwania, a także zdarzenie, na które ma być czekane. Oczekiwanie wątku jest spełnione, gdy zostanie wywołana NdisSetEvent, sygnalizując zdarzenie, albo gdy określony maksymalny interwał czasu oczekiwania wygaśnie, w zależności od tego, co nastąpi wcześniej.
Zazwyczaj zdarzenie jest ustawiane przez współpracujący wątek, który wywołuje NdisSetEvent. Zdarzenia są niesygnalizowane podczas ich tworzenia i muszą być ustawione, aby sygnalizować oczekujące wątki. Zdarzenia pozostają zasygnalizowane do momentu wywołania NdisResetEvent.
Tematy pokrewne
obsługa wieloprocesorów w sterownikach sieciowych