Zasada działania systemu operacyjnego

Zasada działania systemu operacyjnego


System operacyjny jest programem, jednak jego działanie jest dość specyficzne, gdyż musi on nadzorować (monitorować) pracę komputera nawet wówczas, gdy wykonywany jest jakiś program aplikacyjny. System operacyjny musi reagować na błędy w programach aplikacyjnych, porządkować system komputerowy po awariach, z kolei błędy w kodzie jądra systemu operacyjnego mogą zdestabilizować funkcjonowanie całego systemu komputerowego.

Działanie współczesnych systemów operacyjnych jest rezultatem ewolucji w architekturze sprzętowo-programowej, w której potrzeby w zakresie implementacji pewnych mechanizmów systemu operacyjnego wymuszały wprowadzanie stosownych rozwiązań na poziomie architektury komputera (procesora, jednostki zarządzania pamięcią, układu bezpośredniego dostępu do pamięci, procesorów wejścia-wyjścia itp.). Rozwiązania na poziomie architektury komputera otwierały z kolei drogę do dalszego rozwoju oprogramowania systemowego.

System operacyjny w ujęciu wielowarstwowym


slajd 20

Działanie systemu komputerowego można opisywać na różnych poziomach abstrakcji, począwszy od zjawisk fizycznych na poziomie układów półprzewodnikowych, czy też propagacji sygnałów logicznych na poziomie układów techniki cyfrowej. Tak niski poziom abstrakcji jest jednak na ogół mało interesujący dla informatyka, dlatego na najniższym poziomie abstrakcji na slajdzie umieszczona została mikroarchitektura. Poziom mikroarchitektury jest jednak zastrzeżony dla twórców procesorów, natomiast dla programistów systemów komputerowych najniżej dostępny jest poziom maszynowy procesora. Na poziomie tym definiowana jest lista rozkazów procesora, tryby adresowania pamięci, rejestry procesora. Na poziomie tym nie istnieją jednak takie elementy, jak pliki, procesy, mechanizmy komunikacji i synchronizacji. Te elementy uzupełniane są przez system operacyjny, który współtworzy wraz poziomem maszynowym hybrydową warstwę usług dla programów użytkowych. Na bazie tej warstwy budowane są kolejne poziomy abstrakcji, związane z językami programowania niższego lub wyższego poziomu.

W praktyce rzadko kiedy korzysta się bezpośrednio z poziomu maszynowego. Jeśli rzeczywiście istnieje potrzeba pisania programu na tak niskim poziomie, wykorzystywany jest raczej asembler, który nie ogranicza możliwości poziomu maszynowego, a usprawnia tworzenie programu dzięki mnemonikom rozkazów zamiast ich kodów, etykietom zamiast adresów itp.

Architektura von Neumanna


W celu wyjaśnienia, w jaki sposób wykonywany jest program jądra, istotne jest uświadomienie sobie, w jaki sposób w ogóle wykonywany jest program przez procesor.

Działanie współczesnych procesorów opiera się w dużej części na modelu von Neumanna. Architektura komputera, której nazwa przyjęła się od nazwiska jej popularyzatora — Johna von Neumanna, zakłada, że zarówno dane, jak i program (kod instrukcji/rozkazów) znajdują się w pamięci operacyjnej (dziś wydaje się to dość oczywiste). Rozkazy umieszczane są pod kolejnymi adresami w pamięci. Wykonywanie takiego programu sprowadza się zatem do pobierania rozkazów z kolejnych komórek. Adres komórki pamięci, od której rozpoczyna się kod następnego rozkazu do wykonania, przechowywany jest w odpowiednim rejestrze procesora, zwanym licznikiem programu (PC — program counter) lub wskaźnikiem instrukcji (IP — instruction pointer). Zawartość tego rejestru wystawiana jest na szynę adresową magistrali systemowej w celu pobrania z pamięci kodu rozkazu. Po zdekodowaniu operacji licznik ten zwiększany jest odpowiednio do długości pobranego rozkazu, w ten sposób wskazuje następny rozkaz do wykonania. Opisany schemat — domyślny przepływ sterowania — oznacza wykonywanie rozkazów w pewnej sekwencji, wynikającej z ich uporządkowania w programie i tym samym w pamięci. Schemat ten może ulec zmianie w wyniku wykonania specjalnego rozkazu (skoku, wywołania podprogramu, powrotu z podprogramu). Zmiana domyślnego przepływu sterowania jest więc zdefiniowana przez sam program.

Cykl rozkazowy


