Dane oryginału

Original edition copyright © Copyright © 2018, 2013, 2012, 2008 John Wiley & Sons, Inc. All rights reserved. Title of English-language original: Operating System Concepts, 10th Edition by Abraham Silberschatz, Greg Gagne, Peter B. Galvin. Polish edition copyright 2021 by Polish Scientific Publishers PWN Wydawnictwo Naukowe PWN Spółka Akcyjna. All rights reserved.

All Rights Reserved. This translation published under license with the original publisher John Wiley & Sons, Inc.

Przekład Zdzisław Płoski

Projekt okładki polskiego wydania Joanna Andryjowicz

Wydawca Wioleta Szczygielska-Dybciak

Redaktor prowadzący Jolanta Kowalczuk

Redaktor Irena Puchalska

Koordynator produkcji Anna Bączkowska

Skład wersji elektronicznej na zlecenie Wydawnictwo Naukowe PWN
Michał Latusek

Zastrzeżonych nazw firm i produktów użyto w książce wyłącznie w celu identyfikacji.

Copyright © for the Polish edition by Wydawnictwo Naukowe PWN SA

Warszawa 2021

ISBN 978-83-01-21402-9

eBook został przygotowany na podstawie wydania papierowego z 2021r. (Wydanie X (I w WN PWN))

Warszawa 2021

Wydawnictwo Naukowe PWN SA

02-460 Warszawa, ul. Gottlieba Daimlera 2

tel. 22 69 54 321, faks 22 69 54 288

infolinia 801 33 33 88

e-mail: pwn@pwn.com.pl, reklama@pwn.pl
www.pwn.pl

Bezpłatny fragment

Przedmowa

Część 1. Przegląd

Rozdział 1. Wstęp

1.1. Co robią systemy operacyjne?

1.2. Organizacja systemu komputerowego

1.3. Architektura systemu komputerowego

1.4. Działania systemu operacyjnego

1.5. Zarządzanie zasobami

1.6. Ochrona i bezpieczeństwo

1.7. Wirtualizacja

1.8. Systemy rozproszone

1.9. Struktury danych jądra

1.10. Środowiska obliczeniowe

1.11. Wolne systemy operacyjne i systemy o otwartym kodzie

1.12. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Rozdział 2. Struktury systemów operacyjnych

2.1. Usługi systemu operacyjnego

2.2. Interfejs użytkownika z systemem operacyjnym

2.3. Wywołania systemowe

2.4. Usługi systemowe

2.5. Konsolidatory i ładowacze

2.6. Dlaczego aplikacje zależą od systemu operacyjnego

2.7. Projektowanie i implementowanie systemów operacyjnych

2.8. Struktura systemu operacyjnego

2.9. Budowanie i rozruch systemu operacyjnego

2.10. Usuwanie błędów z systemu operacyjnego

2.11. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Część 2. Zarządzanie procesami

Rozdział 3. Procesy

3.1. Koncepcja procesu

3.2. Planowanie procesów

3.3. Działania na procesach

3.4. Komunikacja międzyprocesowa (IPC)

3.5. IPC w systemach z pamięcią dzieloną

3.6. IPC w systemach z przekazywaniem komunikatów

3.7. Przykłady systemów IPC

3.8. Komunikacja w systemach klient-serwer

3.9. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Rozdział 4. Wątki i współbieżność

4.1. Przegląd

4.2. Programowanie wielordzeniowe

4.3. Modele wielowątkowości

4.4. Biblioteki wątków

4.5. Wątkowość niejawna

4.6. Problemy wątkowości

4.7. Przykłady systemów operacyjnych

4.8. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Rozdział 5. Planowanie przydziału CPU (jednostki centralnej)

5.1. Pojęcia podstawowe

5.2. Kryteria planowania

5.3. Algorytmy planowania

5.4. Planowanie wątków

5.5. Planowanie wieloprocesorowe

5.6. Planowanie CPU w czasie rzeczywistym

5.7. Przykłady systemów operacyjnych

5.8. Ocena algorytmów

5.9. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Część 3. Synchronizacja procesów

Rozdział 6. Narzędzia synchronizacji

6.1. Podstawy

6.2. Problem sekcji krytycznej

6.3. Rozwiązanie Petersona

6.4. Sprzętowe środki synchronizacji

6.5. Blokady muteksowe

6.6. Semafory

6.7. Monitory

6.8. Żywotność

6.9. Ocena

6.10. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Rozdział 7. Przykłady synchronizacji

7.1. Klasyczne problemy synchronizacji

7.2. Synchronizacja w jądrze

7.3. Synchronizacja POSIX-owa

7.4. Synchronizacja w Javie

7.5. Podejścia alternatywne

7.6. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Rozdział 8. Zakleszczenia

8.1. Model systemu

8.2. Zakleszczenie w aplikacjach wielowątkowych

8.3. Charakterystyka zakleszczenia

8.4. Metody postępowania z zakleszczeniami

8.5. Zapobieganie zakleszczeniom

8.6. Unikanie zakleszczeń

8.7. Wykrywanie zakleszczenia

8.8. Likwidowanie zakleszczenia

8.9. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Część 4. Zarządzanie zasobami pamięci

Rozdział 9. Pamięć główna (operacyjna)

9.1. Podstawy

9.2. Przydział ciągły pamięci

9.3. Stronicowanie

9.4. Struktura tablicy stron

9.5. Wymiana

9.6. Przykład: 32- i 64-bitowe architektury Intela

9.7. Przykład – architektura ARMv8

9.8. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Rozdział 10. Pamięć wirtualna

10.1. Podstawy

10.2. Stronicowanie na żądanie

10.3. Kopiowanie przy zapisie

10.4. Zastępowanie stron

10.5. Przydział ramek

10.6. Szamotanie

10.7. Kompresja pamięci

10.8. Przydział pamięci dla jądra

10.9. Inne rozważania

10.10. Przykłady z systemów operacyjnych

10.11. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Część 5. Zarządzanie pamięcią masową

Rozdział 11. Struktura pamięci masowej

11.1. Przegląd struktur pamięci masowej

11.2. Planowanie dostępu do dysków twardych (HDD)

11.3. Planowanie nieruchomych urządzeń pamięci (NVM)

11.4. Wykrywanie i korygowanie błędów

11.5. Zarządzanie urządzeniami pamięci masowej

11.6. Zarządzanie obszarem wymiany

11.7. Podłączanie pamięci masowej

11.8. Struktura RAID

11.9. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Rozdział 12. Systemy wejścia-wyjścia

12.1. Przegląd

12.2. Sprzęt wejścia-wyjścia

12.3. Użytkowy interfejs wejścia-wyjścia

12.4. Podsystem wejścia-wyjścia w jądrze

12.5. Przekształcanie zamówień wejścia-wyjścia na operacje sprzętowe

12.6. Strumienie (STREAMS)

12.7. Wydajność

12.8. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Część 6. System plików

Rozdział 13. Interfejs systemu plików

13.1. Pojęcie pliku

13.2. Metody dostępu

13.3. Struktura katalogowa

13.4. Ochrona

13.5. Pliki odwzorowane w pamięci

13.6. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Rozdział 14. Implementacja systemu plików

14.1. Budowa systemu plików

14.2. Operacje systemu plików

14.3. Implementacja katalogu

14.4. Metody przydziału

14.5. Zarządzanie wolną przestrzenią

14.6. Wydajność i osiągi

14.7. Rekonstrukcja

14.8. Przykład – system plików WAFL

14.9. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Rozdział 15. Wewnętrzna organizacja systemu plików

15.1. Systemy plików

15.2. Montowanie systemu plików

15.3. Partycje i montowanie

15.4. Dzielenie plików

15.5. Wirtualne systemy plików

15.6. Zdalne systemy plików

15.7. Semantyka spójności

15.8. NFS

15.9. Podsumowanie

Ćwiczenia

Dalsze lektury

Bibliografia

Ćwiczenia

Ćwiczenia do rozdziału 1

Ćwiczenia do rozdziału 2

Ćwiczenia do rozdziału 3

Ćwiczenia do rozdziału 4

Ćwiczenia do rozdziału 5

Ćwiczenia do rozdziału 6

Ćwiczenia do rozdziału 7

Ćwiczenia do rozdziału 8

Ćwiczenia do rozdziału 9

Ćwiczenia do rozdziału 10

Ćwiczenia do rozdziału 11

Ćwiczenia do rozdziału 12

Ćwiczenia do rozdziału 13

Ćwiczenia do rozdziału 14

Ćwiczenia do rozdziału 15

Źródła i prawa

O Autorach

Przypisy

Przedmowa

Systemy operacyjne są główną częścią każdego systemu komputerowego. Dlatego kurs systemów operacyjnych stanowi zasadniczą część wszelkiej edukacji informatycznej. Dziedzina ta podlega szybkim zmianom, ponieważ komputery są dziś wszechobecne niemal w każdym obszarze codziennego życia – od urządzeń wbudowanych w pojazdy aż po najbardziej wymyślne narzędzia planowania dla rządów i firm międzynarodowych. Podstawowe zasady pozostają mimo to dość przejrzyste i na nich skupiliśmy się w tej książce.

Napisaliśmy ją jako podręcznik do wstępnego wykładu z systemów operacyjnych dla studentów studiów licencjackich lub dla studentów pierwszego roku studiów magisterskich. Mamy nadzieję, że osoby podchodzące do tematu od strony praktycznej też uznają ją za przydatną. Zawiera ona klarowny opis zasad leżących u podstaw systemów operacyjnych. Założyliśmy, że Czytelnik jest zaznajomiony z podstawowymi strukturami danych, budową komputerów oraz z językiem wysokiego poziomu, takim jak C lub Java. Zagadnienia dotyczące sprzętu potrzebne do zrozumienia systemów operacyjnych są przedstawione w rozdziale 1. W tym rozdziale zamieściliśmy również przegląd podstawowych struktur danych powszechnie występujących w większości systemów operacyjnych. Przykłady kodu wyraziliśmy najczęściej w języku C, sporo z nich przedstawiliśmy również w Javie, niemniej zrozumienie omawianych algorytmów nie wymaga od Czytelnika dogłębnej znajomości tych języków.

Podstawowe idee nakreśliliśmy w sposób intuicyjny. Ujęliśmy wyniki teoretyczne, jednak w większości z pominięciem formalnych dowodów. Noty bibliograficzne na końcu każdego rozdziału zawierają odsyłacze do artykułów naukowych, w których dane wyniki pojawiły się po raz pierwszy i zostały udowodnione, a także odniesienia do nowych materiałów przeznaczonych do dalszej lektury. W miejsce dowodów posłużyliśmy się rysunkami i przykładami mającymi sugerować, dlaczego powinniśmy oczekiwać, że dany wynik jest prawdziwy.

Uwzględnione w książce podstawowe koncepcje i algorytmy nawiązują często do rozwiązań przyjętych zarówno w systemach operacyjnych o otwartym kodzie, jak i w systemach komercyjnych. Naszym celem było przedstawienie ich w sposób ogólny, bez łączenia z jednym, konkretnym systemem operacyjnym. Podaliśmy jednak dużo przykładów odnoszących się do najpopularniejszych i najbardziej innowacyjnych systemów operacyjnych, w tym do systemów Linux, Microsoft Windows, Apple macOS (pierwotną nazwę OS X zmieniono w 2016 r., aby dopasować ją do schematu nazewniczego innych wyrobów firmy Apple) i Solaris. Zamieściliśmy również przykłady pochodzące z systemów Android oraz iOS – dwóch najpopularniejszych obecnie systemów operacyjnych dla urządzeń mobilnych.

Układ książki odzwierciedla nasze wieloletnie doświadczenia z nauczania systemów operacyjnych. Uwzględniliśmy również uwagi poczynione przez recenzentów podręcznika oraz wiele komentarzy i sugestii otrzymanych od Czytelników jego poprzednich wydań, a także od naszych byłych studentów. Niniejsze dziesiąte wydanie spełnia również wymagania programu nauczania dziedziny systemów operacyjnych zawartego w Computer Science Curricula 2013 – najnowszym zbiorze zaleceń dotyczących programów nauczania informatyki na studiach dyplomowych, opublikowanym przez IEEE Computing Society oraz Association for Computing Machinery (ACM).

Co nowego w tym wydaniu

W dziesiątym wydaniu skoncentrowaliśmy się na przeglądach i usprawnieniach nakierowanych na obniżenie kosztów ponoszonych przez studentów, lepsze zaangażowanie studentów w proces nauczania i zwiększenie pomocy dla prowadzących zajęcia.

Według badań firmy Outsell, najbardziej wiarygodnej w badaniach rynku wydawniczego, rok 2015 stanowił punkt zwrotny w sposobie korzystania z podręczników – po raz pierwszy studiujący częściej wybierali do nauki materiały cyfrowe niż drukowane i od tej pory zainteresowanie formą cyfrową zwiększa się coraz szybciej.

Mimo że książka drukowana ma wciąż duże znaczenie dla wielu osób studiujących jako pomoc naukowa, w obecnym, dziesiątym wydaniu położyliśmy nacisk na postać przydatną do nauki w formie materiałów cyfrowych. W porównaniu z dziewiątym wydaniem amerykańskim istotnie zmniejszyliśmy wydatki studentów we wszystkich oferowanych postaciach podręcznika. Są to następujące jego formy:

• Samodzielny podręcznik w wersji elektronicznej zawierający obecnie wiele udoskonaleń. W formacie e-podręcznika dziesiątego wydania ćwiczenia wraz z rozwiązaniami umieszczono na końcach głównych części, dodano możliwość ukrywania lub ujawniania definicji najważniejszych terminów i zainstalowano sporo animowanych rysunków. Wersja ta zawiera również dodatkowe „Ćwiczenia praktyczne” do każdego rozdziału (wraz z rozwiązaniami), ćwiczenia specjalne, zadania i projekty programistyczne, punkty „Dalsze lektury”, kompletny słownik i cztery dodatki dotyczące dziedzictwa systemów operacyjnych.

• E-podręcznik w parze ze skróconą wersją drukowaną. Za niewielką dopłatą e-podręcznik jest również dostępny wraz ze skróconą wersją papierową w postaci luźnych arkuszy, zawierającą główny tekst rozdziałów, następujące po nich „Ćwiczenia praktyczne” (z rozwiązaniami dostępnymi online) oraz punkty „Dalsze lektury”. Wykładowcy mogą również zamówić w tym pakiecie oprawione wersje towarzyszące, kontaktując się z przedstawicielem handlowym wydawcy.

Chociaż mocno zachęcamy wszystkich wykładowców i studentów do wykorzystania wszelkich zalet wynikających z ceny, zawartości i korzyści edukacyjnych elektronicznego wydania podręcznika, osoby prowadzące zajęcia mogą za pośrednictwem przedstawiciela handlowego wydawcy przygotować odpowiednie dla siebie wydanie drukowane na papierze.

W celu dokładniejszego zapoznania się z tymi możliwościami lub omówienia innych prosimy o skontaktowanie się z odpowiednim przedstawicielem wydawcy lub wizytę na stronie tego podręcznika w Sieci.

Materiał umieszczony w książce

Książka składa się z 21 rozdziałów i 4 dodatków. Wszystkie rozdziały i dodatki zawierają tekst podstawowy oraz następujące uzupełnienia:

• zbiór ćwiczeń praktycznych łącznie z rozwiązaniami,

• zbiór zwykłych ćwiczeń,

• zbiór zadań do zaprogramowania,

• zbiór projektów programistycznych,

• punkt z wyborem dalszych lektur,

• słownik ważnych terminów,

• animacje ilustrujące określone, istotne koncepcje.

Podręcznik w postaci oprawionej można kupić w księgarniach, a także online. Ta wersja zawiera te same rozdziały co wersja elektroniczna. Nie ma w niej jednak dodatków, zwykłych ćwiczeń, rozwiązań ćwiczeń praktycznych, zadań do zaprogramowania, projektów programistycznych i niektórych innych dodatków zawartych w książce publikowanej elektronicznie.

Zawartość książki

Książka jest podzielona na dziesięć głównych części:

• Przegląd. W rozdziałach 1 i 2 wyjaśniliśmy, czym są systemy operacyjne, co robią, jak są pomyślane i zbudowane. Omówiliśmy w nich typowe cechy systemu operacyjnego oraz to, co system operacyjny robi dla użytkownika. Podaliśmy opis zarówno tradycyjnych systemów operacyjnych komputerów osobistych (PC) i serwerów, jak i systemów operacyjnych urządzeń mobilnych. Z oczywistych względów przedstawiane wiadomości mają charakter motywacyjny i objaśniający. Unikaliśmy w tych rozdziałach omawiania jak co jest zbudowane od wewnątrz. Takie ujęcie jest przystępne dla indywidualnych Czytelników i dla słuchaczy niższych lat studiów, którzy chcą się dowiedzieć, czym jest system operacyjny bez wnikania w szczegóły wewnętrznych algorytmów.

• Zarządzanie procesami. W rozdziałach od 3 do 5 omówiliśmy pojęcia procesu i współbieżności stanowiące sedno nowoczesnych systemów operacyjnych. Proces jest jednostką (cząstką) pracy w systemie. Tak rozumiany system jest zbiorem procesów wykonywanych współbieżnie, z których część wykonuje rozkazy kodu samego systemu, a inne wykonują kod dostarczony przez użytkownika. W wymienionych rozdziałach przedstawiliśmy metody planowania procesów i komunikacji międzyprocesowej. Zamieściliśmy też szczegółowe omówienie wątków oraz przegląd zagadnień dotyczących systemów wieloprocesorowych i programowania równoległego.

• Synchronizacja procesów. Rozdziały od 6 do 8 poświęciliśmy omówieniu metod synchronizacji procesów i postępowania z zakleszczeniami. Ponieważ zwiększyliśmy ilość materiału dotyczącego synchronizacji procesów, stary rozdział 5 („Synchronizacja procesów”) podzieliliśmy na dwa rozdziały: 6, zatytułowany „Narzędzia synchronizacji”, i 7, „Przykłady synchronizacji”.

• Zarządzanie pamięcią główną. W rozdziałach 9 i 10 zajęliśmy się zarządzaniem pamięcią główną (operacyjną) podczas wykonywania procesu. W celu polepszania stopnia wykorzystania procesora, a także szybkości, z jaką odpowiada on swoim użytkownikom, komputer musi przechowywać w pamięci wiele procesów. Istnieje znaczna liczba różnych schematów zarządzania pamięcią operacyjną odzwierciedlających rozmaite podejścia do zarządzania pamięcią, przy czym efektywność danego algorytmu zależy od konkretnej sytuacji.

• Zarządzanie pamięcią masową. W rozdziałach 11 i 12 opisaliśmy wykorzystanie i działanie pamięci masowej oraz wejścia-wyjścia (we-wy) w nowoczesnym systemie komputerowym. Wskutek dużego zróżnicowania urządzeń wejścia-wyjścia podłączanych do komputera system operacyjny musi zapewniać aplikacjom szeroki wybór funkcji, aby umożliwiać im wszechstronne sterowanie urządzeniami. Dogłębnie omówiliśmy system wejścia-wyjścia, zwracając uwagę na jego konstrukcję, interfejsy oraz wewnętrzne struktury i funkcje. Z wielu powodów urządzenia we-wy są najwolniej działającymi ważnymi elementami komputera. Ponieważ są one wąskim gardłem wydajności, przeanalizowaliśmy też zagadnienia dotyczące optymalizacji ich działania.

• Systemy plików. W rozdziałach od 13 do 15 omówiliśmy działanie systemów plików w nowoczesnym systemie komputerowym. Systemy plików dostarczają mechanizmów bezpośredniego magazynowania i dostępu zarówno do danych, jak i do programów. Opisaliśmy klasyczne wewnętrzne algorytmy i struktury zarządzania pamięcią masową (magazynową) i utworzyliśmy mocne podstawy do praktycznego rozumienia stosowanych algorytmów, ich własności, zalet i wad.

