Singularity, czyli lepsze Windows

Sześć lat temu Microsoft postanowił rozwijać nowy system operacyjny – projekt otrzymał nazwę kodową Singularity. W rezultacie programiści z Microsoft Research napisali zupełnie nowy system, mając na uwadze tylko jedno: by był on niezniszczalny. Gdy rok temu Singularity został po raz pierwszy zaprezentowany na targach techniki TechFest, Microsoft ogłosił następujący komunikat: Singularity nie będzie następcą Windows, lecz ma służyć jako generator pomysłów na innowacje technologiczne. Tym stwierdzeniem gigant z Redmond z pewnością chciał poskromić apetyt fanów Windows na nowy system operacyjny. Powód? Windows nie obsługuje niczego, co Singularity wnosi za sprawą swojej nowatorskiej architektury. Dotyczy to także Windows 7. Jeśli ktoś chciałby sprawdzić to sam, może za darmo pobrać system z www.codeplex.com/singularity.
System zawiera mikrojądro, które zarządza zasobami dla procesów.
System zawiera mikrojądro, które zarządza zasobami dla procesów.

Ci, którzy instalują Windows 7, zadają sobie pytanie: w jaki sposób najlepiej zabezpieczę system? Niepewność zmienia się w ulgę, jeśli przynajmniej nowy skaner AV działa na Siódemce, nie eksploatując nadmiernie jej zasobów. Takie zachowanie użytkowników jest dziś w pełni uzasadnione – praktycznie wszystkie współczesne systemy mają prastarą architekturę. Ich koncepcje pochodzą z czasów, gdy nie było Internetu ani wirusów, a pojedynczy komputer miał wielkość trzydrzwiowej szafy.

Większość tradycyjnych systemów operacyjnych,  zaś w szczególności ich jądro, jest pisana w języku C. Dzisiaj programiści już tak nie postępują. Dzieje się tak, ponieważ polecenia w C i jego następcy C++ operują bezpośrednio na rejestrach procesora i mają fizyczny dostęp do RAM-u. Jeśli weźmiemy pod uwagę tylko wydajność, będzie to oczywiście zaleta. Z drugiej strony w takim układzie systemowi trudno reagować na błędy w programie, ponieważ nie może on kontrolować, co robi dany fragment kodu.

Najbardziej znany przykład to błąd wykonania typu przepełnienie bufora: program lub procedura próbuje coś zapisać w pamięci innej procedury czy innej aplikacji. Jest to typowy sposób działania malware’u – dzięki przepełnieniu bufora może on zdobyć uprawnienia administratora w atakowanym systemie. Podobnie rzecz ma się z nieudolnie napisanymi aplikacjami: stara architektura systemu sprawia, że luka w zabezpieczeniach programu staje się jednocześnie luką w systemie.

Kod systemu: Bezpieczne języki programowania

W Singularity ten problem został rozwiązany: w systemie dozwolony jest jedynie bezpieczny kod generowany przez języki, które zapewniają własne środowisko wykonawcze. Nazywa się ono runtime i funkcjonuje jak wirtualna maszyna na poziomie aplikacji. Dobrze znane języki z runtime’em to przede wszystkim Java oraz wszystkie narzecza z rodziny.NET, na przykład C#. Także kod Singularity składa się prawie całkowicie z C#, a dokładniej z jego opracowanej na użytek tego systemu wersji zwanej Sing#.

W przeciwieństwie do C i C++ kompilator języków wymagających runtime’a nie przekłada kodu bezpośrednio na język maszynowy, lecz przekształca go w tzw. kod bitowy. Zaleta: w czasie wykonywania system może sprawdzać poprawność kodu, by np. nie dopuścić do ingerencji w zabronione obszary pamięci. Odbywa się to jednak kosztem wydajności. Jak Singularity to nadrabia, przeczytacie w akapicie “Wydajność”.

Ochrona aplikacji: Izolacja wszystkich procesów

W przeciwieństwie do tradycyjnych systemów Singularity może izolować i chronić procesy już na poziomie programów. W tym celu dla każdej uruchomionej aplikacji wraz z przynależącymi do niej bibliotekami tworzy on tzw. SIP (Software Isolated Process – proces izolowany programowo) i przydziela mu niezbędne zasoby (patrz grafika po prawej stronie). SIP samodzielnie zarządza pamięcią roboczą, którą ma do dyspozycji.

