Im głębiej wchodzi się we wnętrzności systemu, tym dokładniej można zobaczyć, że proces jest bardzo specyficznym obiektem. Tak naprawdę nie ma on wiele wspólnego z działaniem aplikacji (tutaj dużo ciekawsze są wątki) ale przede wszystkim z zarządzaniem tym, co w systemie się dzieje. Temat jest ogromny i zainteresowanych chyba najlepiej odesłać do "Windows Internals" a dokładniej do rozdziału piątego: "Processes, Threads, and Jobs". To, co jest dzisiaj dla mnie ważne, to sposób podejścia systemu Windows do sytuacji, kiedy coś zachowuje się nieładnie. W trybie jądra kończy się to Blue Screenem, a poza tym trybem – Windows po prostu "zabije" tego, kto mu podpadnie. I choć wykonywane są poszczególne wątki i to wątek coś niedobrego zwykle robi – zabity zostanie cały proces. Tak jest prościej, bezpieczniej i z punktu widzenia administratora jest to łatwiejsze do ogarnięcia. Problem polega jednak na tym, że skutkiem zastosowania odpowiedzialności zbiorowej jest to, że razem z wątkiem wadliwym, zakończą się i inne, które w ramach tego samego procesu zachowywały się zupełnie poprawnie. Jeżeli dzieje się tak w jakiejś aplikacji od początku do końca przygotowanej przez jednego programistę – trudno. Niech cierpi za słabą obsługę błędów.

Poważniejszy problem pojawia się wtedy, gdy aplikacja musi załadować moduły napisane przez kogoś innego. Oznacza to, że w ramach procesu pojawiają się wątki, dla których kod wykonywalny pochodzi nieraz z zupełnie nieznanego źródła. I może zdarzyć się, że taki wątek z zewnętrznej biblioteki zachowa się nieładnie. Wtedy system zabije cały proces, razem z podpadniętym wątkiem i wszystkimi innymi wątkami, które mogły robić coś istotnego. Sytuacja taka zdarza się w praktyce w niemal w każdym systemie. Na przykład, w każdym, w którym ktoś chce drukować. W systemie Windows 2008R2, wygląda to domyślnie tak, że usługa spooler (bufor wydruku) ładuje specjalną bibliotekę DLL (PrintIsolationProxy.dll), która ładuje sterowniki drukarek.

pdi01

Błąd w sterowniku powoduje zabicie procesu. Skutki są proste do przewidzenia: usługa spooler przestaje działać i nikt w tym systemie nic już nie wydrukuje dopóki nie zostanie uruchomiona ponownie. Dlatego właśnie, w systemie Windows Server 2008 R2 można skonfigurować system tak, aby dla danego sterownika spooler zachowywał się nieco inaczej. Zamiast bezpośrednio ładować bibliotekę PrintIsolationProxy.dll, uruchomi specjalny proces (PrintIsolationHost.exe) który załaduje bibliotekę, która załaduje sterowniki.

pdi02

A gdy sterownik zachowa się nieładnie? Cóż... Proces PrintIsolationHost.exe zostanie zabity. A spooler przeżyje. Co więcej, można również całość ustawić tak, aby PrintIsolationHost.exe uruchamiany był dla każdego sterownika oddzielnie. Wtedy jeden sterownik może "wywracać się" choćby i kilka razy na sekundę a inne pozostaną niewzruszone. Z punktu widzenia użytkownika, korzyść jest oczywista: można drukować.

Całość jest bardzo prosta do skonfigurowania. Z konsoli "Print Management" wystarczy wybrać gałąź "Drivers", i z menu kontekstowego dla sterownika wybrać jeden z trzech poziomów izolacji:

  • None – sterownik ładowany jest w procesie spoolera
  • Shared – sterownik jest izolowany od spoolera, ale nie od innych sterowników w trybie "shared", z którymi współdzieli jeden proces
  • Isolated – sterownik ma własny proces izolujący.

pdi03

A dlaczego w ogóle nie rozwiązać tego tak, żeby każdy sterownik zawsze miał własną pulę? Dlatego, że takie odizolowanie nie jest "za darmo". Wymaga więcej CPU, zajmuje pamięć tworzy więcej procesów... A awaryjne sterowniki aż tak często się nie zdarzają.

Sterownik może sam z siebie zażądać uruchomienia w trybie izolacji i wtedy zamiast domyślnego poziomu "None" używany jest inny. Przy czym żądanie takie system może zignorować, na przykład w sytuacji, gdy w GPO ustawione są wpisy Execute Print Drivers in Isolated Processes i Override Print Driver Execution Compatibility Setting Reported by Print Driver w gałęzi Computer Configuration\Administrative Templates\Printers

Warto również wiedzieć o gałęzi rejestru HKLM\SYSTEM\CurrentControlSet\Control\Print\ i wpisach PrintDriverIsolationIdleTimeout, PrintDriverIsolationTimeBeforeRecycle oraz PrintDriverIsolationMaxobjsBeforeRecycle. Określają one, kiedy proces izolujący jest usuwany lub restartowany. Pozwala to uwolnić trochę pamięci gdy system nie używa danego sterownika lub skorzystać z tego, że restart procesu oznacza ponowne załadowanie sterowników i w ten sposób zapobiec wyciekom pamięci.

Udanego izolowania!

Autor: Grzegorz Tworek [MVP]