• Bezpieczeństwo i ochrona. W rozdziałach 16 i 17 przedstawiliśmy mechanizmy niezbędne do zapewniania bezpieczeństwa i ochrony systemów komputerowych. Procesy w systemie operacyjnym należy chronić przed wzajemnym oddziaływaniem. Aby umożliwić taką ochronę, musimy zapewnić, że na plikach, pamięci, procesorze i innych zasobach systemowych będą mogły działać tylko te procesy, które uzyskały odpowiednie upoważnienie od systemu operacyjnego. Przez ochronę rozumie się mechanizm kontrolowania dostępu programów, procesów lub użytkowników do zasobów systemu komputerowego. Mechanizm ten musi dostarczać możliwości do określania wymaganej kontroli oraz środki jej egzekwowania. Bezpieczeństwo ma na celu ochronę nienaruszalności informacji przechowywanych w systemie (zarówno danych, jak i kodu) oraz zasobów fizycznych systemu przed nieupoważnionym dostępem, złośliwymi uszkodzeniami lub zmianami, a także przed przypadkowym wprowadzaniem niespójności.

• Tematy zaawansowane. W rozdziałach 18 i 19 omówiliśmy maszyny wirtualne oraz sieci i systemy rozproszone. W rozdziale 18 dokonaliśmy przeglądu maszyn wirtualnych i ich związków ze współczesnymi systemami operacyjnymi. Zamieściliśmy w nim ogólny opis technik sprzętowych i programowych umożliwiających wirtualizację. W rozdziale 19 natomiast zawarliśmy przegląd sieci komputerowych i systemów rozproszonych ze szczególnym uwzględnieniem Internetu i protokołów TCP/IP.

• Przykłady konkretnych systemów. W rozdziałach 20 i 21 szczegółowo omówiliśmy przykłady dwóch prawdziwych systemów operacyjnych: Linuxa i Windows 10.

• Dodatki. W dodatku A zaprezentowaliśmy kilka starych, już nieużywanych systemów operacyjnych, które wywarły duży wpływ na rozwój dziedziny. W dodatkach B, C i D zawarliśmy bardziej szczegółowe ujęcie trzech starszych systemów operacyjnych: Windows 7, BSD i Mach.

Środowiska programowania

W podręczniku zawarliśmy pewną liczbę przykładów programów napisanych w językach C i Java. Są one przeznaczone do pracy w następujących środowiskach programowania:

• POSIX. Zbiór standardów określany mianem POSIX (interfejs przenośnego systemu operacyjnego – Portable Operating System Interface) jest przeznaczony głównie dla uniksowych systemów operacyjnych. Chociaż systemy Windows mogą również wykonywać niektóre programy POSIX-owe, nasze ujęcie POSIX-a koncentruje się na systemach Linux i UNIX. Systemy zgodne ze standardem POSIX muszą implementować rdzenny standard POSIX-a (POSIX.1); przykładami systemów zgodnych z tym standardem są Linux i macOS. W ramach standardu POSIX zdefiniowano również kilka rozszerzeń, w tym rozszerzenia dotyczące czasu rzeczywistego (POSIX.1b) i rozszerzenie definiujące bibliotekę wątków (POSIX.1c, bardziej znane jako P-wątki – Pthreads). Podaliśmy kilka przykładów programowania napisanych w języku C, ilustrujących podstawowy interfejs API standardu POSIX oraz P-wątków i rozszerzeń dotyczących programowania czasu rzeczywistego. Przykładowe programy zostały przetestowane w systemach Linux 4.4 i macOS 10.11 z użyciem kompilatora gcc.

• Java. Java jest szeroko stosowanym językiem programowania wyposażonym w bogaty interfejs programowania aplikacji (API) i wbudowane zaplecze językowe programowania współbieżnego i równoległego. Programy w Javie działają w dowolnym systemie operacyjnym zaopatrzonym w maszynę wirtualną Javy[1] (czyli JVM). Za pomocą programów w Javie przetestowanych z użyciem wersji 1.8 pakietu Java Development Kit (JDK) unaoczniliśmy różne koncepcje systemów operacyjnych i pracy sieciowej.

• Systemy Windows. Podstawowym środowiskiem programowania w systemach Windows jest Windows API zawierający wszechstronny zbiór funkcji do zarządzania procesami, wątkami, pamięcią i urządzeniami zewnętrznymi. Podaliśmy niewielką liczbę programów w C ilustrujących zastosowanie tego API. Programy zostały przetestowane pod kontrolą systemu Windows 10.

Wybraliśmy te trzy środowiska programowania, ponieważ uważamy, że najlepiej reprezentują one dwa najpopularniejsze modele systemów operacyjnych: Linux (lub UNIX) i Windows, oraz będące w powszechnym użyciu środowisko Java. Większość przykładów programowania napisano w języku C i spodziewamy się, że Czytelnicy czują się w nim dobrze. Czytelnikom zaznajomionym zarówno z językiem C, jak i z Javą zrozumienie większości programów w tej książce nie powinno sprawiać trudności.

W niektórych przypadkach – jak na przykład przy tworzeniu wątków – dane pojęcie objaśniliśmy, używając wszystkich trzech środowisk programowania, co umożliwi Czytelnikowi porównanie trzech różnych bibliotek w zastosowaniu do tego samego zadania. Kiedy indziej zdarzało nam się demonstrować koncepcje tylko w jednym z tych API. Na przykład pamięć dzieloną przedstawiliśmy, używając do tego tylko środowiska POSIX API. Programowanie gniazd w TCP/IP naświetliliśmy za pomocą API Javy.

Maszyna wirtualna Linuxa