Runtime każdego SIP-a pracuje niezależnie od systemu i daje się przystosowywać do potrzeb uruchomionej aplikacji. W Windows i Linuksie jest inaczej: jeden runtime obsługuje wszystkie programy – nawet jeśli uruchomimy kilka apletów Javy, nadzoruje je tylko jedna instancja Java Runtime Environment (JRE). Gdy aplet się zawiesi w taki sposób, że unieruchomi JRE, również pozostałe uruchomione programy Javy przestają działać. Inaczej jest w Singularity – jeśli jakiś SIP nie działa, nie ma to wpływu na pozostałe uruchomione procesy. Ba, Singularity tworzy odrębne SIP-y nawet dla rozszerzeń aplikacji: np. kontrolki ActiveX uruchamiane przez Internet Explorera to popularna brama dla szkodników, które mogą zawiesić system. W Singularity w takim wypadku zawiesiłby się tylko SIP kontrolki, a przeglądarka i system działałyby dalej bez zarzutu.

Kanały: Efektywna komunikacja

Jako że SIP-y w dużym stopniu działają niezależnie, komunikacja z jądrem za pomocą Application Binary Interface (ABI) jest ograniczona do kilku poleceń. Dla porównania dodajmy, że ABI Singularity wystarczają 163 funkcje, Windows API (Application  Programming  Interface) ma ich do dyspozycji ponad 14 000. Enkapsulowane SIP-y komunikują się ze sobą za pomocą kanałów, które krzyżują się na stercie wymiany (Exchange Heap), obszarze kontrolowanym przez jądro. Na stercie znajdują się dane przetwarzane przez procesy, ale w określonej chwili wszystkie należą tylko do jednego. Shared memory, czyli wspólne używanie obszarów przestrzeni roboczej, jest w Singularity  zabronione, dozwolony jest tylko wielodostęp przy odczycie.

System kontroluje się za pomocą Wiersza poleceń. Dzięki komendzie »tasklist« wyświetlimy uruchomione procesy.

System kontroluje się za pomocą Wiersza poleceń. Dzięki komendzie »tasklist« wyświetlimy uruchomione procesy.

Gdy przykładowo przeglądarka pobiera plik z Internetu, zapamiętuje go na stercie wymiany. Aby zapisać go na dysku, ta sama przeglądarka otwiera kanał dla sterowników systemu plików. Te ostatnie komunikują się za pomocą innego kanału ze sterownikiem dysku, by fizycznie utrwalić pobrane dane.

Kanały nie występują w innych systemach operacyjnych i to jest powód, dla którego Singularity może być inspiracją dla Windows, ale nie jego następcą. Gdyby tak miało się stać, wszystkie istniejące aplikacje przeznaczone do Okienek musiałby zostać napisane od nowa. Znane programy, np. Photoshop czy Nero, nie uruchomiłyby się pod kontrolą Singularity, ponieważ zostały napisane w językach nienadzorowanych, takich jak C czy C++. Ale nawet aplikacje stworzone w Javie czy językach.NET, które teoretycznie mogły działać, też wymagają zmian uwzględniających istnienie kanałów.

Całkowita stabilność: Niebieskie ekrany? Niemożliwe!

Najbardziej niepewnymi komponentami rdzenia systemu, czyli jądra, są sterowniki. Aż 85 proc. awarii Windows powodują właśnie one. Sterowniki Linuksa są warte niewiele więcej – prawdopodobieństwo, że znajdują się w nich błędy, jest 7-krotnie wyższe niż w przypadku innych części kodu jądra. Błędnie napisane sterowniki są przyczyną katastrof, ponieważ system nie ma kontroli nad tym, jakich zasobów używają, jakich potrzebują oraz z jakimi innymi składnikami systemu mogą wejść w konflikt. Co więcej, wiele sterowników działa, posiadając najwyższe uprawnienia – jeśli się zawieszają, wraz z nimi zawiesza się też cały system.

W Singularity jest to niemożliwe z dwóch powodów. Po pierwsze procedura instalacyjna sterownika musi zawierać tzw. manifest. Jest to dokument XML opisujący, jakich zasobów sprzętowych dany sterownik potrzebuje. Dzięki tym informacjom Singularity może przewidzieć jeszcze przed instalacją, czy sterownik w ogóle zadziała na dostępnym sprzęcie oraz czy może na tym urządzeniu dojść do konfliktów z innym sterownikiem. Po drugie po instalacji sterownika jego kod nie stapia się – jak dzieje się w tradycyjnych systemach – z jądrem, lecz jest traktowany jak zwykła aplikacja i enkapsulowany w SIP-a. Jeśli sterownik się zawiesza z powodu błędów w programie, nie ma to wpływu na jądro. Singularity uruchamia sterownik na nowo i informuje użytkownika o problemie. Jest to typowa cecha mikrojąder, które przechowują wiele zasobów systemowych, ale uruchamiają je tylko w razie potrzeby. Mikrojądra nie są nowością – znalazły się już w systemie Minix z 1987 roku. Jednak ich używanie było przed Singularity problematyczne. Powód: były zbyt wolne.

Wydajność: Odrzucić hamulce