Działania procesora, zmierzające do wykonania rozkazu, powtarzają się cyklicznie, w związku z czym określa się je jako cykl rozkazowy. Realizacja cyklu rozkazowego wymaga na ogół kilku interakcji procesora z pamięcią. Każdą taką interakcję określa się mianem cyklu maszynowego. W każdym cyklu rozkazowym występuje cykl maszynowy pobrania kodu rozkazu (fetch). W zależności od trybu dostępności operandów mogą też wystąpić cykle pobrania operandu z pamięci (albo rejestrów wejścia-wyjścia) lub składowania operandu w pamięci (albo rejestrach wejścia-wyjścia) . (Operandy są argumentami operacji wykonywanej w ramach rozkazu.) Każdy cykl maszynowy oznacza zatem zapis lub odczyt pamięci, przy czym cykl pobrania kodu rozkazu oznacza zawsze odczyt.

Cykl rozkazowy — pobranie rozkazu


slajd 23

Cykl rozkazowy rozpoczyna się od wystawienia kodu rozkazu. W reakcji na towarzyszący temu sygnał sterujący odczytu pamięci na szynie danych udostępniana jest zawartość zaadresowanej komórki, a procesor pobiera ją i zapamiętuje w odpowiednim rejestrze. Następnie zostaje zdekodowana operacja i w zależności od wymaganych operandów następuje odczyt pamięci lub rejestrów wejścia-wyjścia. Odczyt taki wymaga oczywiście wystawienia odpowiedniego adresu na magistrali, a następnie przekazania sygnału sterującego odczytu pamięci. Adres operandu w zależności od trybu adresowania jest częścią kodu rozkazu lub znajduje się w rejestrze procesora (rzadziej w innej komórce pamięci). W przypadku rozkazów wymagających kilku operandów wejściowych cykl maszynowy pobrania operandów może powtórzyć się kilkakrotnie. W szczególnym przypadku rozkaz nie wymaga żadnego operandu wejściowego. Może się też okazać, że operand dostępny jest w rejestrze procesora, w związku z czym nie jest wymagana interakcja z pamięcią albo rejestrami wejścia-wyjścia. W takich przypadkach cykl maszynowy pobrania operandów zostanie pominięty.

Cykl rozkazowy — wykonanie rozkazu


slajd 24

Po zdekodowaniu operacji i skompletowaniu operandów wejściowych można wykonać operację, a następnie umieścić w pamięci albo rejestrach wejścia-wyjścia operandy wyjściowe. Podobnie jak w przypadku operandów wejściowych ten cykl maszynowy może być wykonany wielokrotnie lub w szczególnym przypadku pominięty. Na tym kończy się ciąg działań związany z wykonaniem bieżącego rozkazu i można by przejść do następnego cyklu rozkazowego.

Jeśli w taki sposób byłby wykonywany program użytkownika, to nasuwa się pytanie: „Gdzie jest miejsce na wykonywanie programu jądra systemu operacyjnego?”, „W jaki sposób następuje przekazanie sterowania do jądra systemu operacyjnego?”.

W czasie wykonywania rozkazu mogły nastąpić pewne zdarzenia zewnętrzne w stosunku do procesora, nie związane z bieżącym cyklem rozkazowym, ale wymagające od procesora jakiejś reakcji. Konieczność reakcji zgłaszana jest poprzez sygnał na odpowiedniej linii wejściowej procesora. Ostatnia faza cyklu rozkazowego polega zatem na sprawdzeniu, czy wystąpiło takie zgłoszenie. Jeśli nie było zgłoszenia, rozpoczyna się następny cykl maszynowy. Jeśli jednak było zgłoszenie — nazywane przerwaniem, następuje ciąg działań, zmierzających do zidentyfikowania źródła przerwania, a następnie przekazania sterowania do stosownej procedury obsługi. Procedury obsługi przerwań są częścią programu jądra systemu operacyjnego.

Podstawy działania systemu operacyjnego


Ogólnie, sterowanie przekazywane jest do jądra systemu operacyjnego poprzez przerwania. Program jądra jest więc zbiorem procedur obsługi przerwań i wywoływanych przez nie innych podprogramów. Przerwania, wspomniane na poprzednim slajdzie, pochodzą z układów na zewnątrz procesora, czyli od urządzeń wejścia-wyjścia, czasomierzy, układu bezpośredniego dostępu do pamięci itp. Inny rodzaj to przerwania zgłaszane wewnętrznie przez procesor, będące następstwem wykrycia jakiegoś stanu wyjątkowego. Jeszcze inny rodzaj to przerwania programowe, wynikające z wykonania specjalnej instrukcji procesora, umożliwiające programom użytkownika dostęp do wybranych funkcji jądra systemu operacyjnego.