Aby pomóc studentom w lepszym zrozumieniu systemu Linux, dostarczyliśmy wraz z tym podręcznikiem maszynę wirtualną Linuxa działającą w dystrybucji Ubuntu. Można ją pobrać ze strony podręcznika w Sieci (http://www.os-book.com), tam również znajdują się środowiska pracy, w tym kompilatory: gnu i Java. Większość zadań do zaprogramowania zawartych w książce można wykonać, używając tej maszyny wirtualnej z wyjątkiem zadań wymagających API Windows. Maszynę wirtualną można zainstalować i wykonywać w dowolnym goszczącym systemie operacyjnym, w którym można wykonywać oprogramowanie VirtualBox, a do takich zaliczają się obecnie systemy Windows 10, Linux i macOS.

Wydanie dziesiąte

Podczas pisania niniejszego, dziesiątego wydania Podstaw systemów operacyjnych braliśmy pod uwagę utrzymujący się wzrost w czterech fundamentalnych obszarach wywierających wpływ na systemy operacyjne:

1. Mobilne systemy operacyjne.

2. Systemy wielordzeniowe.

3. Wirtualizacja.

4. Nieulotna pomocnicza pamięć masowa.

Aby uwypuklić te zagadnienia, włączyliśmy do tego nowego wydania niezbędny materiał. Na przykład znacznie więcej miejsca poświęciliśmy mobilnym systemom operacyjnym Android i iOS, a także naszemu ujęciu architektury ARMv8 dominującej w urządzeniach przenośnych. Zwiększyliśmy również ilość materiału dotyczącego systemów wielordzeniowych, w tym środowisk API umożliwiających współbieżność i równoległość. Urządzenia pamięci nieulotnej w rodzaju SSD-ków są obecnie potraktowane na równi z napędami dysków twardych w rozdziałach, w których omówiliśmy wejście-wyjście, pamięć masową i systemy plików.

Niektórzy z naszych Czytelników podkreślali konieczność zwiększenia udziału Javy, toteż zamieściliśmy w tym wydaniu dodatkowe przykłady w Javie.

Ponadto niemal każdy rozdział napisaliśmy na nowo, uaktualniając stary materiał i usuwając wiadomości, które przestały być interesujące lub straciły na znaczeniu. Zreorganizowaliśmy wiele rozdziałów, co zmusiło nas w niektórych przypadkach do przeniesienia porcji tekstu z jednego rozdziału do drugiego. W znacznym stopniu zrewidowaliśmy również szatę graficzną, tworząc sporo nowych rysunków oraz modyfikując istniejące.

Główne zmiany

Dziesiąte, uaktualnione wydanie obejmuje znacznie więcej materiału niż poprzednie uaktualnienia, zarówno pod względem treści, jak i jego nowego ujęcia. Poniżej podajemy w skrócie wykaz głównych zmian w każdym rozdziale:

• Rozdział 1. Wstęp zawiera uaktualnione ujęcie systemów wielordzeniowych, a także nowe podanie systemów NUMA i klastrów (gron) Hadoop. Zaktualizowaliśmy stary materiał i dodaliśmy nowe uzasadnienie studiowania systemów operacyjnych.

• Rozdział 2. Struktury systemów operacyjnych dostarcza istotnie zrewidowane omówienie projektowania i implementowania systemów operacyjnych. Uaktualniliśmy nasze podejście do Androida i iOS-a i zrewidowaliśmy potraktowanie procesu rozruchu systemu, skupiając się na GRUB-ie[2] dla systemów Linux. Zamieściliśmy również nowe ujęcie podsystemu Windows w Linuxie. Dodaliśmy nowe podrozdziały o konsolidatorach („linkerach”) i ładowaczach, a także przedstawiliśmy, dlaczego aplikacje często zależą od konkretnego systemu operacyjnego. Na koniec dodaliśmy jeszcze omówienie uruchomieniowego zestawu narzędziowego BCC.

• Rozdział 3. Procesy. Uprościliśmy w nim rozważania dotyczące planowania, zawężając je do zagadnień planowania przydziału procesora. W obecnym ujęciu mieszczą się: wygląd pamięci w programie C, hierarchia procesów w Androidzie, przekazywanie komunikatów w Machu i androidowe zdalne wywołania procedur (RPC). Zastąpiliśmy również tradycyjne omówienie uniksowo-linuksowego procesu init przedstawieniem funkcji systemd.

• Rozdział 4. Wątki i współbieżność (poprzedni tytuł: „Wątki”) zawiera więcej materiału dotyczącego zaplecza programowania współbieżnego i równoległego na poziomie API i bibliotek. Ponownie przejrzeliśmy podrozdział o wątkach Javy i obecnie zawiera on „futury”[3] i uaktualnioną prezentację architektury Grand Central Dispatch z Apple, uwzględniającą także Swifta[4]. W nowych podrozdziałach omówiliśmy równoległość z użyciem ramy fork-join w Javie, a także intelowe bloki konstrukcyjne wątków.

• Rozdział 5. Planowanie przydziału CPU (jednostki centralnej) (poprzednio rozdz. 6) zawiera zrewidowany materiał dotyczący kolejki wielopoziomowej i planowania przetwarzania wielordzeniowego. Zebraliśmy w jednym miejscu rozsiane wcześniej zagadnienia planowania z uwzględnieniem NUMA, dodając omówienie wpływu takiego rodzaju planowania na równoważenie obciążeń. Przedstawiliśmy również powiązane z tym modyfikacje linuksowego planisty CFS. Obecne ujęcie łączy prezentacje planowania rotacyjnego i priorytetowego, heterogenicznego wieloprzetwarzania i planowania w Windows 10.

• Rozdział 6. Narzędzia synchronizacji (poprzednio stanowił część rozdz. 5, „Synchronizowanie procesów”) dotyczy różnych narzędzi synchronizacji procesów. W zupełnie nowym ujęciu przedstawiliśmy zagadnienia architektoniczne, takie jak reorganizacja instrukcji i opóźniane zapisywanie buforów. Wprowadziliśmy również algorytmy bez blokad, używające instrukcji porównaj-i-zamień (compare-and-swap – CAS). Nie przedstawiliśmy żadnego konkretnego API, zawarliśmy natomiast w rozdziale wprowadzenie do zagadnień szkodliwej rywalizacji i ogólnych narzędzi, których można używać do zapobiegania wyścigom danych. W szczególności po nowemu ujęliśmy modele pamięci, bariery pamięciowe i kwestie żywotności.

• Rozdział 7. Przykłady synchronizacji (poprzednio stanowił część rozdz. 5, „Synchronizowanie procesów”) jest wprowadzeniem w klasyczne problemy synchronizacji; omówiliśmy w nim również konkretny interfejs API do projektowania efektywnych rozwiązań tych problemów. Rozdział zawiera nowe ujęcie nazwanych i beznazwowych semaforów oraz zmiennych warunkowych. Dodaliśmy także nowy podrozdział o synchronizacji w Javie.

• Rozdział 8. Zakleszczenia (poprzednio rozdz. 7) uległ niewielkim uaktualnieniom. Dodaliśmy nowy podrozdział o szamotaninie (żywej blokadzie) i omówienie zakleszczeń jako przykładów żywotnych (trwałych) hazardów. Zawarliśmy w nim nowe ujęcie linuksowego narzędzia lockdep i deadlock_detector z BCC, a także przedstawienie wykrywania zakleszczenia w Javie z użyciem zrzutów pamięci wątków.

• Rozdział 9. Pamięć główna (operacyjna) (poprzednio rozdz. 8) zawiera kilka uaktualnień dotyczących zarządzania pamięcią w nowoczesnych systemach komputerowych. Dodaliśmy nowy opis 64-bitowej architektury ARMv8, uaktualniliśmy omówienie bibliotek konsolidowanych dynamicznie oraz inaczej potraktowaliśmy wymianę, skupiając się obecnie na wymianie stron, a nie procesów. Usunęliśmy także omówienie segmentacji.

• Rozdział 10. Pamięć wirtualna (poprzednio rozdz. 9). Zrewidowaliśmy go w kilku miejscach, dodając uaktualniony materiał o przydzielaniu pamięci w systemach NUMA i przydziale globalnym z użyciem listy wolnych ramek. Na nowo potraktowaliśmy pamięć skompresowaną, poważniejsze i mniej ważne braki stron oraz zarządzanie pamięcią w systemach Linux i Windows 10.

• Rozdział 11. Struktura pamięci masowej (poprzednio rozdz. 10). Uzupełniliśmy go o przedstawienie urządzeń pamięci nieulotnej, takich jak dyski flash i dyski półprzewodnikowe. Uprościliśmy omówienie planowania dysków twardych, sprowadzając je do pokazania tylko tych algorytmów, które są obecnie używane. Dodaliśmy również nowy podrozdział o chmurowej pamięci masowej, uaktualniliśmy przedstawienie architektur RAID i zamieściliśmy nowe omówienie pamięci masowej obiektów.

• Rozdział 12. Systemy wejścia-wyjścia (poprzednio rozdz. 13) zawiera uaktualnione ujęcie materiału dotyczącego technologii i parametrów wydajnościowych, rozszerzone potraktowanie wejścia-wyjścia synchronicznego i asynchronicznego oraz blokowanego i bez blokowania. Jest w nim również nowy podrozdział o wektorowym we-wy. Poszerzyliśmy w nim także zagadnienie zarządzania zasilaniem w mobilnych systemach operacyjnych.

• Rozdział 13. Interfejs systemu plików (poprzednio rozdz. 11) uaktualniliśmy o wiadomości związane z bieżącymi technologiami. W szczególności ulepszyliśmy omówienie struktur katalogowych i uaktualniliśmy kwestie ochrony. Poszerzyliśmy też podrozdział dotyczący plików odwzorowanych w pamięci, a do opisania pamięci dzielonej dodaliśmy przykład z interfejsu API Windows 10. Kolejność tematów w rozdziałach 13 i 14 została zreorganizowana.

• Rozdział 14. Implementacja systemu plików (poprzednio rozdz. 12). Również ten rozdział uaktualniliśmy, uwzględniając bieżące technologie. Obecnie zawiera on omówienie mechanizmu TRIM i systemu plików Apple. Dodaliśmy ponadto zagadnienia wydajności i poszerzyliśmy tematykę rejestrowania (prowadzenia dziennika – journaling).

• Rozdział 15. Wewnętrzna organizacja systemu plików. Jest to nowy rozdział zawierający uaktualnione wiadomości z poprzednich rozdziałów 11 i 12.

• Rozdział 16. Bezpieczeństwo (poprzednio rozdz. 15) poprzedza teraz rozdział dotyczący ochrony. Zawarliśmy w nim zrewidowane i uaktualnione określenia współczesnych zagrożeń bezpieczeństwa i sposobów jego naruszania, w tym takich jak oprogramowanie wymuszające (zbójeckie) i środki zdalnego dostępu. Położyliśmy w nim nacisk na zasadę najmniejszych przywilejów. Przejrzeliśmy na nowo zagrożenia i ataki polegające na wstrzykiwaniu kodu, dodając przykłady kodu. Uaktualniliśmy omówienie technik szyfrowania, koncentrując się na obecnie używanych. Zaktualizowaliśmy i rozszerzyliśmy o przydatne zalecenia uwierzytelnianie (za pomocą haseł i innych metod). Wśród nowych treści znajduje się teraz omówienie randomizacji wyglądu przestrzeni adresowej i nowe podsumowanie sposobów obrony bezpieczeństwa. Przykład z systemu Windows 7 uaktualniliśmy do Windows 10.

• Rozdział 17. Ochrona (poprzednio rozdz. 14). Ten rozdział został istotnie zmieniony. Uaktualniliśmy omówienie pierścieni i warstw ochrony, które obecnie odnosi się do modelu Bell–LaPadula i zawiera przegląd modelu ARM stref zaufania i bezpiecznych wywołań monitora (Trust-Zones and Secure Monitor Calls). Poszerzyliśmy omówienie zasady wiedzy koniecznej i dodaliśmy wiadomości na temat obligatoryjnego kontrolowania dostępu. Zawarliśmy w nim też nowe punkty omawiające uprawnienia w Linuxie, system upoważnień Darwin, ochronę nienaruszalności bezpieczeństwa, filtrowanie wywołań systemowych, zastosowanie piaskownic i podpisywanie kodu. Dodaliśmy także omówienie egzekwowania zasad w fazie wykonania w Javie, w tym techniki inspekcji stosu.

• Rozdział 18. Maszyny wirtualne (poprzednio rozdz. 16). Dodaliśmy w nim szczegóły dotyczące sprzętowych technologii wspierających. Rozszerzyliśmy również temat powstrzymywania ekspansji aplikacji ujmujący obecnie kontenery, strefy (zony) oraz systemy Docker i Kubernetes. W nowym podrozdziale przedstawiliśmy bieżące badania nad wirtualizacją, w tym unijądra, biblioteczne systemy operacyjne, hiperwizory partycjonowania (podziału na strefy dyskowe) i hiperwizory separacji.

• Rozdział 19. Sieci i systemy rozproszone (poprzednio rozdz. 17) został istotnie uaktualniony i obecnie łączy tematykę sieci komputerowych i systemów rozproszonych. Ten materiał przejrzeliśmy pod kątem aktualności, z uwzględnieniem współczesnych sieci komputerowych i systemów rozproszonych. Jeszcze większy nacisk położyliśmy na model TCP/IP, dodaliśmy również omówienie chmurowej pamięci masowej. Usunęliśmy podrozdział o topologiach sieci. Poszerzyliśmy tematykę tłumaczenia nazw i dodaliśmy przykład z Javy. Na nowo przedstawiliśmy również rozproszone systemy plików, dołączając omówienie systemu MapReduce usytuowanego powyżej systemu plików Google’a, a także rozwiązania Hadoop, GPFS i Lustre.

• Rozdział 20. System Linux (poprzednio rozdz. 17) został uaktualniony i zawiera teraz omówienie jądra Linux 4.i.

• Rozdział 21. Windows 10 jest nowy, przedstawiliśmy w nim wewnętrzną organizację systemu Windows 10.

• Dodatek A. Wpływowe systemy operacyjne. Ten dodatek uaktualniliśmy, włączając do niego materiał z rozdziałów, które już nie występują w podręczniku.

Pomocnicza witryna

Odwiedzając witrynę http://www.os-book.com, można zaopatrzyć się w następujące materiały:

• maszynę wirtualną Linuxa,

• kod źródłowy w językach C i Java,

• kompletny zbiór rysunków i ilustracji,

• omówienia przykładowych systemów: FreeBSD, Mach i Windows 7,

• erratę,

• bibliografię.

Uwagi dla prowadzących zajęcia

W witrynie podręcznika zamieściliśmy kilka przykładów programów nauczania z propozycjami różnych sposobów jego wykorzystania, zarówno na kursach dla początkujących, jak i dla zaawansowanych. Ogólnie biorąc, zachęcamy wykładowców do przerabiania rozdziałów po kolei, gdyż ta strategia daje najstaranniejsze studium systemów operacyjnych. Niemniej, korzystając z przykładowych sylabusów, prowadzący zajęcia mogą wybrać inną kolejność omawiania rozdziałów (lub podrozdziałów).

W tym wydaniu dodaliśmy dużo na nowo sformułowanych ćwiczeń oraz zadań i projektów do zaprogramowania. Większość nowych zadań programistycznych dotyczy procesów, wątków, planowania procesów, synchronizacji procesów i zarządzania pamięcią. W niektórych trzeba dodawać moduły jądra do systemu Linux, co wymaga korzystania z towarzyszącej tej książce wirtualnej maszyny Linuxa lub stosowania innej, odpowiedniej dystrybucji Linuxa.

Dla osób prowadzących wykłady, które wybrały ten podręcznik do prowadzenia kursów z systemów operacyjnych, są dostępne rozwiązania ćwiczeń pisemnych i zadań programistycznych. Aby otrzymać te „reglamentowane” materiały, należy się skontaktować z lokalnym przedstawicielem handlowym wydawcy.

Uwagi dla studentów

Zachęcamy Was do korzystania z ćwiczeń praktycznych znajdujących się na końcu każdego rozdziału. Namawiamy również do czytania poradnika studiowania opracowanego przez jednego z naszych studentów. Poza tym studentom niezaznajomionym z systemami UNIX i Linux radzimy, aby sprowadzili sobie i zainstalowali maszynę wirtualną Linuxa. którą załączamy w witrynie pomocniczej. To nie tylko umożliwi Wam nowe doświadczenia informatyczne, lecz dzięki otwartemu charakterowi Linuxa pozwoli na łatwe penetrowanie wewnętrznych szczegółów tego popularnego systemu operacyjnego. Życzymy Wam wszelkiej pomyślności w studiowaniu systemów operacyjnych!

Piszcie do nas

Staraliśmy się wyeliminować z tekstu błędy typograficzne i inne. Jednak, podobnie jak w nowych wydaniach oprogramowania, jest niemal pewne, że zawsze znajdą się jakieś niedociągnięcia. Aktualna errata jest dostępna w witrynie książki. Bylibyśmy zobowiązani, gdybyście zechcieli zawiadamiać nas o wszelkich błędach lub pominięciach w książce, które nie są wyszczególnione na bieżącej liście poprawek.

Bylibyśmy radzi otrzymywać sugestie dotyczące ulepszenia tej książki. Z przyjemnością przyjmiemy też wszelki wkład w witrynę książki w Sieci, który mógłby się przydać przyszłym czytelnikom, taki jak zadania do zaprogramowania, sugestie projektów, pracownie i instruktaże online oraz wskazówki dydaktyczne. Pocztę należy kierować pod adresem os-bookauthors@cs.yale.edu.

Podziękowania

Wiele osób okazało nam pomoc w przygotowaniu tego dziesiątego wydania, jak również przy poprzednich dziewięciu wydaniach, z których ono się wywodzi.

Dziesiąte wydanie

• Rick Farrow służył swoim doświadczeniem i fachowymi radami jako redaktor techniczny.

• Jonathan Levin pomagał w ogarnięciu tematyki systemów mobilnych, ochrony i bezpieczeństwa.

• Alex Ionescu uaktualnił poprzedni rozdział o Windows 7, umożliwiając przygotowanie rozdziału 21: „Windows 10”.

• Sarah Diesburg dokonała przeglądu rozdziału 19: „Sieci i systemy rozproszone”.

• Brendan Gregg dostarczył przewodnik po zestawie narzędziowym BCC.

• Richard Stallman (RMS) pozostawał z nami w kontakcie w związku z opisem oprogramowania „wolnego” (bezpłatnego) i o otwartym kodzie.

• Robert Love naniósł uaktualnienia w rozdziale 20: „System Linux”.

• Michael Shapiro pomagał w opracowaniu szczegółów dotyczących pamięci masowej i wejścia-wyjścia.

• Richard West przeniknął obszary badań nad wirtualizacją.

• Clay Breshears pomógł w ujęciu zagadnień intelowych bloków konstrukcyjnych wątków.

• Gerry Howser kontaktował się z nami w kwestiach motywacji do studiowania systemów operacyjnych, a także wypróbowywał nowy materiał na swoich zajęciach.

• Judi Paige pomogła w opracowaniu rysunków i prezentacji slajdowych.

• Jay Gagne i Audra Rissmeyer przygotowali nową szatę graficzną tego wydania.

• Owen Galvin zapewnił opracowanie techniczne rozdziałów 11 i 12.

• Mark Wogahn dopilnował, aby oprogramowanie użyte do produkcji tej książki (LaTeX i kroje pisma, tzw. fonty) działało jak należy.

• Ranjan Kumar Meher przepisał na nowo część oprogramowania LaTeX-owego użytego do produkcji tego nowego tekstu.

Poprzednie wydania

• Pierwsze trzy wydania. Ta książka wywodzi się z poprzednich wydań, współautorem trzech pierwszych z nich był James Peterson.

• Ogólne zasługi. W przygotowaniu poprzednich wydań pomagały nam również następujące osoby: Hamid Arabnia, Rida Bazzi, Randy Bentson, David Black, Joseph Boykin, Jeff Brumfield, Gael Buckley, Roy Campbell, P. C. Capon, John Carpenter, Gil Carrick, Thomas Casavant, Bart Childs, Ajoy Kumar Datta, Joe Deck, Sudarshan K. Dhall, Thomas Doeppner, Caleb Drake, M. Rasit Eskicioğlu, Hans Flack, Robert Fowler, G. Scott Graham, Richard Guy, Max Hailperin, Rebecca Hartman, Wayne Hathaway, Christopher Haynes, Don Heller, Bruce Hillyer, Mark Holliday, Dean Hougen, Michael Ahmed Kamel, Morty Kewstel, Richard Kieburtz, Carol Kroll, Morty Kwestel, Thomas Blanc, John Leggett, Jerrold Leichter, Ted Leung, Gary Lippman, Carolyn Miller, Michael Molloy, Euripides Montagne, Yoichi Muraoka, Jim M. Ng, Banu Özden, Ed Posnak, Boris Putanec, Charles Qualline, John Quarterman, Mike Reiter, Gustavo Rodriguez-Rivera, Carolyn J. C. Schauble, Thomas P. Skinner, Yannis Smaragdakis, Jesse St. Laurent, John Stankovic, Adam Stauffer, Steven Stepanek, John Sterling, Hal Stern, Louis Stevens, Pete Thomas, David Umbaugh, Steve Vinoski, Tommy Wagner, Larry L. Wear, John Werth, James M. Westall, J. S.Weston i Yang Xiang.

• Specjalne zasługi

– Robert Love uaktualnił zarówno rozdział 20, jak i tematykę Linuxa w obrębie całego tekstu, a także odpowiedział na wiele naszych pytań dotyczących Androida.

– Dodatek B został napisany przez Dave’a Proberta na podstawie rozdziału 22 z ósmego wydania Operating System Concepts.

– Jonathan Katz przyczynił się do powstania rozdziału 16. Richard West dostarczył danych źródłowych do rozdziału 18. Salahuddin Khan uaktualnił podrozdział 16.7, umożliwiając nowe ujęcie zagadnień bezpieczeństwa w systemie Windows 7.

– Fragmenty rozdziału 19 pochodzą z artykułu Levy’ego i Silberschatza [1990].

– Rozdział 20 powstał na podstawie nieopublikowanego artykułu Stephena Tweedie’ego.

– Cliff Martin pomógł w aktualizacji dodatku o UNIX-ie treściami dotyczącymi systemu FreeBSD.

– Niektóre ćwiczenia wraz z rozwiązaniami dostarczył Arvind Krishnamurthy.

– Andrew DeNicola przygotował studencki poradnik studiowania dostępny na naszej witrynie w Sieci. Część slajdów przygotowała Marilyn Turnamian.

– Mike Shapiro, Bryan Cantrill oraz Jim Mauro udzielili odpowiedzi na kilka pytań dotyczących systemu Solaris, a ponadto Bryan Cantrill z Sun Microsystems pomógł w ujęciu wiadomości o systemie plików ZFS. Josh Dees i Rob Reynolds mają swój wkład w opracowanie materiału o oprogramowaniu NET z Microsoftu.

– Owen Galvin pomógł w przygotowaniu do druku rozdziału 18.

Produkcja książki

Redaktorem naczelnym wydania był Don Fowley. Starszym redaktorem technicznym był Ken Santor. Obowiązki niezależnego redaktora prowadzącego pełnił Chris Nelson, a zastępował go Ryann Dannelly. Okładkę zaprojektował Tom Nery. Adjustację wykonała niezależnie Beverly Peavler. Niezależną korektorką była Katrina Avery. Sporządzenie skorowidza powierzono niezależnej firmie WordCo, Inc. Skład w LaTeX-u wykonał zespół z Aptara Inc., a konkretnie Neeraj Saxen i Lav Kush.

Noty osobiste

Avi chciałby podziękować Valerii za jej miłość, cierpliwość i pomoc podczas dokonywania przeglądu tej książki.

Peter pragnie wyrazić wdzięczność swojej żonie Carli i swoim dzieciom: Gwen, Owenowi i Maddie.

Greg za nieustanną pomoc wyraża podziękowania swojej rodzinie: żonie i synom – Thomasowi i Jayowi.

Abraham Silberschatz, New Haven, CT

Peter Baer Galvin, Boston, MA

Greg Gagne, Salt Lake City, UT

Część 1

Przegląd

System operacyjny działa jako pośrednik między użytkownikiem komputera a sprzętem komputerowym. Zadaniem systemu operacyjnego jest tworzenie środowiska, w którym użytkownik może wykonywać programy w wygodny i wydajny sposób.

System operacyjny jest oprogramowaniem, które zarządza sprzętem komputerowym. Sprzęt musi dostarczać odpowiednich mechanizmów zapewniających poprawne działanie systemu komputerowego i zapobiegających zaburzaniu przez programy właściwego działania systemu.

Wewnętrzna budowa systemów operacyjnych jest mocno zróżnicowana, ponieważ są one zorganizowane pod różnymi kątami. Zaprojektowanie nowego systemu operacyjnego jest poważnym przedsięwzięciem, toteż jest istotne, aby cele systemu były dobrze zdefiniowane przed rozpoczęciem projektu.

Ponieważ system operacyjny jest duży i skomplikowany, musi być tworzony fragmentami. Każdy z tych fragmentów powinien być dobrze wydzieloną porcją systemu ze starannie zdefiniowanymi danymi wejściowymi, danymi wyjściowymi i funkcjami.

ROZDZIAŁ 1

Wstęp

System operacyjny (operating system) jest oprogramowaniem, które zarządza sprzętem komputera. Stanowi on również bazę dla programów użytkowych i działa jako pośrednik między użytkownikiem komputera a sprzętem komputerowym. Zadziwiającym aspektem systemów operacyjnych jest ich różnorodność, jeśli chodzi o sposób, w jaki spełniają te zadania w najrozmaitszych środowiskach komputerowych. Systemy operacyjne występują wszędzie – od samochodów i sprzętu domowego wyposażonego w urządzenia „internetu rzeczy”, po smartfony, komputery osobiste, komputery w przedsiębiorstwach i środowiska obliczeń chmurowych.

Aby badać rolę, jaką odgrywa system operacyjny w nowoczesnym środowisku obliczeniowym, trzeba najpierw zrozumieć organizację i architekturę sprzętu komputerowego. Mamy na myśli jednostkę centralną, pamięć główną (operacyjną), urządzenia wejścia-wyjścia oraz pamięć masową. Podstawowym obowiązkiem systemu operacyjnego jest przydzielanie tych zasobów programom.

Ponieważ system operacyjny jest duży i skomplikowany, musi być tworzony kawałek po kawałku – fragmentami. Każdy fragment musi być dobrze odgraniczoną od innych porcją systemu ze starannie zdefiniowanym wejściem, wyjściem oraz funkcjami. W tym rozdziale dokonujemy ogólnego przeglądu ważnych składowych współczesnego systemu komputerowego oraz funkcji pełnionych przez system operacyjny. Poruszamy ponadto kilka innych tematów, aby dać podstawę do studiowania dalszych treści: struktury danych używane w systemach operacyjnych, środowiska obliczeniowe oraz systemy operacyjne o otwartym kodzie i wolne od opłat.

CELE ROZDZIAŁU

• Nakreślenie ogólnej organizacji systemu komputerowego i znaczenia przerwań.

• Opisanie elementów współtworzących nowoczesny wieloprocesorowy system komputerowy.

• Zobrazowanie przechodzenia od trybu użytkownika do trybu jądra.

• Przedstawienie różnych sposobów stosowania systemów operacyjnych w rozmaitych środowiskach obliczeniowych.

• Podanie przykładów wolnych systemów operacyjnych i mających powszechnie dostępny kod.

1.1. Co robią systemy operacyjne?

Nasze omówienie zaczniemy od przyjrzenia się roli systemu operacyjnego w ogólnie rozumianym systemie komputerowym. System komputerowy można z grubsza podzielić na cztery części: sprzęt, system operacyjny, programy użytkowe i użytkowników (rys. 1.1).

Rys. 1.1. Abstrakcyjne wyobrażenie elementów systemu komputerowego

Sprzęt (hardware), czyli procesor – zwany też jednostką centralną (central processing unit – CPU), pamięć i urządzenia wejścia-wyjścia to podstawowe zasoby systemu komputerowego. Programy użytkowe (aplikacje) – takie jak edytory tekstów, arkusze kalkulacyjne, kompilatory i przeglądarki sieciowe – określają sposoby użycia tych zasobów do rozwiązywania zadań stawianych przez użytkowników. System operacyjny nadzoruje i koordynuje posługiwanie się sprzętem przez różne programy użytkowe, które pracują na zlecenie różnych użytkowników.

Możemy również spojrzeć na system komputerowy jako na złożony ze sprzętu, oprogramowania i danych. System operacyjny dostarcza środków do właściwego użycia tych zasobów w działającym systemie komputerowym. Podobnie jak rząd, system operacyjny nie wykonuje sam żadnej użytecznej funkcji. Po prostu tworzy środowisko (environment), w którym inne programy mogą wykonywać pożyteczne prace.

Żeby pełniej zrozumieć rolę systemu operacyjnego, spojrzymy teraz na systemy operacyjne z dwóch punktów widzenia: użytkownika i systemu.

1.1.1. Perspektywa użytkownika

Perspektywa użytkownika komputera zmienia się w zależności od zastosowanego interfejsu. Wielu użytkowników siedzi z laptopem lub przed komputerem PC składającym się z monitora, klawiatury i myszy. System ów jest tak zaprojektowany, aby jeden użytkownik mógł monopolizować jego zasoby. Celem jest maksymalizowanie efektów pracy (lub zabawy) wykonywanej przez użytkownika. W tym przypadku system operacyjny jest zaprojektowany głównie pod kątem wygody użycia, z pewnym uwzględnieniem wydajności i bezpieczeństwa, bez uwzględniania wykorzystania zasobów (resource utilization), czyli tego, w jaki sposób poszczególne zasoby sprzętowe i programowe są współużytkowane.

Coraz więcej użytkowników pracuje z urządzeniami mobilnymi[5], takimi jak smartfony i tablety. Niektórym użytkownikom zastępują one komputery biurkowe i laptopy. Urządzenia te są na ogół podłączone do sieci z zastosowaniem technologii komórkowych lub innych środków komunikacji bezprzewodowej. Interfejsy użytkownika w komputerach mobilnych są zwykle zaopatrzone w ekran dotykowy (touch screen); użytkownik kontaktuje się z systemem, dotykając go i przesuwając po nim palcami zamiast posługiwać się fizyczną klawiaturą. Wiele urządzeń mobilnych umożliwia również użytkownikom interakcję interfejsem rozpoznawania głosu (voice recognition), takim jak Siri z Apple.

W odniesieniu do niektórych komputerów perspektywa użytkownika jest nikła lub nie występuje wcale. Na przykład komputery wbudowane (embedded computers) w urządzenia domowe i samochody mogą mieć klawiatury numeryczne i mogą zapalać lub gasić lampki (diody) kontrolne w celu ukazywania stanu, lecz one same oraz ich systemy operacyjne i aplikacje są pomyślane głównie do działania bez interwencji użytkownika.

1.1.2. Perspektywa systemu

Z perspektywy komputera system operacyjny jest programem najmocniej powiązanym ze sprzętem. W tym kontekście możemy uważać system operacyjny za dystrybutora zasobów (alokator zasobów – resource allocator). System komputerowy ma wiele zasobów, które mogą być potrzebne do rozwiązania zadania: czas procesora, obszary pamięci operacyjnej i pamięci masowej, urządzenia wejścia-wyjścia itd. System operacyjny pełni funkcję zarządcy owych dóbr. Mając do czynienia z licznymi i, być może, konfliktowymi zamówieniami zasobów, system operacyjny musi rozstrzygać, jak przydzielać je poszczególnym programom i użytkownikom, mając na względzie wydajne i uczciwe działanie całego systemu komputerowego.

W nieco innym spojrzeniu na system operacyjny akcentuje się zapotrzebowanie na sterowanie rozmaitymi urządzeniami wejścia-wyjścia i programami użytkownika. System operacyjny jest programem sterującym. Program sterujący (control program) nadzoruje pracę programów użytkownika, przeciwdziałając błędom i zapobiegając niewłaściwemu użyciu komputera. Zajmuje się zwłaszcza obsługiwaniem i kontrolowaniem pracy urządzeń wejścia-wyjścia.

1.1.3. Definiowanie systemów operacyjnych

Widać już zapewne, że termin system operacyjny obejmuje wiele ról i funkcji. Jest to prawdą, przynajmniej po części, z powodu olbrzymiej liczby konstrukcji i zastosowań komputerów. Komputery występują w opiekaczach, pojazdach, jednostkach pływających i statkach kosmicznych, w domach i w biznesie. Stanowią podstawowy element konsoli do gier, tunerów telewizji kablowej i systemów sterowania obiektami przemysłowymi.

Aby wyjaśnić to zróżnicowanie, możemy posłużyć się historią komputerów. Choć komputery mają stosunkowo krótką historię, rozwijały się niezwykle gwałtownie. Obliczenia cyfrowe rozpoczęto tytułem eksperymentu mającego ustalić, co dałoby się zrobić, lecz szybko przesunięto je do etapu systemów o określonym celu – znalazły zastosowania wojskowe, takie jak łamanie szyfrów i wykreślanie trajektorii, oraz okazały się przydatne w administracji państwowej, na przykład do pomocy w spisie ludności. Te wczesne komputery wyewoluowały do postaci tzw. komputerów głównych (mainframes), wielofunkcyjnych i mających ogólne przeznaczenie, i to właśnie wtedy narodziły się systemy operacyjne. W latach 60. ubiegłego wieku prawo Moore'a (Moore's Law) przewidywało, że liczba tranzystorów w układzie scalonym będzie się podwajać co 18 miesięcy i to przewidywanie się sprawdziło. Komputery zyskały na funkcjonalności i zmniejszyły swoje wymiary, co zaowocowało olbrzymią liczbą zastosowań i wielką różnorodnością systemów operacyjnych (zob. dodatek A, w którym zawarliśmy więcej szczegółów dotyczących historii systemów operacyjnych).

Jak wobec tego zdefiniować, czym jest system operacyjny? Ogólnie biorąc, nie mamy żadnej kompletnej i adekwatnej definicji systemu operacyjnego. Systemy operacyjne istnieją, ponieważ stanowią rozsądny sposób rozwiązywania problemu kreowania użytecznego systemu obliczeniowego. Podstawowym celem systemów komputerowych jest wykonywanie programów i ułatwianie rozwiązywania zadań stawianych przez użytkownika. Do spełnienia tego celu konstruuje się sprzęt komputerowy. Programy te wymagają pewnych wspólnych operacji, takich jak sterowanie pracą urządzeń wejścia-wyjścia. Wspólne funkcje sterowania i przydzielania zasobów gromadzi się zatem w jednym fragmencie oprogramowania – systemie operacyjnym.