Wszystkie procesory x86 mają wbudowane poziomy ochrony bezpieczeństwa sprzętowego zwane ringami. Przewidziano ich cztery, ale systemy operacyjne używają tylko dwóch: Ring0 oraz Ring3. Procesy działające na poziomie Ring0 mają dostęp do wszystkich rozkazów procesora oraz fizycznej pamięci RAM; te z poziomu Ring3 w ogóle nie mają dostępu do sprzętu, pamięci ani do procesów z Ring0.

W Windows, Linuksie oraz Mac OS procesy krytyczne dla systemu, np. programy sterowników, działają w Ring0 (Kernel Modus); aplikacje takie jak Firefox – w Ring3 (User Modus). To naprawdę świetna sprawa. Niestety, ochrona sprzętowa faworyzuje monolityczne jądra, w których wszystkie sterowniki działają w Kernel Modus. A ta właśnie cecha powoduje, że system staje się tak wrażliwy na zawarte w nich błędy.

Dlatego dobrym rozwiązaniem byłoby mikrojądro, które zostawia sterowniki w Ring3, w efekcie czego system pozostaje stabilny nawet po awarii któregoś z nich. Tyle że to redukuje wydajność: każde żądanie aplikacji (Ring3) wysłane do systemu musi być przełożone na Ring0 – sama aplikacja nie ma do niego dostępu. Te przeliczenia zajmują czas oraz obniżają wydajność. I to znacznie: odwołania przez granicę pomiędzy ringami są 5- do 10-krotnie wolniejsze niż odwołania wewnątrz tych samych poziomów bezpieczeństwa sprzętowego.

Dlatego jądra monolityczne, które mniej komunikują się z ringami, są szybsze niż typowe mikrojądra. Ale Singularity chroni się programowo – przez enkapsulowane SIP-y – i nie potrzebuje ringów. Innymi słowy, Singularity pracuje wyłącznie w Kernel Modus i dlatego jest szybsze niż każdy inny system. Zespół Microsoftu tworzący Singularity zmierzył, co się stanie z wydajnością, jeśli dodatkowo wprowadzi się w tym systemie ochronę sprzętową. W tym celu programiści podzielili komponenty systemowe Singularity na ringi, powielając model jądra stosowany w Windows. Rezultat: Singularity stracił 30 proc. wydajności.

Wynika stąd, że Windows, Linux oraz Mac OS mogłyby przyspieszyć bez żadnej optymalizacji o 30 proc. – musiałyby jedynie wyłączyć ochronę sprzętową i gwizdać na stabilność – ale wtedy użytkowników czeka droga przez mękę. W przeciwieństwie do konkurentów, Singularity może zrezygnować z ochrony, pozostając przy tym szczupłym i stabilnym systemem. Obydwa te czynniki umożliwiają uzyskanie wspaniałych osiągów (patrz wykresy po lewej stronie) i usprawiedliwiają stosowanie bezpieczniejszych, choć wolniejszych języków programowania. Z drugiej strony Singularity może w pojedynczych przypadkach sięgać do mechanizmów ochrony sprzętowej – np. po to, żeby w bezpiecznej wydzielonej piaskownicy uruchamiać podejrzany kod.

Reasumując: Microsofcie, udało ci się! Wprawdzie Singularity oferuje obecnie jedynie Wiersz poleceń, więc pożytek z niego będę mieli tylko ci, którzy lubią eksperymentować. Jednak przyszłościowy OS może bez wątpienia służyć jako model dla następcy idealnego Windows.

Najszybszy system

Poniższe zestawienie pokazuje, jak wiele cykli zegarowych potrzebuje dany system operacyjny na wykonanie określonego zadania. Oczywiście im mniej czasu to zajmuje, tym lepiej. Jak widać, Singularity zostawia rywali daleko w tyle w dowolnej konkurencji.

Wywołanie API jądra

Aplikacja wysyła zapytanie o funkcję jądra systemu operacyjnego.

Singularity – 80
Windows XP – 627
Linux – 437
FreeBSD (Unix) – 878

Zatrzymywanie wątku

Jądro zatrzymuje wątek, aby przetwarzać inne wątki.

Singularity – 365
Windows XP – 753
Linux – 906
FreeBSD (Unix) – 911

Otrzymanie wiadomości/odpowiedź na wiadomość

1-bajtowa wiadomość jest przesyłana z jednego procesu do innego.

Singularity – 1040
Windows XP –  6340
Linux –  5800
FreeBSD (Unix) –  13 300

Tworzenie/uruchamianie procesu

System operacyjny tworzy w pamięci nowy proces i uruchamia go.

Singularity – 388 000
Windows XP –  5 380 000
Linux –  719 000
FreeBSD (Unix) –  1 030 000