Stabilność pracy systemu wymaga ochrony przynajmniej jądra systemu operacyjnego przed niekontrolowaną ingerencją użytkowników. Wymaga to monitorowania odniesień do pamięci i weryfikowania poprawności adresów. Ze względów wydajnościowych zadanie to realizowane jest sprzętowo, ale odpowiednie dane na potrzeby weryfikacji musi dostarczyć system. W celu zabezpieczenia tych (i innych) newralgicznych danych wyróżnione są pewne instrukcje uprzywilejowane, niedostępne dla programów aplikacyjnych. Powstaje jednak problem odróżnienia programów systemowych od aplikacyjnych, którego rozwiązaniem jest wyodrębnienie dwóch (w niektórych procesorach nawet większej liczby) poziomów pracy (trybów pracy). Możliwe staje się narzucenie sprzętowych restrykcji odnośnie wykonywania niektórych instrukcji na odpowiednich poziomach. Proces użytkownika uruchamiany jest w trybie nieuprzywilejowanym, w związku z czym nie może wykonać pewnych instrukcji, dostępnych tylko w trybie uprzywilejowanym, tym samym ma ograniczoną możliwość swobodnego ingerowania w „obszary” zastrzeżone dla jądra systemu operacyjnego.

Przerwania w systemie komputerowym


System przerwań umożliwia niesekwencyjne (współbieżne) wykonywanie programów. Zmiana sekwencji wykonywania instrukcji polega na tym, że w reakcji na przerwanie następuje zapamiętanie bieżącego stanu przetwarzania (najważniejszych rejestrów procesora), przekazanie sterowania do ustalonej procedury obsługi i rozpoczęcie wykonywania instrukcji tej procedury. W szczególności może to prowadzić do przełączenia kontekstu, czyli przekazania sterowania po zakończeniu procedury obsługi przerwania do innego przetwarzania, niż to które zostało przerwane.

Źródła przerwań


Przerwania od urządzeń zewnętrznych zgłaszane są po zakończeniu operacji wejścia-wyjścia i przekazywane na specjalne wejście procesora najczęściej przez sterownik przerwań. Tą samą ścieżką zgłaszane są również przerwania od układów ściśle współpracujących z procesorem — czasomierzy, układów bezpośredniego dostępu do pamięci itp. Są to typowe przerwania, gdyż ich źródło jest poza procesorem i jest od niego niezależne.

W przeciwieństwie do przerwań zewnętrznych, przerwania programowe są wynikiem wykonania specjalnej instrukcji procesora, np. int (interrupt) w procesorach firmy Intel, sc (system call) w procesorach PowerPC firm IBM, Motorola i Apple.