Nie dysponujemy ponadto uniwersalnie akceptowaną definicją tego, co jest częścią systemu operacyjnego. W uproszczeniu przyjmuje się, że w jego skład wchodzi wszystko, co dostawca wysyła w odpowiedzi na nasze zamówienie na system operacyjny. Jednakże w zależności od rodzaju systemu oferowane właściwości są bardzo zróżnicowane. Niektóre systemy zajmują mniej niż 1 MB pamięci i nie mają pełnoekranowego edytora, podczas gdy inne wymagają gigabajtów przestrzeni i w całości są oparte na graficznych systemach okien. Częściej spotykana definicja – i to z niej będziemy zwykle korzystać – określa system operacyjny jako ten program, który działa w komputerze nieustannie (nazywany zazwyczaj jądrem – kernel). Oprócz jądra istnieją dwa inne typy programów: programy systemowe (system programs), powiązane z systemem operacyjnym, lecz niekoniecznie będące częścią jądra, i programy użytkowe, czyli tzw. aplikacje (application programs), do których zalicza się wszystkie programy niezwiązane z działaniem systemu.

Pytanie o to, co tworzy system operacyjny, nabrało większego znaczenia wraz z szerokim upowszechnieniem komputerów osobistych i nastaniem coraz bardziej wyrafinowanych systemów operacyjnych. W 1998 roku Ministerstwo Sprawiedliwości Stanów Zjednoczonych wniosło pozew przeciw firmie Microsoft, oskarżając ją w istocie o zaliczenie zbyt wielu funkcji do jej systemu operacyjnego i w ten sposób uniemożliwienie konkurowania z nim dostawców aplikacji (np. nieodłączną częścią microsoftowych systemów operacyjnych była przeglądarka sieciowa). W rezultacie Microsoft został uznany winnym stosowania w swoim systemie operacyjnym praktyk monopolistycznych mających ograniczyć konkurencję.

Gdy jednak dzisiaj spojrzymy na systemy operacyjne urządzeń przenośnych, widzimy, że liczba cech współtworzących system operacyjny zwiększyła się jeszcze bardziej. Mobilne systemy operacyjne zawierają zwykle nie tylko rdzenne jądro, lecz również warstwę pośrednią (middleware) – zbiór ram programowych (software frameworks) dostarczających dodatkowe usługi twórcom aplikacji. Na przykład każdy z dwóch najbardziej znanych systemów operacyjnych: iOS z Apple i Android z Google oprócz jądra jest wyposażony w warstwę pośrednią udostępniającą bazy danych, multimedia i możliwości graficzne (by wymienić tylko niektóre).

PO CO STUDIOWAĆ SYSTEMY OPERACYJNE?

Choć wiele osób uprawia informatykę od strony praktycznej, tylko niewielki ich odsetek będzie zaangażowany w tworzenie lub modyfikowanie systemu operacyjnego. Dlaczego zatem miałyby one studiować systemy operacyjne oraz ich działanie? Z tego prostego powodu, że prawie cały kod jest wykonywany powyżej systemu operacyjnego, więc znajomość działania systemów operacyjnych ma zasadnicze znaczenie dla właściwego, wydajnego, efektywnego i bezpiecznego programowania. Rozumienie podstaw systemów operacyjnych, tego, w jaki sposób sterują one sprzętem komputerowym i co umożliwiają aplikacjom, jest nie tylko istotne dla tych, którzy programują systemy, lecz również bardzo przydatne dla osób, które piszą programy usytuowane powyżej nich i z takich programów korzystają.

Podsumowując, na nasz użytek system operacyjny pojmujemy jako składający się z nieustannie działającego jądra, ram usytuowanych w warstwie pośredniej, ułatwiających budowanie aplikacji i dostarczających innych możliwości, oraz z programów systemowych pomagających w zarządzaniu systemem podczas jego pracy. W tym podręczniku w większości skupiamy się na jądrze systemów operacyjnych ogólnego przeznaczenia, niemniej omawiamy też inne komponenty, gdy jest to potrzebne do pełnego wyjaśnienia konstrukcji i działania systemu operacyjnego.

1.2. Organizacja systemu komputerowego

Współczesny system komputerowy ogólnego przeznaczenia składa się z jednego lub więcej procesorów[6] i pewnej liczby (sprzętowych) sterowników urządzeń (device controllers) połączonych wspólną szyną (magistralą – bus) umożliwiającą kontakt ze wspólną pamięcią (rys. 1.2). Każdy sterownik urządzenia odpowiada za określony typ urządzenia (np. za napędy dysków, urządzenia dźwiękowe lub wyświetlacze obrazu). Do sterownika, zależnie od jego typu, można podłączyć więcej niż jedno urządzenie. Na przykład jeden systemowy port USB można podłączyć do koncentratora („huba”) USB, do którego można podłączyć kilka urządzeń. Sterownik urządzenia zarządza pewną ilością lokalnej pamięci buforowej i zbiorem specjalizowanych rejestrów. Sterownik taki odpowiada za przemieszczanie danych między urządzeniami zewnętrznymi, nad którymi sprawuje nadzór, a swoją lokalną pamięcią buforową.

Dla każdego sterownika urządzenia systemy operacyjne mają zwykle moduł sterujący urządzenia (device driver)[7]. Ów moduł sterujący „rozumie” sterownik (rozpoznaje jego sygnały) i tworzy dla reszty systemu operacyjnego jednolity interfejs do urządzenia. Jednostka centralna i sterowniki urządzeń mogą pracować równolegle, rywalizując o cykle pamięci. Zapewnienie uporządkowanego dostępu do pamięci jest zadaniem sterownika pamięci, który ten dostęp synchronizuje.

W następnych podrozdziałach podajemy niektóre podstawowe wiadomości dotyczące działania takiego systemu, koncentrując się na jego trzech zasadniczych aspektach. Zaczynamy od przerwań, które powiadamiają jednostkę centralną o zdarzeniach wymagających uwagi. Potem omawiamy strukturę pamięci i budowę wejścia-wyjścia.

Rys. 1.2. Typowy system komputerowy PC

1.2.1. Przerwania

Rozważmy typowe działanie komputera – program wykonujący operację wejścia-wyjścia. Aby rozpocząć operację we-wy, moduł obsługi urządzenia umieszcza zawartość odpowiednich rejestrów w sterowniku urządzenia. Wtedy sterownik urządzenia sprawdza zawartość tych rejestrów, żeby określić, jaką czynność ma podjąć (np. „czytaj znak z klawiatury”). Sterownik zaczyna przesyłanie danych z urządzenia do swojego lokalnego bufora. Gdy przesyłanie danych się zakończy, sterownik urządzenia poinformuje moduł obsługi urządzenia, że zakończył działanie. Moduł obsługi przekazuje wówczas sterowanie do innych części systemu operacyjnego, być może zwracając dane lub wskaźnik do danych, jeśli była to operacja czytania. W przypadku innych operacji moduł obsługi zwraca informację statusową w rodzaju „pisanie zakończone pomyślnie” lub „urządzenie zajęte”. W jaki sposób jednak (sprzętowy) sterownik informuje (programowy) moduł obsługi, że zakończył operację? Robi to za pomocą przerwania (interrupt).

1.2.1.1. Przegląd

Sprzęt może spowodować przerwanie w dowolnej chwili, wysyłając sygnał do procesora, zwykle za pośrednictwem szyny systemowej. (W systemie komputerowym może być wiele szyn, lecz szyna systemowa jest główną magistralą komunikacyjną między jego głównymi komponentami). Przerwania są używane w wielu różnych celach, są również podstawowym sposobem współpracy systemów operacyjnych ze sprzętem.

Procesor (jednostka centralna) po otrzymaniu sygnału przerwania wstrzymuje aktualnie wykonywaną pracę i natychmiast przechodzi do ustalonego miejsca w pamięci. Miejsce to zawiera na ogół adres startowy procedury obsługującej dane przerwanie. Następuje wykonanie procedury obsługi przerwania, po zakończeniu której jednostka centralna wznawia przerwane obliczenia. Przebieg czasowy tych operacji widać na rysunku 1.3.

Przerwania są ważnym elementem architektury komputera. Poszczególne rodzaje komputerów mają indywidualne mechanizmy przerwań, niemniej jednak kilka ich funkcji jest wspólnych. Przerwanie musi przekazywać sterowanie do odpowiedniej procedury obsługi przerwania. Prosty sposób spowodowania tego polega na wywołaniu ogólnej procedury sprawdzającej informacje opisujące przerwanie, która na tej podstawie wywoła konkretną procedurę obsługi przerwania. Jednak przerwania muszą być obsługiwane szybko, gdyż występują bardzo często. Procedura obsługi przerwania jest więc wywoływana pośrednio z użyciem tablicy, bez konieczności stosowania procedury pośredniczącej. Ta tablica wskaźników (do procedur obsługujących przerwania) jest na ogół przechowywana w pamięci dolnej, zajmując w niej – powiedzmy – pierwszych sto komórek. Nosi ona nazwę wektora przerwań (interrupt vector) i jest indeksowana jednoznacznym numerem urządzenia podanym w zamówieniu (żądaniu) przerwania, dzięki czemu otrzymuje się właściwy adres procedury obsługującej przerwanie zgłoszone przez dane urządzenie. Nawet tak różne systemy operacyjne jak Windows i UNIX kierują przerwania do obsługi w opisany sposób.

W architekturze przerwań trzeba również uwzględniać przechowywanie informacji o stanie tego, co zostało przerwane, aby można było go odtworzyć po obsłużeniu przerwania. Jeżeli procedura obsługi chce zmienić stan procesora – na przykład przez zmodyfikowanie wartości rejestrów – to musi jawnie przechować stan bieżący, a przy końcu swojego działania musi go odtworzyć. Po obsłużeniu przerwania następuje pobranie do licznika rozkazów przechowanego adresu powrotnego i wznowienie przerwanych obliczeń, tak jakby przerwania nie było.

Rys. 1.3. Przebieg czasowy wykonywania operacji wejścia-wyjścia w jednym programie

1.2.1.2. Implementacja

Podstawowy mechanizm przerwania działa następująco. Osprzęt procesora ma ścieżkę nazywaną linią żądania przerwania (interrupt-request line), którą procesor bada po wykonaniu każdego rozkazu. Jeśli procesor (CPU) wykryje, że sterownik zasygnalizował coś w linii żądania przerwania, to przeczyta numer przerwania i wykona skok do procedury obsługi przerwania (interrupt-handler routine), posługując się numerem przerwania jako indeksem w wektorze przerwań. Rozpoczyna wtedy wykonywanie (następnych rozkazów), poczynając od adresu skojarzonego z indeksem. Procedura obsługi przerwania przechowuje wszystko, co dotyczy stanu, który zostanie zmieniony podczas jej działania, rozpoznaje przyczynę przerwania, wykonuje niezbędne czynności, a po nich rozkaz powrotu z przerwania (return from interrupt) mający na celu przywrócenie procesora do stanu wykonywania sprzed przerwania. Mówimy, że sterownik urządzenia zgłasza (raises) przerwanie za pomocą sygnału podawanego w linii żądania przerwania, procesor przechwytuje (catches) przerwanie i ekspediuje je do procedury obsługi przerwania, a ta czyści przerwanie, obsługując urządzenie. Na rysunku 1.4 są pokazane elementy cyklu wejścia-wyjścia sterowanego za pomocą przerwania.

Opisany podstawowy mechanizm przerwania umożliwia procesorowi reagowanie na zdarzenia asynchroniczne, takie jak wystąpienie w sterowniku urządzenia gotowości do pracy. W nowoczesnym systemie operacyjnym potrzebujemy jednak bardziej złożonych możliwości obsługiwania przerwań.

1. Jest nam potrzebne opóźnianie obsługi przerwania wówczas, gdy są wykonywane działania krytyczne.

2. Potrzebujemy sprawnego sposobu przejścia do procedury obsługi przerwania właściwej dla danego urządzenia.

3. Są nam potrzebne przerwania wielopoziomowe, aby system operacyjny mógł odróżniać przerwania wysokopriorytetowe od niskopriorytetowych i reagować z odpowiednim stopniem pilności.

W nowoczesnym sprzęcie komputerowym te trzy własności są zapewniane przez jednostkę centralną oraz sprzęt sterownika przerwań (interrupt controller hardware).

Rys. 1.4. Cykl wejścia-wyjścia obsługiwany za pomocą przerwań

Większość procesorów ma dwie linie żądania przerwań. Jedna z nich służy do przerwań niemaskowalnych (nonmaskable interrupts) i jest zarezerwowana dla takich zdarzeń jak nieusuwalne błędy pamięci. Druga linia przerwań jest maskowalna (mascable) – można ją wyłączyć za pomocą procesora przed wykonaniem krytycznych ciągów rozkazów, których nie wolno przerywać. Przerwania maskowalne są używane przez sterowniki urządzeń do zgłaszania żądań obsługi.

Przypomnijmy, że celem mechanizmu wektorowego jest zredukowanie konieczności przeszukiwania przez poszczególne procedury obsługi przerwań wszystkich możliwych źródeł przerwań, aby rozstrzygnąć, które wymaga obsługi. Jednak w praktyce komputery mają więcej urządzeń (a więc i procedur obsługi przerwań) niż pozycji adresowych w wektorze przerwań. Popularnym sposobem rozwiązywania tego problemu jest zastosowanie techniki łańcuchowania przerwań (interrupt chaining), w której każdy element wektora przerwań wskazuje na czoło listy procedur obsługi przerwań. Po zgłoszeniu przerwania procedury obsługowe z odpowiedniej listy są wywoływane jedna po drugiej, aż zostanie odnaleziona ta, która potrafi obsłużyć dane żądanie. Struktura taka jest kompromisem między kosztem wielkiej tablicy przerwań a nieefektywnością kierowania przerwania do jednej procedury obsługi.

Na rysunku 1.5 pokazano rozwiązanie wektora przerwań przyjęte w procesorach Intela. Zdarzenia z pozycji od 0 do 31 są niemaskowalne; sygnalizują rozmaite sytuacje błędne. Pozycje od 32 do 255 są maskowalne i są używane do sygnalizowania takich zdarzeń jak przerwania generowane przez urządzenia.

Rys. 1.5. Tablica wektorów zdarzeń procesora Intela

W skład mechanizmu przerwań wchodzi też system poziomów priorytetów przerwań (interrupt priority levels). Poziomy te umożliwiają opóźnianie przez procesor obsługi przerwań niskopriorytetowych bez maskowania wszystkich przerwań i pozwalają przerwaniom wysokopriorytetowym wywłaszczać obsługę przerwań niskopriorytetowych.

Podsumowując, przerwania są używane w nowoczesnych systemach operacyjnych do obsługi asynchronicznych zdarzeń (i do innych celów, które omówimy dalej). Sterowniki urządzeń i błędy sprzętowe powodują zgłaszanie przerwań. Aby umożliwić wykonywanie większości pilnych prac w pierwszej kolejności, nowoczesne komputery wykorzystują system priorytetów przerwań. Ponieważ przerwania są używane bardzo intensywnie do przetwarzania, w którym bardzo zależy na czasie, do zapewnienia dobrej wydajności systemu jest potrzebna sprawna obsługa przerwań.

1.2.2. Struktura pamięci

Jednostka centralna może pobierać rozkazy tylko z pamięci, dlatego każdy program musi być przed wykonaniem do niej załadowany. Komputery ogólnego przeznaczenia wykonują większość swoich programów w pamięci wielokrotnie zapisywalnej, nazywanej pamięcią główną (pamięcią operacyjną – main memory), a niekiedy też pamięcią o dostępie swobodnym (random access memory), czyli RAM. Pamięć główna jest zwykle wykonana w technologii półprzewodnikowej, zwanej dynamiczną pamięcią o swobodnym dostępie (dynamic random access memory – DRAM).

Komputery używają również innych rodzajów pamięci. Na przykład pierwszym programem wykonywanym po włączeniu komputera jest program rozruchowy (bootstrap program), który następnie ładuje system operacyjny. Ponieważ pamięć RAM jest ulotna (volatile) – traci zawartość po wyłączeniu zasilania – nie możemy jej powierzyć przechowywania programu rozruchowego. Do tego, i do paru innych celów, komputer używa elektrycznie kasowalnej programowalnej pamięci tylko do czytania (electrically erasable programmable read-only memory – EEPROM) oraz innych rodzajów oprogramowania układowego (firmware) – pamięci, która jest rzadko zapisywana i nieulotna. Zawartość EEPROM można zmieniać, lecz nie można dokonywać tych zmian często. Ponadto pamięć ta działa wolno, zawiera więc najczęściej statyczne programy i dane, które nie są często używane. Na przykład w iPhonie wykorzystuje się EEPROM do przechowywania numerów seryjnych i sprzętowych informacji o tym urządzeniu.

Wszystkie postaci pamięci tworzą tablicę słów. Każdy bajt ma własny adres. Współpraca z pamięcią główną (operacyjną) odbywa się za pomocą ciągu rozkazów load (pobierz) lub store (przechowaj) odnoszących się do określonych adresów. Rozkaz load powoduje pobranie słowa z pamięci głównej do wewnętrznego rejestru jednostki centralnej[8], natomiast rozkaz store powoduje umieszczenie zawartości rejestru procesora w pamięci głównej. Oprócz jawnych pobrań i przechowań procesor automatycznie pobiera z pamięci głównej rozkazy do wykonania.

POJĘCIA I OZNACZENIA ZWIĄZANE Z PAMIĘCIĄ

Podstawową jednostką pamięci komputera jest bit (binary digit – cyfra dwójkowa). Bit może zawierać jedną z dwu wartości: 0 lub 1. Wszelka inna pamięć w komputerze jest oparta na zbiorach bitów. Zdumiewa, że mając wystarczająco dużo bitów, komputer może reprezentować liczby, litery, obrazy, filmy, dźwięki, dokumenty i programy – by wymienić tylko kilka[9]. Osiem bitów stanowi ilość pamięci, którą określa się jako bajt (byte) i dla większości komputerów jest to najmniejsza wygodna porcja pamięci. Na przykład większość komputerów nie ma rozkazów przesuwania bitu, lecz ma rozkaz przesunięcia bajta. Mniej popularnym terminem jest słowo (word) będące jednostką danych właściwą dla architektury danego komputera. Słowo składa się z jednego lub więcej bajtów. Na przykład komputer, który ma 64-bitowe rejestry i 64-bitowe adresowanie pamięci, ma zazwyczaj 64-bitowe słowa. Wiele spośród swoich instrukcji komputer wykonuje od razu na właściwych sobie słowach, a nie po jednym bajcie.

Pamięć komputera oraz w większości przypadków jego przepustowość są na ogół mierzone w bajtach i ciągach bajtów, i w takich również porcjach dokonuje się na niej manipulacji. Kilobajt (kilobyte), czyli KB, to 1024 bajty, megabajt, czyli MB, wynosi 1024^2 bajtów, gigabajt, czyli GB, oznacza 1024^3 bajtów, terabajt, czyli TB, to 1024^4 bajtów, a petabajt, czyli PB, to 1024^5 bajtów. Producenci dysków często zaokrąglają te liczby, mówiąc że megabajt wynosi 1 milion bajtów, a gigabajt to miliard bajtów[10]. Wyjątek od tej ogólnej reguły stanowią miary stosowane w sieciach – są podawane w bitach (ponieważ sieci przenoszą dane bit za bitem).

Typowy cykl rozkazowy w systemie o architekturze von Neumanna zaczyna się od pobrania rozkazu z pamięci i przesłania go do rejestru rozkazów (instruction register). Rozkaz jest następnie dekodowany i może spowodować pobranie argumentów z pamięci i umieszczenie ich w jakimś rejestrze wewnętrznym. Po wykonaniu rozkazu na argumentach jego wynik można z powrotem przechować w pamięci. Zauważmy, że jednostka pamięci „widzi” tylko strumień adresów pamięci. Nie jest jej znany sposób, w jaki one powstały (z użyciem licznika rozkazów, indeksowania, modyfikacji pośredniej, adresów literalnych itp.) ani czemu służą (rozkazy lub dane). Z uwagi na to możemy pominąć sposób generowania adresu pamięci przez program. Interesujemy się tylko ciągiem adresów pamięci wytwarzanych przez wykonywany program.

W idealnych warunkach moglibyśmy sobie życzyć, aby program i dane stale pozostawały w pamięci głównej. W większości systemów zwykle nie jest to możliwe z dwu powodów:

1. Pamięć główna jest zazwyczaj za mała, aby przechowywać na stałe wszystkie potrzebne programy i dane.

2. Pamięć główna, jak już wspominaliśmy, jest ulotna – traci swoją zawartość po odłączeniu zasilania.

Wobec tego większość systemów komputerowych jest wyposażona w pamięć pomocniczą (pamięć drugorzędną – secondary storage), która rozszerza pamięć główną. Od pamięci pomocniczej wymaga się przede wszystkim, aby mogła trwale przechowywać duże ilości danych.

Najpopularniejszymi urządzeniami pamięci pomocniczej są napędy dysków twardych (hard-disk drives – HDDs) oraz nieruchome urządzenia pamięci (nonvolatile memory devices – NVM)[11] umożliwiające zapamiętywanie zarówno programów, jak i danych. Większość programów (systemowych i aplikacji) przechowuje się w pamięci pomocniczej, zanim nastąpi ich umieszczenie w pamięci głównej. Wiele programów używa potem pamięci pomocniczej zarówno jako źródła, jak i miejsca przeznaczenia przetwarzanych przez siebie informacji. Dlatego właściwe zarządzanie pamięcią pomocniczą ma zasadnicze znaczenie w systemie komputerowym, co omówiliśmy w rozdziale 11.

W szerszym jednak rozumieniu przedstawiona przez nas struktura pamięci – składająca się z rejestrów, pamięci głównej i pamięci pomocniczej – jest tylko jednym z wielu możliwych rozwiązań systemu pamięci. Do innych stosowanych składowych należą pamięci podręczne, pamięci na płytach CD-ROM lub Blu-ray, taśmy magnetyczne itd. Te z nich, które są dość wolne, lecz wystarczająco duże, tak że nadają się tylko do specjalnych zastosowań – na przykład do przechowywania kopii zapasowych materiałów przechowywanych na innych urządzeniach – są nazywane pamięcią trzeciorzędną (tertiary storage). Każdy system pamięci spełnia podstawowe funkcje magazynowania i długotrwałego utrzymywania danych, aby mogły one być z niego odzyskane. To, co przede wszystkim różni te systemy pamięci, to szybkość działania, koszt, rozmiar i ulotność.

Rozmaite rodzaje systemów pamięci można zorganizować w hierarchię (rys. 1.6) według ich pojemności i czasu dostępu. Jako ogólną regułę przyjmuje się kompromis między rozmiarem a szybkością, przy czym mniejsza i szybsza pamięć występuje bliżej procesora. Jak pokazano na rysunku, oprócz różnic w szybkości i pojemności różne systemy pamięci są ulotne albo nieulotne. Pamięć ulotna – co powiedziano już wcześniej – traci zawartość po odcięciu zasilania od urządzenia, więc w celu bezpiecznego przechowywania dane muszą być zapisywane w pamięci nieulotnej.

Rys. 1.6. Hierarchia urządzeń pamięci

Przedstawione na rysunku cztery górne poziomy pamięci są zbudowane z użyciem pamięci półprzewodnikowej (semiconductor memory), która składa się z półprzewodnikowych układów elektronicznych. Urządzenia NVM na czwartym poziomie mają kilka odmian, lecz ogólnie biorąc są szybsze niż dyski twarde. Najpopularniejszą postacią urządzeń NVM jest pamięć flash, powszechnie stosowana w urządzeniach przenośnych, takich jak smartfony i tablety. Pamięć flash w coraz większym stopniu jest również używana w charakterze pamięci długotrwałej w laptopach, komputerach biurkowych i serwerach.

Ponieważ pamięć odgrywa ważną rolę w strukturze systemu operacyjnego, będziemy się do niej często odwoływać w tej książce. Ogólnie biorąc, będziemy się posługiwać następującą terminologią:

• Pamięć ulotną będziemy nazywać po prostu pamięcią (memory)[12]. Jeśli będziemy potrzebowali podkreślić, że chodzi o jakiś konkretny typ pamięci (np. rejestr), zrobimy to jawnie.

• Pamięć nieulotna zachowuje swą zawartość również po utracie zasilania. Będziemy ją określać skrótowo – NVS. Nasze uwagi dotyczące pamięci NVS najczęściej będą się odnosiły do pomocniczej (drugorzędnej) pamięci magazynującej. Ten rodzaj pamięci można podzielić na dwa typy:

– mechaniczny – kilkoma przykładami systemów takiej pamięci są dyski twarde (HDDs), dyski optyczne, holograficzna pamięć magazynująca i taśma magnetyczna; gdy będziemy chcieli zwrócić uwagę na konkretny typ urządzenia mechanicznej pamięci magazynującej (np. na taśmę magnetyczną), uczynimy to jawnie;

– elektryczny – przykładami systemów takiej pamięci są: pamięć flash, FRAM, NRAM i SSD; elektryczna pamięć magazynująca będzie określana jako NVM, jeśli będziemy chcieli zwrócić uwagę na konkretny typ urządzenia elektrycznej pamięci magazynującej (np. SSD), zrobimy to jawnie.

Pamięć mechaniczna jest na ogół większa i tańsza w przeliczeniu na bajt niż pamięć elektryczna. I na odwrót, pamięć elektryczna jest z reguły droższa, mniejsza i szybsza niż pamięć mechaniczna.

W projekcie kompletnego systemu pamięci trzeba zrównoważyć wszystkie wymienione czynniki. Należy w nim uwzględnić tylko tyle drogiej pamięci, ile jest konieczne, dodając tyle niedrogiej, nieulotnej pamięci magazynującej, ile się da. Tam, gdzie występują duże dysproporcje w czasie dostępu lub szybkości przesyłania między dwoma komponentami, w celu polepszenia wydajności można zainstalować pamięci podręczne.

1.2.3. Budowa wejścia-wyjścia

Duża część kodu systemu operacyjnego służy do zarządzania wejściem-wyjściem, zarówno dlatego, że jest to ważne z uwagi na niezawodność i sprawne działanie systemu, jak i z powodu różnorodności urządzeń.

Przypomnijmy, zostało to już powiedziane na początku tego podrozdziału, że uniwersalny system komputerowy składa się z wielu urządzeń wymieniających dane za pośrednictwem wspólnej szyny. Sterowanie wejściem-wyjściem za pomocą przerwań, opisane w punkcie 1.2.1, jest do przyjęcia w przypadku przemieszczania małej ilości danych, lecz mogłoby spowodować znaczne nakłady, gdyby zastosować je do przemieszczania wielkiej ilości danych, jak w przypadku działań we-wy na pamięci NVS. Aby rozwiązać ten problem, stosuje się bezpośredni dostęp do pamięci (direct memory access – DMA). Po ustawieniu buforów, wskaźników i liczników sterownik danego urządzenia przesyła bezpośrednio cały blok danych między własnym buforem a pamięcią główną – bez żadnej interwencji procesora. Przerwanie wypada wówczas jeden raz na cały blok danych, aby powiadomić moduł sterujący o zakończeniu operacji, a nie po przesłaniu każdego bajta, jak to się dzieje w przypadku powolnych urządzeń zewnętrznych. Gdy sterownik urządzenia wykonuje te czynności, procesor może zajmować się inną pracą.

W niektórych systemach wysokiej klasy zamiast architektury szynowej stosuje się przełączniki. W tych systemach wiele elementów może się komunikować z innymi współbieżnie, nie rywalizując o cykle na współużytkowanej szynie. W takim przypadku technika DMA jest jeszcze efektywniejsza. Na rysunku 1.7 pokazano współpracę wszystkich składowych systemu komputerowego.

Rys. 1.7. Działanie nowoczesnego systemu komputerowego

1.3. Architektura systemu komputerowego

W podrozdziale 1.2 przedstawiliśmy ogólną strukturę typowego systemu komputerowego. System komputerowy może być zorganizowany na wiele sposobów, które z grubsza możemy podzielić stosownie do liczby użytych procesorów ogólnego przeznaczenia.

1.3.1. Systemy jednoprocesorowe

Przed wieloma laty w większości systemów komputerowych używano jednego procesora zawierającego jedną jednostkę centralną (CPU) z jednym rdzeniem przetwarzającym. Rdzeń (core) jest elementem wykonującym rozkazy i używającym rejestrów do lokalnego przechowywania danych. Jedna główna jednostka centralna ze swoim rdzeniem potrafi wykonywać zbiór rozkazów ogólnego przeznaczenia, w tym rozkazów pochodzących z procesów. Te systemy mają też inne procesory, o specjalnym przeznaczeniu. Mogą one występować jako specjalizowane procesory w poszczególnych urządzeniach, takich jak dyski, klawiatura i sterownik graficzny.

Każdy z tych specjalizowanych procesorów wykonuje ograniczony zbiór rozkazów i nie wykonuje procesów. Niekiedy są one zarządzane przez system operacyjny w tym sensie, że system operacyjny wysyła im informacje o ich kolejnych zadaniach i monitoruje ich stan. Na przykład mikroprocesor sterownika dysku otrzymuje ciąg zamówień od głównego procesora i realizuje własną kolejkę dyskową oraz algorytm planowania. Taka organizacja uwalnia jednostkę centralną od konieczności planowania dostępu do dysku. Komputery PC zawierają w klawiaturze mikroprocesor do zamiany uderzeń w klawisze na kody wysyłane do CPU. W innych systemach lub okolicznościach specjalizowane procesory są niskopoziomowymi komponentami wbudowanymi w sprzęt. System operacyjny nie może komunikować się z tymi procesorami; swoje prace wykonują one autonomicznie. Zastosowanie mikroprocesorów specjalnego przeznaczenia jest powszechne i nie przekształca systemu jednoprocesorowego w wieloprocesor. Jeżeli mamy do czynienia tylko z jedną uniwersalną CPU z jednym rdzeniem przetwarzania, to taki system jest jednoprocesorowy. Jednak zgodnie z tą definicją bardzo niewiele współczesnych systemów komputerowych jest jednoprocesorowych.

1.3.2. Systemy wieloprocesorowe

W krajobrazie techniki obliczeniowej tworzonym przez nowoczesne komputery – od urządzeń mobilnych po serwery – dominują systemy wieloprocesorowe (multiprocessor systems). W tradycyjnym rozumieniu systemy takie mają dwa procesory (lub więcej), każdy z jednordzeniową jednostką centralną (CPU). Procesory dzielą (współużytkują) szynę komputera, a czasami również zegar, pamięć i urządzenia zewnętrzne. Podstawową zaletą systemów wieloprocesorowych jest zwiększona przepustowość. To znaczy spodziewamy się, że dzięki zwiększeniu liczby procesorów więcej pracy zostanie wykonane w krótszym czasie. Jednak współczynnik przyspieszenia przy N procesorach jest mniejszy niż N. Kooperacyjne wykonywanie jakiegoś zadania przez wiele procesorów nie obywa się bez pewnych kosztów wynikających z dbałości o poprawne działanie wszystkich części. Ten nakład oraz rywalizacja o dzielone zasoby zmniejsza oczekiwany zysk z zastosowania dodatkowych procesorów.

W większości typowych systemów wieloprocesorowych jest stosowane wieloprzetwarzanie symetryczne (symmetric multiprocessing – SMP), w którym każdy z równorzędnych procesorów wykonuje wszystkie prace, łącznie z funkcjami systemu operacyjnego i procesami użytkowników. Na rysunku 1.8 przedstawiono typową architekturę SMP z dwoma procesorami, z których każdy ma własną CPU. Zauważmy, że procesor w każdej jednostce centralnej ma własny zbiór rejestrów, a także prywatną – lub lokalną – pamięć podręczną. Jednak wszystkie procesory dzielą pamięć fizyczną poprzez szynę systemową.

Korzyść z tego modelu polega na tym, że jednocześnie można wykonywać wiele procesów. Jeśli mamy N jednostek centralnych, to N procesów może działać bez istotnego pogarszania wydajności. Ponieważ jednak jednostki centralne są odosobnione, któraś może pozostawać bezczynna, podczas gdy inna może być nadmiernie obciążona, co prowadzi do nieefektywności. Tych nieefektywności uda się uniknąć, jeśli procesory będą dzieliły pewne struktury danych. Taki system wieloprocesorowy będzie umożliwiał dynamiczne obdzielanie procesami i zasobami – na przykład pamięcią – poszczególne procesory, co może się przyczynić do zmniejszenia nierównomierności ich obciążenia. System taki musi być napisany starannie, o czym przekonamy się w rozdziałach 5 i 6.

Z biegiem czasu definicja wieloprocesora ewoluowała i obecnie obejmuje systemy wielordzeniowe (multicore), w których wiele rdzeni obliczeniowych rezyduje w jednym chipie (układzie scalonym). Systemy wielordzeniowe mogą być znacznie wydajniejsze niż systemy z wieloma jednordzeniowymi chipami, gdyż komunikacja w obrębie chipu jest szybsza niż między chipami.

Rys. 1.8. Architektura wieloprzetwarzania symetrycznego

Ponadto jeden chip z wieloma rdzeniami zużywa znacznie mniej energii niż wiele chipów jednordzeniowych, co ma niebagatelne znaczenie w urządzeniach mobilnych oraz w laptopach.

Na rysunku 1.9 pokazaliśmy konstrukcję dwurdzeniową z dwoma rdzeniami w jednym układzie procesora. W tym projekcie każdy rdzeń ma własny zbiór rejestrów oraz własną, lokalną pamięć podręczną, często określaną jako kasz poziomu 1, czyli L1. Zauważmy też, że pamięć podręczna poziomu 2 (L2) jest lokalna dla chipu, lecz jest dzielona przez owe dwa rdzenie przetwarzające. W większości architektur występuje ten schemat – łączy on lokalne i dzielone pamięci podręczne, przy czym kasze lokalne, niższego poziomu, są na ogół mniejsze i szybsze niż współużytkowane kasze wyższego poziomu. Pomijając rozważania architektoniczne, takie jak pamięć podręczna, pamięć główna i rywalizacja o szynę, wielordzeniowy procesor z N rdzeniami jawi się systemowi operacyjnemu niczym N standardowych jednostek centralnych (CPU). Taka charakterystyka zmusza (a przynajmniej – skłania) projektantów systemów operacyjnych oraz osoby programujące aplikacje do efektywnego wykorzystania tych rdzeni przetwarzających – zagadnieniem tym zajmiemy się w rozdziale 4. Praktycznie wszystkie współczesne systemy operacyjne, w tym Windows, macOS i Linux, a także mobilne systemy Android i iOS, umożliwiają pracę w trybie SMP na wielordzeniowym sprzęcie.

Rys. 1.9. Konstrukcja dwurdzeniowa z dwoma rdzeniami w jednym układzie scalonym

DEFINICJE KOMPONENTÓW SYSTEMU KOMPUTEROWEGO

• CPU (jednostka centralna) – sprzęt wykonujący rozkazy.

• Procesor – fizyczny chip (układ scalony) zawierający jedną lub więcej CPU.

• Rdzeń – podstawowa jednostka obliczeniowa w CPU.

• Wielordzeń (system wielordzeniowy – muticore) – zawiera wiele rdzeni obliczeniowych w jednej CPU.

• Wieloprocesor – zawiera wiele procesorów.

Chociaż prawie wszystkie systemy są obecnie wielordzeniowe, stosujemy ogólny termin CPU (jednostka centralna) na określenie pojedynczej jednostki obliczeniowej systemu komputerowego oraz rdzeń, a także wielordzeń, gdy odwołujemy się konkretnie do jednego lub więcej rdzeni w CPU.

Zwiększenie liczby CPU w systemie wieloprocesorowym zwiększa jego moc obliczeniową. Jak jednak zauważono wcześniej, pomysł ten niezbyt dobrze się skaluje i gdy dodamy zbyt dużo CPU, powstaje rywalizacja o szynę systemową, która staje się wąskim gardłem i wydajność spada. Alternatywne podejście polega na wyposażeniu każdej jednostki centralnej (lub grupy CPU) we własną lokalną pamięć dostępną przez małą, szybką, lokalną szynę. Jednostki centralne są połączone dzielonym łączem wewnętrznym systemu (shared system interconnect), dzięki czemu wszystkie dzielą jedną fizyczną przestrzeń adresową. Takie rozwiązanie, znane jako niejednolity dostęp do pamięci (non-uniform memory access), czyli NUMA, jest przedstawione na rysunku 1.10. Jego przewaga polega na tym, że gdy CPU sięga do swojej lokalnej pamięci, dostęp do niej jest nie tylko szybki, lecz nie występuje również żadna rywalizacja w połączeniach wewnątrzsystemowych. A zatem systemy NUMA mogą być skalowane znacznie skuteczniej i można do nich dodawać więcej procesorów.

Potencjalną wadą w systemie NUMA jest zwiększone opóźnienie, gdy CPU musi sięgać do zdalnej pamięci przez wewnętrzne połączenia systemu, co może powodować pogorszenie sprawności. Innymi słowy, CPU_0 nie może uzyskać dostępu do lokalnej pamięci CPU_3 równie szybko jak do własnej lokalnej pamięci, zwalniając działanie. Systemy operacyjne mogą minimalizować ten koszt NUMA przez staranne planowanie CPU i odpowiednie zarządzanie pamięcią, co omówiono w punktach 5.5.2 i 10.5.4. Ponieważ systemy NUMA mogą być skalowane na wielką liczbę procesorów, stają się coraz popularniejsze w serwerach, a także w wysokowydajnych systemach obliczeniowych.

Na koniec serwery kasetowe (blade servers) to systemy, w których wiele kart z procesorami, kart urządzeń i kart sieciowych jest zamontowanych na jednej płycie montażowej. Różnica między nimi a konwencjonalnymi systemami wieloprocesorowymi polega na tym, że rozruch każdej karty procesora kasetowego następuje niezależnie i działa ona pod kontrolą własnego systemu operacyjnego. Niektóre karty serwerów kasetowych są również wieloprocesorami, co zaciera linie podziału między typami komputerów. W gruncie rzeczy te serwery składają się z wielu niezależnych systemów wieloprocesorowych.

Rys. 1.10. Architektura wieloprzetwarzania NUMA

1.3.3. Systemy zgrupowane

Innym typem systemu wieloprocesorowego jest system zgrupowany (system klastrowy – clustered system) gromadzący wiele jednostek centralnych. Systemy zgrupowane różnią się od systemów wieloprocesorowych opisanych w punkcie 1.3.2, gdyż składają się z dwóch lub więcej indywidualnych systemów – czyli węzłów – połączonych ze sobą. Każdy węzeł jest zazwyczaj systemem wielordzeniowym. Systemy takie uważa się za luźno powiązane (loosely coupled). Powinniśmy zwrócić uwagę, że definicja zgrupowania („klastrowości”) nie jest zwarta – wiele pakietów handlowych i będących w wolnym obiegu daje świadectwo zmagań z określeniem, czym jest „system klastrowy” i argumentacją, dlaczego jedna jego postać miałaby być lepsza od drugiej. Ogólnie przyjmuje się definicję, że grona komputerów (komputery klastrowe) dzielą pamięć masową i są blisko połączone siecią lokalną (local-area network – LAN), jak opisano w rozdziale 19, lub szybszymi łączami wewnętrznymi w rodzaju InfiniBand.

Klastrów (gron) używa się zwykle w celu uzyskania usług o dużej dostępności (high availability service), tj. usług świadczonych nawet wtedy, gdy jeden lub więcej systemów w klastrze ulegnie awarii. Dużą dostępność uzyskujemy na ogół, dodając do systemu pewien stopień nadmiarowości. W węzłach działa warstwa oprogramowania klastra. Każdy węzeł może nadzorować (za pośrednictwem sieci) jeden lub więcej innych węzłów. Jeśli nadzorowana maszyna ulega awarii, to maszyna nadzorująca może przejąć jej zasoby pamięciowe i wznowić aplikacje, które działały na maszynie uszkodzonej. Użytkownicy i klienci aplikacji zauważą tylko krótką przerwę w świadczeniu usług.