Przerwania diagnostyczne są z kolei generowane wewnętrznie przez procesor w sytuacji zajścia określonego stanu. Są zatem pośrednim skutkiem wykonania określonego ciągu rozkazów prowadzących do osiągnięcia tego stanu. Tego typu przerwania w literaturze określa się jako pułapki lub wyjątki. Przykładami tego typu przerwań są:

  • pułapki które pojawiają się, gdy licznik rozkazów osiągnie określoną wartość, tzn. będzie wskazywał na instrukcję, na której założono pułapkę. Mechanizm ten wykorzystywany jest w konstrukcji debugger’ów.
  • błędy programowe, typu błąd dzielenia przez 0, naruszenie ochrony pamięci, nieprawidłowy format rozkazu procesora. W reakcji na to przerwanie jądro najczęściej usuwa proces, który je spowodował.
  • błędy sprzętowe, wymagające odpowiedniej obsługi ze strony systemu operacyjnego, np. błąd braku strony w przypadku systemów z pamięcią stronicowaną. Zadaniem jądra jest doprowadzenie systemu do takiego stanu, żeby wznowienie tego samego rozkazu nie spowodowało ponownie błędu.
  • Przerwania w ujęciu wielowarstwowym


    slajd 28

    Przerwanie diagnostyczne ma swoje źródło na poziomie maszynowym procesora. Przerwanie programowe też ma swoje źródło na poziomie maszynowym procesora, ale bezpośrednią przyczyną jego wystąpienia jest rozkaz w programie wykonywanym przez procesor. Rozkaz taki najczęściej jest w programie aplikacyjnym, ale może również być w kodzie jądra systemu operacyjnego. Przerwanie zewnętrzne zgłaszane jest procesorowi poprzez podanie odpowiedniego sygnału na specjalne wejście. Procesor może mieć kilka takich wejść — w najprostszym przypadku ma jedno wejście przerwań maskowalnych (przerwanie zgłaszane na tym wejściu można ignorować) oraz jedno wejście przerwań niemaskowalnych (nie można go zignorować).

    Zasady ochrony pamięci


    slajd 29

    Kluczowa dla stabilnej pracy i bezpieczeństwa systemu komputerowego jest ochrona pamięci jądra systemu operacyjnego przed ingerencją programów wykonywanych w trybie użytkownika — przede wszystkim przed modyfikacją kodu lub danych, ale ze względów bezpieczeństwa również przed odczytem (np. haseł). W systemach wielozadaniowych ważna jest również ochrona pamięci jednego zadania (procesu) przed ingerencją innego zadania.

    W najprostszym przypadku ochrona pamięci polega na ograniczeniu zakresu dostępnych adresów do pewnego podzbioru, opisanego przez podanie najniższego i najwyższego dopuszczalnego adresu. W przedstawionym schemacie najniższy dopuszczalny adres przechowywany jest w rejestrze bazowym. Rejestr graniczny z kolei określa wielkość dostępnego obszaru pamięci. Dopuszczalne adresy należą do zatem do przedziału

    W środowisku wielozadaniowym z każdym przetwarzaniem mogą być związane inne ograniczenia na dostępność obszarów pamięci. Przełączanie między zadaniami wymaga zatem zmiany zawartości rejestrów, ograniczających zakres dostępności pamięci. W rozważaniach pominięte zostały też kwestie ograniczonego dostępu do określonych obszarów (np. tylko do odczytu). Dostępność obszaru w takim przypadku zależy od rodzaju cyklu maszynowego. Tego typu zagadnienia będą rozważane przy omawianiu zarządzania pamięcią.

    Pierścienie ochrony


    slajd 30

    Dla procesora program jest ciągiem instrukcji. Na tym poziomie nie ma rozróżnienia użytkownika, administratora, operatora itd. Narzucenie restrykcji odnośnie wykonywania niektórych instrukcji wymaga zatem wskazania trybu pracy (związanego z poziomem uprzywilejowania), który musi być czytelny dla procesora w celu weryfikowania dostępności instrukcji.

    W najprostszym przypadku wystarczą dwa tryby, ale większa ich liczba może usprawnić tworzenie oprogramowania systemowego. Na rysunku wyszczególniono 3 tryby, zwane też pierścieniami ochrony. W architekturze Intel IA-32 wyróżniono 4 pierścienie. W trybie najbardziej uprzywilejowanym (trybie jądra, poziomie nr 0 w procesorach Intel) dostępne są wszystkie instrukcje i rejestry procesora. W każdym następnym (mniej uprzywilejowanym) trybie jest coraz mniej dostępnych instrukcji lub rejestrów.

    Procesor pracuje zatem zawsze w jednym z trybów uprzywilejowania. Programu jądra wykonywany jest w trybie najbardziej uprzywilejowanym, z którego można się przełączyć w tryb mniej uprzywilejowany, co ma miejsce przy uruchamianiu programu aplikacyjnego. Powrót do trybu uprzywilejowanego możliwy jest poprzez odpowiednie przerwania (lub podobne mechanizmy), ale procedura obsługi przerwania dostarczona jest przez jądro. Program użytkownika nie może zatem zmienić trybu pracy na potrzeby wykonania dowolnego własnego kodu.

    Przerwanie zegarowe


    slajd 31

    Czasomierz jest licznikiem, na wejście którego podawane są impulsy o stałej częstotliwości, niezależnej od częstotliwości zegara procesora. Do licznika wpisywana jest pewna wartość, która zmniejszana jest o 1 z każdym impulsem. Po osiągnięciu wartości 0 generowane jest przerwanie, zwane zegarowym. Jest to jedno z przerwań zewnętrznych. Wystąpienie tego przerwania nazywany jest taktem zegara i pojawia się zależnie od architektury kilkadziesiąt lub kilkaset razy na sekundę (zwykle około 100 razy).

    W wyniku przerwania zegarowego system operacyjny przejmuje sterowanie, wykonuje pewne zadania okresowe (np. zbiera dane statystyczne w celu optymalizacji pracy systemu) i podejmuje decyzje, czy wznowić przerwane zadanie, czy przełączyć się na inne zadanie (dokonać przełączenia kontekstu). W ten sposób niemożliwe jest zawłaszczenie procesora przez program użytkownika.