Duża dostępność zapewnia większą niezawodność, co ma zasadnicze znaczenie w wielu zastosowaniach. Zdolność kontynuowania usług w stopniu proporcjonalnym do liczby ocalałego sprzętu jest nazywana łagodną degradacją (graceful degradation). Niektóre systemy wychodzą poza łagodną degradację i są nazywane systemami tolerującymi awarie (fault-tolerant), ponieważ mogą przetrwać awarię dowolnej pojedynczej składowej, wciąż kontynuując działanie. Tolerowanie awarii wymaga mechanizmu wykrywania awarii, ich diagnozowania i – w miarę możliwości – usuwania.

Klastrom można nadawać strukturę asymetryczną lub symetryczną. W klastrach asymetrycznych (gronach asymetrycznych – asymmetric clustering) jedna z maszyn pozostaje w pogotowiu (hot standby mode), podczas gdy druga wykonuje aplikacje. Komputer będący w pogotowiu nie robi niczego poza pilnowaniem aktywnego serwera. Jeśli serwer ulegnie awarii, komputer pozostający w pogotowiu uaktywnia się jako serwer. W klastrach symetrycznych (gronach symetrycznych – symmetric clustering) dwa lub więcej komputerów sieciowych wykonuje aplikacje, doglądając się wzajemnie. Jest to oczywiście układ wydajniejszy, ponieważ wykorzystuje się w nim cały dostępny sprzęt. Występuje w nim jednak zapotrzebowanie na wykonywanie więcej niż jednej aplikacji.

Ponieważ klaster składa się z kilku systemów komputerowych połączonych siecią, można za pomocą klastrów uzyskiwać również środowiska o dużej wydolności obliczeniowej (obliczenia wysokowydajne – high-performance computing). Takie systemy mogą dostarczać znacznie więcej mocy obliczeniowej niż systemy jednoprocesorowe, a nawet systemy SMP, ponieważ mogą wykonywać aplikację współbieżnie na wszystkich komputerach klastra. Żeby wykorzystać zalety klastra, aplikacja musi być jednak specjalnie napisana. Uwzględnia się tu technikę równoległości (parallelization), w której program jest dzielony na osobne komponenty wykonywane równolegle na poszczególnych rdzeniach komputera lub komputerów klastra. Aplikacje takie są zwykle tak projektowane, że gdy każdy z węzłów obliczeniowych wykona swoją porcję zadania, wyniki ze wszystkich węzłów są łączone w ostateczne rozwiązanie.

Do innych rodzajów systemów zgrupowanych należą klastry równoległe oraz klastry tworzone w sieciach rozległych (wide-area Network – WAN), co opisano w rozdziale 19. Klastry równoległe umożliwiają dostęp wielu komputerów sieci do tych samych danych w dzielonej pamięci masowej. Ponieważ w większości systemów operacyjnych brakuje zaplecza umożliwiającego wielu komputerom sieciowym („hostom”) jednoczesność dostępu do danych, klastry równoległe zwykle wymagają zastosowania specjalnych wersji oprogramowania i specjalnych wydań aplikacji. Na przykład Oracle Real Application Server jest wersją bazy danych firmy Oracle zaprojektowaną do działania w klastrach równoległych. Każda maszyna wykonuje kod Oracle, a kontrolowanie dostępu do wspólnego dysku odbywa się za pomocą specjalnej warstwy oprogramowania. Każda maszyna ma pełny dostęp do wszystkich danych w bazie. Aby umożliwić ten dzielony dostęp, system musi również realizować kontrolowanie dostępu i ryglowanie (inaczej: blokowanie) w celu zagwarantowania bezkonfliktowości operacji. Ta funkcja, powszechnie nazywana zarządcą rozproszonego ryglowania (distributed lock manager – DLM), występuje w niektórych rozwiązaniach klastrów.

PŁYTA GŁÓWNA KOMPUTERA PC

Spójrzmy na pokazaną niżej płytę biurkowego komputera osobistego:

Ta płyta po zapełnieniu złączy staje się w pełni funkcjonalnym komputerem. Składa się z gniazda procesora zawierającego CPU, gniazd pamięci DRAM, szczelin szyny PCI i miejsc przyłączania urządzeń wejścia-wyjścia różnego typu. Nawet najtańsza jednostka centralna ogólnego przeznaczenia zawiera wiele rdzeni. Niektóre płyty mają po kilka gniazd procesorowych. W bardziej rozbudowanych komputerach może występować więcej płyt systemowych tworzących system NUMA.

Technika budowy klastrów wciąż ulega szybkim zmianom. Niektóre produkty klastrowe zawierają tysiące systemów w jednym klastrze, a poszczególne węzły mogą dzielić kilometrowe odległości. Wiele z tych udogodnień stało się możliwych dzięki sieciom pamięciowym (sieciom pamięci masowych – storage-area networks – SANs) co omówiono w punkcie 11.7.4, które umożliwiają łatwe dołączanie wielu systemów do puli pamięci masowej. Jeśli aplikacje i ich dane są przechowywane w SAN-ie, to oprogramowanie klastra może zlecić wykonanie aplikacji na dowolnym hoście[13] podłączonym do SAN-u. Jeśli host ulegnie awarii, dowolny inny host może przejąć jego obowiązki. W bazie danych klastra dziesiątki hostów mogą współużytkować tę samą bazę danych, co znacznie zwiększa wydajność i niezawodność. Na rysunku 1.11 ukazano ogólną budowę systemu zgrupowanego.

Rys. 1.11. Ogólna budowa systemu zgrupowanego (klastra)

1.4. Działania systemu operacyjnego

Po przedstawieniu podstawowych wiadomości dotyczących organizacji i architektury systemów komputerowych możemy przejść do omawiania systemów operacyjnych. System operacyjny tworzy środowisko wykonywania innych programów. Pod względem organizacji wewnętrznej systemy operacyjne znacznie się różnią, są bowiem zorganizowane na wiele różnych sposobów. Niemniej mają one też wiele wspólnego i tym właśnie się zajmiemy w tym podrozdziale.

Aby komputer mógł rozpocząć pracę, na przykład gdy zostaje podłączony do zasilania lub gdy wznawia się jego działanie, musi w nim nastąpić wykonanie jakiegoś wstępnego programu. Jak wcześniej zauważono, ów wstępny program, nazywany też programem rozruchowym (bootstrap program), jest zazwyczaj prosty. Jest on zwykle pamiętany w sprzęcie komputera jako oprogramowanie układowe. Program rozruchowy określa początkowy stan wszystkich elementów systemu, poczynając od rejestrów jednostki centralnej, przez sterowniki urządzeń, aż po zawartość pamięci. Program rozruchowy musi wiedzieć, jak załadować system operacyjny i rozpocząć jego działanie. Aby się z tego wywiązać, musi zlokalizować i wprowadzić do pamięci jądro systemu operacyjnego.

HADOOP

Hadoop jest ramą programową (software framework) o otwartym źródle, używaną do przetwarzania wielkich zbiorów danych (określanych jako wielkie dane – big data) w systemie zgrupowanym (klastrowym) złożonym z prostych, niedrogich komponentów sprzętowych. Hadoop jest zaprojektowany z myślą o skalowaniu od pojedynczego systemu do klastra zawierającego tysiące węzłów obliczeniowych. Zadania są przydzielane do węzła w klastrze, a Hadoop organizuje komunikację między węzłami, aby umożliwić wykonywanie równoległych obliczeń i scalanie wyników. Hadoop wykrywa również i radzi sobie z awariami węzłów, tworząc skuteczną i wysoce niezawodną rozproszoną obsługę obliczeń.

Hadoop jest zorganizowany z trzech składowych. Są to:

1. Rozproszony system plików zarządzający danymi i plikami między rozproszonymi węzłami w klastrze.

2. Rama YARN (Yet Another Resource Negotiator – jeszcze jeden negocjator zasobów) administrująca zasobami w obrębie klastra oraz planująca zadania w jego węzłach.

3. System MapReduce, który umożliwia równoległe przetwarzanie danych w węzłach klastra.

Hadoop jest zaprojektowany do działania w systemach Linux, a aplikacje dla niego można pisać w kilku językach programowania, w tym w językach skryptowych, takich jak PHP, Perl i Python. Popularnym językiem opracowywania aplikacji w ramie Hadoop jest Java, ponieważ Hadoop ma kilka bibliotek Javy udostępniających MapReduce. Więcej wiadomości na temat systemów MapReduce i Hadoop można znaleźć pod lokalizatorami https://hadoop.apache.org/docs/r1.2.1/mapred_tutorial.html i https://hadoop.apache.org.

Załadowane i uruchomione jądro może rozpocząć świadczenie usług dla systemu i jego użytkowników. Niektóre usługi pochodzą z zewnątrz jądra, są udostępniane przez programy systemowe ładowane do pamięci w czasie rozruchu systemu. Stają się one demonami (daemons), które są gotowe do działania przez cały czas wykonywania jądra. W Linuxie pierwszym programem systemowym jest systemd, uruchamia on wiele innych demonów. Po zakończeniu tej fazy system jest w pełni uruchomiony i oczekuje na wystąpienie jakichś zdarzeń.

Jeśli nie ma procesów do wykonywania, żadne urządzenia wejścia-wyjścia nie wymagają obsługi i nikt z użytkowników nie oczekuje odpowiedzi, to system operacyjny będzie spokojnie czekał na jakieś zdarzenie. Zdarzenia są prawie zawsze sygnalizowane za pomocą przerwań. W punkcie 1.2.1 opisaliśmy przerwania sprzętowe. Odmianą przerwania jest pułapka (trap) albo wyjątek (exception). Jest to rodzaj przerwania generowanego przez oprogramowanie, a powodowanego albo przez błąd (np. dzielenie przez zero lub próbę niedozwolonego dostępu do pamięci), albo przez specjalne zamówienie (żądanie) obsługi przez system operacyjny, pochodzące z programu użytkownika i składane za pomocą specjalnej operacji zwanej wywołaniem systemowym (system call).

1.4.1. Wieloprogramowanie i wielozadaniowość

Jednym z najważniejszych aspektów systemów operacyjnych jest zdolność do wykonywania wielu programów, gdyż ogólnie biorąc jeden program nie jest w stanie angażować cały czas procesora lub urządzeń wejścia-wyjścia. Co więcej, również użytkownicy zwykle chcą wykonywać wiele programów naraz. Wieloprogramowanie (multiprogramming) zwiększa wykorzystanie procesora[14] oraz zadowolenie użytkowników dzięki takiej organizacji zadań, aby procesor miał zawsze któreś z nich do wykonania. W systemie wieloprogramowym program w trakcie wykonywania jest określany jako proces (process).

Idea jest następująca – w tym samym czasie system operacyjny przechowuje w pamięci pewną liczbę procesów (rys. 1.12). System operacyjny wybiera któryś z tych procesów i rozpoczyna jego wykonywanie. Prędzej czy później proces ten może zacząć oczekiwać na jakąś pracę, na przykład na zakończenie operacji wejścia-wyjścia. W systemie bez wieloprogramowania CPU pozostawałaby bezczynna. W systemie wieloprogramowym następuje po prostu przełączenie do wykonywania innego procesu. Gdy i tamten proces będzie musiał zaczekać, CPU przełącza się do jeszcze innego procesu itd. Pierwszy proces przestanie w końcu czekać i z powrotem dostanie CPU. Dopóki przynajmniej jeden proces potrzebuje być wykonywany, CPU nigdy nie próżnuje.

Takie postępowanie jest typowe dla zwyczajnych sytuacji życiowych. Na przykład adwokat nie prowadzi na ogół sprawy tylko jednego klienta. Kiedy jeden pozew czeka na rozprawę lub przygotowanie dokumentów, wtedy adwokat może pracować nad inną sprawą. Przy wystarczającej liczbie klientów adwokatowi nigdy nie zabraknie zajęcia. (Bezczynni adwokaci mają skłonności do zostawania politykami, toteż utrzymywanie adwokatów w ciągłym zatrudnieniu ma pewną wartość społeczną).

Rys. 1.12. Wygląd pamięci w systemie wieloprogramowym

Wielozadaniowość (multitasking) stanowi logiczne rozszerzenie wieloprogramowości. W systemach wielozadaniowych CPU wykonuje na przemian wiele procesów, dokonując między nimi przełączeń, przy czym przełączenia następują często, dając użytkownikowi krótki czas odpowiedzi (response time). Zważmy, że wykonywany proces zwykle działa tylko przez krótką chwilę, zanim zakończy pracę lub będzie potrzebował wykonania we-wy. Operacje we-wy mogą być interaktywne, to znaczy użytkownikowi zostaje coś wyświetlone, po czym użytkownik wprowadza dane wejściowe z klawiatury, posługując się myszą lub ekranem dotykowym. Ponieważ interaktywne we-wy zazwyczaj działa w tempie typowym dla człowieka, może trwać długo. Wejście może być na przykład ograniczone przez tempo maszynopisania użytkownika: siedem znaków na sekundę znaczy szybko dla ludzi, lecz skrajnie wolno dla komputerów. Zamiast pozwalać jednostce centralnej na bezczynność podczas takiego interaktywnego wprowadzania danych, system operacyjny będzie błyskawicznie przełączał ją do innych procesów.

Utrzymywanie wielu programów w pamięci w tym samym czasie wymaga jakiegoś zarządzania pamięcią – tym zajmujemy się w rozdziałach 9 i 10. Ponadto, jeżeli kilka procesów jest gotowych do działania w tym samym czasie, to system musi wybrać któryś z nich. Tego rodzaju decyzje są planowaniem przydziału procesora (CPU scheduling), które omówiliśmy w rozdziale 5. Poza tym współbieżne wykonywanie wielu procesów wymaga nieustannego ograniczania ich możliwości wzajemnego oddziaływania na siebie we wszystkich fazach działania systemu operacyjnego, w tym podczas planowania procesów, kontaktu z pamięcią masową i administrowania pamięcią główną. Tym kwestiom poświęciliśmy wiele miejsca w podręczniku.

Wielozadaniowy system operacyjny musi gwarantować sensowny czas reakcji. Typową metodą osiągania tego jest pamięć wirtualna (virtual memory) – technika umożliwiająca wykonywanie procesu, który nie znajduje się w całości w pamięci głównej (rozdz. 10). Zasadniczą jej zaletą jest to, że pozwala ona użytkownikom na wykonywanie programów większych niż rzeczywista pamięć fizyczna (physical memory). Co więcej, pamięć wirtualna pozwala utworzyć abstrakcyjną pamięć główną (operacyjną) w postaci olbrzymiej, jednolitej tablicy do magazynowania informacji, i oddzielić pamięć logiczną (logical memory), oglądaną przez użytkownika, od pamięci fizycznej. Uwalnia to programistów od trosk związanych z ograniczeniami ilościowymi pamięci.

Systemy wieloprogramowe i wielozadaniowe muszą również realizować system plików (rozdz. 13, 14 i 15). System plików rezyduje w pamięci pomocniczej, co z kolei wymaga administrowania pamięcią masową (rozdz. 11). Ponadto system musi chronić zasoby przed nieodpowiednim użyciem (rozdz. 17). Aby zapewnić porządek wykonywania, system musi również udostępniać mechanizmy synchronizacji i komunikacji procesów (rozdz. 6 i 7) i może gwarantować, że procesy nie ulegną zakleszczeniu, wzajemnie oczekując na siebie (rozdz. 8).

1.4.2. Dualny i wielotrybowy sposób działania

Ponieważ system operacyjny i jego użytkownicy wspólnie wykorzystują (dzielą – share) sprzętowe i programowe zasoby systemu komputerowego, właściwie zaprojektowany system operacyjny musi zapewniać, że wadliwy (lub nikczemny) program nie spowoduje niepoprawnego działania innych programów – lub nawet samego systemu operacyjnego. Aby zagwaratować właściwe działanie systemu, musimy umieć rozróżnić wykonywanie kodu systemu operacyjnego i kodu zdefiniowanego przez użytkownika. Podejście stosowane w większości systemów komputerowych polega na wsparciu sprzętowym, umożliwiającym rozróżnienie odmiennych trybów wykonywania.

Potrzebujemy rozróżniania co najmniej dwu oddzielnych trybów pracy: trybu użytkownika (user mode) i trybu jądra (kernel mode), nazywanego także trybem nadzorcy (supervisor mode), trybem systemu (system mode) lub trybem uprzywilejowanym (privileged mode)[15]. W sprzęcie komputerowym istnieje bit, zwany bitem trybu (mode bit), którego stan wskazuje bieżący tryb pracy: jądro (0) albo użytkownik (1). Za pomocą bitu trybu możemy odróżnić działania wykonywane na zamówienie systemu operacyjnego od działań wykonywanych na zamówienie użytkownika. Gdy system komputerowy działa w imieniu aplikacji użytkownika, system jest w trybie użytkownika. Gdy jednak aplikacja użytkownika poprosi system operacyjny o jakąś usługę (za pomocą wywołania systemowego), system musi przejść z trybu użytkownika do trybu jądra, aby ją wykonać. Pokazano to na rysunku 1.13. Jak zobaczymy, takie ulepszenie architektury sprzętu jest użyteczne ze względu na wiele innych aspektów pracy systemu.

Rys. 1.13. Przejście z trybu użytkownika do trybu jądra

W czasie rozruchu systemu sprzęt rozpoczyna działanie w trybie jądra. Następuje załadowanie systemu operacyjnego, który uruchamia procesy użytkowe w trybie użytkownika. Przy każdym wystąpieniu pułapki lub przerwaniu sprzęt zmienia tryb pracy z trybu użytkownika na tryb jądra (tzn. zmienia stan bitu trybu na 0). Tak więc zawsze gdy system operacyjny przejmuje sterowanie komputerem, jest w trybie jądra. Przed przekazaniem sterowania do programu użytkownika system zawsze przełącza tryb pracy na tryb użytkownika (ustawiając bit trybu na 1).

Dualny tryb działania komputera dostarcza środków do ochrony systemu operacyjnego przed nieodpowiedzialnymi użytkownikami, a także do chronienia nieodpowiedzialnych użytkowników wzajemnie przed sobą. Ochrona ta jest uzupełniana za pomocą oznaczenia potencjalnie niebezpiecznych rozkazów kodu maszynowego jako rozkazów uprzywilejowanych (privileged instructions). Sprzęt pozwala wykonywać rozkazy uprzywilejowane tylko w trybie jądra. Próba wykonania rozkazu uprzywilejowanego w trybie użytkownika nie zakończy się wykonaniem go przez sprzęt. Przeciwnie – sprzęt potraktuje taki rozkaz jako niedopuszczalny i spowoduje przejście do systemu operacyjnego.

Przełączenie do trybu jądra jest przykładem rozkazu uprzywilejowanego. Do innych przykładów należą rozkazy sterowania wejściem-wyjściem, czasomierzem i obsługiwania przerwań. Wiele innych rozkazów uprzywilejowanych będzie omówionych dalej w książce.

Pomysł dwóch trybów pracy można rozszerzyć. Na przykład procesory Intela mają cztery odrębne pierścienie ochrony (protection rings), przy czym pierścień 0 jest trybem jądra, a pierścień 3 – trybem użytkownika. (Choć pierścienie 1 i 2 mogłyby być zastosowane w różnych usługach systemu operacyjnego, w praktyce są rzadko używane). Systemy ARMv8 mają siedem trybów. Jednostki centralne umożliwiające wirtualizację (podrodz. 18.1) często mają osobny tryb do wskazywania, kiedy systemem steruje zarządca maszyny wirtualnej (virtual machine manager – VMM)[16]. W tym trybie VMM ma więcej przywilejów niż procesy użytkownika, lecz mniej niż jądro. Ten poziom przywilejów jest mu potrzebny, aby mógł tworzyć i zarządzać maszynami wirtualnymi, zmieniając w tym celu stan CPU.

Obecnie możemy lepiej zrozumieć cykl wykonania rozkazu w systemie komputerowym. Na początku sterowanie jest po stronie systemu operacyjnego – rozkazy są wówczas wykonywane w trybie jądra. Gdy sterowanie jest przekazywane do aplikacji użytkownika, następuje przełączenie do trybu użytkownika. W końcu sterowanie wraca do systemu operacyjnego na skutek przerwania, pułapki lub wywołania systemowego. Większość współczesnych systemów operacyjnych, takich jak Microsoft Windows, UNIX i Linux, korzysta z tej możliwości dualnego trybu, co zwiększa ich ochronę.

Wywołania systemowe[17] umożliwiają programowi użytkownika zlecanie systemowi operacyjnemu wykonania w jego imieniu prac zarezerwowanych dla systemu operacyjnego. Wywołanie systemowe jest rozpoczynane na wiele sposobów, w zależności od własności danego procesora. We wszystkich odmianach jest to metoda, za pomocą której proces zamawia działanie systemu operacyjnego. Wywołanie systemowe zwykle przyjmuje postać przejścia do określonego miejsca w wektorze przerwań. Przejście to może być wykonane za pomocą ogólnego rozkazu trap, choć niektóre systemy mają specjalny rozkaz syscall powodujący wywołanie systemowe.

Podczas wykonywania wywołania systemowego sprzęt na ogół traktuje je jako przerwanie programowe. Za pośrednictwem wektora przerwań sterowanie jest przekazywane do odpowiedniej procedury obsługi w systemie operacyjnym, a bit trybu zostaje przełączony na tryb jądra. Procedura obsługi wywołania systemowego jest częścią systemu operacyjnego. Jądro sprawdza rozkaz przerywający, aby określić, które wywołanie systemu miało miejsce. Rodzaj usługi, na którą użytkownik zgłasza zapotrzebowanie, jest określony przez parametr wywołania systemowego. Dodatkowe informacje potrzebne w związku z zamówieniem na obsługę mogą być przekazane w rejestrach albo umieszczone na stosie lub w pamięci (wówczas w rejestrach podaje się wskaźniki do komórek pamięci). Jądro sprawdza poprawność i dopuszczalność parametrów, wykonuje zamówienie i przekazuje sterowanie do rozkazu następującego po wywołaniu systemowym. Bardziej szczegółowo opisujemy wywołania systemowe w podrozdziale 2.3.

Ochrona sprzętowa umożliwia wykrywanie błędów naruszania trybów. Ich obsługą zajmuje się na ogół system operacyjny. Gdy program użytkownika dopuści się jakiegoś uchybienia, na przykład próbuje wykonać niedozwolony rozkaz lub sięgnąć po adres pamięci nienależący do jego przestrzeni adresowej, sprzęt spowoduje przejście do systemu operacyjnego. Tak jak przerwanie, taka pułapka powoduje przejście do systemu operacyjnego za pomocą wektora przerwań. Jeśli w programie wystąpił błąd, system operacyjny wymusza awaryjne zakończenie programu. Zdarzenie takie jest obsługiwane za pomocą tego samego kodu co żądanie awaryjnego zakończenia programu pochodzące od użytkownika. Pojawia się odpowiedni komunikat o błędzie, po czym może nastąpić składowanie (zrzut) pamięci programu. Obraz pamięci programu jest zazwyczaj zapisywany w pliku, użytkownik lub osoba programująca może go więc przeanalizować i, po ewentualnej poprawce, spróbować uruchomić program od nowa.

1.4.3. Czasomierz

Musimy zapewnić sprawowanie przez system operacyjny kontroli nad jednostką centralną (CPU). Nie wolno dopuścić, by program użytkownika utknął w nieskończonej pętli lub uległ awarii, wywołując usługi systemu, i nigdy nie oddał systemowi operacyjnemu sterowania. Osiąga się to przez zastosowanie czasomierza. Czasomierz (timer) można ustawić tak, aby generował w komputerze przerwanie po wyznaczonym okresie. Okres ten może być stały (np. 1/60 s) lub zmienny (np. od 1 ms do 1 s). Czasomierz zmienny (variable timer) jest zwykle realizowany za pomocą zegara stałookresowego i licznika. System operacyjny ustawia licznik. Przy każdym tyknięciu zegara następuje zmniejszenie licznika. Z chwilą wyzerowania licznika powstaje przerwanie. Na przykład 10-bitowy licznik z 1-milisekundowym zegarem umożliwia przerwania w odstępach od 1 do 1024 ms, z przyrostem co 1 ms.

Przed oddaniem sterowania do programu użytkownika system operacyjny dopilnowuje ustawienia czasomierza na przerwanie. Kiedy czasomierz spowoduje przerwanie, wtedy sterowanie wraca automatycznie do systemu operacyjnego, który może uznać to przerwanie za nieusuwalny błąd lub zdecydować o przyznaniu programowi więcej czasu. Rozkazy modyfikujące działanie czasomierza są oczywiście zastrzeżone do użytku jądra.

CZASOMIERZE W LINUXIE

W systemach linuksowych parametr HZ konfiguracji jądra określa częstotliwość przerwań czasomierza. Wartość HZ równa 250 oznacza, że czasomierz generuje 250 przerwań na sekundę, czyli jedno przerwanie co każde 4 milisekundy. Wartość HZ zależy od skonfigurowania jądra, a także od typu maszyny i architektury, w której ono pracuje. Związana jest z tym zmienna jądrowa jiffies reprezentująca liczbę przerwań czasomierza, które wystąpiły od rozruchu systemu. Dalszych kwestii mierzenia czasu w jądrze Linuxa dotyczy projekt programistyczny omówiony w rozdziale 2.

1.5. Zarządzanie zasobami

Jak widzieliśmy, system operacyjny jest zarządcą zasobów (resource manager). Jednostka centralna (CPU) systemu, obszar pamięci, przestrzeń pamięci plików i urządzenia wejścia-wyjścia znajdują się wśród zasobów, nad którymi system operacyjny musi sprawować pieczę.

1.5.1. Zarządzanie procesami

Program nie może zrobić niczego dopóty, dopóki jego rozkazy nie zaczną być wykonywane przez CPU. Program w trakcie wykonywania – jak już wspomniano – jest procesem. Działający program kompilatora jest procesem i program edytora tekstu wykonywany przez indywidualnego użytkownika na PC-cie też jest procesem. Na tej samej zasadzie apka (app) mediów społecznościowych w urządzeniu mobilnym jest procesem. Na razie możemy uważać proces za egzemplarz (instance) wykonywanego programu, lecz później przekonamy się, że pojęcie to jest znacznie ogólniejsze. Jak opisano w rozdziale 3, istnieją wywołania systemowe, za pomocą których procesy mogą tworzyć podprocesy wykonywane współbieżnie.

Do wykonania zadania proces potrzebuje pewnych zasobów, w tym czasu CPU, pamięci, plików i urządzeń we-wy. Te zasoby są zwykle przydzielane procesowi podczas jego wykonywania. Oprócz rozmaitych fizycznych i logicznych zasobów, które proces otrzymuje, gdy jest tworzony, mogą mu być przekazane różne dane inicjujące (wejściowe). Rozważmy na przykład proces wykonujący kod przeglądarki sieciowej, mającej wyświetlić treść strony Sieci na ekranie. Procesowi takiemu zostanie podany na wejściu lokalizator URL, po czym będzie on wykonywał stosowne rozkazy i wywołania systemowe w celu uzyskania i wyświetlenia pożądanych informacji na ekranie. Po jego zakończeniu system operacyjny odzyska wszystkie zasoby nadające się do ponownego użycia.

Podkreślamy, że sam program nie jest procesem. Program jest obiektem pasywnym, podobnie jak zawartość pliku przechowywanego na dysku, natomiast proces jest obiektem aktywnym. Jednowątkowy proces ma jeden licznik programu (licznik rozkazów – program counter) określający następny rozkaz do wykonania. (Wątki są omówione w rozdz. 4). Wykonanie takiego procesu musi przebiegać sekwencyjnie. CPU wykonuje jeden rozkaz procesu po drugim, aż do zakończenia procesu. Co więcej, w każdej chwili w związku z procesem jest wykonywany najwyżej jeden rozkaz. Tak więc gdyby dwa procesy skojarzono nawet z tym samym programem, ich wykonanie będzie stanowiło dwa osobne ciągi. Proces wielowątkowy ma wiele liczników programu, a każdy z nich wskazuje rozkaz do wykonania w danym wątku.

Proces jest jednostką pracy w systemie. System składa się ze zbioru procesów: niektóre z nich są procesami systemu operacyjnego (te, które wykonują kod systemu), a pozostałe są procesami użytkowymi (wykonującymi kod użytkownika). Wszystkie procesy mogą być w zasadzie wykonywane współbieżnie: przez multipleksowanie jednej CPU lub równolegle – na wielu rdzeniach CPU.

W odniesieniu do zarządzania procesami system operacyjny odpowiada za następujące czynności:

• tworzenie i usuwanie zarówno procesów użytkowych, jak systemowych;

• planowanie procesów i wątków na procesorach;

• wstrzymywanie i wznawianie procesów;

• dostarczanie mechanizmów synchronizacji procesów;

• dostarczanie mechanizmów komunikacji procesów.

Metody zarządzania procesami są szczegółowo omówione w rozdziałach  3–7.

1.5.2. Zarządzanie pamięcią

Już w punkcie 1.2.2 dowiedzieliśmy się, że pamięć główna[18] odgrywa centralną rolę w działaniu współczesnego systemu komputerowego. Pamięć ta jest wielką tablicą bajtów, których liczba waha się od setek tysięcy do miliardów. Każdy bajt ma przypisany adres. Pamięć główna stanowi magazyn (repository) szybko dostępnych danych eksploatowanych wspólnie przez jednostkę centralną i urządzenia wejścia-wyjścia. Procesor czyta rozkazy z pamięci głównej podczas wykonywania cyklu pobierania rozkazów, a podczas cyklu pobierania danych zarówno czyta, jak i zapisuje dane do tej pamięci (w architekturze von Neumanna). Jak już wcześniej zauważono, pamięć główna jest w zasadzie jedyną dużą pamięcią, którą procesor może adresować bezpośrednio. Na przykład, aby procesor mógł przetwarzać dane znajdujące się w pamięci dyskowej, musi najpierw sprowadzić je do pamięci głównej za pomocą wygenerowanych przez siebie wywołań wejścia-wyjścia. Podobnie, aby procesor mógł wykonywać rozkazy, muszą one znajdować się w tej pamięci.

Aby program mógł być wykonany, musi zostać odwzorowany w adresach bezwzględnych oraz załadowany do pamięci. Podczas wykonywania programu rozkazy i dane są pobierane z pamięci za pomocą generowania tych właśnie adresów bezwzględnych. Kiedy program zakończy działanie, wtedy jego miejsce w pamięci jest oznaczane jako wolne, co umożliwia załadowanie i wykonanie następnego programu.

W celu poprawienia zarówno wykorzystania jednostki centralnej, jak i szybkości reagowania komputera na polecenia użytkowników uniwersalne komputery muszą przechowywać wiele programów w pamięci operacyjnej. Jest wiele różnych sposobów zarządzania pamięcią, przy czym skuteczność poszczególnych algorytmów zależy od konkretnej sytuacji. W wyborze sposobu zarządzania pamięcią musimy brać pod uwagę wiele czynników, a zwłaszcza rozwiązania sprzętowe zastosowane w danym systemie. Każdy algorytm wymaga swoistego wspomagania sprzętowego.

W odniesieniu do zarządzania pamięcią system operacyjny odpowiada za:

• utrzymywanie ewidencji aktualnie zajętych części pamięci wraz z informacją, który proces ich używa;

• przydzielanie i zwalnianie obszarów pamięci stosownie do potrzeb;

• decydowanie o tym, które procesy (lub części procesów) mają być przeniesione do (lub z) pamięci.

Metody zarządzania pamięcią są omówione w rozdziałach 9 i 10.

1.5.3. Zarządzanie systemem plików

Aby uczynić system komputerowy wygodny dla użytkowników, system operacyjny tworzy jednolity, logiczny obraz magazynowanych informacji. System operacyjny definiuje plik (file), czyli logiczną jednostkę przechowywanej informacji, niezależną od fizycznych właściwości używanych urządzeń pamięci. System operacyjny odwzorowuje pliki na fizyczne nośniki informacji i umożliwia do nich dostęp za pomocą urządzeń pamięci.

Zarządzanie plikami jest jedną z najbardziej widocznych części składowych systemu operacyjnego. Komputery mogą przechowywać informację na nośnikach fizycznych kilku różnych typów. Najbardziej rozpowszechniona jest pamięć pomocnicza (drugorzędna), choć pamięć trzeciorzędna również jest możliwa. Każdy z tych nośników ma charakterystyczne parametry techniczne i swoistą organizację fizyczną. Kontrolę nad nimi przeważnie sprawuje urządzenie, takie jak sterownik dysku, mające również specyficzne cechy. Są to: szybkość działania, pojemność, szybkość przesyłania danych oraz metoda dostępu (dostęp sekwencyjny lub swobodny).

Plik jest zbiorem powiązanych ze sobą informacji zdefiniowanych przez jego twórcę. W plikach zazwyczaj przechowuje się programy (w postaci źródłowej lub wynikowej) oraz dane. Pliki danych mogą być liczbowe, tekstowe alfanumeryczne lub binarne. Format plików może być swobodny, jak w przypadku plików tekstowych, lub ściśle określony (np. ze stałymi polami, jak w pliku muzycznym mp3). Jest oczywiste, że pojęcie pliku jest wyjątkowo ogólne[19].

System operacyjny realizuje abstrakcyjną koncepcję plików przez zarządzanie nośnikami pamięci masowych oraz nadzorującymi je urządzeniami. Oprócz tego pliki są zwykle zorganizowane w katalogi, co ułatwia ich użytkowanie. Poza tym, jeśli wielu użytkowników ma dostęp do plików, to może być pożądane sprawowanie kontroli nad tym, który użytkownik i w jaki sposób korzysta z tego dostępu (np. mogąc czytać, zapisywać lub dopisywać).

W odniesieniu do zarządzania plikami system operacyjny odpowiada za:

• tworzenie i usuwanie plików;

• tworzenie i usuwanie katalogów w celu organizowania plików;

• dostarczanie elementarnych operacji do manipulowania plikami i katalogami;

• odwzorowywanie plików na obszary pamięci masowej;

• składowanie plików na trwałych (nieulotnych) nośnikach pamięci.

Metody zarządzania plikami są omówione w rozdziałach 13, 14 i 15.

1.5.4. Zarządzanie pamięcią masową

Jak już widzieliśmy, system komputerowy musi być wyposażony w pamięć pomocniczą, aby można było składować w niej zawartość pamięci głównej. Większość współczesnych systemów komputerowych korzysta z urządzeń HDD (dysków twardych) i NVM jako podstawowych, dostępnych online nośników magazynowania zarówno programów, jak i danych. Większość programów, w tym kompilatory, przeglądarki sieciowe, edytory i gry, przechowuje się na tych urządzeniach, skąd mogą być załadowane do pamięci. Programy używają wtedy tych urządzeń w czasie przetwarzania, już to jako źródła danych, już jako miejsca przeznaczenia swoich wyników. Dlatego właściwe zarządzanie pamięcią pomocniczą pozostaje w centrum uwagi systemu komputerowego. W odniesieniu do zarządzania pamięcią pomocniczą system operacyjny odpowiada za:

• montowanie i demontowanie,

• zarządzanie obszarami wolnymi,

• przydzielanie pamięci,

• planowanie dostępu do dysków,

• wydzielanie stref dyskowych („partycjonowanie”),

• ochronę.

Ponieważ pamięć pomocnicza jest często i intensywnie używana, musi być użytkowana efektywnie. Ogólna szybkość działania komputera może zależeć od szybkości podsystemu pamięci pomocniczej i stosowanych w niej algorytmów.

Zarazem istnieje wiele zastosowań wolniejszych i tańszych pamięci masowych (a niekiedy również pojemniejszych) niż pamięć drugorzędna. Przykładami są: składowanie danych dyskowych, magazynowanie rzadko używanych danych i magazynowanie długoterminowe. Typowymi urządzeniami takich pamięci trzeciorzędnych są przewijaki taśm magnetycznych wraz z taśmami oraz napędy i płyty CD, DVD i Blu-ray.

Pamięć trzeciorzędna nie ma istotnego wpływu na wydajność systemu, lecz również należy nią administrować. Niektóre systemy operacyjne podejmują się tego zadania, inne pozostawiają zarządzanie pamięcią trzeciorzędną programom użytkowym. Do niektórych funkcji, które mogą realizować systemy operacyjne, należą montowanie i demontowanie nośników w urządzeniach, przydzielanie urządzeń na wyłączny użytek poszczególnym procesom i ich zwalnianie oraz wędrówka danych z pamięci pomocniczych do trzeciorzędnych.

Metody zarządzania pamięciami drugorzędnymi i trzeciorzędnymi są omówione w rozdziale 11.

1.5.5. Zarządzanie pamięcią podręczną

Przechowywanie podręczne (kaszowanie – caching) jest ważną zasadą w systemach komputerowych. Działa to następująco. Informacje są zwykle trzymane w jakimś systemie pojemnej pamięci (np. w pamięci głównej). Przed użyciem są kopiowane czasowo do szybszego systemu pamięci, tj. do pamięci podręcznej. Gdy jest potrzebny jakiś fragment informacji, sprawdza się najpierw, czy nie ma go w pamięci podręcznej (w „kaszu”). Jeśli jest, można skorzystać z informacji wprost z kaszu. Jeśli go tam nie ma, korzysta się z informacji ze źródła, umieszczając ich kopię w pamięci podręcznej z założeniem, że będą niebawem znów potrzebne.

Ponadto wewnętrzne, programowalne rejestry służą jako bardzo szybka pamięć podręczna pamięci głównej. Programista (lub kompilator) realizuje przydział rejestrów i algorytmy zastępowania ich zawartości, decydując o tym, które informacje mają być przechowywane w nich, a które w pamięci głównej.

Inne pamięci podręczne są realizowane w całości sprzętowo. Na przykład większość systemów ma pamięć podręczną rozkazów do przechowywania rozkazów, co do których przewiduje się, że będą wykonywane w następnej kolejności. Bez tego kaszu procesor musiałby czekać przez kilka cykli na pobranie rozkazu z pamięci głównej. Z podobnych przyczyn większość systemów ma w hierarchii pamięci jedną lub więcej szybkich pamięci podręcznych przeznaczonych na dane. W tej książce nie zajmujemy się tymi wyłącznie sprzętowymi pamięciami podręcznymi, gdyż są one poza kontrolą systemu operacyjnego.

Zarządzanie pamięcią podręczną (cache management) jest ważnym zagadnieniem projektowym ze względu na ograniczone rozmiary tych pamięci. Staranny dobór wielkości pamięci podręcznej i polityka zastępowania w niej informacji mogą znacznie zwiększyć sprawność działania, co można sprawdzić, analizując tabelę na rysunku 1.14. Algorytmy zastępowania informacji w nadzorowanych programowo pamięciach podręcznych są omówione w rozdziale 10.

Przemieszczanie informacji między poziomami hierarchii pamięci może być jawne lub niejawne – zależnie od konstrukcji sprzętu i nadzoru ze strony oprogramowania systemu operacyjnego. Na przykład przesyłanie danych z pamięci podręcznej do CPU i rejestrów jest zwykle funkcją sprzętową, niewymagającą żadnej interwencji ze strony systemu operacyjnego. Z kolei przesyłanie danych z dysku do pamięci głównej jest zwykle nadzorowane przez system operacyjny.

W hierarchicznej strukturze pamięci te same dane mogą występować na różnych jej poziomach. Załóżmy na przykład, że należy zwiększyć o 1 liczbę całkowitą A umieszczoną w pliku B, a plik B rezyduje na dysku. Operację zwiększenia poprzedza wykonanie operacji wejścia-wyjścia kopiującej blok dyskowy, w którym znajduje się A, do pamięci głównej. Po tej operacji następuje przekopiowanie A do pamięci podręcznej, a stamtąd do wewnętrznego rejestru. Wskutek tego kopia A pojawia się w kilku miejscach: na dysku twardym, w pamięci głównej, w pamięci podręcznej i w wewnętrznym rejestrze (rys. 1.15). Po wykonaniu zwiększenia w wewnętrznym rejestrze wartość A będzie różna w różnych systemach pamięci. Staje się ona taka sama dopiero po zapisaniu jej z wewnętrznego rejestru z powrotem na twardym dysku.

Poziom

1

2

3

4

5

Nazwa

Rejestry

Pamięć podręczna

Pamięć główna

Dysk półprzewodnikowy

Dysk magnetyczny

Typowy rozmiar

< 1 KB

< 16 MB

< 64 GB

< 1 TB

< 10 TB

Technologia wykonania

Pamięć specjalizowana z wieloma portami CMOS

Układowa lub pozaukładowa CMOS SRAM

CMOS SRAM

Pamięć flash

Dysk magnetyczny

Czas dostępu (ns)

0,25–0,5

0,5–25

80–250

25 000–50 000

5 000 000

Przepustowość (MB/s)

20 000–100 000

5000–10 000

1000–5000

500

20–150

Zarządzana przez

Kompilator

Sprzęt

System operacyjny

System operacyjny

System operacyjny

Miejsce składowania

Pamięć podręczna

Pamięć główna

Dysk

Dysk

Dysk lub taśma

Rys. 1.14. Charakterystyka różnych typów pamięci masowej

Rys. 1.15. Wędrówka wartości całkowitej A z dysku do rejestru

W środowisku obliczeniowym, w którym w danym czasie jest wykonywany tylko jeden proces, sytuacja taka nie powoduje żadnych trudności, ponieważ dostęp do liczby całkowitej A będzie dotyczyć zawsze jej kopii na najwyższym poziomie hierarchii. Jednak w środowisku wielozadaniowym, w którym procesor jest przełączany tam i z powrotem między różnymi procesami, należy przedsięwziąć skrajne środki ostrożności, aby zapewnić, że gdy kilka procesów będzie chciało sięgnąć po A, każdy z nich otrzyma jej najnowszą wartość.

Sytuacja staje się bardziej skomplikowana w środowisku wieloprocesorowym, gdzie – oprócz utrzymywania wewnętrznych rejestrów – każdy procesor zawiera również lokalną pamięć podręczną. W takim środowisku kopia zmiennej A może istnieć jednocześnie w wielu pamięciach podręcznych. Ponieważ różne jednostki centralne mogą działać współbieżnie, musimy zapewnić, że uaktualnienie wartości A w jednej z pamięci podręcznych znajdzie natychmiast odbicie we wszystkich innych pamięciach podręcznych, które również przechowują A. Mamy tu do czynienia z problemem zgodności pamięci podręcznej (cache coherency), zazwyczaj stanowiącym zagadnienie sprzętowe (poniżej poziomu systemu operacyjnego).

W środowisku rozproszonym sytuacja komplikuje się w jeszcze większym stopniu. W takim środowisku w różnych komputerach może być przechowywanych wiele kopii (czyli replik) tego samego pliku. Ze względu na to, że różne repliki mogą być udostępniane i aktualizowane współbieżnie, niektóre systemy rozproszone zapewniają, że aktualizacja kopii w jednym miejscu pociągnie za sobą możliwie jak najszybsze uaktualnienie wszystkich innych kopii. Istnieje wiele różnych sposobów osiągania tego stanu, co przedstawimy w rozdziale 19.

1.5.6. Zarządzanie systemem wejścia-wyjścia

Jednym z celów systemu operacyjnego jest ukrywanie przed użytkownikiem specyficznych cech urządzeń sprzętowych. Na przykład w UNIX-ie osobliwości urządzeń we-wy są ukrywane przed większą częścią samego systemu operacyjnego za pomocą podsystemu wejścia-wyjścia (I/O subsystem). Podsystem wejścia-wyjścia składa się z kilku komponentów:

• komponentu zarządzania pamięcią obejmującego buforowanie, kaszowanie i spooling;

• ogólnego interfejsu modułów sterujących urządzeń;

• modułów obsługi poszczególnych urządzeń sprzętowych.

Własności danego urządzenia są znane tylko modułowi obsługi, który został do niego przypisany.

W tym rozdziale omówiliśmy już sposób użycia modułów obsługi przerwań i modułów sterujących urządzeń w konstruowaniu sprawnych podsystemów we-wy. W rozdziale 12 omówimy, w jaki sposób podsystem we-wy tworzy interfejs do innych składowych systemu, zarządza urządzeniami, przesyła dane i wykrywa zakończenie operacji wejścia-wyjścia.

1.6. Ochrona i bezpieczeństwo

Skoro system komputerowy ma wielu użytkowników i umożliwia współbieżne wykonywanie wielu procesów, dostęp do danych musi być regulowany. Cel ten jest realizowany przez mechanizmy, które zapewniają, że na plikach, segmentach pamięci, CPU i innych zasobach mogą działać tylko te procesy, które otrzymały od systemu operacyjnego odpowiednie upoważnienia. Na przykład sprzęt adresujący pamięć gwarantuje, że proces będzie działał tylko w obrębie swojej przestrzeni adresowej. Czasomierz zapewnia, że żaden proces nie przejmie na stałe kontroli nad jednostką centralną, nigdy jej nie zwalniając. Rejestry sterujące urządzeń nie są dostępne dla użytkowników, co chroni nienaruszalność wszelkich urządzeń zewnętrznych.

Przez ochronę (protection) rozumie się zatem każdy mechanizm kontrolowania dostępu procesów i użytkowników do zasobów zdefiniowanych przez system komputerowy. Mechanizm ten musi dostarczać sposobów określania sprawowanej kontroli i środków jej wymuszania.

Za pomocą działań ochronnych polegających na wykrywaniu błędów ukrytych w interfejsach między składowymi podsystemami można polepszać niezawodność systemu. Wczesne wykrywanie błędów w interfejsach może często zapobiec zanieczyszczeniu zdrowego podsystemu przez inny, źle funkcjonujący podsystem. Co więcej, niechroniony zasób nie może się obronić przed użyciem (lub nadużyciem) przez nieupoważnionego lub niekompetentnego użytkownika. System ochrony rozporządza środkami rozróżniania uprawnionego i nieuprawnionego użycia, co omówiliśmy w rozdziale 17.

System może mieć odpowiednią ochronę, a mimo to nadal może być podatny na awarie i zezwalać na niewłaściwy dostęp. Rozważmy użytkownika, któremu ukradziono jego informacje uwierzytelniające (dane, za pomocą których udowadnia swoją tożsamość w systemie). Jego dane mogą zostać skopiowane lub usunięte mimo działającej ochrony plików i pamięci. Obrona systemu przed zewnętrznymi i wewnętrznymi atakami jest przedmiotem bezpieczeństwa (security). Istnieje olbrzymia różnorodność takich ataków, zaliczają się do nich wirusy i robaki, ataki polegające na odmowie świadczenia usług (w których wszystkie zasoby systemu są używane w takim stopniu, że uniemożliwiają dostęp do niego legalnym użytkownikom), kradzież tożsamości oraz kradzież usług (nieupoważnione korzystanie z systemu). W pewnych systemach zapobieganie niektórym z tych ataków jest uważane za funkcję systemu operacyjnego, podczas gdy w innych pozostawia się to w gestii polityki (zasad postępowania) lub dodatkowego oprogramowania. Wskutek niepokojącego wzrostu naruszeń bezpieczeństwa zaopatrywanie systemów operacyjnych w środki zabezpieczeń jest przedmiotem szybko rozwijających się badań i implementacji. Bezpieczeństwo omówimy w rozdziale 16.

Ochrona i bezpieczeństwo wymagają od systemu zdolności do identyfikowania wszystkich jego użytkowników. Systemy operacyjne operują przeważnie listą nazw użytkowników i przyporządkowanymi im identyfikatorami użytkowników (user identifiers – IDs). W żargonie Windows określa się to jako ID bezpieczeństwa (security ID). Te numeryczne identyfikatory (ID) są jednoznaczne – każdemu użytkownikowi odpowiada jeden niepowtarzalny ID. Gdy użytkownik rozpoczyna sesję w systemie („loguje się”), na etapie uwierzytelniania ustala się jego odpowiedni ID. Identyfikator danego użytkownika przypisuje się wszystkim procesom i wątkom tego użytkownika. Jeśli zachodzi potrzeba uczynienia ID czytelnym dla użytkownika, identyfikator jest tłumaczony z powrotem na nazwę użytkownika za pomocą listy nazw użytkowników.

W niektórych okolicznościach potrzebujemy rozróżniać nie poszczególnych użytkowników, lecz ich zbiory. Na przykład właścicielowi pliku w systemie UNIX można zezwolić na wykonywanie na jego pliku wszelkich operacji, podczas gdy zbiorowi wybranych użytkowników zezwala się tylko na czytanie. Żeby tego dokonać, musimy zdefiniować nazwę grupy i zbiór użytkowników należących do tej grupy. Funkcjonalność grupy można zrealizować na zasadzie ogólnosystemowej listy nazw grup i identyfikatorów grup (group identifiers). Użytkownik może należeć do jednej lub więcej grup, zależnie od decyzji podjętych w projekcie systemu operacyjnego. Identyfikatory grup użytkowników również są dołączane do każdego procesu i wątku.

Podczas normalnego użytkowania systemu ID użytkownika i grupowy ID użytkownika wystarczają. Jednak niekiedy użytkownik potrzebuje wzmocnić przywileje (escalate privilege), aby zyskać specjalne pozwolenia w związku z jakąś czynnością. Na przykład użytkownikowi może być potrzebny dostęp do urządzenia o ograniczonym (zastrzeżonym) dostępie. Systemy operacyjne oferują rozmaite metody wzmacniania przywilejów. Na przykład w UNIX-ie atrybut setuid przypisany programowi powoduje, że program działa z ID właściciela pliku, a nie z ID bieżącego użytkownika. Proces jest wykonywany z tym efektywnym identyfikatorem użytkownika (effective UID) do czasu, aż wyłączy specjalne przywileje lub zakończy pracę.

1.7. Wirtualizacja

Wirtualizacja (virtualization) jest techniką umożliwiającą abstrahowanie sprzętu pojedynczego komputera (CPU, pamięci, napędów dyskowych, kart interfejsów sieciowych itd.) do postaci kilku różnych środowisk wykonywania, co tworzy iluzję, że każde środowisko z osobna działa na swoim prywatnym komputerze. Te środowiska można traktować jako różne, indywidualne systemy operacyjne (np. Windows i UNIX), które mogą działać w tym samym czasie i ze sobą współpracować. Użytkownik maszyny wirtualnej (virtual machine) może przełączać się między różnymi systemami operacyjnymi w taki sam sposób, jak dokonuje przełączeń między rozmaitymi procesami wykonywanymi współbieżnie w jednym systemie operacyjnym.

Wirtualizacja umożliwia wykonywanie systemów operacyjnych jako aplikacji w obrębie innego systemu operacyjnego. Na pierwszy rzut oka może się wydawać, że tego rodzaju funkcjonalności nie da się rozsądnie uzasadnić. A jednak przemysł wirtualizacji jest olbrzymi i stale się rozrasta, dając świadectwo jej użyteczności i znaczenia.

Mówiąc najogólniej, oprogramowanie wirtualizacji zalicza się do klasy, do której należy również emulacja. Emulację (emulation), obejmującą programowe symulowanie sprzętu komputerowego, stosuje się zazwyczaj wówczas, gdy źródłowa jednostka centralna różni się od docelowej. Na przykład, gdy Apple przechodził w swoich laptopach z procesora IBM Power na CPU Intela, dołączał udogodnienie emulacji nazwane „Rosetta”, dzięki któremu aplikacje skompilowane dla IBM-owskiej jednostki centralnej mogły działać na procesorach Intela. Ten sam pomysł można rozszerzyć, umożliwiając całemu systemowi operacyjnemu, napisanemu dla jednej platformy, działanie na innej. Emulacja narzuca jednak wysoki koszt – każdy rozkaz z poziomu maszynowego, który w źródłowym systemie jest wykonywany wprost na rdzennym sprzęcie, musi być tłumaczony na równoważną funkcję w systemie docelowym, co niejednokrotnie prowadzi do kilku rozkazów docelowych. Jeśli jednostki centralne: źródłowa i docelowa mają podobne poziomy wydajności, emulowany kod może działać znacznie wolniej niż kod rodzimy („natywny”).

W przypadku wirtualizacji rzeczy mają się inaczej. System operacyjny skompilowany rdzennie dla konkretnej architektury CPU działa wewnątrz innego systemu operacyjnego również jako rdzenny dla danej jednostki centralnej. Wirtualizacja po raz pierwszy pojawiła się na IBM-owskich komputerach głównych (mainframe) jako metoda umożliwiająca wielu użytkownikom współbieżne wykonywanie zadań. Puszczenie w ruch wielu maszyn wirtualnych umożliwiało (i wciąż umożliwia) wielu użytkownikom wykonywanie zadań w systemie zaprojektowanym dla jednego użytkownika. W późniejszym czasie, w reakcji na problemy z wykonywaniem wielu aplikacji Microsoft Windows na procesorach Intel x86, firma VMware opracowała nową technologię wirtualizacji w postaci aplikacji pracującej pod systemem Windows. Ta aplikacja wykonywała jedną lub więcej goszczonych (guest) kopii Windows lub innych rdzennych wobec x86 systemów operacyjnych, z których każdy wykonywał własne aplikacje (rys. 1.16).

Windows był tutaj goszczącym (host) systemem operacyjnym, a aplikacja VMware zarządcą maszyn wirtualnych (virtual machine manager – VMM)[20]. Zarządca VMM wykonuje goszczone systemy operacyjne, administruje używanymi przez nie zasobami i chroni każdy system goszczony przed innymi.

Rys. 1.16. Komputer wykonujący (a) jeden system i (b) trzy maszyny wirtualne

Mimo że współczesne systemy operacyjne są w pełni zdolne do niezawodnego wykonywania wielu aplikacji, zastosowania wirtualizacji nieustannie rosną. W laptopach i desktopach VMM pozwala użytkownikowi na zainstalowanie wielu systemów operacyjnych do eksploracji lub wykonywania aplikacji napisanych dla systemów operacyjnych innych niż rdzenny host[21]. Na przykład laptop Apple'a działający pod kontrolą systemu macOS na CPU x86 może wykonywać Windows 10 w charakterze systemu goszczonego, umożliwiając wykonywanie jego aplikacji. Firmy piszące oprogramowanie dla wielu systemów mogą korzystać z wirtualizacji, aby wykonywać wszystkie te systemy na jednym fizycznym serwerze w celu opracowywania takiego oprogramowania, testowania go i oczyszczania z błędów. W centrach danych wirtualizacja stała się popularną metodą wykonywania i administrowania środowiskami obliczeniowymi. VMM-y, takie jak VMware ESX i XenServer, nie działają już w goszczących systemach operacyjnych – same stały się goszczącymi systemami operacyjnymi oferującymi usługi i administrowanie zasobami procesom maszyn wirtualnych.

Wraz z tą książką udostępniamy maszynę wirtualną Linuxa, która umożliwia działanie systemu Linux oraz udostępnianych przez nas narzędzi w Twoim osobistym systemie, niezależnie od typu systemu operacyjnego działającego w Twoim hoście. Komplet szczegółów dotyczących własności i implementowania wirtualizacji można znaleźć w rozdziale 18.

O Autorach

ABRAHAM SILBERSCHATZ

Od 2005 roku profesor informatyki na Uniwersytecie Yale. Przed dołączeniem do Yale był wiceprezesem Centrum Badań Informacji Naukowej w Bell Laboratories. Wcześniej przewodniczył profesurze na Wydziale Nauk Komputerowych Uniwersytetu Teksańskiego w Austin.

PETER BAER GALVIN

Principal Solutions Architect i GUI Product Manager w Starfish Storage i Chief Technologist w Cambridge Computer Services. Wcześniej m.in. CTO w firmie Corporate Technologies, Chief solutions architect w Pluribus Networks, DELL. Do 2012 roku był wykładowcą na Uniwersytecie w Bostonie, gdzie prowadził zajęcia z wprowadzenia do systemów operacyjnych.

GREG GAGNE

Kierownik Wydziału Informatyki w Westminster College w Salt Lake City, gdzie uczy od 1990 roku.

Przypisy

[1] Nie jest błędem nazywanie JVM również „maszyną wirtualną Java” – przyp. tłum.

[2] Popularny i uniwersalny program rozruchowy i ładowacz systemów operacyjnych – przyp. tłum.

[3] W oryginale futures, specjalny typ obiektów – przyp. tłum.

[4] Taką nazwę (swift – dosłownie – jerzyk) nosi w systemach operacyjnych m.in. pewien język programowania, o którym autorzy wspominają w rozdziale 4 – przyp. tłum.

[5] Chodzi oczywiście o urządzenia przenośne, a nie ruchome. W przekładzie używamy przymiotnika „mobilny” tylko ze względu na powszechną manierę jego stosowania w podobnych kontekstach – przyp. tłum.

[6] Inaczej: jednostek centralnych (central processing units, CPUs), co semantycznie nieco koliduje z decentralizacją współczesnych architektur sprzętowych – przyp. tłum.

[7] Dalej będziemy używać krótszych określeń: moduł sterujący lub moduł obsługi. W obiegu jest również nazwa „sterownik” (a nawet „drajwer”); takie jej przypisanie koliduje z nazwą sterownik w znaczeniu sprzętowym (trzeba by go wówczas określać mianem „kontrolera”). Jeszcze inna spotykana nazwa to program sterujący urządzenia – przyp. tłum.

[8] Nie usuwając oczywiście oryginalnego słowa z pamięci – przyp. tłum.

[9] Jeżeli podejść do tego z drugiej strony, tzn. uzmysłowić sobie, że reprezentację wszystkich tych odrębności można stopniowo, hierarchicznie sprowadzić do ciągów bitów, zdumienie będzie mniejsze – przyp. tłum.

[10] W związku z tą rozbieżnością interpretacji wielkości jednostek kilobajt, megabajt, gigabajt itd. istnieje propozycja obocznych nazw: kibibajt, mibibajt, gibibajt itd. dla zaznaczenia, że chodzi o potęgi liczby 1024 (czyli 2^10), z pozostawieniem utartych nazw w znaczeniu potęg liczby 1000 – przyp. tłum.

[11] Które należą również do klasy pamięci nieulotnych (w znaczeniu ang. nonvolatile) – przyp. tłum.

[12] W angielskiej literaturze przedmiotu są używane terminy storage i memory. Po polsku często oba te słowa są utożsamiane, choć storage oznacza też magazyn. W przekładzie w razie potrzeby będziemy więc używać określenia pamięć magazynująca, choć najczęściej wystarczy termin pamięć masowa.Termin „pamięć” pozostawiamy natomiast w znaczeniu pamięci operacyjnej, częściej obecnie nazywanej – w ślad za terminem angielskim – pamięcią główną (co również czynimy). Angielskie terminy storage memory i mass memory ukazują de facto dwa aspekty pamięci jednej klasy – przyp. tłum.

[13] Host (host – gospodarz) oznacza tu komputer sieciowy goszczący jakiś proces, na przykład aplikację, i w tym znaczeniu będzie używane dalej to popularne zapożyczenie – przyp. tłum.

[14] Według przyjętych w tym wydaniu rozróżnień (p. 1.3.2) procesor może zawierać więcej niż jedną CPU, jednak tam, gdzie to rozróżnienie nie jest istotne, zastępujemy czasem termin jednostka centralna (CPU) określeniem procesor, przyjmując, że chodzi o procesor z jedną CPU – przyp. tłum.

[15] Dawniej nazywanego jeszcze trybem monitora (monitor mode) – przyp. tłum.

[16] Być może należałoby tutaj używać nawet liczby mnogiej – zarządca maszyn wirtualnych. Oprogramowanie to jest również nazywane monitorem maszyny wirtualnej (virtual machine monitor) lub hipernadzorcą; będziemy go także nazywać hiperwizorem – przyp. tłum.

[17] Ang. system calls; inne spotykane nazwy: odwołania do systemu, funkcje systemowe – przyp. tłum.

[18] Przypominam, że terminy pamięć główna, pamięć operacyjna i pamięć wszędzie oznaczają to samo – przyp. tłum.

[19] A różnych formatów (typów) plików są setki, por. http://www.fileinfo.com – przyp. tłum.

[20] Inni autorzy (np. Stallings) rozwijają skrót VMM jako virtual machine monitor – monitor maszyny wirtualnej – przyp. tłum.

[21] Choć ang. host ma różne znaczenia zależnie od kontekstu (komputer sieciowy lub goszczący inny system), zważywszy na jego zwięzłość, będziemy dalej posługiwać się tym zapożyczeniem bez cudzysłowów – przyp. tłum.