Sciaga Systemy wyklad kolo 27 01 2008, szkola, systemy operacyjne i mikroprocesory


1. CELE ISTNIENIA SYSTEMÓW OPERACYJNYCH

Zarys historii rozwoju systemów operacyjnych

Najwcześniejsze komputery (konstruowane w latach 40-tych i na początku 50-tych) nie miały

żadnego stałego oprogramowania. Programy użytkowników były pisane w kodzie maszynowym

i bezpośrednio (w postaci ciągu bitów) wprowadzane do pamięci operacyjnej komputera.

Wyniki działania programów również były odczytywane bezpośrednio z pamięci. Takie

programowanie „surowej maszyny” było bardzo niedogodne dla użytkowników.

Aby ułatwić używanie komputera, zaczęto do niego przyłączać dodatkowe urządzenia służące do

przyspieszenia wprowadzania programów i danych do pamięci komputera oraz odczytywania

wyników. Zostały one nazwane urządzeniami zewnętrznymi komputera (dawniej nazywano je

też urządzeniami peryferyjnymi lub peryferałami (peripheral)).

Rodzaje najwcześniejszych urządzeń zewnętrznych:

1) urządzenie typu „dalekopis” (sterowana elektryczna maszyna do pisania) pełniące rolę konsoli

operatorskiej, to jest służące do wprowadzania i odczytywania niewielkich porcji informacji

w postaci znakowej;

2) czytniki nośników papierowych (taśmy i karty perforowane) umożliwiające dość szybkie

wprowadzenie większych programów i danych przygotowanych na oddzielnych urządzeniach

(perforatorach);

3) drukarki (wierszowe i głowicowe) umożliwiające dość szybki wydruk wyników.

Nieco później pojawiły się przewijaki taśm magnetycznych, umożliwiające wielokrotny zapis

i odczyt większych ilości danych przeznaczonych do czasowego przechowania (na przykład

pośrednich wyników obliczeń lub wyników kompilacji programów).

Programowanie obsługi przez komputer urządzeń zewnętrznych (czyli prawidłowego

komunikowania się komputera z nimi) należało do najtrudniejszych i najbardziej

skomplikowanych zadań programistycznych - i tak jest po dzień dzisiejszy.

W początkowym okresie komputery były bardzo drogimi urządzeniami. Czas ich pracy był

wyceniany bardzo wysoko i w związku z tym zaczęły pojawiać się koncepcje mające na celu

zaoszczędzenie czasu ich używania przez poszczególnych użytkowników.

Podstawowe koncepcje to:

1) zatrudnianie zawodowych operatorów, którzy szybko i bezbłędnie komunikowali się

z komputerem za pośrednictwem konsoli;

2) przygotowywanie na oddzielnych urządzeniach większej liczby tekstów programów i zestawów

danych dla nich i łączenie ich we wsady (batch), które umożliwiają natychmiastowe rozpoczęcie

wykonywania następnego programu po zakończeniu poprzedniego (lub odrzuceniu go z powodu

błędów);

3) uruchamianie na początku pracy komputera specjalnego programu przebywającego w jego

pamięci przez cały czas i zawiadującego współpracą komputera z urządzeniami zewnętrznymi

oraz kolejnością uruchamiania innych programów.

Trzecia z powyższych koncepcji doprowadziła do powstania systemów operacyjnych, których

pierwotną postacią był tak zwany monitor rezydujący. Był to program przebywający przez cały

czas w pamięci operacyjnej komputera i wprowadzający do pamięci i nadzorujący wykonywanie

innych programów (przekazanie im sterowania i przejęcie go z powrotem po ich zakończeniu).

O kolejności wprowadzania programów decydował operator podając odpowiednie polecenia

z konsoli, mogło to być też zautomatyzowane poprzez użycie tak zwanych kart sterujących

poprzedzających karty z zapisem treści programu.

Jedną z podstawowych funkcji operatora systemu komputerowego (poza fizyczną obsługą urządzeń

wejścia/wyjścia - wkładaniem i wyjmowaniem nośników papierowych, zakładaniem i zdejmowaniem

taśm magnetycznych) było zainicjowanie pracy komputera po włączeniu zasilania. Ponieważ

komputery nie posiadały pierwotnie pamięci trwałych (ROM) umożliwiających automatyczny start,

operator musiał ręcznie (przy użyciu kluczy) wprowadzić do pamięci kilka rozkazów maszynowych

umożliwiających wczytanie z nośnika papierowego bardziej złożonego programu (odpowiednika

dzisiejszego BIOS-u), który z kolei umożliwiał wczytanie systemu operacyjnego z taśmy

magnetycznej.

Rozwój systemów operacyjnych miał na celu jak najefektywniejsze wykorzystanie możliwości

sprzętu komputerowego i zawsze był nierozłącznie związany z rozwojem samego sprzętu.

Ponieważ urządzenia zewnętrzne zawsze działają znacznie wolniej od samego komputera (jego

procesora), dążono do jak największego uniezależnienia pracy procesora od czasu wprowadzania

danych i wyprowadzania wyników. Uzyskiwano to poprzez:

1) odchodzenie od współpracy procesora bezpośrednio z urządzeniami operującymi na nośniku

papierowym (są zdecydowanie najwolniejsze) poprzez przepisywanie danych i wyników na/z

taśmy magnetyczne przy użyciu wyspecjalizowanych urządzeń lub mniejszych, pomocniczych

komputerów (tak zwane przetwarzanie satelitarne);

2) buforowanie, czyli wydzielenie fragmentu pamięci operacyjnej do celów komunikacyjnych

i umożliwienie urządzeniom zewnętrznym (po zainicjowaniu transmisji przez procesor) niezależny

(autonomiczny) zapis/odczyt do/z tego fragmentu;

3) spooling, związany z zastępowaniem taśm magnetycznych dyskami magnetycznymi (szybszy

dostęp do danych), będący w istocie buforowaniem na dysku większej ilości zarówno danych, jak

i programów, i umożliwiający wykonywanie programów i wykorzystywanie zestawów danych

w innej kolejności, niż były wczytane.

Wspólną ideą powyższych rozwiązań było dążenie do zrównoleglenia pracy procesora i urządzeń

zewnętrznych poprzez zapewnienie im jak największej niezależności czasowej od siebie (czyli

spowodowanie, aby mogły one pracować asynchronicznie). Oczywiście nawet przy pełnym

zrównolegleniu nie można oczekiwać, że łączny czas pracy urządzeń zewnętrznych oraz pracy

procesora będą sobie równe - zadania obliczeniowe wymagające istotnie większego nakładu czasu

procesora nazywamy zadaniami zorientowanymi na przetwarzanie (typowe przykłady - duże

obliczenia naukowe i inżynierskie), zaś wymagające głównie pracy urządzeń zewnętrznych -

zadaniami zorientowanymi na operacje wejścia/wyjścia (typowy przykład - drukowanie

rachunków).

Uwaga

Istotną cechą architektury współczesnych komputerów jest wyposażenie prawie wszystkich urządzeń

zewnętrznych (oraz komunikujących się z nimi układów elektronicznych wewnątrz komputera)

w wyspecjalizowane mikroprocesory zawiadujące ich pracą i komunikacją. W tym sensie prawie

każdy współczesny system komputerowy jest maszyną równoległą, w której jeden (lub więcej)

procesorów ogólnego użytku oraz pewna liczba procesorów wyspecjalizowanych pracują w dużym

stopniu współbieżnie.

Po utworzeniu ośrodków obliczeniowych i zatrudnieniu w nich wykwalifikowanego personelu (a tym

samym uniemożliwieniu użytkownikom bezpośredniej styczności z komputerem) podstawowym

problemem stał się długi łączny czas przetwarzania zadania z punktu widzenia pojedynczego

użytkownika (zaniesienie danych i programu, odebranie następnego dnia wydruków komunikatów

o błędach, dostarczenie skorygowanych danych ...). Użytkownicy byli zmuszeni do bardzo dużej

koncentracji przy przygotowywaniu tekstów programów i danych, jak również musieli przygotowy-

wać dla operatorów komputerów szczegółowe instrukcje postępowania, uwzględniające wszystkie

możliwe scenariusze rozwoju sytuacji w trakcie przetwarzania.

Pierwszym krokiem w kierunku rozwiązania problemu stało się wprowadzenie wielozadaniowości

(wieloprogramowości) systemów komputerowych. Systemy operacyjne zaczęto konstruować tak,

aby mogły ładować do różnych fragmentów pamięci komputera wiele programów jednocześnie

i wykonywać je kawałkami, przerzucając sterowanie od jednego programu do drugiego wtedy, kiedy

wykonywany program musi na coś zaczekać (na przykład na transmisję większego bloku danych):

program A program B program A program C program B

Wiązało się to z zaprojektowaniem systemu operacyjnego tak, aby skutecznie radził sobie z:

a) zarządzaniem przydziałem pamięci; b) planowaniem przydziału procesora programom.

Wielozadaniowość zmniejszyła średni czas przetwarzania pojedynczego zadania, ale w dalszym

ciągu nie umożliwiła użytkownikom bezpośredniej współpracy z komputerem. Rozwiązaniem

okazało się dopiero:

1) rozwinięcie technologii monitorów ekranowych (oszczędność ogromnej ilości papieru);

2) konstrukcja systemów operacyjnych z podziałem czasu, czyli takich, w których wielu

użytkowników może współpracować z jednym komputerem pozornie jednocześnie w trybie

interakcyjnym (czyli w trybie wymiany komunikatów: polecenie użytkownika - odpowiedź

systemu) korzystając z wielu podłączonych do komputera terminali składających się z monitora

ekranowego i klawiatury (nieco później również myszy).

Konstrukcja dobrze działających systemów operacyjnych z podziałem czasu okazała się bardzo

trudna. Tylko w najprostszych przypadkach można było stosować cykliczny przydział równych

kwantów czasu wszystkim użytkownikom. Na ogół stosowany jest system priorytetów zadań

i zmienna długość oraz kolejność przydzielanych odcinków czasu.

Współczesne rozwiązania i tendencje rozwojowe

1) Wielodostęp vs. komputery indywidualne

Ogromny rozwój technologii półprzewodnikowych i związany z nim gwałtowny spadek cen

sprzętu komputerowego umożliwił pod koniec lat 70-tych produkcję małych komputerów

dostępnych dla dużej liczby indywidualnych nabywców (personal computer). Początkowo były

one wyposażane w bardzo proste, jednozadaniowe systemy operacyjne umożliwiające

gospodarowanie niewielkimi zasobami komputera. W następnych latach komputery osobiste

zrobiły nieoczekiwanie dużą karierę, zaspokajając część zapotrzebowania na moc obliczeniową

małych firm i prywatnych osób. Współczesne PC-ty mają moc obliczeniową i pojemność pamięci

operacyjnej oraz zewnętrznej wielokrotnie przewyższającą zasoby większości dużych komputerów

sprzed 20 lat.

Przeciwieństwem komputerów osobistych są teraz naprawdę duże (wieloprocesorowe, o pojem-

ności pamięci operacyjnej rzędu gigabajtów) komputery wielodostępne (mainframe). Oferują one

swoim użytkownikom czasowy dostęp do zasobów znacznie większych, niż w PC-tach.

2) Systemy skupione vs. systemy rozproszone

We współczesnych dużych komputerach coraz częściej instalowanych jest wiele procesorów, co

umożliwia: a) rzeczywiste (nie pozorne) wykonywanie zadań wielu użytkowników jednocześnie;

b) realizację rzeczywistej (nie pozornej) współbieżności procesów wspólnie pracujących nad

zadaniem obliczeniowym jednego użytkownika. Systemy komputerowe wieloprocesorowe ogólnie

dzielimy na:

a) systemy skupione (ściśle powiązane) - procesory mają wspólną pamięć operacyjną i zegar,

odległości pomiędzy elementami są niewielkie (wspólna obudowa);

b) systemy rozproszone (luźno powiązane) - procesory mają odrębne pamięci operacyjne i są

taktowane oddzielnymi zegarami (czyli pracują asynchronicznie), mogą być zarówno zbiorem

oddzielnych płyt umieszczonych we wspólnej obudowie (cluster), jak i zbiorem oddzielnych

komputerów połączonych kablami lub łączami telekomunikacyjnymi w sieć.

Konstrukcja zarówno systemów operacyjnych przeznaczonych dla wieloprocesorów, jak i rozproszo-

nych systemów operacyjnych jest zadaniem bardzo trudnym i jest obecnie przedmiotem wielu prac

naukowych.

Wskutek gwałtownego rozwoju technologii cyfrowych łącz telekomunikacyjnych w ostatnich latach,

model sieciowy systemu komputerowego bardzo się rozpowszechnił i prawdopodobnie jego znaczenie

będzie nadal rosło. Obecne komputery indywidualne często pełnią rolę terminali inteligentnych, czyli

same przetwarzają takie zadania, które nie przekraczają ich możliwości, a w przypadku zapotrzebowania

na większą moc obliczeniową lub pojemność pamięci komunikują się przez sieć lokalną lub Internet

z dużymi komputerami, często zgrupowanymi w farmie serwerów. Terminale mogą komunikować się

z serwerami w trybie tekstowym, ale coraz częściej komunikują się w trybie graficznym - są wtedy

określane jako graficzne stacje robocze.

Zaletą systemów sieciowych jest zarówno lepsze wykorzystanie sprzętu, jak i informacji przechowy-

wanych w pamięciach komputerów. Istotne są też aspekty niezawodnościowe i komunikacyjne.

Podział systemów operacyjnych ze względu na czas reakcji

Systemy operacyjne pracujące w trybie wsadowym (bardziej ogólnie: programy komputerowe,

których czas reakcji na podanie danych może być dowolnie długi) nazywamy też systemami

pracującymi off-line. Ich przeciwieństwem są systemy, których czas reakcji musi zmieścić się

w z góry określonych granicach (zazwyczaj systemy interakcyjne) - nazywamy je pracującymi

on-line. Szczególnym przypadkiem tych drugich są systemy, od których wymagana jest praktycznie

natychmiastowa reakcja na dane (w każdym razie przed nadejściem następnych) - nazywamy je

systemami czasu rzeczywistego (real-time). Typowymi przykładami systemów czasu rzeczywistego

są systemy sterujące procesami technologicznymi w zakładach przemysłowych.

Podsumowanie

Głównymi celami tworzenia systemów operacyjnych są:

1) wygoda użytkowników komputerów;

2) efektywność wykorzystania sprzętu komputerowego;

3) niezawodność pracy systemów komputerowych.

2. SPRZĘTOWE MECHANIZMY OCHRONY PROCESÓW OBLICZENIOWYCH

procesor

urządzenia

pamięć zewnętrzne

operacyjna

Płyta główna

Ogólny schemat logiczny komputera (pomijający organizację magistral, pamięć podręczną itd.)

W pamięci komputera może jednocześnie przebywać wiele programów (wśród nich system

operacyjny) oraz danych dla nich.

program 1

dane1

program 5

dane 5 Przykładowa zawartość pamięci operacyjnej

(dane niekoniecznie muszą być umieszczane

w pobliżu programów, które z nich korzystają)

program 17

dane 17

Programem (wykonywalnym) nazywamy zapis ciągu rozkazów (instrukcji) przeznaczonych do

wykonania przez procesor komputera.

Procesem (obliczeniowym) nazywamy „wykonywanie programu”, czyli ciąg stanów, jakie kolejno

przyjmuje procesor (jego rejestry) oraz obszar danych programu w związku z wykonywaniem przez

procesor tego programu (uwaga: kolejność wykonywanych instrukcji nie musi być taka sama, jak

kolejność ich zapisu w programie).

Pierwotnie użytkownik komputera sprawował nad nim „nieograniczoną władzę” - miał dostęp do

wszystkich instrukcji procesora, wszystkich lokat pamięci operacyjnej oraz wszystkich portów.

Dawało mu to pełną swobodę poczynań, ale jednocześnie nieograniczone możliwości szkodzenia

zarówno samemu sobie, jak i innym użytkownikom komputera.

Obecnie tak szerokie możliwości mają jedynie:

1) użytkownicy komputerów indywidualnych (którzy świadomie chcą z tych możliwości korzystać);

2) administratorzy wielodostępnych systemów komputerowych.

Zadaniem systemu operacyjnego jest chronienie sprzętu komputerowego oraz wykonywanych na nim

procesów obliczeniowych przed uszkodzeniem przez nieświadomych lub złośliwych użytkowników.

Jakiego rodzaju szkody może wyrządzać błędny program ?

1) Niewłaściwa komunikacja z urządzeniem zewnętrznym może spowodować:

a) utratę części bądź całości danych przekazywanych do / z urządzenia;

b) trwałe uszkodzenie urządzenia lub jego przedwczesne zużycie.

2) Program użytkownika może się zapętlić i angażować procesor komputera aż do wyłączenia

zasilania (restartu systemu).

3) Program użytkownika może wywołać rozkaz zatrzymania pracy procesora (zazwyczaj jest to

rozkaz halt).

4) Program może błędnie zmodyfikować swój własny kod (zapis) lub zniszczyć swoje dane.

5) W przypadku pracy w systemie wielozadaniowym program może również zniszczyć programy

i dane należące do innych użytkowników (zarówno w pamięci operacyjnej, jak i na nośnikach

zewnętrznych). W szczególności może popsuć system operacyjny.

Nawet najlepiej zaprojektowane systemy operacyjne nie są w stanie zapobiegać takim zjawiskom,

jeśli nie umożliwiają tego odpowiednie rozwiązania sprzętowe. Ogólnie, systemy operacyjne są

projektowane pod kątem funkcjonowania na konkretnym sprzęcie, a dokładniej, na sprzęcie od

którego można oczekiwać pewnych konkretnych własności.

Jakie są minimalne wymogi wobec sprzętu komputerowego, aby można było zapewnić ochronę

jemu oraz procesom obliczeniowym ?

Musi być zapewniona możliwość ciągłego śledzenia pewnych aspektów wykonywania programów

użytkowników oraz możliwość natychmiastowej interwencji w przypadku próby wykonania

instrukcji potencjalnie szkodliwej.

Minimum sprzętowe niezbędne w każdym przypadku to posiadanie przez procesor bitu trybu pracy

(co najmniej jednego). Tradycyjnie był to jeden bit, którego ustawienie na 1 oznaczało tryb

użytkownika, zaś ustawienie na 0 - tryb monitora (nadzorcy systemu).

Instrukcje procesora, które są potencjalnie niebezpieczne (w sensie wyżej wymienionych zagrożeń)

załadowane do rejestru rozkazów procesora będącego w trybie pracy użytkownika powodują:

1) przełączenie trybu pracy na tryb nadzorcy;

2) zarzucenie wykonywania programu użytkownika i przejście do wykonywania kodu systemu

operacyjnego.

Aby stwierdzić, czy instrukcja pobrana do wykonania jest potencjalnie niebezpieczna, procesor musi

przeanalizować: 1) co dana instrukcja ma wykonać; 2) na czym (na jakim miejscu w pamięci) ma być

wykonana. Musi zatem sprawdzić zarówno część kodową instrukcji, jak i jej część adresową.

Instrukcje potencjalnie niebezpieczne ze względu na swoją część kodową (na przykład instrukcja

zawieszenia lub zatrzymania procesora) nazywane są czasem uprzywilejowanymi (dawniej

nazywano je nielegalnymi). Instrukcje odwołujące się do lokat w pamięci, które nie zostały

przydzielone danemu procesowi, powodują błąd adresowania.

Opisany wyżej przeskok do wykonywania innego programu, połączony z zapamiętaniem informacji

o aktualnym stanie procesora (w tym jego wskaźnika instrukcji) nazywany jest przerwaniem

(interrupt). Mechanizm przerwania jest dużo bardziej ogólny i niekoniecznie musi wiązać się z próbą

wykonania „niebezpiecznego” rozkazu przez program użytkownika. Możliwość wywoływania

i obsługi przerwań wykorzystywana jest w praktycznie wszystkich procesorach, niezależnie od

istniejących w nich zabezpieczeń.

Ogólna idea przerwań: procesor powinien móc natychmiast reagować na zdarzenia asynchroniczne,

czyli takie, których moment pojawienia się był niemożliwy do przewidzenia (nie wynikał w logiczny

sposób z ciągu wykonywanych instrukcji programu).

Ogólna klasyfikacja przerwań:

1) przerwania sprzętowe (hardware interrupt);

2) przerwania związane z sytuacjami wyjątkowymi (exception interrupt);

3) przerwania programowe (software interrupt).

ad. 1) Przerwania sprzętowe mogą pochodzić od autonomicznie pracujących układów zawiadujących

pracą urządzeń zewnętrznych (kontrolerów urządzeń), które na przykład mogą powiadamiać

procesor o zakończeniu transmisji danych. Mogą też pochodzić od zegara systemowego, co

umożliwia procesorowi cykliczne przeprowadzenie pewnych rutynowych czynności (nie musi

tego wykorzystywać).

ad.2) Przerwania związane z sytuacjami wyjątkowymi pozwalają reagować nie tylko na próby

wykonania instrukcji niebezpiecznych, ale również na próby wykonania instrukcji uznanych

za błędne z innych powodów (na przykład próba dzielenia przez zero).

ad.3) Mechanizm przerwań objawił tak wiele zalet, że w listach instrukcji procesorów przewidziano

instrukcję „specjalnego” wywołania przerwania przez program użytkownika (to już trudno

nazwać „zdarzeniem asynchronicznym”), która powoduje zapamiętanie bieżącego stanu

procesu i przeskok do podprogramu obsługi przerwania.

Typowy ciąg czynności procesora po otrzymaniu sygnału przerwania:

1) identyfikacja przyczyny przerwania;

2) stwierdzenie, czy przerwanie może być obsłużone (być może sygnał przerwania nadszedł

w trakcie obsługiwania innego przerwania o wyższym priorytecie, wtedy obsługiwanie

przerwań o niższym priorytecie jest czasowo zamaskowane);

3) jeśli może być obsłużone, zapamiętywana jest bieżąca zawartość rejestrów procesora i ustawiana

jest maska sygnałów;

4) następuje przeskok do odpowiedniego miejsca w pamięci (czyli wpisanie odpowiedniej wartości

do wskaźnika instrukcji, zależnej od wykrytej przyczyny przerwania) i wykonanie podprogramu

obsługi przerwania;

5) na ogół (jeśli umożliwia to przyczyna przerwania) po zakończeniu wykonywania podprogramu

obsługi przerwania następuje odtworzenie zapamiętanego stanu rejestrów procesora i powrót do

programu, z którego nastąpił wyskok.

Jak zatem systemy operacyjne dysponujące mechanizmem przerwań radzą sobie z wymienionymi

wcześniej rodzajami błędów w programach użytkowników ?

1) Każda próba bezpośredniego wykonania operacji na urządzeniach zewnętrznych przez program

użytkownika będzie uznana za nielegalną - użytkownik może tylko poprosić o taką przysługę

system operacyjny (wywołać funkcję systemową);

2) Zapętleniu zapobiega możliwość czasowego przyjmowania przerwań od zegara systemowego

i wykorzystanie ich obsługi do zatrzymania zapętlonego procesu;

3) Instrukcja zatrzymania (oraz inne instrukcje uprzywilejowane) nie będą wykonane z poziomu

programu użytkownika;

4) i 5) Ochrona zawartości pamięci operacyjnej musi być zapewniona sprzętowo. Procesy mają

przydzielone oddzielne segmenty pamięci na kody programów oraz na dane. Z każdym segmentem

związane są dwa rejestry: rejestr bazowy oraz rejestr graniczny. W rejestrze bazowym pamiętany

jest początkowy adres w danym segmencie, a w rejestrze granicznym - wielkość segmentu.

wzrost segment base base

adresów pamięci base + limit limit

Część adresowa instrukcji pobranej do wykonania z programu użytkownika musi mieścić się

w przedziale wyznaczonym przez zawartości tych dwóch rejestrów (dotyczy to rejestrów zwią-

zanych z segmentem danych w przypadku instrukcji operujących na danych, zaś rejestrów

związanych z segmentem kodu w przypadku instrukcji skoku). W przypadku wykrycia przekroczenia

system zapamiętuje przyczynę i przechodzi do uprzywilejowanego trybu pracy (przeskakuje do

odpowiedniego podprogramu obsługi przerwania).

Ochrona dostępu do plików realizowana jest przez wielozadaniowe systemy operacyjne programowo.

Każdy plik musi mieć określonego właściciela oraz prawa dostępu. Właściciel pliku ustanawia

prawa dostępu do niego (będzie to bardziej szczegółowo omówione przy okazji omawiania

konkretnych systemów). Ponieważ procesy również mają swoich właścicieli, przy każdym odwołaniu

procesu do pliku system operacyjny sprawdza najpierw, czy odwołanie to jest legalne z punktu

widzenia obowiązujących praw.

Ochrona plików jest zatem dwojakiego rodzaju:

1) niskopoziomowa (podobnie jak dostępu do każdego innego urządzenia zewnętrznego);

2) wysokopoziomowa (w sensie wymuszenia przestrzegania praw własności).

Oddzielnym problemem jest chronienie procesów przed skutkami awarii sprzętu w systemach

komputerowych, od których sprawności działania bardzo dużo zależy. Tu również możliwości

systemów operacyjnych w istotny sposób zależą od własności sprzętu (wykrywanie czasowego

zaniku zasilania i awaryjne zapamiętywanie najważniejszych danych, instalowanie dodatkowych

procesorów i dublowanie obliczeń na wypadek awarii jednego z nich itd.).

3. PODSTAWOWE FUNKCJE SYSTEMÓW OPERACYJNYCH

Głównym celem istnienia systemu operacyjnego jest wygoda użytkowników komputera. System musi

zatem umieć przyjmować polecenia od użytkowników i je wykonywać (lub zgłaszać, że wykonać ich

nie jest w stanie, lub stwierdzać składniową niepoprawność wydanego polecenia). Jedną z podstawo-

wych części składowych każdego systemu operacyjnego przeznaczonego do bezpośredniego komuniko-

wania się z użytkownikiem jest interpretator poleceń (interpreter), który współpracuje z użytkowni-

kiem w sposób interaktywny (konwersacyjny), czyli prowadząc z nim dialog.

Gdyby funkcjonowanie systemu operacyjnego porównać do funkcjonowania dużej firmy, interpreter

poleceń pełniłby w niej rolę „biura obsługi klienta”.

Język porozumiewania się użytkownika z systemem musi podlegać pewnym formalnym regułom

składniowym (gramatycznym). Projektanci systemów dążą do tego, aby język ten był możliwie prosty

i wymagał jak najmniejszej wiedzy o konstrukcji systemu. Dlatego też zakładają zawsze pewien

uproszczony, abstrakcyjny (tak zwany logiczny) model systemu komputerowego, w którym są

pominięte różne nieistotne z punktu widzenia użytkownika szczegóły jego fizycznej konstrukcji.

Podstawowy sposób prowadzenia dialogu użytkownika z interpreterem poleceń to naprzemienne

wypisywanie tekstów (obecnie na ekranie monitora, kiedyś na papierze konsoli operatorskiej).

Dążąc do maksymalnego uproszczenia porozumiewania się, w połowie lat 80-tych zaprojektowano

pierwsze graficzne interfejsy użytkownika, które umożliwiają wydawanie poleceń dla systemu bez

wypisywania tekstów, tylko poprzez wykonywanie operacji przy użyciu wskaźnika (pointer) na

wyświetlanych na ekranie obiektach graficznych. Zaoszczędza to użytkownikom konieczności

pamiętania składni poleceń tekstowych (która jest na ogół traktowana jako uciążliwość) i przyspiesza

komunikację.

Tryb tekstowy wydawania poleceń w dalszym ciągu uważany jest jednak za podstawowy tryb

porozumiewania się z systemem, gdyż daje dużo szersze możliwości, niż tryb graficzny (na ogół

obejmujący tylko najbardziej „pospolite” czynności). Co więcej, tryb tekstowy umożliwia

konstruowanie poleceń złożonych oraz tworzenie plików wsadowych (skryptów) zawierających

całe ciągi poleceń, które na przykład rutynowo wykonuje codziennie administrator systemu.

Pewną namiastką możliwości zapamiętywania ciągów poleceń w trybie tekstowym jest możliwość

tworzenia makr w trybie graficznym.

Jakiego rodzaju typowe usługi są oczekiwane od systemu operacyjnego przez użytkowników ?

Jest ich dużo, najbardziej typowe przykłady to:

1) ładowanie do pamięci i uruchamianie programów;

2) obsługa systemu plików (tworzenie i usuwanie plików i katalogów);

3) obsługa urządzeń zewnętrznych (na przykład drukowanie);

4) jeśli system jest wielodostępny, to umożliwienie otwarcia i zamknięcia sesji pracy (zalogowanie

i wylogowanie się);

5) jeśli system jest sieciowy, to umożliwienie komunikacji w sieci (przesyłanie plików, zdalne

sesje pracy, porozumiewanie się w czasie rzeczywistym);

6) jeśli system wielodostępny jest używany do celów komercyjnych, to umożliwienie rozliczania się

z użytkownikami na podstawie rejestrowania wykorzystywania przez nich zasobów systemowych.

Każda z tego rodzaju usług jest, z punktu widzenia programisty, czynnością bardzo skomplikowaną.

W trakcie jej wykonywania system musi (w sposób niewidoczny dla użytkownika) dbać o bardzo

wiele różnych jej aspektów - musi prawidłowo i bezkonfliktowo przydzielać zasoby wielu procesom

wykonywanym współbieżnie, oszczędnie gospodarować pamięcią, chronić dane przed zniszczeniem itp.

Ze względu na swój stopień komplikacji systemy operacyjne (tak jak każdy duży program) mają

strukturę warstwową. Oznacza to, że konstrukcja ich opiera się na dużej liczbie podprogramów,

które są uporządkowane hierarchicznie - na bazie zbioru najbardziej podstawowych procedur oparte

są procedury wykonujące bardziej złożone czynności itd.

Ogólnie, zbiór podprogramów wykorzystywanych jako „elementy konstrukcyjne” systemu

operacyjnego i wykonywanych w całości w trybie jądra systemu (czyli w trybie, w którym mogą być

wykonywane operacje uprzywilejowane) nazywamy funkcjami systemowymi. Najbardziej podstawowe

funkcje systemowe wykonujące bezpośrednio operacje na urządzeniach i lokatach pamięci nazywamy

niskopoziomowymi.

Co najmniej część spośród funkcji systemowych może być wywoływanych z poziomu procesów

działających w trybie użytkownika - następuje wtedy czasowe przełączenie z trybu użytkownika

w tryb jądra i „fachowe” wykonanie żądanej usługi przez system operacyjny, a następnie zwrócenie

sterowania do procesu użytkownika. Te funkcje systemowe, które są dostępne dla programistów

tworzących programy przeznaczone do pracy w trybie użytkownika nazywane są wywołaniami

systemowymi, a ich zbiór zaimplementowany w postaci biblioteki w konkretnym języku

programowania (na przykład w C lub BASIC-u) - interfejsem programisty w danym języku.

Klasyfikacja rodzajów funkcji systemowych (według [A. Silberschatz et al.])

1) Funkcje związane z nadzorowaniem procesów:

a) utworzenie, załadowanie, wykonanie, zakończenie (lub zaniechanie), usunięcie procesu;

b) zawieszenie, wznowienie procesu;

c) pobranie, określenie (ustawienie) atrybutów procesu;

d) przydział pamięci, zwolnienie pamięci przydzielonej procesowi.

2) Funkcje wykonujące operacje na plikach:

a) utworzenie, usunięcie pliku;

b) otwarcie, zamknięcie pliku;

c) odczyt z pliku, zapis do pliku;

d) pobranie, określenie (ustawienie) atrybutów pliku.

3) Funkcje wykonujące operacje na urządzeniach:

a) żądanie przydzielenia urządzenia, zwolnienie urządzenia;

b) logiczne przyłączenie, logiczne odłączenie urządzenia;

c) odczyt z urządzenia, zapis do urządzenia;

d) pobranie atrybutów urządzenia, określenie (ustawienie) atrybutów urządzenia.

4) Funkcje operujące na informacji systemowej:

a) pobranie czasu lub daty, określenie (ustawienie) czasu lub daty;

b) pobranie danych systemowych, określenie (ustawienie) danych systemowych.

5) Funkcje komunikacyjne

a) utworzenie połączenia, usunięcie połączenia;

b) nadanie komunikatu, odebranie komunikatu;

c) nadanie, odebranie informacji o stanie połączenia;

d) przyłączenie, odłączenie urządzenia wymiennego.

Interfejs funkcji systemowych wykorzystują programiści (wymaga to dość zaawansowanej wiedzy

technicznej), natomiast użytkownicy systemu komputerowego korzystają z możliwości wydawania

poleceń systemowych interpreterowi poleceń, który prezentuje użytkownikom znacznie prostszy

(będący na wyższym poziomie abstrakcji) logiczny model komputera. Interpreter (przyjmujący

polecenia w trybie tekstowym lub w trybie graficznym) albo sam zajmuje się obsługą wydanego

polecenia (jeśli jest to jego polecenie wewnętrzne), albo uruchamia w celu obsłużenia go inny

program systemowy (mówimy wtedy o poleceniu zewnętrznym). W gruncie rzeczy z punktu

widzenia interfejsu użytkownika nie ma różnicy pomiędzy wywołaniem i wykonaniem polecenia

wewnętrznego, programu systemowego lub dowolnego innego programu zainstalowanego lub

utworzonego przez użytkownika.

Polecenia systemowe w trakcie swojego wykonywania dokonują zazwyczaj wielu wywołań

systemowych (na przykład otwarcie, odczyt i zamknięcie pliku).

Jest rzeczą w dużym stopniu umowną, jakie elementy określa się jako części składowe systemu

operacyjnego. Przyjmuje się, że sterowniki urządzeń oraz jądro systemu składające się ze zbioru

funkcji systemowych oraz abstrakcyjnych struktur danych, na których te funkcje operują, stanowią

jego integralne elementy. Często przyjmuje się również, że programy wykonywane na poziomie

użytkownika, ale standardowo udostępniane przez producenta wraz z jądrem (interpreter komend

oraz stowarzyszone z nim programy, edytory tekstu, kompilatory, programy konfiguracyjne i dia-

gnostyczne itp.) również stanowią elementy systemu.

Koncepcja maszyny wirtualnej

Systemy wielodostępne z podziałem czasu są konstruowane w taki sposób, żeby ich użytkownicy

pracujący jednocześnie przy wielu terminalach nie mogli sobie wzajemnie przeszkadzać. Często

system zapewnia wręcz poszczególnym użytkownikom całkowitą izolację od innych tak, że mają

wrażenie, że są jedynymi użytkownikami komputera (co najwyżej nieco wolniejszego i o mniejszej

pojemności pamięci). Taki logiczny obraz systemu z punktu widzenia pojedynczego użytkownika

nazywany jest maszyną wirtualną. Każda maszyna wirtualna pozorowana przez dany system

zapewnia taki sam interfejs programisty i interfejs użytkownika - umożliwia wywoływanie funkcji

systemowych i ich wykonywanie w trybie jądra, udostępnia egzemplarz interpretera komend oraz

innych programów realizujących polecenia systemowe, udostępnia wirtualny dysk do zapisu plików

oraz inne wirtualne urządzenia zewnętrzne.

Wykonywanie programów na maszynach wirtualnych związane jest między innymi z translacją

(przeliczaniem) adresów w wykonywanych instrukcjach programów tak, aby niezależnie od

umiejscowienia programu we wspólnej pamięci fizycznej był on zawsze wykonywany w taki sam

sposób, i nie powodował kolizji z innymi wykonywanymi współbieżnie programami.

4. ZARZĄDZANIE PROCESAMI

Pojęcie procesu jest trudne do formalnego zdefiniowania. Określenie podane w wykładzie 2 jest

nieprecyzyjne, gdyż nie uwzględnia wielu możliwości stwarzanych przez współczesne systemy

operacyjne - na przykład wykonywania wielu procesów jednocześnie i ich komunikowania się przy

użyciu współdzielonego segmentu pamięci. Nie uwzględnia też czasowego przekazywania sterowania

z programu użytkownika do jądra systemu. Intuicyjnie przez proces rozumiemy ciąg kolejno wykony-

wanych przez procesor i logicznie powiązanych ze sobą instrukcji. Z punktu widzenia systemu

operacyjnego każdy proces jest skojarzony z logiczną strukturą danych utrzymywaną przez jądro

systemu i zawierającą niezbędne informacje pozwalające systemowi decydować, kiedy proces

powinien być wykonywany, a kiedy zawieszony, kiedy jakie zasoby systemowe może mieć

przydzielone, czy należy czasowo zawartość jego segmentów pamięci przepisać na dysk do pliku

wymiany, jakim kosztem jego wykonywania należy obciążyć jego właściciela itp.

Co należy znać, aby scharakteryzować jakiś proces w pewnym momencie jego wykonywania ?

- kod binarny jego programu;

- dane zapisane w pamięci (wartości stałych i aktualne wartości zmiennych);

- zawartość stosu;

- zawartości rejestrów procesora, a w szczególności wskaźnika instrukcji;

- usytuowanie programu, danych i stosu w pamięci;

- aktualny przydział zasobów (np. dostęp do urządzeń zewnętrznych);

i wiele innych danych przechowywanych przez system operacyjny. W szczególności trzeba znać

aktualny stan procesu.

Ogólny graf stanów procesu:

nowy gotowy aktywny zatrzymany

czekający

Podstawowe dane o procesie wykorzystywane przez system operacyjny do planowania i zarządzania

są zebrane w pamięci jądra systemu w postaci bloku kontrolnego procesu (Process Control Block).

Na ogół zawiera on następujace informacje:

- identyfikator procesu;

- stan procesu (np. wg diagramu na poprzedniej stronie);

- stan rejestrów procesora (utrwalony w momencie czasowego zawieszenia procesu);

- informacje istotne dla planowania przydziału procesora (priorytet procesu, wskaźniki do położeń

w kolejkach szeregujących zamówienia na zasoby itp.);

- informacje istotne dla zarządzania pamięcią (zawartości rejestrów bazowych i granicznych, tablic

stron w pamięci itp.);

- informacje do rozliczeń (zużyty czas procesora, zużyte czasy wykorzystania urzadzeń zawnętrznych,

ograniczenia czasowe, numer rachunku i in.);

- informacje o stanie przydziału urządzeń zewnętrznych procesowi (urządzenia aktualnie przydzielone,

zamówienia oczekujące na realizację, wykaz otwartych plików itp.).

Zawartość pamięci operacyjnej przydzielonej procesowi oraz informacje przechowywane w bloku

kontrolnym nazywamy kontekstem procesu.

Dwa procesy nazywamy współbieżnymi, jeżeli każdy z nich rozpoczął się przed zakończeniem

drugiego procesu.

Przykłady

1)

procesy są współbieżne

2)

procesy są współbieżne

3)

procesy nie są współbieżne

Uwaga.

O współbieżności możemy mówić zarówno w przypadku wykonywania procesów na tym samym

komputerze, jak i na oddzielnych komputerach.

W literaturze można napotkać następujące określenia:

* współbieżny (concurrent)

* równoległy (parallel)

* rozproszony (distributed)

Są one używane w różnych kontekstach. Zazwyczaj pierwsze dwa używane są zamiennie, przy

czym nieco częściej określenie „równoległy” oznacza „wykonywany współbieżnie na tym samym

komputerze”. Natomiast określenie „rozproszony” oznacza „wykonywany współbieżnie na

oddzielnych komputerach połączonych w sieć”.

Obliczenia równoległe (na jednym komputerze) mogą odbywać się jako:

1) rzeczywiście równoległe - jeśli komputer ma wiele procesorów i każdy procesor obsługuje co

najwyżej jeden proces w dowolnej chwili czasu;

2) pozornie równoległe - jeśli czas pracy jednego procesora dzielony jest na krótkie odcinki

przydzielane różnym procesom na zasadzie przeplotu.

Współbieżność wykonywania procesów jest opłacalna ze względu na:

1) lepsze wykorzystanie zasobów fizycznych (sprzętu);

2) lepsze wykorzystanie zasobów logicznych (na przykład informacji zawartych w plikach);

3) przyspieszenie obliczeń (jeśli są wykonywane w warunkach rzeczywistej równoległości);

4) ułatwienie konstrukcji dużych programów (modularność);

5) wygoda użytkowników (przykład: kopiowanie za pośrednictwem schowka w Windows).

Dwa procesy współbieżne nazywamy niezależnymi, jeżeli fakt wykonywania któregokolwiek z

nich w żaden sposób nie wpływa na wykonywanie drugiego. W przeciwnym przypadku procesy

nazywamy zależnymi lub współpracującymi.

Uwaga

W rzeczywistości to, czy dwa procesy uznamy za niezależne, czy za zależne, w wielu przypadkach

zależy od poziomu abstrakcji (ignorowania szczegółów) na jakim rozpatrujemy działanie tych

procesów.

Własności procesu niezależnego:

- żaden inny proces nie komunikuje się z nim (nie wpływa na jego kontekst);

- działa deterministycznie (wynik zależy wyłącznie od danych i stanu początkowego);

- może być dowolną liczbę razy wstrzymywany i wznawiany.

Własności procesu współpracującego:

- jego bieżący stan jest zależny zarówno od jego poprzedniego stanu, jak i od stanów innych

procesów;

- może wykazywać niedeterminizm (obliczane wyniki mogą zależeć od względnej kolejności

wykonywania procesów w systemie);

- zachowanie procesu może nie być przewidywalne (na przykład może czasem zapętlać się,

a czasem nie).

Uwaga

Programy współbieżne powinny być projektowane tak, aby nie wykazywały losowości wykonania.

Tradycyjnie procesy w systemie wykonują się w odrębnych przestrzeniach adresowych pamięci,

mogąc komunikować się ze sobą jedynie za pośrednictwem plików lub łączy komunikacyjnych.

Takie procesy nazywane są czasem procesami ciężkimi (ich utworzenie wiąże się z dość dużym

narzutem czasowym ze strony systemu operacyjnego). Dużo później, niż koncepcja procesu pojawiła

się koncepcja wątku (thread), nazywanego też procesem lekkim. Zbiór wątków pracujących nad

wspólnym zadaniem obliczeniowym wykonuje wspólny kod programu (co najwyżej różne jego

podprogramy) i operuje we wspólnej przestrzeni adresowej.

Uwaga

Rzeczywisty obraz sytuacji we współczesnych systemach operacyjnych jest bardziej skomplikowany,

niż by wynikało z powyższych definicji. Procesy ciężkie mogą mieć wspólny segment kodu (gdyż

jest on przeznaczony tylko do odczytu), dopóki jeden z nich nie wczyta nowego kodu z pliku. Mogą

też współdzielić segment danych, dopóki jest on wykorzystywany przez nie tylko do odczytu.

Ponadto mogą mieć przydzielony dodatkowy segment pamięci wspólnej (dzielonej), który jest

wykorzystywany jako jeden z możliwych środków komunikacji międzyprocesowej.

Wątki posiadają najbardziej istotne cechy procesów - dysponują własnymi zestawami rejestrów,

a w szczególności wskaźnikami instrukcji (zatem mają przydzielone procesory - rzeczywiste lub

wirtualne). Mają też własne stosy (więc mogą wywoływać funkcje). Wiele cech wątków zależy od

konkretnej implementacji - w Linuksie zaimplementowana jest biblioteka wątków odpowiadająca

normie POSIX (pthread).

Jednym z podstawowych problemów, jakie muszą rozstrzygnąć implementatorzy, to czy wątki mają

być reprezentowane w strukturach jądra systemu (jako procesy lekkie) i podlegać szeregowaniu

globalnemu, czy też mają być niewidoczne dla jądra i podlegać szeregowaniu lokalnemu (w obrębie

swojego procesu). W tym drugim przypadku programista może mieć wybór strategii szeregowania

w obrębie poszczególnych procesów. Szeregowanie wiąże się z pojęciem priorytetu (wątki, tak jak

procesy ciężkie, mają swoje indywidualne priorytety): czy priorytety wątków mają być traktowane

jako bezwzględne współczynniki pilności wykonania, czy też mają być relatywizowane względem

priorytetu ich procesu ?

Kolejnym problemem do rozstrzygnięcia są skutki wywołań funkcji systemowych przez poszczególne

wątki. Intencją projektantów jest uniezależnienie innych wątków w procesie od skutków wywołania

funkcji przez jeden z nich. W szczególności wątek czasowo zawieszony z powodu oczekiwania na

dane do wczytania nie powinien blokować pracy innych wątków tego samego procesu.

Uproszczony diagram stanów wątku jest taki sam, jak uproszczony diagram stanów procesu.

Jeżeli jeden proces powołuje do życia drugi proces, to ten pierwszy jest nazywany procesem

rodzicielskim, a ten drugi - procesem potomnym. Tylko proces rozpoczynający pracę systemu

operacyjnego nie ma swojego procesu rodzicielskiego. Proces potomny może otrzymać wstępny

przydział zasobów wprost od systemu operacyjnego (niezależnie od zasobów dzierżawionych przez

proces rodzicielski), ale zazwyczaj dziedziczy zasoby procesu rodzicielskiego. W szczególności

dziedziczy zawartość segmentów pamięci operacyjnej - kod programu i dane. Proces rodzicielski

może też przekazać swojemu potomkowi pewne dane początkowe (wartości zmiennych środowiska).

Możliwe są dwa scenariusze wykonywania procesów:

1) proces rodzicielski zostaje zawieszony aż do zakończenia pracy jego procesu potomnego (tak

jest na przykład w systemie DOS);

2) proces rodzicielski i wszystkie jego procesy potomne wykonują się współbieżnie (tak jest na

przykład w systemie Unix);

Możliwe sposoby zakończenia procesu:

1) zakończenie naturalne (dojście do ostatniej instrukcji, przekazanie informacji rodzicowi);

2) zakończenie przedwczesne (usunięcie przez inny, uprawniony do tego proces);

Jeżeli nastąpi zakończenie procesu, który ma współbieżnie wykonywane procesy potomne, to

również możliwe są różne scenariusze:

1) może nie mieć to wpływu na wykonywanie procesów potomnych (w systemie Unix są one

„adoptowane” przez systemowy proces Init (numer 1) i od tej chwili pamiętają jego numer jako

numer procesu rodzicielskiego);

2) może nastąpić natychmiastowe usunięcie wszystkich procesów potomnych, ich potomków itd.

(całego „drzewa genealogicznego” procesów) - jest to tak zwane zakończenie kaskadowe.

Powyższe określenia i scenariusze dotyczyły procesów w tradycyjnym sensie (procesów ciężkich).

W przypadku wątków relacje pomiędzy rodzicami i potomkami są bardziej zatarte i zależą od

konkretnej implementacji. Na ogół implementatorzy dążą do tego, aby wątki wykonywane we

wspólnej przestrzeni adresowej dostrzegały się wzajemnie jako równoprawne „rodzeństwo”,

niezależnie od tego, który z nich w rzeczywistości zainicjował utworzenie którego wątku.

Zagadnienia planowania przydziału procesora

Procesy umieszczone w pamięci operacyjnej, lecz aktualnie nie wykonywane, a oczekujące na

przydział pewnego zasobu - procesora (procesy gotowe) lub urządzenia zewnętrznego, są ustawiane

w kolejki. Kolejki mogą być obsługiwane na zasadzie FIFO (First In, First Out), lecz częściej

procesy są wybierane z nich według bardziej skomplikowanych reguł (np. przy uwzględnieniu

ich priorytetów ). Proces systemowy, który zajmuje się wybieraniem procesów z kolejek

i przydzielaniem im zasobów, nazywany jest planistą (scheduler). Rola planisty może być

rozdzielona między kilka procesów (np. planista długoterminowy i planista krótkoterminowy).

Na następnym slajdzie podany jest ogólny diagram kolejek w systemie (wg [Silberschatz et al.]).

Zatrzymanie lub zawieszenie wykonywania procesu przez procesor może nastąpić wskutek:

1) zakończenia procesu;

2) przejścia do stanu oczekiwania (na przydział urządzenia zewnętrznego lub przekazanie

informacji o zakończeniu procesu potomnego);

3) przerwania (związanego z upływem kwantu czasu lub nastąpieniem oczekiwanego czy

nieoczekiwanego zdarzenia).

Jeżeli zmiana przydziału procesora może nastąpić tylko w przypadku 1) lub w przypadku 2) ale

tylko na czas oczekiwania na przydział urządzenia, mówimy o niewywłaszczeniowym algorytmie

przydziału procesora. W pozostałych przypadkach mówimy o algorytmie wywłaszczeniowym.

Zmiana przydziału procesora wiąże się z:

1) zapamiętaniem parametrów dotychczasowego procesu (zawartości rejestrów procesora itd.)

w bloku kontrolnym procesu (jeśli proces ma być jeszcze kontynuowany);

2) załadowaniem parametrów kolejnego procesu wybranego przez planistę (nowego lub

kontynuowanego).

Zespół czynności systemu operacyjnego wiążący się z 1) i 2) nazywany jest przełączaniem

kontekstu. Od prędkości przełączania kontekstu w istotnym stopniu zależy wydajność systemu.

Kryteria brane pod uwagę w algorytmach planowania przydziału procesora:

1) wykorzystanie procesora (minimalizacja przestojów);

2) przepustowość systemu (liczba zakończonych procesów w jednostce czasu);

3) czas cyklu przetwarzania (od utworzenia do zakończenia procesu);

4) łączny czas oczekiwania w kolejkach na przydział zasobów;

5) czas reakcji procesu na otrzymane dane (szczególnie w systemach czasu rzeczywistego).

Ostateczny algorytm przydziału procesora realizuje na ogół skomplikowaną funkcję biorącą pod

uwagę powyższe kryteria z uwzględnieniem różnych współczynników wagowych.

5. ZAGADNIENIA KOORDYNACJI PROCESÓW

Przez koordynację procesów będziemy rozumieli ich synchronizację (czyli uwzględnianie

wzajemnych uzależnień czasowych) oraz komunikację (czyli wzajemne przekazywanie sobie

informacji).

Potrzeba synchronizacji jest na ogół związana z komunikacją - informacja nie może zostać odczytana

przez jeden proces, jeśli nie została wcześniej zapisana przez inny proces. Komunikacja pomiędzy

procesami może odbywać się przez kanał komunikacyjny (na przykład realizowany jako bufor

współdzielonego pliku) lub przez pamięć dzieloną (segment pamięci fizycznej udostępniony przez

system operacyjny obydwóm procesom).

Zapisy / odczyty do / z pamięci dzielonej mogą być dokonywane przez oba procesy bez żadnych

ograniczeń, więc muszą być synchronizowane przez dodatkowe mechanizmy systemowe.

Kanał komunikacyjny służy zazwyczaj tylko do komunikacji jednokierunkowej, a mechanizmy

synchronizacji są zawarte w samych procedurach zapisu / odczytu do / z kanału.

Jeśli jeden proces tylko produkuje dane i umieszcza je w pewnym medium komunikacyjnym, a drugi

proces tylko je stamtąd pobiera (w takiej samej kolejności, w jakiej zostały włożone), mówimy, że te

procesy tworzą układ producent - konsument (producer - consumer). Jeśli medium komunikacyjne

posiada pewną pojemność (bufor), działania producenta są w pewnym stopniu niezależne od działań

konsumenta - może on produkować dane „na zapas” i umieszczać je w buforze. Konsument natomiast

nie może „konsumować” danych szybciej, niż dostarcza je producent.

Jeśli pojemność bufora jest nieskończenie duża, proces producenta jest całkowicie niezależny od

procesu konsumenta. Jeśli pojemność bufora jest zerowa, musi zachodzić pełna synchronizacja

pomiędzy produkcją i konsumpcją (włożenie i pobranie każdej danej musi nastąpić w tej samej

chwili). Najbardziej typowym przypadkiem jest skończona, niezerowa pojemność bufora. Bufor

skończony jest często realizowany jako tablica cykliczna.

Niektóre procesy systemowe pełnią rolę serwerów, czyli dostarczycieli określonych usług (na

przykład obliczają jakąś funkcję, podają czas, wysyłają pliki pod podany adres itp.). Serwery

przeważnie przebywają w stanie zawieszenia, oczekując na zgłoszenia procesów potrzebujących

usługi, czyli klientów. Po zgłoszeniu zapotrzebowania klient zostaje przeważnie zawieszony i czeka

na dostarczenie odpowiedzi przez serwer. Potem pobiera odpowiedź, a serwer zostaje ponownie

zawieszony.

serwer aktywność

klient zawieszenie

t

Uwaga

1) Okresowo mogą występować spiętrzenia zgłoszeń klientów przysyłanych szybciej, niż serwer

jest w stanie je obsłużyć. W takim przypadku są one wpisywane do kolejki zgłoszeń serwera

i wybierane stamtąd w takiej samej kolejności, jak przybyły.

2) Serwery dzielą się na iteracyjne i współbieżne. Serwery iteracyjne same (jako jeden proces)

zajmują się pobieraniem zgłoszeń z kolejki, obsługą i odesłaniem odpowiedzi. Serwery współ-

bieżne pobierają tylko zgłoszenia, a następnie tworzą procesy potomne i im zlecają całą dalszą

obsługę. Obecnie większość serwerów jest realizowanych jako współbieżne.

Przekazywanie informacji pomiędzy procesami powinno odbywać się w określonych porcjach.

Byłoby niekorzystne, gdyby proces odczytujący pobrał tylko fragment komunikatu przekazywanego

przez inny proces. Podobnie w przypadku aktualizacji zapisu pewnego rekordu w bazie danych przez

jeden proces, inne procesy nie powinny w tym samym czasie próbować odczytać tego rekordu, gdyż

mogłyby odczytać częściowo nową, a częściowo starą zawartość - otrzymałyby wtedy niespójne dane.

Fragment kodu procesu, który wykonuje operację na współdzielonych danych taką, że w tym samym

czasie żaden inny proces nie powinien operować na tych danych, nazywamy sekcją krytyczną

(critical section) tego procesu. Założenie, że w dowolnej chwili co najwyżej jeden proces (spośród

grupy współpracujących procesów) może wykonywać swoją sekcję krytyczną, nazywamy zasadą

wzajemnego wykluczania (mutual exclusion).

Aby wzajemne wykluczanie mogło być zrealizowane, każde wykonanie sekcji krytycznej musi być

poprzedzone wykonaniem sekcji wejściowej, w której proces zgłasza potrzebę wejścia do sekcji

krytycznej (i czeka na zgodę), a po wykonaniu sekcji krytycznej musi nastąpić wykonanie sekcji

wyjściowej, w której proces informuje inne o opuszczeniu sekcji krytycznej. Pozostałą część kodu

procesu (nie należącą do żadnej z powyższych sekcji) nazywamy resztą.

Przyjęcie założenia o istnieniu sekcji krytycznych pociąga za sobą konieczność spełnienia

następujących warunków:

1) wzajemne wykluczanie;

2) postęp (progress) - jeżeli w pewnym momencie żaden proces nie wykonuje swojej sekcji

krytycznej, a pewna liczba procesów kandyduje do tego (weszła do swoich sekcji wejściowych),

to wybór jednego z nich musi nastąpić w ograniczonym czasie;

3) jeżeli którykolwiek proces wszedł do swojej sekcji wejściowej, to przed otrzymaniem przez niego

zgody na wejście do sekcji krytycznej tylko ograniczona liczba innych procesów może taką zgodę

otrzymać.

Powyższe warunki związane są z unikaniem pewnych niekorzystnych zjawisk, które mogą wystąpić

w przypadku niewłaściwej koordynacji procesów. Te zjawiska to blokada (deadlock) oraz głodzenie

(starvation) procesów.

Blokada zachodzi, gdy pewna liczba procesów jest w stanie oczekiwania na zdarzenie, które może być

wynikiem jedynie wykonywania (postępu) jednego z nich.

Przykład

Przepis prawny obowiązujący w stanie Kanzas na przełomie XIX i XX wieku: „Jeżeli dwa pociągi

zbliżają się do siebie jadąc po krzyżujących się torach, to każdy z nich powinien zatrzymać się i nie

ruszać, dopóki ten drugi nie odjedzie”.

Przykład

Jeśli cztery pojazdy dojadą jednocześnie z różnych stron do skrzyżowania dróg równorzędnych,

każdy z nich powinien udzielić pierwszeństwa pojazdowi po jego prawej stronie.

Przykład

Problem ucztujących filozofów (por. następny slajd).

Problem ucztujących filozofów.

n = 5

Założenia:

a) każdy filozof może przebywać w dwóch stanach: myślenie i jedzenie ;

b) każdy widelec jest zasobem współdzielonym przez dwóch sąsiadów na zasadzie wyłączności

dostępu;

c) do jedzenia potrzebne są dwa widelce;

d) widelce muszą być brane sekwencyjnie (nie jednocześnie) przez jednego filozofa;

e) czasy przebywania w stanach myślenie i jedzenie są zawsze skończone.

Jeżeli wszyscy filozofowie naraz podniosą np. lewe widelce, dojdzie do blokady.

Warunki konieczne i dostateczne powstawania blokad:

1) wzajemne wykluczanie (niepodzielność pewnego zasobu - np. procesora lub wybranej lokaty

w pamięci);

2) brak wywłaszczeń (system nie może „siłą” odbierać pewnych zasobów procesom);

3) oczekiwanie cykliczne (musi istnieć zbiór procesów { P1, P2, ... , Pn }, gdzie n > 1, taki, że P1 czeka

na zasób przetrzymywany przez P2, ... , Pn czeka na zasób przetrzymywany przez P1).

Wynika z tego, że każdy z procesów biorących udział w blokadzie musi mieć przydzielony pewien

zasób i dodatkowo czekać jeszcze na przydzielenie pewnego innego zasobu.

Głodzenie zachodzi, gdy co najmniej jeden proces oczekuje w nieskończoność na przydzielenie

pewnego zasobu (choć teoretycznie mógłby go otrzymać), podczas gdy inne procesy wymieniają się

tym zasobem.

Przykład

Obsługa na zasadzie stosu (last in, first servised).

Do zjawiska głodzenia procesów może doprowadzić niewłaściwie zorganizowany system priorytetów

procesów (współczynników liczbowych przyporządkowanych procesom, które wskazują, na ile

wykonywanie danego procesu jest pilne z punktu widzenia systemu operacyjnego). Jeśli przez cały

czas pojawiają się w systemie procesy o wysokich priorytetach, może się zdarzyć, że pewien proces

o niskim priorytecie nigdy nie będzie wybrany do wykonania. Metodą zapobiegania takiemu zjawisku

jest system priorytetów zmiennych w czasie - priorytet każdego oczekującego na wykonanie procesu

stopniowo rośnie, aż wreszcie w którymś momencie musi on uzyskać przydział procesora. Takie

rozwiązanie jest zastosowane na przykład w systemie Unix.

W języku matematycznej teorii zarządzania procesami własność systemu zapewniająca brak głodzenia

procesów oczekujących na przydział zasobów nazywana jest uczciwością (fairness). Wyróżniane są

różne rodzaje uczciwości w zależności od tego, czy proces zgłasza zapotrzebowanie na zasób przez

cały czas, czy też okresowo, i od tego, czy czas oczekiwania na przydział jest zależny od liczby innych

procesów ubiegających się o ten sam zasób.

W programowaniu procesów współbieżnych istotną rolę odgrywa pojęcie niepodzielności operacji.

W przypadku wykonywania sekcji krytycznej chcemy mieć zapewnione, że proces będzie mógł ją

wykonać jako całość, bez przerywania wykonania przez inne procesy. Jeśli operacja na zasobach

jest wykonywana przez pojedyncze wywołanie funkcji systemowej, to niepodzielność jej wykonania

jest gwarantowana przez system operacyjny (procesy wykonywane w trybie jądra systemu nie

podlegają wywłaszczaniu). Przykładami takich operacji są pojedyncze zapisy i odczyty do / z

kanału komunikacyjnego (na przykład kolejki komunikatów).

Gwarancji niepodzielności nie ma natomiast w przypadku operowania na pamięci wspólnej.

Pojedyncze instrukcje w językach wyższego poziomu, takie jak na przykład x = y + z ; w języku C,

gdzie x, y, z są nazwami zmiennych, którym zostały przyporządkowane lokaty w segmencie

pamięci wspólnej, są tłumaczone na cały ciąg rozkazów maszynowych, którego wykonywanie może

być przeplatane z wykonaniami innych procesów (być może również operujących na tych

zmiennych) w dowolny sposób.

Jeżeli mają być wykonywane operacje na pamięci wspólnej, lub bardziej złożone operacje na

dowolnych zasobach, programista musi sam zadbać o ich zabezpieczenie. Najprostszym mecha-

nizmem abstrakcyjnym służącym do tego celu są semafory. Podstawowym rodzajem semafora

jest semafor binarny, będący obiektem, którego jedyne pole może przyjmować tylko wartości

0 i 1, a jedyne operacje, jakie można na nim wykonać, to:

P (czekaj)

V (sygnalizuj)

Definicje tych operacji jest następująca:

P(S) - jeżeli S>0, to zmniejsz S o 1, w przeciwnym razie wstrzymaj wykonywanie procesu;

V(S) - jeżeli są jakieś procesy wstrzymane przez semafor S, to wznów jeden z nich, w przeciwnym

razie jeśli S=0, to zwiększ S o 1.

Uwaga.

1) Skutek próby otwarcia otwartego semafora binarnego zależy od implementacji. Dojście do

takiej sytuacji świadczy o błędzie w programie (i system operacyjny zazwyczaj reaguje

sygnalizacją błędu).

2) Same operacje na semaforach muszą być wykonywane niepodzielnie. W systemach z przeplo-

tem realizowane są jako funkcje systemowe, natomiast w sytuacji rzeczywistej równoległości

ich implementacja musi być wspierana przez odpowiedni sprzęt (procesory z niepodzielnymi

rozkazami typu test-and-set).

3) Niedeterminizm uruchamiania procesów czekających pod semaforem może podlegać różnym

ograniczeniom. Wyróżniamy na przykład semafor ze zbiorem oczekujących, gdzie wybór

procesu spośród oczekujących pod semaforem jest zupełnie przypadkowy, i semafor z kolejką

oczekujących, gdzie procesy są uwalniane spod semafora w takiej samej kolejności, w jakiej

do niego przybyły.

Jednym z klasycznych problemów programowania współbieżnego jest tak problem czytelników

i pisarzy. Problem ten jest abstrakcją problemu dostępu do wielodostępnej bazy danych. Każdy

proces aktualizujący dane (pisarz) musi mieć wyłączność dostępu do danych (czytelni), ale procesy,

które tylko czytają (czytelnicy), mogą pracować jednocześnie.

Problem ten jest możliwy, ale trudny do rozwiązania przy użyciu tak elementarnych narzędzi, jak

semafory. Typowe rozwiązania opierają się na wysokopoziomowych mechanizmach synchronizacji,

takich jak na przykład monitory lub regiony (dostępnych w językach wysokiego poziomu przeznaczo-

nych do programowania współbieżnego - na przykład w języku ADA).

Idea rozwiązania: (gwarantującego niemożliwość głodzenia zarówno czytelników, jak i pisarzy)

Przybywający pisarze ustawiani są w kolejkę prostą, przybywający czytelnicy gromadzeni są

w zbiorze. Mechanizm koordynujący wpuszcza na przemian pojedynczych pisarzy i cały zbiór

zgromadzonych czytelników. Wpuszczenie może mieć miejsce dopiero po opuszczeniu czytelni

przez poprzednich użytkowników / użytkownika. Jeżeli kolejka oczekujących pisarzy jest pusta,

a w czytelni przebywają czytelnicy, każdy nowo przybyły czytelnik jest od razu wpuszczany.

Jeśli w zbiorze nie oczekują żadni czytelnicy, kolejno przybywający pisarze są wpuszczani po

zakończeniu pobytu w czytelni przez poprzednika.

6. ZARZĄDZANIE PAMIĘCIĄ

Pamięć operacyjna z punktu widzenia systemu operacyjnego może być postrzegana jako bardzo duża

jednowymiarowa tablica, czyli struktura o dostępie swobodnym, która może być scharakteryzowana

przez:

1) zakres adresów (indeksów);

2) wielkość pojedynczej lokaty (jednostki adresowalnej).

0

1

2

MAX - 1

W prawie wszystkich współczesnych komputerach jednostką adresowalną jest bajt (8 bitów).

Uproszczony model bloku pamięci:

adres

w w

e y

j j

ś PAMIĘĆ ś

c c

i i

e e

zapis / odczyt

Jeżeli szyna adresowa ma n bitów, to może podawać 2 różnych adresów w pamięci. Podawane

adresy, będące liczbami z zakresu 0 * 2 - 1, to adresy fizyczne, określające rzeczywiste położenie

bajtu w pamięci. Sam zakres adresów nazywany jest fizyczną przestrzenią adresową.

Uwaga

Czas dostępu do pamięci jest jednym z najistotniejszych czynników wpływających na szybkość pracy

komputera. Ze względu na to, że wiele rozkazów procesora operuje na większych jednostkach pamięci,

niż 1 bajt, oraz na to, że kolejno wykonywane rozkazy często odnoszą się do sąsiednich lokat pamięci,

w czasie jednego dostępu do pamięci jest zwykle pobierana znacznie większa porcja, niż 1 bajt.

Procesory najdawniejszych komputerów w swoich instrukcjach operowały bezpośrednio na adresach

fizycznych. Było to związane z tym, że pojemności pamięci operacyjnych były małe, a systemy

operacyjne - jednozadaniowe (zatem program i jego dane mogły być umieszczone w z góry ustalonych

obszarach pamięci). Implementacja wielozadaniowości była związana z koniecznością umieszczania

wielu programów (oraz ich danych i stosów) w różnych obszarach pamięci jednocześnie, tak aby

mogły pracować współbieżnie i wzajemnie sobie nie przeszkadzały. Główny problem był związany

z trudnością przewidzenia, w jakim obszarze pamięci fizycznej program zostanie umieszczony w celu

jego wykonania.

Powyższy problem w gruncie rzeczy przeważnie nie dotyczy programistów, gdyż pisząc programy,

posługują się oni adresami symbolicznymi (nazwami zmiennych lub etykietami instrukcji

w programie). Programista powinien co najwyżej orientować się w ograniczeniach nałożonych na

dostępność zasobów (głównie pamięci) w systemie, aby jego program oraz dane nie przekroczyły

pewnej dopuszczalnej wielkości. Z punktu widzenia programu jest zatem dostępny pewien zakres

adresów, który może być różny od fizycznej przestrzeni adresowej (i używać swojej własnej

numeracji adresów), który nazywamy logiczną przestrzenią adresową.

Decyzja, w którym obszarze pamięci fizycznej zostanie umieszczony program, może być podjęta:

1) na etapie kompilacji programu, czyli przetwarzania jego postaci źródłowej, napisanej przez

programistę, na postać wykonywalną (binarną);

2) na etapie ładowania programu wykonywalnego z pamięci zewnętrznej do pamięci operacyjnej;

3) na etapie wykonywania programu.

W pierwszym przypadku mamy do czynienia z bezwzględnym (nieprzemieszczalnym) kodem

wykonywalnym, czyli takim, który nadaje się do wykonania tylko po umieszczeniu go w ściśle

określonym miejscu fizycznej przestrzeni adresowej. Dla najdawniejszych (jednozadaniowych)

systemów operacyjnych była to początkowo jedyna możliwość (przykład - programy typu COM

w systemie DOS), obecnie ma zastosowanie jedynie w programach inicjujących działanie komputera.

W drugim przypadku mamy do czynienia z programami przemieszczalnymi (relokowalnymi).

Program ładujący (loader), przepisując program binarny z pamięci zewnętrznej do określonego

miejsca w pamięci operacyjnej, wykonuje przekształcenia części adresowych przepisywanych

instrukcji tak, aby program mógł być prawidłowo wykonany (jest to relokacja statyczna).

Uwaga

Na listach rozkazów różnych procesorów mogą występować zarówno rozkazy korzystające z adresów

bezwzględnych (fizycznych), jak i rozkazy korzystające z adresów względnych (w takim przypadku

adres fizyczny jest obliczany na podstawie adresu względnego i zawartości rejestrów procesora).

W przypadku procesorów dysponujących wystarczająco bogatą listą rozkazów korzystających

z adresów względnych, zadanie loadera może zredukować się do odpowiedniego ustawienia

początkowych zawartości rejestrów procesora.

W trzecim przypadku program może być przemieszczany w pamięci operacyjnej w trakcie swojego

wykonywania (może to dotyczyć całości programu bądź niektórych jego fragmentów). W takim

przypadku wszystkie adresy w programie załadowanym do pamięci są traktowane jako adresy

logiczne, a proces ich przekształcania na adresy fizyczne musi odbywać się „na bieżąco” (musi to

być umożliwione przez architekturę komputera). Nazywamy to relokacją dynamiczną.

Przetwarzanie adresów logicznych na adresy fizyczne nazywa się wiązaniem (translacją) adresów.

W jakich sytuacjach fragmenty programów (lub całe programy) są przemieszczane w pamięci

operacyjnej w trakcie wykonania ?

1) W sytuacji, gdy całkowity rozmiar programu przekraczał cały dostępny obszar pamięci fizycznej,

stosowano niegdyś nakładkowanie. Polegało to na umieszczeniu najpierw w pamięci najwcześniej

wykonywanego fragmentu programu, potem zaś usunięciu go stamtąd (w całości lub w części)

i wpisaniu na jego miejsce kolejnego fragmentu (nakładki). Odpowiedzialność za prawidłowość

nakładkowania spoczywała na programiście. Nakładkowanie było trudnym zadaniem, gdyż

wymagało od programisty dużej wiedzy technicznej - znajomości wewnętrznej reprezentacji

danych i wielkości zajmowanego obszaru przez program i jego dane, oraz umiejętności właściwego

przekazania sterowania pomiędzy kolejnymi nakładkami.

2) Jeśli procesy czekają na dysku w kolejce do załadowania do pamięci operacyjnej i wykonania,

system operacyjny może uznać za słuszne czasowe przeniesienie z pamięci na dysk jakiegoś

niezbyt pilnego procesu (na przykład i tak oczekującego na transmisję danych) i wpisanie na jego

miejsce bardziej pilnego procesu z dysku. Postępowanie takie nazywane jest wymianą.

Wymieniony proces może po pewnym czasie powrócić do pamięci, ale już w inne miejsce.

3) Procesy w pamięci w zależności od swojego przebiegu wykonania mogą chcieć korzystać z różnych

funkcji bibliotecznych. W najprostszym przypadku biblioteka funkcji, której użycie przewiduje

programista, jest dołączana do programu już na etapie kompilacji i ładowana do pamięci razem

z nim (biblioteka łączona statycznie). Ze względu na oszczędność pamięci wskazane jest

ładowanie do pamięci funkcji bibliotecznych dopiero wtedy, gdy w trakcie wykonywania programu

okaże się, że rzeczywiście będą potrzebne. Biblioteki, które coś takiego umożliwiają, nazywane są

bibliotekami łączonymi dynamicznie (dynamic linked library).

Obsługą wywołania przez program funkcji z biblioteki łączonej dynamicznie zajmuje się system

operacyjny. Przejmuje on wtedy sterowanie i sprawdza, czy dana biblioteka nie przebywa już gdzieś

w pamięci operacyjnej. Jeśli nie, sprowadza ją z dysku i umieszcza w wolnym miejscu pamięci,

a następnie przekazuje programowi wywołującemu informację o adresie wywoływanej funkcji.

W przypadku, gdy po pewnym czasie system stwierdzi deficyt miejsca w pamięci, może (niepotrzebną

już) bibliotekę ponownie usunąć z pamięci.

W pewnej chwili pracy komputera zawartość jego pamięci operacyjnej może się przedstawiać

następująco:

0

dziury

MAX - 1

Obszary w pamięci, które w danej chwili nie są wykorzystane, nazywamy dziurami.

W przypadku ładowania do pamięci nowego programu / fragmentu programu (lub programu, który

wcześniej uległ czasowemu przeniesieniu na dysk), istotnym problemem jest wybór najwłaściwszej

dziury, w której można go umieścić. Kryterium jakości dokonanego wyboru jest związane z liczbą

procesów, które można jednocześnie przechowywać w pamięci (im więcej, tym lepiej). Ponieważ

system operacyjny nie wie z góry dokładnie, kiedy, ile i jak dużych procesów zostanie zgłoszonych

do wykonania, musi kierować się wcześniejszymi statystykami i aktualną zawartością kolejki

procesów. W gruncie rzeczy stworzenie optymalnego algorytmu gospodarowania pamięcią przez

system operacyjny nie jest możliwe, a w praktyce główną rolę odgrywają badania empiryczne

(oparte o eksperymenty).

Ze względu na szybkość realizacji, algorytmy wyboru dziury mają na ogół prostą postać (na

przykład „pierwsza wolna”, „najmniejsza wystarczająco duża”, „największa” itp.). System

operacyjny musi dysponować pełną informacją o aktualnie wykorzystanych i niewykorzystanych

obszarach pamięci, żeby realizować jedną z tych strategii.

Dynamiczne wiązanie adresów w instrukcjach musi być wspierane sprzętowo. Programy relokowalne

dynamicznie po wprowadzeniu ich do pamięci zawierają w dalszym ciągu adresy logiczne, które są

przeliczane na adresy fizyczne dopiero w chwili pobierania danej instrukcji programu do wykonania

przez procesor. Jedną z najprostszych koncepcji jest wykorzystanie rejestrów służących do ochrony

przydzielonych fragmentów pamięci, czyli rejestru bazowego i granicznego. Jeśli adresy logiczne

w programie wykorzystują zakres od 0 do k-1, to (po sprawdzeniu, czy nie przekraczają zawartości

rejestru granicznego) są po prostu sumowane z zawartością rejestru bazowego. W ten sposób następuje

odwzorowanie spójnego zakresu [ 0, k - 1 ] na zakres [ baza, baza + k - 1 ].

Stosowanie tak prostej metody translacji adresów ma następujące wady:

1) umiejscowienie danych i stosu nie jest niezależne od umiejscowienia programu (zatem nie można

wykorzystać kilku oddzielnych dziur);

2) nie jest możliwe współdzielenie fragmentu pamięci przez kilka procesów (przy zapewnieniu

jednocześnie właściwej ochrony pamięci).

Większe możliwości gospodarowania pamięcią stwarza koncepcja adresowania deskryptorowego

(opisowego). W tym trybie adresowania adres logiczny traktowany jest jako dwuczęściowy: jedna

część jest numerem pewnej pozycji w tablicy deskryptorów (opisów), która zawiera informacje nie

tylko o adresie bazowym, ale również o ochronie (prawach dostępu) do danego fragmentu pamięci.

Druga część (przesunięcie) jest sumowana z uzyskanym z tablicy deskryptorów adresem bazowym.

Adresowanie deskryptorowe umożliwia zarówno lepsze zagospodarowywanie dziur, jak i stosowanie

selektywnej ochrony różnych fragmentów pamięci przy zachowaniu możliwości ich współdzielenia.

Musi być jednak wspierane przez odpowiednio zaprojektowany sprzęt, aby z powodu swojej

komplikacji nie spowolniło istotnie pracy procesora. Najtrudniejszą przeszkodą do pokonania jest

czas wyszukiwania informacji w tablicy deskryptorów, która może być bardzo duża (czasem nawet

nie jest w całości przechowywana w pamięci).

W zależności od sposobu podejścia do wyróżniania fragmentów pamięci opisywanych w oddzielnych

pozycjach tablicy opisów, powyższą metodę adresowania nazywamy segmentacją lub stronicowaniem,

a same fragmenty segmentami lub stronami. Mechanizmy segmentacji i stronicowania mogą być

nałożone na siebie (w sensie złożenia dwóch funkcji), dając segmentację stronicowaną.

Segmentacja jest związana z logiczną strukturą programu. Fragmenty pamięci opisywane w tablicy

są nazywane segmentami i zazwyczaj mają różne wielkości. Typowym postępowaniem systemu

operacyjnego jest przydzielenie oddzielnych segmentów dla kodu programu, dla danych i stosu, jak

również dynamiczne przydzielanie i odbieranie procesom segmentów pamięci dzielonej. Jest też

możliwa zmiana wielkości segmentu w trakcie jego wykorzystywania.

Ponieważ segmenty mają zmienną wielkość, dla każdego segmentu trzeba przechowywać informację

zarówno o adresie bazowym, jak i o wielkości (czyli zakresie adresów logicznych).

Ciekawą możliwość stwarza przydzielenie różnych deskryptorów jednemu i temu samemu blokowi

pamięci fizycznej. Umożliwia to operowanie w jednym programie na kilku „wcieleniach” jednej

i tej samej zmiennej w pamięci, „zapętlenie” przestrzeni adresowej itp.

Jeżeli segment jest segmentem pamięci dzielonej, musi mieć (podobnie, jak współdzielone pliki),

licznik dowiązań. Każde przyłączenie takiego segmentu do logicznej przestrzeni adresowej pewnego

procesu zwiększa licznik o 1, zaś odłączenie - zmniejsza o 1. Fizyczna likwidacja segmentu (zamiana

na dziurę) może nastąpić dopiero wtedy, gdy licznik dowiązań będzie zawierał 0 (nikt nie korzysta).

Stronicowanie bazuje na podziale całej fizycznej przestrzeni adresowej na nieduże fragmenty

jednakowej wielkości zwane stronami. Niektóre procesory umożliwiają określanie rozmiaru strony

(w trakcie konfiguracji systemu operacyjnego). Obecnie typową wielkością strony jest 4KB (4096

bajtów). Podobnie, jak w przypadku segmentacji, adres logiczny jest rozkładany na dwie części:

numer strony w tablicy stron i przesunięcie (w obrębie strony). Numer strony przy użyciu tablicy jest

przekodowywany na adres (a dokładnie, jego bardziej znaczącą część) odpowiedniego fragmentu

pamięci fizycznej. Uzyskana część adresu jest następnie składana z bitami przesunięcia, tworząc

pełny adres fizyczny. Stronicowanie wiąże się z rezygnacją ze spójności fizycznego obrazu spójnej

logicznej przestrzeni adresowej.

Mechanizm stronicowania, podobnie jak segmentacji, umożliwia kontrolę praw dostępu procesów do

lokat w pamięci. Wyróżniane są prawa dostępu w sensie zapisu, odczytu, bądź wykonywania (rwx).

Uwaga

W literaturze często stronami nazywane są odpowiednie zakresy adresów logicznych, zaś

odpowiadające im zakresy adresów fizycznych - ramkami.

Zjawisko występowania „niezagospodarowanej” przestrzeni w pamięci w pewnej liczbie oddzielnych

fragmentów (dziur) nazywane jest fragmentacją pamięci (pojęcie to odnosi się zarówno do pamięci

operacyjnej, jak i do pamięci zewnętrznych). Zjawisko fragmentacji jest niekorzystne, gdyż utrudnia

gospodarowanie pamięcią (może na przykład okazać się, że żadna z dziur nie ma wystarczającej

wielkości, żeby pomieścić dany obiekt, choć suma wszystkich dziur byłaby wystarczająca).

Dopóki możliwe jest znajdywanie dziur wystarczającej wielkości, system stara się stosować jeden

z algorytmów wyboru. W przypadku, gdy odpowiednich dziur zaczyna brakować, można próbować

przeprowadzić upakowanie pamięci (w przypadku pamięci zewnętrznej nazywane zazwyczaj

defragmentacją). Polega to na odpowiednim poprzesuwaniu zapisanych obiektów w pamięci tak, aby

uzyskać jak największą komasację dziur.

Upakowanie pamięci operacyjnej przeprowadza system operacyjny (jeśli sprzęt umożliwia relokację

dynamiczną). Algorytmy upakowania zwykle stanowią kompromis pomiędzy ścisłością upakowania

i czasem jego wykonywania. Defragmentację dysku wykonują zazwyczaj programy wykonywane na

poziomie użytkownika. Mogą być uruchamiane przez użytkownika lub automatycznie przez system

operacyjny (i wykonywane „w tle” z niewysokim priorytetem).

W przypadku stosowania segmentacji mamy na ogół do czynienia z sytuacją, gdy niezagospodarowane

fragmenty pamięci występują na zewnątrz segmentów (w postaci dziur). Sytuację taką nazywamy

fragmentacją zewnętrzną.

W przypadku, gdy niezagospodarowane fragmenty pamięci są małe, nie opłaca się utrzymywać

w systemie informacji o ich istnieniu - lepiej jest włączyć je do przestrzeni adresowej zagospodarowa-

nych fragmentów. Mamy wtedy do czynienia z fragmentacją wewnętrzną. Fragmentacja wewnętrzna

jest charakterystyczna dla stronicowania - z każdą jednostką logiczną (kodem programu, blokiem

danych itp.) związane jest niewykorzystanie średnio pół strony. W przypadku stronicowania nie

występuje natomiast fragmentacja zewnętrzna, gdyż każda ramka w pamięci fizycznej może być

niezależnie zagospodarowana (co jest związane z rezygnacją z konieczności zachowywania spójności

obrazu fizycznego przestrzeni adresowej programu).

Jak z tego widać, wielkość strony jest wynikiem kompromisu pomiędzy jakością upakowania

informacji w pamięci, a czasem jej wyszukiwania - zmniejszanie rozmiaru strony powoduje

zmniejszenie średniej fragmentacji, ale jednocześnie powoduje rozrost tablicy stron (a co za tym

idzie, wydłużenie czasu wyszukiwania opisów stron).

Segmentacja stronicowana uważana jest za najdogodniejsze narzędzie gospodarowania pamięcią, ale

jednocześnie jest najtrudniejsza do zaimplementowania. W takim przypadku każdy segment dysponuje

swoją własną tablicą stron. Adres logiczny pobrany z instrukcji jest traktowany jako trzyczęściowy:

jedna z jego części wskazuje pozycję w (globalnej) tablicy segmentów, która teraz zamiast informacji

o adresie bazowym i wielkości segmentu zawiera informację o adresie bazowym tablicy stron

segmentu i liczbie jej pozycji. Pozostałe dwie części adresu są traktowane tak, jak w przypadku

„czystego” stronicowania, to jest jako numer pozycji w tablicy stron i przesunięcie na stronie.

Proces obliczania adresu fizyczne go jest w tym przypadku bardziej złożony i czasochłonny, niż

w przypadku samej tylko segmentacji lub samego tylko stronicowania. Dodatkowym problemem jest

wielkość tablicy segmentów i tablic stron, które w przypadku przechowywania ich w pamięci

operacyjnej same podlegają stronicowaniu. Aby przyspieszyć translację adresów, procesor może

przechowywać część informacji związanej z aktualnie wykonywanym procesem w szybkiej podręcznej

pamięci asocjacyjnej (skojarzeniowej).

7. KONCEPCJA PAMIĘCI WIRTUALNEJ

Systemy operacyjne i wykonywane pod ich nadzorem programy są zazwyczaj w stanie wykorzystać

dużo większą przestrzeń adresową, niż umożliwia to fizyczna pamięć operacyjna wbudowana

w komputer. Ponadto może występować duża różnica pomiędzy możliwościami adresowania

wynikającymi z formatu rozkazów procesora (i wielkości jego rejestrów), a możliwościami

wynikającymi z szerokości szyny adresowej.

W przypadku procesorów Intel, tryb rzeczywisty adresowania (wykorzystywany przez system

operacyjny DOS) umożliwia zaadresowanie jedynie około 1MB pamięci:

Jeden segment: 64 KB (ustalona wielkość).

1 MB + Przesunięcie pomiędzy początkami kolejnych segmentów: 16 B.

64 KB - Adresowanie: segment : offset , gdzie zarówno numer segmentu,

16 B jak i wielkość offsetu (przesunięcie względem początku segmentu)

są liczbami 16-bitowymi, a cały adres - 20-bitowy (w procesorach

80286 i późniejszych można obsługiwać nadmiar na 21-szym bicie).

Procesor 80286 pracujący w trybie chronionym (stosujący deskryptorowy tryb obliczania adresu

fizycznego, umożliwiający ochronę pamięci), ma możliwość zaadresowania 16 MB pamięci fizycznej

(choć jego sposób obliczania adresu teoretycznie umożliwiałby zaadresowanie 1 GB pamięci).

Procesory o numerach 80386 i wyższych mogą zaadresować 4 GB pamięci fizycznej (choć

teoretycznie mogłyby adresować nawet do 64 TB pamięci).

1 KB = 2 B = 1024 B 1 MB = 2 B (ponad 1 milion bajtów)

1 GB = 2 B (ponad 1 miliard bajtów) 1 TB = 2 B (ponad 1 bilion bajtów)

Koncepcja pamięci wirtualnej sprowadza się do symulowania większej ilości pamięci operacyjnej

przy użyciu szybkiej pamięci dyskowej. Może być zrealizowana zarówno w oparciu o stronicowanie,

jak i o segmentację (stronicowanie jest dużo dogodniejszym narzędziem). Korzystanie przez system

z pamięci wirtualnej nie powinno być dostrzegane przez programy wykorzystujące logiczną przestrzeń

adresową. Realizacja pamięci wirtualnej musi być wspierana przez odpowiedni sprzęt.

Aby móc zaimplementować pamięć wirtualną, należy wydzielić na dysku odpowiednio dużą partycję

przeznaczoną na przestrzeń wymiany (swap file). W przestrzeni wymiany przechowywane są

fragmenty logicznego obrazu pamięci, które aktualnie nie mieszczą się w pamięci operacyjnej.

Translacja adresów jest teraz realizowana dwustopniowo:

1) przy użyciu rejestrów procesora i tablic deskryptorów następuje przeliczenie adresu logicznego na

adres liniowy, który może odpowiadać pewnemu adresowi w fizycznej pamięci operacyjnej, bądź

być adresem zewnętrznym (pojęcie pamięci wirtualnej w gruncie rzeczy odpowiada zakresowi

adresów liniowych, który może być dużo większy od zainstalowanej pamięci fizycznej).

2) w przypadku, gdy adres liniowy jest adresem zewnętrznym, następuje przerwanie nazywane brakiem

jednostki (missing item), w wyniku którego wykonywany jest podprogram sprowadzenia z dysku do

pamięci operacyjnej brakującej jednostki (strony lub segmentu), a następnie (po zaktualizowaniu

wpisów w tablicy deskryptorów) ostateczne obliczenie adresu fizycznego.

W sytuacji, gdy w pamięci fizycznej brakuje miejsca na wpisanie jednostki sprowadzonej z dysku,

system musi podjąć decyzję, jaką jednostkę czasowo przepisać z pamięci operacyjnej na dysk.

Ogólnie, wymiana jednostek nie musi być wykonywana bardzo często dzięki zasadzie lokalności

odniesień (stwierdzającej rzadkość skoków na duże odległości w logicznej przestrzeni adresowej).

Wydajność działania systemu jest jednak w dużym stopniu zależna od doboru właściwego algorytmu

wymian.

Jeśli kompilator programów napisanych w pewnym języku został zaprojektowany pod kątem pracy

w systemie z wymianą, przypada mu ważna rola dostosowania logicznej struktury skompilowanego

programu do jednostek wymiany. Jest bardzo wskazane, na przykład, żeby duże struktury danych,

takie jak duże tablice, były przechowywane w jak najmniejszej liczbie jednostek. Jeśli program używa

podprogramów, najkorzystniej jest, jeśli zajmują one oddzielne jednostki.

Istotną rzeczą jest odseparowanie kodu programu (oraz ewentualnie używanych stałych) od obszaru

wykorzystywanego przez zmienne programu. Jednostki zajmowane przez kod i stałe przeznaczone są

tylko do odczytu, zatem przy czasowym usuwaniu ich z pamięci nie jest potrzebna aktualizacja zapisu

na dysku (cały czas przebywają tam w tym samym miejscu), co znacznie przyspiesza wykonywanie

procesu.

Aby system operacyjny wiedział, czy zachodzi konieczność aktualizacji zapisu w przestrzeni

wymiany na dysku w przypadku usuwania jednostki z pamięci operacyjnej, czy też nie, w opisach

jednostek umieszczane są bity modyfikacji, nazywane też bitami zabrudzenia (dirty bit).

W przypadku, gdy zawartość jednostki nie podlega modyfikacji, wartość tego bitu wynosi 0.

Jeśli w jednostce został zmieniony choćby jeden bajt, bit modyfikacji jest ustawiany na 1.

Jednostki zawierające kod i stałe mają przez cały czas wyzerowany bit modyfikacji. W przypadku

jednostek zawierających wartości zmiennych może się przez przypadek również zdarzyć, że od

chwili sprowadzenia ich do pamięci operacyjnej przez pewien czas będą używane wyłącznie do

odczytu - w tym czasie ich bit modyfikacji pozostaje wyzerowany, co przyspiesza ewentualną

wymianę.

Uwaga

Dostęp systemu operacyjnego do przestrzeni wymiany na dysku jest na ogół dużo szybszy, niż

dostęp do systemu plików, gdyż korzysta z dużo prostszych mechanizmów (może nie stosować

buforowania ani skomplikowanych algorytmów wyszukiwania miejsca zapisu / odczytu).

Najczęściej stosowanym sposobem implementacji pamięci wirtualnej jest stronicowanie na żądanie

(demand paging). W tym przypadku niektóre strony procesu (jego kodu i danych) mogą przebywać

w pamięci operacyjnej, a niektóre na dysku. System operacyjny prowadzi „leniwą” politykę

sprowadzania stron do pamięci - sprowadza je dopiero wtedy, gdy okażą się potrzebne („na żądanie

procesu”). Problemy, jakie trzeba rozstrzygnąć, to:

1) ile ramek wstępnie przydzielać każdemu procesowi ?

2) jaką politykę wymiany stron prowadzić, gdy nie ma już wolnych ramek w pamięci ?

Algorytmy, które rozwiązują te problemy, nazywane są odpowiednio algorytmami wstępnego

przydziału i algorytmami zastępowania. Jeśli na początku działania procesy nie mają

przydzielanych żadnych ramek, mówimy o czystym stronicowaniu na żądanie.

Wstępny przydział ramek nie musi być taki sam dla wszystkich procesów - może zależeć od

wielkości jego programu, od priorytetu, logicznej struktury lub danych na temat jego wcześniejszych

wykonań.

Algorytm zastępowania może być algorytmem zastępowania lokalnego lub algorytmem zastępowania

globalnego. W pierwszym przypadku każdy proces ma liczbę ramek przydzieloną na stałe i w jej

granicach operuje wymieniając swoje strony w miarę własnych potrzeb (stosując na przykład liczniki

użycia ramek, system priorytetów ramek i inne kryteria). W drugim przypadku procesy mogą

rywalizować o ramki - algorytm wymiany musi pełnić rolę arbitra i brać pod uwagę na przykład

priorytety procesów, wielkość programów czy logiczną strukturę (podobnie, jak algorytm przydziału

wstępnego).

W przypadku niewłaściwego doboru algorytmów w systemie mogą występować różne niekorzystne

zjawiska. Przykładowo, może się zdarzyć, że globalny algorytm wymiany stron przydzieli pewnemu

niskopriorytetowemu procesowi bardzo małą liczbę ramek. W takiej sytuacji prawdopodobnie byłoby

najkorzystniejsze czasowe zawieszenie takiego procesu i wznowienie go dopiero po zmniejszeniu się

obciążenia systemu. Jeśli jednak w takich warunkach proces będzie nadal się wykonywał, będzie musiał

co chwila wymieniać ramki, i łączny czas dokonywania tych wymian może przekroczyć łączny czas

jego efektywnej pracy - takie zjawisko nazywamy szamotaniem się procesu (migotaniem).

Fizyczna realizacja stronicowania na żądanie opiera się na istnieniu w każdej pozycji tablicy stron

(w każdym opisie strony) bitu poprawności odniesienia, który informuje, czy dana strona przebywa

aktualnie w pamięci operacyjnej (adres jest adresem wewnętrznym), czy też przebywa na dysku

(adres jest adresem zewnętrznym). W tym drugim przypadku żądanie dostępu do strony generuje

przerwanie (page fault). Po sprowadzeniu strony do pamięci (co ewentualnie może być związane z

wymianą) system aktualizuje tablicę stron i kontynuuje wykonywanie przerwanego procesu.

Realizacja segmentacji na żądanie wygląda podobnie - również oparta jest na istnieniu bitów

poprawności odniesienia w deskryptorach segmentów. Jest jednak algorytmicznie dużo trudniejsza

(a zatem powolniejsza) ze względu na zmienną wielkość dziur w pamięci i ewentualną potrzebę ich

okresowej komasacji.

Segmentację na żądanie wykorzystywały systemy dedykowane procesorowi Intel 80286 - na

przykład OS / 2.

Czynnikiem, jaki musi być uwzględniony w przypadku algorytmów wymiany jednostek pamięci, jest

możliwość współdzielenia tych jednostek. Odnosi się to do:

1) współdzielenia przez procesy wykonywane na tym samym procesorze głównym;

2) współdzielenia przez procesy wykonywane na różnych procesorach fizycznych w systemie

wieloprocesorowym;

3) współdzielenia przez proces oraz transmisję danych wykonywaną przez autonomiczne (działające

asynchronicznie) urządzenie zewnętrzne.

Z oczywistych względów nie można usunąć z pamięci jednostki przed zakończeniem transmisji

danych. W przypadku współdzielenia fragmentu pamięci przez różne procesy można usunąć ten

fragment dopiero po zmniejszeniu się stanu jego licznika dowiązań do zera. Uwzględnienie takiej

sytuacji jest szczególnie trudne w systemie wieloprocesorowym - gospodarka pamięcią dzieloną

w takim systemie musi być gospodarką scentralizowaną.

Mechanizmem umożliwiającym zabezpieczenie strony przed przedwczesnym usunięciem jej

z pamięci operacyjnej może być bit blokowania umieszczony w opisie strony. Ustawienie wartości

tego bitu na 1 stanowi informację dla systemu operacyjnego, że strona czasowo nie powinna podlegać

usuwaniu w ramach algorytmu wymiany.

Stosowanie bitu blokowania może być przydatne zarówno w sytuacji oczekiwania na zakończenie

transmisji danych czy współdzielenia strony, jak też w sytuacji, kiedy strona została dopiero co

sprowadzona do pamięci przez jakiś proces i jeszcze nie była ani razu użyta. Jeżeli proces ma niski

priorytet, mogłoby się zdarzyć, że inny, wysokopriorytetowy proces będzie usiłował pozbawić go

od razu miejsca w pamięci. Ponieważ sprowadzenie strony do pamięci jest czasochłonne, należy

zadbać o to, żeby choć przez pewien czas była ona wykorzystana.

Procesy użytkowników mogą z „egoistycznych pobudek” starać się nadużywać mechanizmu

blokowania stron w pamięci. Z tego powodu systemy operacyjne często traktują ustawienie bitu

blokowania jako „blokowanie zalecane”, nie „blokowanie obowiązkowe” i w sytuacji dużego

spiętrzenia wysokopriorytetowych procesów mogą same podejmować ostateczną decyzję co do

trzymania strony w pamięci lub też jej usunięcia.

Korzyści z implementacji pamięci wirtualnej:

1) podobnie, jak w przypadku nakładkowania, w pamięci mogą być przechowywane jedynie

fragmenty kodów programów, a nie całe kody;

2) programiści tworzący bardzo duże programy nie muszą wnikać w organizację wymiany ich

fragmentów pomiędzy pamięcią operacyjną a pamięcią zewnętrzną;

3) można równolegle wykonywać więcej procesów, niż wynika to z ograniczeń narzucanych przez

rozmiar pamięci operacyjnej, co polepsza wykorzystanie sprzętu i zwiększa wydajność systemu.

Procesor Intel 80286 umożliwiał realizację pamięci wirtualnej tylko w oparciu o mechanizm

segmentacji. Procesory o numerach 80386 i wyższych umozliwiają implementację pamięci wirtualnej

zarówno w oparciu o segmentację, jak i stronicowanie, przy czym zdecydowanie zalecany jest ten

drugi sposób.

8. SYSTEMY PLIKÓW

Poza pamięcią operacyjną (pamięcią główną) komputery są wyposażone w pamięć zewnętrzną

(pamięć pomocniczą), która jest realizowana w oparciu o nośnik magnetyczny lub optyczny.

Programy aktualnie wykonywane (oraz ich dane i stosy), a przynajmniej aktualnie wykorzystywane

fragmenty muszą przebywać w pamięci operacyjnej, gdyż tylko stamtąd procesor może bezpośrednio

pobierać rozkazy do wykonywania i tylko tam może bezpośrednio odczytywać lub zapisywać dane

(mówimy, że pamięć operacyjna jest pamięcią o dostępie bezpośrednim).

Do pamięci zewnętrznej procesor ma dostęp za pośrednictwem układu sterującego danym urządzeniem

(kontrolera), któremu może podawać rozkazy zapisu lub odczytu całych bloków danych (zatem nie

jest możliwe zapisywanie lub odczytywanie pojedynczych bajtów bez ponoszenia kosztów związanych

z transmisją całego bloku). Dostęp do informacji zapisanej w pamięci zewnętrznej jest wielokrotnie

wolniejszy, niż do informacji zapisanej w pamięci operacyjnej.

W związku z blokowym charakterem transmisji i zapisu danych, w pamięci zewnętrznej, podobnie

jak w pamięci operacyjnej, mamy do czynienia ze zjawiskiem fragmentacji.

Przyczyny stosowania pamięci zewnętrznych:

1) ze względów technologicznych mogą mieć dużo większą pojemność, niż aktualnie produkowane

układy pamięci operacyjnej;

2) są dużo tańsze od pamięci operacyjnej (w tym sensie, że jeden bajt pamięci zewnętrznej kosztuje

dużo mniej, niż jeden bajt pamięci operacyjnej);

3) pamięć zewnętrzna jest pamięcią trwałą - w przypadku wyłączenia zasilania cała zawartość

pamięci operacyjnej zostaje utracona, a zawartość pamięci zewnętrznej nie.

Pamięci zewnętrzne są produkowane w postaci dysków stałych (zwanych powszechnie twardymi

dyskami (hard disc)) oraz dysków wymiennych (dyskietek, płytek CD). Rozgraniczenie pomiędzy

dyskami stałymi i wymiennymi jest dość zatarte, gdyż twarde dyski mogą być umieszczane w obudo-

wach umożliwiających ich łatwą wymianę i przenoszenie do innego komputera. Pamięci służące do

okresowego zapisywania bardzo dużych ilości danych ze względów bezpieczeństwa (backup) mają

często postać taśmy magnetycznej. W komputerach często jest instalowanych kilka różnych

urządzeń pamięci zewnętrznej.

Pomijając przypadek przestrzeni wymiany omówiony na poprzednim wykładzie, zapis w pamięci

zewnętrznej odbywa się w logicznie wyodrębnionych jednostkach o zmiennej wielkości, zwanych

plikami (file). Pliki są obiektami logicznymi, które są „najbardziej widoczne” dla użytkowników

systemów komputerowych - większość użytkowników patrzy na pracę komputera przez pryzmat

operacji wykonywanych na pojedynczych plikach, bądź też ich zbiorach.

Ze względu na dużą liczbę plików przechowywanych we współczesnych komputerach ich zbiorowi

jest zwykle narzucana pewna struktura - pliki są pogrupowane w katalogi (directory) zwane również

folderami. W zależności od systemu operacyjnego cała pamięć zewnętrzna, jaką dysponuje komputer

(być może zorganizowana w postaci kilku oddzielnych urządzeń fizycznych) może być postrzegana

przez użytkownika jako jedna duża, wspólna przestrzeń służąca do przechowywania plików, bądź też

może być podzielona na kilka urządzeń logicznych (partycji) (partition), przy czym podział na

urządzenia logiczne wcale nie musi odzwierciedlać podziału na urządzenia fizyczne.

W poszczególnych partycjach przechowywane są odrębne struktury katalogów. Jedna z partycji

zwykle przeznaczana jest na przestrzeń wymiany (do której dostęp jest zorganizowany za pomocą

innych procedur, niż dostęp do plików, i która nie jest bezpośrednio widoczna dla użytkownika).

Z punktu widzenia interfejsu użytkownika plik jest najmniejszą jednostką pamięci zewnętrznej, na

której użytkownik może wykonywać operacje. W plikach mogą być przechowywane programy

wykonywalne (lub ich fragmenty), lub dane. Formalnie, każdy plik może być postrzegany jako zbiór

danych przeznaczonych do wykorzystania przez konkretny program (lub grupę programów).

Program wykonywalny (binarny) jest przeznaczony do pobrania i załadowania do pamięci przez

program ładujący (loader), plik zawierający program źródłowy (nieskompilowany) stanowi dane dla

przetwarzającego go kompilatora, plik zawierający zapis tekstu lub obrazu stanowi dane dla edytora

tekstowego lub graficznego (lub programu odtwarzającego) itd.

Zapis informacji w plikach musi mieć ściśle określoną strukturę (format), znaną programowi, który

będzie tę informację wykorzystywał. Szczególnymi przypadkami plików są:

- plik wykonywalny (executable file), przeznaczony do załadowania do pamięci operacyjnej

i interpretacji jako ciągu rozkazów dla procesora;

- plik tekstowy, zawierający ciąg bajtów traktowany jako ciąg znaków ASCII, w tym znaczników

końca linii (występujących nie rzadziej, niż określona liczba pozycji) i znacznik końca pliku.

Niektóre programy mogą traktować pliki jako „bezpostaciowe”, czyli ciągi bajtów o zupełnie

dowolnej strukturze.

Z każdym plikiem związany jest pewien zbiór jego atrybutów (zwany też czasem metryką pliku).

Atrybuty są podstawowymi informacjami na temat własności pliku i sposobów jego użytkowania.

Różne systemy plików stosują różne atrybuty, ale przeważnie są to takie informacje, jak:

- nazwa pliku;

- typ pliku;

- umiejscowienie w fizycznej pamięci zewnetrznej;

- rozmiar pliku;

- prawa dostępu (czy i kto może zapisywać, odczytywać, wykonywać ...);

- identyfikator właściciela pliku;

- data i czas utworzenia pliku, ostatniej modyfikacji i ostatniego dostępu do pliku.

Informacje na temat właściciela dotyczą tylko plików w wielodostępnych systemach operacyjnych.

W takim przypadku odmienne prawa dostępu mogą być wyznaczone dla właściciela pliku, odmienne

dla grupy jego współpracowników, a jeszcze inne dla pozostałych użytkowników systemu. Prawa

dostępu nadaje i zmienia właściciel pliku.

Plik jako obiekt logiczny wywodzi się z abstrakcji taśmy magnetycznej, a w związku z tym zbiór

typowych funkcji systemowych operujących na plikach odzwierciedla w pewnym stopniu fizyczne

operacje związane z obsługą taśmy. Plik należy zatem wyobrażać sobie jako ciąg lokat o jednakowej

wielkości, zakończony znacznikiem końca pliku. Wzdłuż pliku posuwa się wskaźnik bieżącego

położenia w pliku (wskaźnik pliku, odpowiadający fizycznej głowicy zapisująco-odczytującej).

eof (end of file)

Tradycyjną metodą dostępu do danych w pliku jest dostęp sekwencyjny, czyli zapisywanie lub

odczytywanie danych w pliku w kolejności zgodnej z ich umiejscowieniem (od początku do końca

pliku). Od chwili, gdy zaczęto implementować pliki w pamięci dyskowej, pojawiła się możliwość

„przeskakiwania” wskaźnika pliku z jednego miejsca w drugie - ten tryb dostępu, podobnie jak

w przypadku pamięci operacyjnej, nazwano dostępem swobodnym.

Typowe czynności wykonywane przez funkcje systemowe operujące na plikach to:

- utworzenie pliku (początkowo pustego, czyli wyznaczenie mu jedynie miejsca w pamięci

fizycznej i zapisanie atrybutów);

- otwarcie pliku (wpisanie informacji do systemowej tablicy plików otwartych, przydzielenie bufora

w pamięci operacyjnej, ustawienie wskaźnika na początku lub na końcu pliku);

- pisanie do pliku (zapis ciągu bajtów poczynając od bieżącej pozycji w pliku, ustawienie wskaźnika

na kolejnej pozycji po ostatniej zapisanej);

- odczyt z pliku (odczyt ciągu bajtów poczynając od bieżącej pozycji w pliku, ustawienie wskaźnika

na kolejnej pozycji po ostatniej odczytanej);

- zmiana położenia bieżącego w pliku (tylko przeniesienie wskaźnika w inne miejsce);

- zmiana atrybutów pliku (wywoływanie tej funkcji zwykle podlega pewnym ograniczeniom);

- zamknięcie pliku (przepisanie zawartości bufora do pamięci zewnętrznej i usunięcie wpisu

w tablicy plików otwartych);

- usunięcie pliku (zwolnienie miejsca zajmowanego przez plik w pamięci zewnętrznej i usunięcie

metryki pliku).

W systemach umożliwiających współbieżną pracę wielu użytkowników, prawidłowe zorganizowanie

ich dostępu do plików jest rzeczą o wiele trudniejszą, niż w systemach jednozadaniowych. Ponieważ

system plików jest wspólny dla wszystkich użytkowników, poszczególne pliki mogą być używane

przez wiele procesów jednocześnie. Wiąże się to z koniecznością zaprojektowania funkcji systemo-

wych tak, aby zapewnić logiczny i spójny obraz danych w pliku wszystkim procesom. W szczegól-

ności implementacja powinna uwzględniać:

- możliwość niezależnego otwierania i zamykania pliku przez każdy proces. Każdy plik powinien

posiadać licznik otwarć, który zwiększa się o 1 przy każdym otwarciu przez proces, a zmniejsza

o 1 przy każdym zamknięciu. Plik może być fizycznie usunięty tylko wtedy, gdy jego licznik

otwarć zawiera zero;

- możliwość niezależnego czytania i pisania przez każdy proces. Każdy proces powinien posiadać

swój własny wskaźnik pliku i móc go przemieszczać niezależnie od innych procesów. Operacje

czytania i pisania powinny być zaimplementowane jako operacje niepodzielne, aby zapewnić

logiczną spójność danych. Skutek każdego dokonanego przez jeden proces zapisu w pliku powinien

być natychmiast widoczny dla wszystkich innych procesów czytających ten plik.

W niektórych zastosowaniach (na przykład w implementacjach wielodostępnych baz danych) procesy

mogą potrzebować wykonywania bardziej złożonych operacji na współdzielonych plikach, niż tylko

takich, które mogą być zrealizowane przez pojedyncze wywołania funkcji systemowych (w teorii baz

danych takie operacje nazywane są transakcjami). Aby zapewnić niepodzielność wykonywania

transakcji, powinny być zaimplementowane systemowe mechanizmy blokowania (locking) całych

plików, bądź poszczególnych rekordów zapisanych w danym pliku.

Ponieważ operacje zapisu i odczytu do / z pliku przez procesy są w rzeczywistości zaimplementowane

jako operacje na buforach plików w pamięci, do implementacji blokowania mogą być użyte

odpowiednie mechanizmy ochrony fragmentów pamięci (omówione na poprzednich wykładach).

Uwaga

Same pliki mogą też służyć jako specyficzne mechanizmy koordynacji procesów. Jednym

z najstarszych i najdłużej wykorzystywanych prostych mechanizmów jest tak zwany plik zamkowy

(lockfile). Plik zamkowy jest pustym plikiem, czasowo tworzonym w wybranym i znanym procesom

katalogu. Działa on jak prosty semafor - fakt jego istnienia informuje procesy o tym, że dany zasób

jest właśnie zajęty przez pewien proces, zaś jeśli go nie ma, oznacza to, że zasób jest wolny.

Pierwotnie pliki tworzyły w pamięci zewnętrznej niezorganizowany zbiór. Gdy liczby plików

w systemach stały się bardzo duże, pojawiła się potrzeba narzucenia ich zbiorowi pewnej struktury,

aby łatwiej było wyszukiwać poszczególne pliki i wykonywać na nich operacje. Tak powstały

katalogi, będące w istocie specjalnym rodzajem plików, zawierających informacje o innych plikach

i przeznaczonych do operowania na nich za pomocą innych poleceń systemowych, niż na zwykłych

plikach.

Z punktu widzenia użytkownika, katalogi zawierają wykazy nazw plików wraz ze związanymi z nimi

informacjami (wielkość, położenie w pamięci zewnętrznej itp.) - w rzeczywistości informacje te są

zazwyczaj przechowywane w innym miejscu, a katalogi zawierają jedynie adresy tych miejsc.

Początkowo katalogi były jednopoziomowe (czyli stanowiły uporzadkowaną listę wszystkich plików),

potem pojawiły się dwupoziomowe (na przykład: poziom pierwszy - identyfikatory uzytkowników,

poziom drugi - pliki pogrupowane w zbiory należące do poszczególnych użytkowników). Obecnie

prawie wyłącznie spotyka się wielopoziomowe struktury katalogów zorganizowane w postaci

drzewa lub grafu acyklicznego.

Przykładowa organizacja drzewa katalogów:

GŁÓWNY

SYSTEM BINARNE TEKSTOWE UŻYTKOWNICY

STEROWNIKI ....... abc.bin xx.bin ... info.txt instr.txt .... ALA JAN OLA

ZADANIA PROGRAMY

zad1.txt zad2.txt ... pierwszy.pas .....

Katalog najwyższego poziomu (główny katalog systemu plików na danym urządzeniu logicznym)

nazywany jest korzeniem drzewa katalogów. Może zawierać zarówno katalogi niższego poziomu

(podkatalogi), jak i pliki. Podobnie katalogi niższego poziomu również mogą zawierać zarówno

podkatalogi, jak i pliki, itd.

W każdej chwili sesji pracy użytkownika z systemem operacyjnym użytkownik ma przyporządkowany

katalog bieżący (current directory), do którego zawartości przez domniemanie odnoszą się polecenia

użytkownika. Użytkownik może (wydając odpowiednie polecenie) zmienić swój katalog bieżący na

dowolny inny (w granicach posiadanych praw dostępu).

Nazwy plików i podkatalogów w obrębie jednego katalogu muszą być unikalne, natomiast w różnych

katalogach mogą występować takie same nazwy. Użytkownik, chcąc wydać polecenie dotyczące

obiektu umieszczonego w innym katalogu, niż jego katalog bieżący, musi nazwę tego obiektu

poprzedzić ścieżką dostępu. Ścieżki dostępu dzielą się na bezwzględne (podające położenie danego

obiektu względem korzenia drzewa katalogów) i względne (podające położenie obiektu względem

bieżącego katalogu użytkownika wydającego polecenie).

Przykład

Przyjmując strukturę drzewa katalogów z poprzedniego slajdu i zakładając, że katalogiem bieżącym

jest ALA, możemy stwierdzić, że bezwzględna ścieżka dostępu do pliku zad1. txt składa się z ciągu

katalogów GŁÓWNY, UŻYTKOWNICY, JAN, ZADANIA , zaś bezwzględna -

ALA, UŻYTKOWNICY, JAN, ZADANIA.

GŁÓWNY

SYSTEM BINARNE TEKSTOWE UŻYTKOWNICY

STEROWNIKI ....... abc.bin xx.bin ... info.txt instr.txt .... ALA JAN OLA

ZADANIA PROGRAMY

zad1.txt zad2.txt ... pierwszy.pas .....

- katalog bieżący

- bezwzględna ścieżka dostępu

- względna ścieżka dostępu

Uwaga

Nazwa pliku poprzedzona bezwzględną ścieżką dostępu (tak zwana pełna nazwa ścieżkowa pliku)

stanowi jednoznaczny identyfikator pliku na danym urządzeniu logicznym.

Graf acykliczny stanowi uogólnienie drzewa. Na ogół, podobnie jak drzewo, posiada jeden

wyróżniony wierzchołek (korzeń), ale od korzenia do innych węzłów drzewa może prowadzić już

więcej, niż jedna ścieżka.

katalog

plik

W tego rodzaju strukturze jeden i ten sam plik może figurować w więcej, niż jednym katalogu.

Podobnie, pewien katalog może mieć więcej nadkatalogów, niż tylko jeden. Struktura grafu

acyklicznego jest bardziej elastyczna, niż struktura drzewa, lecz jednocześnie jej implementacja

stwarza wiele istotnych problemów:

- usunięcie lub przemieszczenie pliku lub katalogu wymaga konsekwentnego usunięcia lub zmiany

wszystkich dowiązań;

- algorytmy przeszukiwania całej takiej struktury są bardziej skomplikowane, niż w przypadku

drzewa;

- nieumiejętne tworzenie dowiązań do katalogów może spowodować powstanie cyklu (pętli)

w grafie, co może wiązać się nawet z koniecznością reinstalacji całego systemu plików (dlatego

w systemie Unix prawo tworzenia dowiązań do katalogów ma jedynie administrator systemu).

W Uniksie poza tak zwanymi dowiązaniami twardymi istnieją też dowiązania miękkie, których

tworzenie nie powoduje zmian w rzeczywistej strukturze katalogów, i które nie stwarzają wyżej

opisanego zagrożenia.

Systemy plików mogą być implementowane w pamięci zewnętrznej na różne sposoby. Logiczny

obraz dysku jest ciągiem bloków o jednakowej wielkości, które są ponumerowane kolejno, czyli

adresy ich tworzą liniową przestrzeń adresową (adresy liniowe na dysku są przetwarzane na

kilkuskładnikowe adresy fizyczne, określające numer powierzchni itd.).

Najprostszym sposobem przydziału miejsca na plik jest przydział ciągły, czyli przydział odpowiedniej

liczby bloków o kolejnych adresach logicznych. Taka implementacja wiąże się z niedogodnościami

podobnymi do tych, jakie związane są z niestronicowaną pamięcią operacyjną - fragmentacja

zewnętrzna i konieczność ewidencjonowania dziur i stosowania algorytmów wyboru.

Wady tej nie ma przydział listowy miejsca na dysku, gdzie ciąg przydzielonych bloków stanowi

listę jednostronnie wiązaną (czyli strukturę, gdzie na końcu każdego bloku przechowywana jest

informacja o położeniu następnego bloku lub znacznik końca listy). Implementacja taka ma inną wadę -

praktycznie wyklucza dostęp swobodny do plików (a dostęp sekwencyjny też nieco spowalnia, wskutek

możliwości chaotycznego rozrzucenia bloków jednego pliku po całym dysku).

Przydział indeksowy polega na skupieniu całej informacji o położeniu kolejnych bloków należących

do pliku w specjalnym bloku indeksowym. Jest to rozwiązanie analogiczne do stronicowania pamięci

operacyjnej (przy czym blok indeksowy pełni rolę tablicy stron). Pozwala ono uniknąć fragmentacji

zewnętrznej i ułatwia dostęp swobodny, ale również wiąże się z pewnymi problemami implementa-

cyjnymi:

- krótkie pliki (a takich jest zwykle w systemie bardzo dużo) marnują dużo miejsca w swoich blokach

indeksowych;

- jeśli plik jest bardzo długi, pojedynczy blok indeksowy może być dla niego niewystarczający,

w takim przypadku należy utworzyć listę wiązaną złożoną z kilku bloków indeksowych (co od razu

spowalnia dostęp).

W praktyce w systemach plików często są stosowane mieszane metody implementacji.

9. SYSTEMY OPERACYJNE DOS I WINDOWS

Systemy operacyjne wytwarzane przez firmę Microsoft bazują na procesorach Intel i kompatybilnych

z nimi. Wyjątkiem od tej reguły jest system Windows NT, który posiada emulatory listy rozkazów

32-bitowych procesorów Intel dla niektórych innych procesorów (w tym procesorów o architekturze

RISC). Windows NT są zaprojektowane pod kątem zastosowań profesjonalnych w zakładach pracy,

wszystkie pozostałe produkty (wcześniejsze i późniejsze) są zasadniczo przeznaczone do zastosowań

amatorskich lub w niedużych, kilkuosobowych firmach.

Początkowe wersje systemów operacyjnych (w latach 80-tych) powstawały przy współpracy z firmą

IBM. Najwcześniejszy produkt - DOS (Disk Operating System), w wersji MS-DOS 1.00 powstał

w 1981 r. i był przeznaczony do pracy w komputerach zawierających procesor Intel 8086 i nie

zawierających dysku twardego. Kolejne wersje DOS-a pojawiały się aż do początków lat 90-tych,

osiągając numery 6.xx . Cały czas były to systemy jednozadaniowe, przeznaczone do wykonywania

programów na komputerach klasy PC i wykorzystujących tryb rzeczywisty pracy procesorów Intel.

Możliwości trybu chronionego udostępniane przez kolejne procesory Intel mogły być pod DOS-em

wykorzystywane jedynie „niejawnie”. DOS mógł współpracować z prostym protokołem sieciowym.

Najwcześniejsza wersja systemu DOS dysponowała prostym, jednopoziomowym systemem plików

wzorowanym na wykorzystywanym we wcześniejszym systemie operacyjnym CP/M. W późniejszych

wersjach pojawiła się hierarchiczna, wielopoziomowa struktura systemu plików wzorowana na rozwią-

zaniu istniejącym w systemie Unix. Pojawiła się też możliwość wydzielania dysków logicznych,

zwanych również strefami i oznaczanych pojedynczymi literami alfabetu (A: , B: , C: , ... ).

Kolejnymi „zdobyczami” systemu DOS (również wzorowanymi na rozwiązaniach systemu Unix) były

zestandaryzowane strumienie danych i możliwość ich przekierowywania oraz przetwarzanie potokowe

(ale tylko w wersji sekwencyjnej).

Zasadniczym interfejsem użytkownika systemu DOS był zawsze interfejs tekstowy, udostępniający

zestaw komend wykonujących typowe czynności na plikach i katalogach (dir, cd, type, copy, ... ).

Po pewnym czasie został on wzbogacony o komendy sterujące przydatne przy tworzeniu prostych

plików wsadowych (odpowiedników uniksowych skryptów) - for, if, goto, shift oraz call.

Dopiero na poczatku lat 90-tych do zestawu sterowników urządzeń DOS-a dodano sterownik myszy

umożliwiający obsługę prostego interfejsu graficznego (podobnego do nakładki Norton Commander).

Ponieważ DOS był oparty na trybie rzeczywistym pracy procesorów Intel, jego obszar danych

i programów systemowych w pamięci operacyjnej nie był w żaden sposób chroniony - użytkownik

mógł w każdej chwili dokonać odczytu lub zapisu w obszarze systemowym i dowolnie zmodyfikować

lub uniemożliwić działanie systemu (co powodowało konieczność jego restartu).

System DOS rozpoczynając działanie korzystał ze zbioru podstawowych funkcji obsługujących

urządzenia zewnętrzne zapisanych w pamięci stałej ROM, następnie uzupełniał go o zbiór własnych

funkcji systemowych zapisywanych w pamięci RAM. Większa część funkcji systemowych była

oficjalnie udostępniana programistom w postaci interfejsów do języków Asembler, Basic, a nieco

później również Pascal i C (za podstawowy język był uważany Basic, którego interpreter był

dołączany do każdej wersji DOS-a).

Niektóre istotne funkcje systemowe nie były oficjalnie opublikowane i udokumentowane przez firmę

Microsoft (pod pozorem przewidywanych ich zmian w kolejnych wersjach systemu), choć wiele firm

tworzących oprogramowanie znało je i wykorzystywało. Istniały też nieudokumentowane struktury

danych systemowych (na przykład Lista List - LL).

Mapa obszaru pamięci wykorzystywanego przez system DOS w trybie rzeczywistym:

Adres pola Długość pola Zawartość

00000 - 003FF 1 KB Tablica wektorów przerwań ROM BIOS

00400 - 004FF 256 B Obszar roboczy BIOS Pamięć

00500 - 005FF 256 B Obszar roboczy DOS konwencjonalna

0F420 - 9FFFF Programy systemowe i użytkowe 640 KB

A0000 - FFFFF Pamięć ekranu kart graficznych, Pamięć górna

udostępniona też dla innych sterowników 384 KB

Powyżej adresu 1 MB pamięć może być wykorzystywana przez procesory pracujące w trybie

chronionym. Istnieje biblioteka funkcji systemowych DOS umożliwiająca wymianę bloków pamięci

umieszczonych w obszarze powyżej adresu 1 MB z blokami umieszczonymi poniżej tego adresu

(wiąże się to z chwilowym przełączaniem procesora do trybu chronionego i z powrotem w sposób

niewidoczny dla programów pracujących w trybie rzeczywistym), co udostępnia programom

DOS-owym coś w rodzaju bardzo szybkiej pamięci zewnętrznej, na przykład tak zwany RAM-disk.

Pierwsze graficzne interfejsy użytkownika oparte na systemie okien i umożliwiające obsługę przy

użyciu wskaźnika (myszy) były opracowane przez firmę Xerox w 1981 r. i przeznaczone dla terminali

dużych komputerów. Komputery osobiste były w tym czasie jeszcze zbyt ubogie sprzętowo, aby

umożliwiać takie rozwiązania. Pierwszym systemem mikrokomputerowym wyposażonym w interfejs

graficzny był Macintosh (1984 r.). W tym czasie Microsoft równolegle prowadził własne prace nad

najwcześniejszą wersją systemu Windows (Windows 1.0 pojawił się w 1985 r. i miał interfejs

graficzny, ale wykorzystywał tylko tryb rzeczywisty 8086) oraz, we współpracy z IBM, nad systemem

operacyjnym OS / 2 (pojawił się w 1987 r. i wykorzystywał tryb chroniony 80286, ale pierwotnie miał

tylko interfejs tekstowy, a graficzny uzyskał dopiero rok później).

Kolejne wersje systemu Windows pojawiające się pod koniec lat 80-tych wykorzystywały już 16-bitowy

tryb adresowania procesorów Intel. Pierwszą wersją, która osiągnęła znaczący sukces komercyjny, była

wersja 3.0 (pojawiła się w 1990 r.). W tym samym roku doszło do rozwiązania współpracy pomiędzy

IBM (który przejął system OS / 2) i Microsoftem (który dalej rozwijał system Windows). Windows 3.1

pojawiły się w 1992 r. i zawierały między innymi ulepszone sterowniki urządzeń multimedialnych

oraz ulepszone programy biurowe (dysponujące różnymi zestawami czcionek i mogące wymieniać

obiekty pomiędzy sobą).

Wersje 3.x systemu Windows wykorzystywały 16-bitowy tryb adresowania oferowany przez procesory

Intel 80286 i 80386, dodatkowo od wersji 3.1 w górę mogły wykorzystywać mechanizm stronicowania

pamięci udostępniany przez Intel 80386.

Algorytm zarządzania procesami w systemach Windows 3.x był algorytmem niewywłaszczeniowym.

Programy użytkowe dostarczane przez producenta wraz systemem były tworzone w taki sposób, aby

dobrowolnie przekazywały procesor innym procesom po upłynięciu określonego czasu, natomiast

programy tworzone przez użytkowników miały możliwość „zawłaszczania” procesora na dowolnie

długi czas.

Wszystkie systemy Windows dostarczają dość bogaty interfejs programisty API (Application Program

Interface), który poza podstawowymi funkcjami systemowymi udostępnia też funkcje obsługujące

elementy interfejsu graficznego - przesuwalnych okien o zmiennych rozmiarach, menu rozwijalnych,

przycisków, okien dialogowych itp. Dostarczają też tekstowy interfejs użytkownika, który wzorowany

jest na pierwotnym interfejsie DOS-a i może być uruchamiany zarówno w trybie pełnoekranowym

(czyli trybie tekstowym sterownika karty graficznej), jak również w wielu oknach tekstowych

symulowanych w trybie graficznym.

Procesory Intel o numerach 80286 i wyższych w ramach zapewniania kompatybilności udostępniają

tryb rzeczywisty jako jeden ze swoich trybów pracy. Oznacza to, że programy użytkowe przeznaczone

do wykonywania na procesorze 8086 mogą być wykonywane również na procesorach o wyższych

numerach. Procesory te udostępniają również tak zwany tryb wirtualny 8086, w którym wiele

programów przeznaczonych dla trybu rzeczywistego może być współbieżnie (z podziałem czasu)

wykonywanych w oddzielnych przestrzeniach adresowych o pojemnościach 1 MB.

Własność tę wykorzystują systemy Windows, które umożliwiają wykonywanie (w wielu oknach)

programów DOS-owych. Utrudnienie stanowi fakt, że niektóre programy DOS-owe nie zawsze

korzystają z funkcji systemowych komunikując się z urządzeniami zewnętrznymi, ale, w imię

przyspieszenia działania, bezpośrednio wykonują operacje na fizycznych portach i lokatach pamięci

operacyjnej wykorzystywanych przez system. Takie programy nie mogą być uruchamiane pod

systememami Windows, gdyż systemy te (jak wszystkie przeznaczone do pracy w trybie chronionym)

zapewniają ochronę pamięci i sterowników urządzeń zewnętrznych.

Windows NT w wersji 3.1 pojawił się w 1993 r. i był pierwszym spośród systemów Windows, który

wykorzystywał 32-bitowy tryb adresowania udostępniany przez procesory Intel o numerach 80386

i wyższych. Był też wyposażony w emulatory procesorów intelowskich dla niektórych innych

procesorów. Posiadał interfejs graficzny Windows 3.x. Z założenia był przeznaczony do pracy

w sieciach lokalnych niedużych firm i miał mniejsze wymagania sprzętowe, niż system Unix. Dużo

rozwiązań jest „żywcem” przeniesionych z systemu Unix do Windows NT, również większa część

kodu jądra została napisana w języku C (utworzonym specjalnie dla Uniksa) oraz C++. Jednocześnie

wiele wewnętrznych struktur jądra systemu Windows i funkcji systemowych jest źle udokumentowa-

nych (lub w ogóle nieudokumentowanych).

W 1995 r. pojawił się Windows 95 - pierwszy 32-bitowy system Windows przeznaczony dla

komputerów osobistych. Został on wyposażony w nowy interfejs graficzny użytkownika (ale oparty

na podobnych do dotychczasowych wywołaniach funkcji systemowych, co umożliwia wykonywanie

pod nim programów „okienkowych” napisanych dla Windows 3.x). Interfejs ten został przejęty przez

kolejną wersję Windows NT - 4.0, oraz wszystkie kolejne wersje Windows przeznaczone dla

komputerów osobistych (98, 2000, Millenium, XP, ... ).

Wszystkie wersje systemu Windows poczynając od Windows NT 3.1 oraz Windows 95 mają 32-

bitowy interfejs programisty Win32 API, ale umożliwiają też wykonywanie programów napisanych

przy użyciu interfejsu DOS-owego i interfejsu 16-bitowego Win API (w takich granicach, w jakich

pozwalają wymogi bezpieczeństwa - nie jest możliwe przełamywanie ochrony pamięci i portów oraz

zawłaszczanie całego czasu procesora). W związku z poszerzeniem możliwości adresowania

zwiększyły się też możliwości obsługi dużych plików na dysku i dysków o większej pojemności.

Pomimo reorganizacji systemu plików nowe wersje Windows (poza NT) mogą obsługiwać również

starsze systemy plików na wydzielonych dyskach logicznych. Pewnym problemem stała się zmiana

DOS-owego interfejsu użytkownika tak, aby mógł obsługiwać dłuższe nazwy i rozszerzenia nazw

plików (pod DOS-em nazwa mogła być co najwyżej 8-znakowa, a rozszerzenie - co najwyżej

3-znakowe).

Wszystkie 32-bitowe systemy Windows implementują podział czasu i dysponują wywłaszczeniowym

algorytmem szeregowania procesów. Gospodarowanie pamięcią operacyjną oparte jest na mechanizmie

segmentacji stronicowanej.

Jedną z różnic organizacyjnych pomiędzy Windows w wersjach 16-bitowych i Windows w wersjach

32-bitowych jest to, że te pierwsze przechowywały parametry uruchamiania programów użytkowych

w oddzielnych plikach konfiguracyjnych (rozszerzenie ini) związanych z poszczególnymi programami,

natomiast te drugie przechowują ustawienia we wspólnym obiekcie nazywanym Rejestrem. Jest to

związane z faktem, że Windows 32-bitowe rozróżniają swoich użytkowników (którzy muszą mieć

przydzielone nazwy i hasła, i logować się na początku sesji pracy, a wylogowywać się na końcu).

Użytkownicy mogą mieć swoje indywidualne preferencje (wygląd ekranu, kolory, czcionki itp.)

które system zapamiętuje i uwzględnia przy logowaniu i uruchamianiu programów użytkowych.

Windows mogą korzystać z prostego protokołu komunikacyjnego Microsoft Networking dla sieci

lokalnych (jest on protokołem nietrasowalnym i nie obsługuje systemu adresów logicznych). Protokół

ten umożliwia wzajemne udostępnianie plików i urządzeń zewnętrznych w obrębie sieci.

Dla wszystkich systemów operacyjnych firmy Microsoft zostały skonstruowane programy obsługujące

protokół internetowy IP, jak również niektóre protokoły wyższego poziomu oparte na IP. Nawet w trybie

rzeczywistym możliwe jest korzystanie (w trybie tekstowym) z usług ftp i telnet, zaś pod Windows -

z przeglądarek internetowych oraz graficznych interfejsów ftp.

Systemy Windows NT są wytwarzane w wersjach Server (serwer) i Workstation (stacja robocza).

Serwery NT instalowane są na sprzęcie o większych możliwościach i na ogół przeznaczone są do

dostarczania usług sieciowych (serwer stron domowych, serwer ftp, serwer poczty elektronicznej itp.).

W przeciwieństwie do systemów uniksowych, serwery Windows NT (do wersji 4.0) nie umożliwiają

otwierania zdalnych sesji pracy użytkownikom nieuprzywilejowanym (zatem są przez nich postrzegane

głównie jako serwery plików).

Systemy Windows NT charakteryzują się większym stopniem niezawodności i bezpieczeństwa od

systemów Windows przeznaczonych dla indywidualnych komputerów PC (poza 2000 i XP). Dotyczy to

zarówno odporności na ataki w sieci komputerowej, jak i ochrony przed wadliwym oprogramowaniem

i przypadkowym zniszczeniem danych (na przykład utratą części danych na dysku w przypadku awarii).

Struktura systemów Windows NT jest warstwowa i wyraźnie izoluje warstwę zależną od sprzętu

(rodzaju procesora) od warstw wyższych (operujących na obiektach logicznych). W celu umożliwienia

wykonywania programów przeznaczonych dla innych (kompatybilnych) systemów operacyjnych,

w strukturze Windows NT wyodrębniono tak zwane podsystemy środowiskowe, emulujące

środowiska OS / 2, Windows 16-bitowych, DOS-a i środowisko zgodne ze standardem POSIX.

Wszystkie wyżej wymienione podsystemy współpracują z podsystemem środowiskowym Win32,

który jest najniższą warstwą wykonującą się w trybie użytkownika. Podsystem ten komunikuje się

z wykonującym się w trybie uprzywilejowanym egzekutorem, który składa się z modułów o nazwach:

- zarządca wejścia - wyjścia ;

- zarządca obiektów ;

- monitor bezpieczeństwa odwołań ;

- zarządca procesów ;

- zarządca pamięci wirtualnej ;

- udogodnienie wywoływania procedur lokalnych .

Pozostałymi (poza egzekutorem) częściami składowymi systemu wykonywanymi w trybie uprzywile-

jowanym są:

- jądro systemu ;

- warstwa abstrakcji sprzętu (Hardware Abstraction Layer, HAL).

Warstwa abstrakcji sprzętu umożliwia współpracę jądra z różnymi rodzajami procesorów (w wersji

4.0 z układami zawierającymi nie więcej, niż 8 procesorów).

Jądro systemu Windows NT jest tak zwanym mikrojądrem, czyli zawiera jedynie najprostsze,

najbardziej podstawowe kody funkcji systemowych (wszystkie pozostałe są już realizowane jako

procedury w wyższych warstwach systemu i nie mają zagwarantowanej niepodzielności wykonania).

Strony przechowujące kod i dane jądra nigdy nie są usuwane z pamięci operacyjnej, a procesy jądra

nigdy nie podlegają wywłaszczaniu.

Jądro ma organizację obiektową, co oznacza, że ze strukturami danych jądra (atrybutami) są

jednoznacznie związane zbiory funkcji (metod) operujących na nich. Wyróżniane są dwa rodzaje

obiektów jądra:

- obiekty dyspozytora ;

- obiekty sterujące .

Wśród obiektów jądra Windows NT są zarówno procesy (ciężkie), jak i wątki. Każdy proces

dysponuje pewną przestrzenią adresową, w której wykonuje się pewna liczba (jeden lub więcej)

wątków. Wątki mają priorytety będące liczbami naturalnymi z zakresu 0 - 31. Priorytety z zakresu

16 - 31 określają klasę czasu rzeczywistego wątków, a priorytety z zakresu 0 - 15 - klasę zmienną

wątków (choć system nie gwarantuje wątkom czasu rzeczywistego żadnych limitów czasowych).

Obiekty dyspozytora są bezpośrednio związane z synchronizacją i zmianą przydziału procesora. Są to:

- wątki (obiekty będące podstawowymi jednostkami szeregowania i wykonania);

- zdarzenia (obiekty odnotowujące fakt wystąpienia zdarzenia asynchronicznego);

- semafory, muteksy i mutanty (obiekty służące do synchronizacji wątków);

- czasomierze (obiekty mierzące czas wykonywania procedur i przerywające je w razie potrzeby).

Do obiektów sterujących zaliczane są:

- procesy (obiekty zawierające informacje o właścicielu, priorytecie, przydziale pamięci itp.);

- przerwania (obiekty kojarzące rodzaje zdarzeń z obsługującymi je procedurami);

- obiekt stanu zasilania (odnotowuje, czy wystąpiła awaria) i obiekt informujący o zasilaniu (wywołuje

procedurę obsługi w przypadku wystąpienia awarii);

- obiekty profilujące (mierzące czas wykonania wybranych fragmentów kodu - do celów

diagnostycznych);

- asynchroniczne wywołania procedur (programowe przerwania wykonań wątków).

Algorytm szeregowania wątków stosowany przez jądro systemu NT przywiązuje dość dużą wagę do

sprawności wątków interakcyjnych, czyli takich, które obsługują procesy bezpośredniego komuniko-

wania się użytkownika z systemem. Z tego powodu priorytety takich wątków oczekujących w kolejce

do wykonania przyrastają szybciej, niż na przykład priorytety wątków obsługujących komunikację

z urządzeniami dyskowymi lub sieciowymi.

Priorytet wątku zawieszonego wskutek upłynięcia przydzielonego kwantu czasu zostaje obniżony do

poziomu priorytetu podstawowego (związanego z jego procesem). Priorytety wątków klasy czasu

rzeczywistego nie ulegają zmianie. Każdy wątek klasy czasu rzeczywistego umieszczony w kolejce

wątków gotowych do wykonania powoduje wywłaszczenie wykonywanego wątku klasy zmiennej.

W systemach wieloprocesorowych wątki mogą oczekiwać na przydział dowolnego procesora lub

wybranego procesora. Jeżeli w pewnym momencie pewien procesor nie ma żadnego wątku, który

mógłby wykonywać, dyspozytor przydziela mu do wykonania tak zwany wątek bezczynny (idle).

Zarządzanie pamięcią stronicowaną bazuje na dwupoziomowej strukturze:

Katalog tablic stron procesu

Tablica stron nr 0 Tablica stron nr 1 Tablica stron nr 1024

Strona Strona Strona Strona Strona

4 KB 4 KB 4 KB 4 KB 4 KB

Zarówno katalog, jak i pojedyncza tablica stron mogą zawierać do 1024 wpisów.

Strona może przebywać w pamięci operacyjnej lub w pliku na dysku. Procesy uprzywilejowane

mają prawo zabronić usuwania wybranych stron z pamięci do pliku. Strony posiadają atrybuty, które

informują o stopniu ich ochrony (na przykład read-only).

Procesy mogą współdzielić obiekty pamięci, w których mogą mieć odwzorowane fragmenty swoich

przestrzeni adresowych.

Rdzennym systemem plików systemów operacyjnych Windows NT jest NTFS. System jest logicznie

podzielony na tomy (volume). Każdy tom zawiera hierarchiczną (drzewiastą) strukturę katalogów.

Jednostką przydziału miejsca na dysku są nie pojedyncze sektory, lecz całe ciągi przyległych sektorów

nazywane gronami (cluster). Wielkość gron jest ustalana na etapie konfiguracji systemu (mogą być

przyjęte wielkości domyślne).

System plików na dysku jest obsługiwany przez program FtDisk, którego założeniem jest uzyskanie

dużej odporności na skutki awarii systemu. Stosuje on kilka wariantów (skomplikowanego) zapisu

na dysku przy uwzględnieniu pewnej nadmiarowości, która w razie awarii pozwala odtworzyć

przynajmniej część zapisu z pewnego momentu przed awarią. W skrajnym przypadku program ten

stosuje zapis lustrzany (mirroring) dublujący zapis każdej informacji przy użyciu dwóch niezależnych

sterowników.

System NTFS posiada również wbudowaną możliwość kompresji danych w poszczególnych plikach

w celu zaoszczędzenia miejsca na dysku.

10. SIECIOWY SYSTEM OPERACYJNY NOVELL NETWARE

Systemy Novell NetWare są serwerami plików zaprojektowanymi pod kątem obsługi użytkowników

sieci lokalnej pracujących przy komputerach wyposażonych w systemy operacyjne firmy Microsoft

(DOS, Windows w dowolnej wersji), ale mogącymi również współpracować z programami klienckimi

dla systemów OS / 2, Macintosh i Unix (programy te nie są dostarczane przez firmę Novell wraz

z oprogramowaniem serwera i muszą być zakupione oddzielnie).

Wszystkie wersje systemów NetWare umożliwiają dostarczanie nie tylko usług dostępu do sieciowego

systemu plików, ale również podstawowych usług internetowych, takich jak FTP, DNS, DHCP czy

udostępnianie stron domowych (www).

Systemy te stanowią w pewnym stopniu konkurencję dla systemów Windows NT Server, gdyż ceny

są porównywalne, a pod wieloma względami oba serwery są funkcjonalnie równoważne. Według

danych z 1998 r., serwery NetWare stanowiły około 38 procent wszystkich serwerów na świecie,

serwery zgodne z systemem Unix - około 21 procent, zaś serwery Windows NT - około 16 procent.

Firma Novell poza serwerem NetWare dostarcza programy klientów Novell Client (zarówno w wersji

16-bitowej, przeznaczonej dla DOS-a i Windows 3.*, jak i w wersji 32-bitowej, przeznaczonej dla

wszystkich nowszych systemów Windows). Firma Microsoft również dostarcza własne programy

klienckie przeznaczone do komunikowania się z serwerami NetWare dołączone do oprogramowania

systemów Windows 32-bitowych, ale nie są one w stanie wykorzystać wszystkich możliwości

nowszych wersji serwerów NetWare.

Dla serwerów NetWare do wersji 4.* standardowym protokołem sieciowym służącym do komuniko-

wania się z klientami jest protokół IPX. Jest to protokół przeznaczony dla sieci lokalnych, nietraso-

walny (bazuje na systemie adresów fizycznych urządzeń sieciowych). Serwery NetWare w wersjach

5.* mogą komunikować się zarówno przy użyciu protokołu IPX, jak i przy użyciu protokołu

internetowego IP, przy czym ten drugi jest dla nich protokołem domyślnym. Wykorzystanie IP

umożliwia (jeśli administrator sieci przydzieli takie uprawnienia i odpowiednio skonfiguruje serwer)

zdalny dostęp (spoza sieci lokalnej) klientów do sieciowego systemu plików.

Systemy operacyjne Novell NetWare są systemami wielozadaniowymi z wywłaszczaniem, natomiast

nie oferują wielodostępu (użytkownicy nie mogą otwierać na nich sesji pracy). Administrator systemu

komunikuje się z nim z konsoli serwera, która dysponuje zarówno interfejsem tekstowym (zestaw

poleceń jest inny, niż zestaw poleceń systemów firmy Microsoft), jak i interfejsem graficznym. Jeśli

w sieci lokalnej jest wiele serwerów Netware, administrator może pracować przy którymkolwiek

z nich. W przypadku nowszych wersji serwera Netware (mogących komunikować się przy użyciu

protokołu IP), administrator może również zdalnie zarządzać całą siecią.

Nowsze wersje systemów Netware mają większe wymagania sprzętowe, niż starsze wersje, ale też

oferują dużo większe możliwości. Są w stanie wykorzystywać wieloprocesorowe płyty główne

komputerów i posiadają funkcje wyrównywania obciążenia procesorów serwerów. System plików

NSS korzysta z 64-bitowego systemu adresowania, w związku z czym górne ograniczenie rozmiaru

pliku wynosi 8 TB (takie samo jest górne ograniczenie rozmiaru jednego dysku logicznego),

a maksymalna liczba dysków logicznych - 256.

Wewnętrzna baza danych systemów Netware 5.* oparta jest na systemie Oracle.

Wersje systemu NetWare do 3.* były serwerocentryczne, co oznacza, że poszczególne serwery

w sieci lokalnej były obiektami logicznymi dostrzegalnymi dla użytkowników tej sieci - mieli oni

konta na konkretnych serwerach i dostęp do kont jednego użytkownika na różnych serwerach był

zabezpieczany oddzielnymi mechanizmami autoryzacji.

Nowsze wersje NetWare (od 4.*) są określane jako sieciocentryczne, co oznacza, że użytkownik sieci

lokalnej nie musi być świadomy liczby i nazw serwerów w tej sieci - posługując się jednym kontem

(i jednym hasłem) uzyskuje on dostęp do całego rozproszonego systemu plików, fizycznie rozmiesz-

czonego na różnych dyskach różnych serwerów.

Wersje sieciocentryczne NetWare operują na rozmaitych rodzajach obiektów logicznych (ponad 20

klas obiektów). Wszystkie obiekty są zarejestrowane w jednej wspólnej rozproszonej bazie danych

o nazwie Katalog NDS (Novell Directory Services). Katalog ten jest wykorzystywany przez funkcje

serwera Netware nazywane usługami katalogowymi. Taka organizacja systemu znacznie ułatwia

jego administrowanie - wyróżnianie grup użytkowników, przydzielanie uprawnień itp.

Starsze wersje NetWare używały oddzielnych baz danych (segregatorów) na poszczególnych serwerach.

Ogólny podział klas obiektów Novell NetWare:

- obiekt korzeń [Root];

- obiekty pojemniki (container), nazywane też kontenerami;

- obiekty liście (leaf).

Katalog NDS ma organizację drzewa, które posiada korzeń (jest on zawsze oznaczany jako [Root],

tworzony w czasie instalacji systemu i nie może być usuniety ani zmienić nazwy), węzły wewnętrzne

(pojemniki) oraz węzły końcowe (liście).

[Root]

pojemniki

liście

Uwaga

Katalog NDS jest strukturą organizującą różne rodzaje obiektów systemowych i nie powinien być

mylony z żadnym z systemów plików (które też mają drzewiastą organizację).

Rodzaje obiektów w dużym stopniu zostały zaprojektowane pod kątem wykorzystania w typowych

firmach.

Przykłady klas obiektów - pojemników:

- [Root] ;

- Country (kraj) - stosowane są dwuliterowe oznaczenia według standardu X.500 - na przykład

US, FR, PL, DE, RU, ... ( jeśli jest, musi wystąpić bezpośrednio pod [Root] );

- Locality (mniejsza jednostka administracyjna - stan, region, województwo, ...);

- Organization (firma, instytucja, organizacja, ...);

- Organizational Unit (jednostka organizacyjna - dział, filia, wydział, ...).

Przykłady klas obiektów - liści:

- User (użytkownik) - osoba posiadająca konto na serwerze;

- Group (grupa) - grupa użytkowników (zazwyczaj wykonujących wspólne zadanie i posiadających

jednakowe uprawnienia);

- NetWare Server - pojedynczy serwer pracujący we wspólnej sieci NetWare;

- Volume (tom) - system plików na jednym z dysków logicznych jednego z serwerów;

- Printer (drukarka udostępniona w sieci).

Uwaga

Wszystkie wyżej wymienione klasy obiektów są widoczne dla administratora systemu, który ich

nazwami posługuje się w swoich poleceniach dla serwera. Użytkownicy nie muszą być świadomi całej

struktury Katalogu NDS.

Każdy obiekt w Katalogu NDS posiada swój zbiór właściwości (właściwy dla jego klasy). Niektóre

właściwości są wymagane, niektóre mogą być opcjonalne.

Przykład

Właściwościami wymaganymi obiektu klasy User są: Login Name (nazwa konta) i Last Name (nazwisko).

Właściwościami opcjonalnymi są: Title (tytuł) i Telephone Number (numer telefonu).

Większość właściwości jest jednowartościowa (każdy obiekt może mieć przyporządkowaną tylko jedną

wartość takiej właściwości), ale niektóre mogą być wielowartościowe (obiekt może mieć całą listę

właściwości takiego rodzaju - na przykład pojedynczy użytkownik może mieć przyporządkowaną całą listę

numerów telefonów).

Poszczególne obiekty w Katalogu NDS posiadają swoje nazwy. Podobnie, jak w systemach plików,

wszystkie obiekty w obrębie jednego pojemnika muszą mieć nazwy unikalne, natomiast w różnych

pojemnikach nazwy mogą się powtarzać. Pojęciem analogicznym do ścieżki dostępu do pliku (lub

katalogu) jest pojęcie kontekstu w Katalogu NDS. Podobnie, jak w systemach plików wyróżniamy

względne oraz bezwzględne nazwy ścieżkowe obiektów (nazwy poprzedzone względnymi lub

bezwzględnymi ścieżkami dostępu, które jednoznacznie identyfikują obiekt w obrębie całego systemu

plików), w przypadku Katalogu NDS mówimy o bezwzględnych nazwach jednoznacznych (krótko:

nazwach jednoznacznych) i względnych nazwach jednoznacznych.

Notacja kontekstów i nazw jednoznacznych jest, niestety, dość odmienna od zapisu stosowanego

w przypadku systemów plików (co bywa mylące).

Poszczególne elementy zapisu kontekstu (nazwy kolejnych pojemników na ścieżce) są wymieniane

w odwrotnej kolejności, niż w przypadku systemów plików (czyli poczynając od pojemnika najniższe-

go poziomu), z pominięciem [Root], i są oddzielone kropkami.

Przykład ELBLAG . WARMINSKO_MAZURSKIE . PL

Nazwy jednoznaczne obiektów zaczynają się od kropki, po której następuje nazwa obiektu, a po

kolejnej kropce - kontekst.

Przykład

. KOWALSKI . PRACOWNICY . PWSZ . EDUKACJA . PL

Względne nazwy jednoznaczne określane są względem kontekstu bieżącego. Zaczynają się od

nazwy obiektu, po której następuje kropka i ciąg nazw kolejnych pojemników - od pojemnika, w

którym bezpośrednio jest umieszczony dany obiekt, aż do wspólnego pojemnika kontekstu tego

obiektu i kontekstu bieżącego wyłącznie, po którym (na końcu) umieszczonych jest tyle kropek, ile

poziomów do góry trzeba przejść z kontekstu bieżącego.

Przykład

Jeśli kontekstem bieżącym jest

STUDENCI . PWSZ . EDUKACJA . PL

to względną nazwą jednoznaczną obiektu podanego w poprzednim przykładzie będzie

KOWALSKI . PRACOWNICY .

Jednymi z podstawowych czynności wykonywanych w czasie pracy z systemami sieciowymi są

operacje na uprawnieniach (rights). W systemie Novell NetWare większość obiektów może otrzymać

uprawnienia do większości innych obiektów. Obiekt, który otrzymał jakieś uprawnienia, nazywany

jest powiernikiem (trustee). Powiernicy są przeważnie obiektami z następujących klas:

- [Root] ;

- Organization;

- Organizational Unit;

- Organizational Role;

- Group;

- User;

- [Public] (umożliwia nadawanie uprawnień wszystkim użytkownikom sieci, również niezalogo-

wanym, co z kolei może umożliwić im zalogowanie się).

W systemie Netware inne rodzaje uprawnień związane są z dostępem do pojedynczych plików lub

katalogów, a inne z dostępem do obiektów w Katalogu NDS.

Rodzaje uprawnień do plików i katalogów:

- Read [R] - prawo do odczytu zawartości pliku;

- Write [W] - prawo do zapisu do istniejącego pliku;

- Create [C] - prawo do tworzenia nowego pliku lub podkatalogu w katalogu;

- Erase [E] - prawo do usuwania istniejącego pliku lub katalogu;

- Modify [M] - prawo do zmiany nazwy i atrybutów pliku;

- File Scan [F] - prawo do przeglądania zawartości katalogu;

- Access Control [A] - prawo do nadawania powyższych uprawnień do pliku lub katalogu innym

obiektom ;

- Supervisor [S] - wszystkie prawa.

Uwaga

Obiekt mający tylko uprawnienie A do pewnego pliku lub katalogu nie może nadawać innym

obiektom prawa A do tego pliku (katalogu).

Jeżeli obiekt posiada uprawnienia do pewnego katalogu, to domyślnie posiada te same uprawnienia do

wszystkich podkatalogów tego katalogu, wszystkich podkatalogów tych podkatalogów, i tak dalej -

takie przenoszenie uprawnień na niższe poziomy w drzewie katalogów nazywane jest dziedziczeniem

(inheritance) uprawnień. Z każdym katalogiem jest związany filtr dziedziczenia uprawnień

(Inherited Rights Filter, IRF), w starszych wersjach NetWare nazywany maską dziedziczenia

uprawnień (Inherited Rights Mask, IRM), który może ograniczać możliwości dziedziczenia uprawnień

z nadkatalogu. Domyślny filtr pozwala na dziedziczenie wszystkich uprawnień.

Uprawnienia obiektu do katalogu mogą być uprawnieniami nadanymi bezpośrednio lub prawami

odziedziczonymi z nadkatalogu. Ostateczny zestaw uprawnień do katalogu jest obliczany następująco:

(prawa nadane bezpośrednio) * (prawa odziedziczone * IRF)

gdzie symbole * i * mają interpretację operacji bitowych. Obliczony zestaw praw nazywany jest

zestawem praw efektywnych (effective rights).

Uwaga

Uprawnienie S nigdy nie podlega filtrowaniu przez IRF.

Innym rodzajem zabezpieczeń plików i katalogów są ich atrybuty (attribute). Zbiór rodzajów

atrybutów stosowany w systemie NetWare jest nadzbiorem zbioru rodzajów atrybutów stosowanych

w systemach firmy Microsoft. Niektóre atrybuty ustawiane są przez właściciela pliku (katalogu) lub

administratora, niektóre zaś są ustawiane automatycznie przez system - te drugie nazywane są

znacznikami statusu (status flag) . Niektóre atrybuty są specyficzne dla plików, niektóre dla

katalogów, a niektóre mogą być stosowane do obu rodzajów obiektów.

Przykłady

A (Archieve) - znacznik statusu informujący, że zawartość pliku zmieniła się (w związku z czym

może wymagać zrobienia kopii zapasowej);

H (Hidden) - atrybut powodujący, że dany plik lub katalog będzie traktowany jako ukryty (domyślnie

jego nazwa nie będzie wyświetlana w spisie nazw);

P (Purge) - atrybut uniemożliwiający odzyskanie pliku lub katalogu po jego skasowaniu;

Ro (Read only) - atrybut zabezpieczający plik przed usunięciem, zmianą zawartości i zmianą nazwy

(przesłania działanie uprawnień Write, Erase i Modify);

X (Execute only) - atrybut ustawiany przez administratora, uniemożliwia wykorzystanie pliku do

czegokolwiek innego, niż wykonanie (np. uniemożliwia jego skopiowanie).

W starszych wersjach systemu Netware użytkownicy systemu plików operowali na uprawnieniach

przy użyciu poleceń trybu tekstowego:

RIGHTS nazwa - wyświetlenie swoich uprawnień do danego pliku lub katalogu

GRANT uprawnienia FOR nazwa TO użytkownik - przyznanie praw wybranemu użytkownikowi

do danego pliku lub katalogu

REVOKE uprawnienia FOR nazwa TO użytkownik - odbiera przyznane uprawnienia

ALLOW nazwa uprawnienia - ustawia maskę dziedziczenia praw dla katalogu o podanej nazwie

Do odebrania użytkownikowi wszystkich praw do pliku lub katalogu może też posłużyć polecenie:

GRANT NO RIGHTS FOR nazwa TO użytkownik

W nowszych wersjach systemu Netware do operowania na uprawnieniach służą programy posiadające

interfejs graficzny (na przykład program Filer).

System uprawnień związany z obiektami w Katalogu NDS ma podobny charakter, jak system upraw-

nień do plików i katalogów, ale jest bardziej skomplikowany. Uprawnienia te ogólnie dzielimy na

uprawnienia do obiektów i uprawnienia do właściwości. Podział ten jest odzwierciedleniem ogólnej

idei obiektowości w oprogramowaniu: uprawnienia do obiektów są związane z czynnościami, jakie

można wykonywać na całych obiektach (metodami), zaś uprawnienia do właściwości - z dostępem do

poszczególnych właściwości (pól) obiektu.

Rodzaje uprawnień do obiektów:

Browse - prawo do dostrzegania danego obiektu przy przeglądaniu Katalogu NDS;

Create - prawo do tworzenia nowych obiektów wewnątrz danego obiektu (tylko dla pojemników);

Delete - prawo do usunięcia danego obiektu;

Rename - prawo do zmiany nazwy danego obiektu;

Inheritable - prawo do przekazywania w dziedzictwie uprawnień do obiektów wewnątrz danego

obiektu (tylko dla pojemników, w wersjach 5.* NetWare);

Supervisor - wszystkie powyższe prawa oraz wszystkie niżej wymienione prawa do właściwości

danego obiektu.

Rodzaje uprawnień do właściwości:

Read - pozwala odczytać wartość danej właściwości;

Write - pozwala dodawać, usuwać i modyfikować wartości właściwości;

Compare - pozwala porównywać wartość właściwości z wartością zadaną (implikowane przez Read);

Add Self - pozwala dodawać, usuwać i modyfikować swoje własne właściwości (implikowane przez

Write);

Inheritable - pozwala przekazywać w dziedzictwie prawa do własciwości obiektów wewnątrz

danego obiektu (tylko dla pojemników, w wersjach 5.* NetWare);

Supervisor - wszystkie prawa do danej właściwości.

Uwaga

Jedną z właściwości każdego obiektu w Katalogu NDS jest lista praw dostępu (Access Control List).

Należy zachować dużą ostrożność w przyznawaniu uprawnień do tej właściwości.

Podobnie, jak z każdym katalogiem, z każdym obiektem w Katalogu NDS związany jest filtr

dziedziczenia uprawnień. Filtr ten wpływa zarówno na uprawnienia do danego obiektu, jak i na

uprawnienia do jego właściwości. W odróżnieniu od filtrów dla katalogów, filtry dla obiektów są

w stanie zablokować przekazywanie uprawnień Supervisor.

Stosowanie praw (przefiltrowanych przez IRF) do obiektów zawartych w danym obiekcie-pojemniku

nazywamy dziedziczeniem, natomiast przekazywanie praw posiadanych przez pewien obiekt-

pojemnik zawartym w nim obiektom nazywamy odpowiedniością pośrednią. Należy pamiętać, że

prawa wynikające z odpowiedniości pośredniej nie są filtrowane przez IRF danego obiektu-pojemnika.

Każdy użytkownik (czyli obiekt klasy User) może ponadto otrzymać uprawnienia w drodze

odpowiedniości bezpośredniej. Może to nastąpić na trzy sposoby:

- jeśli użytkownik jest właścicielem obiektu klasy Organizational Role, otrzymuje wszystkie prawa

tego obiektu;

- jeśli użytkownik należy do obiektu klasy Group, otrzymuje wszystkie prawa tego obiektu;

- użytkownik otrzymuje wszystkie prawa obiektów wpisanych na listę w jego własności Security

Equal To.

Obliczanie praw efektywnych w Katalogu NDS jest bardziej skomplikowane, niż w systemie plików.

Odbywa się według następujących reguł:

- jeżeli prawa do jakiegoś obiektu lub właściwości zostały przypisane bezpośrednio, tylko one są

obowiązujące;

- jeżeli nie ma praw przypisanych bezpośrednio, prawa efektywne obliczamy nastepująco:

(prawa odziedziczone * IRF) * (prawa z odpowiedniości bezpośredniej) * (prawa

z odpowiedniości pośredniej)

Ze względu na stopień komplikacji obliczania efektywnych praw w Katalogu NDS zalecane jest

sprawdzanie prawidłowości ich wyznaczenia przy pomocy programów narzędziowych.

11. HISTORIA ROZWOJU I OGÓLNA CHARAKTERYSTYKA SYSTEMU UNIX

System operacyjny Unix w swojej najwcześniejszej wersji powstał w 1969 roku w Bell Telephone

Laboratories - jednostce badawczej, której firmą macierzystą była telekomunikacyjna firma AT&T.

Na jego powstanie miały wpływ prace nad projektem systemu operacyjnego Multics prowadzone od

1965 roku wspólnie przez Bell Laboratories, General Electric Company i Massachusetts Institute of

Technology (Bell Labs odstąpiły od tego projektu w 1969 roku). Multics z założenia miał być systemem

wieloprocesowym i wielodostępnym, oferującym swoim użytkownikom dużą moc obliczeniową i dużą

pojemność pamięci, jak również łatwość współdzielenia danych.

Najwcześniejsza wersja Uniksa była napisana przez K. Thompsona dla maszyny PDP-7. Do pracy nad

systemem przyłączyli się D. Ritchie (biorący uprzednio udział w projektowaniu systemu Multics), nieco

później B. Kernighan i inni pracownicy Bell Labs. Thompson i Ritchie opracowali pierwotny system

plików Uniksa i nieduży zbiór prostych funkcji systemowych.

Pomysłodawcą nazwy Unix był B. Kernighan. Nazwa ta przez długie lata była zastrzeżonym znakiem

towarowym firmy AT&T, nabytym dopiero w latach 90-tych przez firmę Novell.

Istotnym momentem w rozwoju systemu Unix było opracowanie przez D. Ritchi'ego języka C (pod

wpływem opracowanego przez K. Thompsona języka B) i napisanie w 1973 roku na nowo całego

jądra systemu w tym języku. Spowodowało to, że konstrukcja systemu stała się przejrzysta i łatwo

przenośna na wszystkie komputery, dla których opracowano kompilator języka C oraz sterowniki

urządzeń zewnętrznych udostępniające wirtualny obraz sprzętu oczekiwany przez jądro systemu

Unix (tak zwaną maszynę wirtualną Uniksa). Od 1973 roku Unix stał się nierozerwalnie związany

z językiem C - kompilator C jest dostępny w każdej wersji Uniksa i wszystkie uniksowe biblioteki

systemowe mają interfejsy programisty w języku C.

Na początku lat 70-tych firma AT&T, nie mając prawa handlowania produktami komputerowymi,

przekazała licencje systemu Unix innym firmom komercyjnym i uniwersytetom amerykańskim.

Spowodowało to ogromną popularyzację systemów uniksowych i rozwój prac nad ich ulepszaniem

w wielu ośrodkach uniwersyteckich. Największy wpływ na dalszy rozwój Uniksa miały prace

prowadzone na Uniwersytecie Kalifornijskim w Berkeley - głównie prace związane z pamięcią

wirtualną i mechanizmem stronicowania. Spowodowały one zainteresowanie rządu amerykańskiego

projektem i wybranie Uniksa jako standardowego systemu operacyjnego dla potrzeb rządowych.

Prace nad systemem Unix w Berkeley były finansowane przez rządową agencję DARPA (Defense

Advanced Research Projects Agency), w dużym stopniu w związku z zapotrzebowaniem rządu na

opracowanie i implementację protokołów sieci Internet pozwalających na komunikację pomiędzy

lokalnymi sieciami komputerowymi opartymi na różnym sprzęcie i różnych protokołach warstwy

łącza. Spowodowało to szybki rozwój systemów uniksowych jako sieciowych systemów operacyjnych,

jak również rozwój samego Internetu, pełniącego początkowo rolę sieci służącej do celów militarnych

i akademickich w Stanach Zjednoczonych.

Wersje źródłowe systemu Unix były udostępniane przez AT&T bez żadnego oprogramowania użytko-

wego. Wiele firm produkowało swoje własne oprogramowanie dla systemów uniksowych i sprzedawało

swoje produkty pod rozmaitymi nazwami kojarzącymi się z nazwą Unix, na przykład Ultrix, Xenix itp.

Nawet duże firmy prowadzące własne prace badawcze, takie jak Hewlett-Packard, DEC czy IBM

decydowały się na implementacje systemów zgodnych z systemem Unix.

Na przełomie lat 70-tych i 80-tych firma AT&T zdecydowała się na wyprodukowanie własnej komer-

cyjnej wersji Uniksa - pierwsza taka wersja pojawiła się w 1982 roku pod nazwą Unix System III.

Różne wersje Uniksów rozpowszechniane przez różne firmy i uczelnie były ze sobą zgodne z punktu

widzenia zwykłego użytkownika, ale nie było pełnej zgodności pomiędzy ich interfejsami programisty

(nie oferowały jednakowych zbiorów funkcji systemowych). Przy porównywaniu ich własności

typowymi „punktami odniesienia” były najbardziej rozpowszechnione wersje systemów z AT&T

(System III, a od roku 1983 System V) oraz z Berkeley (BSD - Berkeley Software Distribution).

Pierwszą szeroko rozpowszechnioną wersją Uniksa z Berkeley była wersja 4.2 BSD.

Ostatnią wersją utworzoną w Bell Labs była SVR4 (System V Release 4), zaś na Uniwersytecie

w Berkeley - 4.4 BSD. Spośród innych wersji Uniksa najszerzej rozpowszechnionych na świecie

należy wymienić system Solaris firmy SUN Microsystems. Istnieją również systemy operacyjne,

które oferują nie-uniksowy interfejs użytkownika, ale są w dużym stopniu zgodne z Uniksem pod

względem wewnętrznej konstrukcji - typowym przykładem jest system Windows NT firmy Microsoft.

Różnorodność i rozpowszechnienie systemów uniksowych spowodowały podjęcie prac nad międzynaro-

dową standaryzacją Uniksa. Standard taki został opracowany przez organizację IEEE pod nazwą

POSIX (Portable Operating Systems Interface). Standaryzacji uległ także język C (ANSI C).

Rodziną systemów operacyjnych zgodnych z systemem Unix są też rozpowszechniane (w niektórych

wersjach nieodpłatnie) systemy Linux. Najwcześniejsza wersja Linuksa została napisana w 1991 roku

przez Linusa Torvaldsa (studenta z Finlandii) i udostępniona w Internecie. Była przeznaczona dla

procesora Intel 80386. Prace nad Linuksem spontanicznie podjęła międzynarodowa społeczność

informatyków w celu utworzenia pełnowartościowego, dostępnego dla wielu różnych rodzajów sprzętu

i niezależnego od firm komercyjnych systemu operacyjnego zgodnego z normą POSIX. Obecnie Linux

jest rozpowszechniany zgodnie z regułami GPL (General Public Licence) określonymi przez międzyna-

rodową organizację FSF (Free Software Foundation). Reguły te pozwalają kopiować i rozpowszechniać

programy podlegające licencji GPL i nakazują udostępnianie kodu źródłowego (nie tylko kodu

binarnego) każdego takiego programu.

Obecnie istnieje wiele różnych odmian systemu Linux, przy czym każda z nich pojawia się w wielu

coraz to nowszych wersjach. Najczęściej spotykane odmiany to Red Hat, Debian i Slackware.

Uwaga

Z Linuksem może również współpracować oprogramowanie komercyjne (nie podlegające licencji GPL).

Ogólne założenia projektowe systemów uniksowych:

- jak największa prostota, przejrzystość i hierarchizacja konstrukcji (nawet kosztem optymalizacji

czasowej i pamięciowej);

- prosty i spójny logicznie interfejs użytkownika dostarczający wszystkich podstawowych usług;

- dostarczenie narzędzi ułatwiających tworzenie bardziej złożonych programów na bazie prostszych

programów;

- korzystanie z hierarchicznego systemu plików, w którym logicznym obrazem zarówno plików, jak

i urządzeń zewnętrznych są niesformatowane strumienie bajtów;

- wielodostęp i wieloprocesowość (przy czym każdy użytkownik może współbieżnie wykonywać

wiele procesów);

- ochrona plików i procesów poszczególnych użytkowników;

- ukrycie przed użytkownikami szczegółów sprzętu komputerowego, na którym funkcjonuje system,

co umożliwia tworzenie oprogramowania łatwo przenośnego do innych systemów uniksowych.

Ogólny diagram jądra systemu Unix ( według [M. Bach] ):

Programy użytkowników

Poziom użytkownika Biblioteki

Poziom jądra

Interfejs funkcji systemowych

Podsystem Komunikacja

plików międzyprocesowa

Podsystem Przydział

Pamięć sterowania procesora

buforowa procesami Zarządzanie

pamięcią

Znakowe Blokowe

Programy obsługi urzadzeń

Sterowniki sprzętu

Poziom jądra

Poziom sprzętu

Sprzęt

Z punktu widzenia użytkownika system umożliwia mu komunikację poprzez urządzenie zewnętrzne

zwane terminalem. Unix powstawał w czasach, gdy podstawowym rodzajem terminala był dalekopis -

urządzenie elektryczne posiadające klawiaturę i umożliwiające drukowanie linii tekstu na wysuwającej

się wstędze papieru. Urządzenie takie posiadało bardzo ubogie możliwości korekty napisanego tekstu

i nie umożliwiało żadnego innego trybu pracy nad tekstem, niż kolejnymi wierszami. Klasyczny

terminal uniksowy (w obecnych czasach traktowany jako abstrakcyjne urządzenie symulowane przez

zestaw złożony z klawiatury i monitora ekranowego wyświetlającego tekst) oznaczony jest symbolem

vt100.

W miarę ulepszania sprzętu komputerowego komplikacji ulegał również logiczny obraz terminali

użytkowników. Terminal tekstowy vt102 obsługuje klawiaturę o większej liczbie przycisków

funkcyjnych i umożliwia bardziej zaawansowaną edycję tekstu. Obecnie dostępnych jest wiele rodzajów

terminali, z których prawdopodobnie najpopularniejszym i udostępniającym najbardziej typowe funkcje

trybu tekstowego współczesnego sprzętu (przesuwanie kursora we wszystkich kierunkach, różne kolory

znaków i tła, rozszerzony kod ASCII itp.) jest terminal ansi.

Unix dość wcześnie (w latach 80-tych) został wyposażony w możliwość obsługi terminali graficznych

(X terminal) udostępniających graficzny interfejs użytkownika (Graphical User Interface - GUI).

X terminal składa się z klawiatury, wskaźnika (myszy) i monitora ekranowego wyświetlającego

informację w graficznym trybie okienkowym. Pierwszy uniksowy interfejs graficzny został

opracowany w MIT przy wspólpracy z firmą DEC.

W klasycznych systemach uniksowych terminal tekstowy był terminalem nieinteligentnym, czyli

urządzeniem obsługującym jedynie prosty protokół komunikacji z komputerem (mainframe) i nie

umożliwiającym żadnego lokalnego przetwarzania. Obecnie zazwyczaj bezpośrednio z komputerem

połączona jest jedynie konsola operatorska (console), a użytkownicy współpracują z systemem

przy pomocy graficznych stacji roboczych, będących samodzielnymi komputerami połączonymi

z systemem uniksowym poprzez sieć lokalną i komunikującymi się z nim przy użyciu protokołu

graficznego (X protocol).

Uwaga

Graficzny tryb pracy umożliwia otwieranie wielu okien tekstowych (pseudoterminali) jednocześnie

na ekranie jednego terminala graficznego.

Jednym z najważniejszych programów systemowych jest interpreter poleceń, przyjmujący i wyko-

nujący polecenia użytkownika w trybie tekstowym. Uniksowe interpretery poleceń znane są pod nazwą

powłok (shell). Istnieje kilka rodzajów powłok, wzajemnie zgodnych między sobą w zakresie podsta-

wowych poleceń:

- shell Bourne'a (sh) - najstarszy, uważany za klasyczny, o najuboższych możliwościach;

- C shell (csh) - powstał w Berkeley, w większym stopniu przypomina język programowania C;

- shell Korna (ksh) - shell początkowo najczęściej używany w komercyjnych wersjach Uniksa.

W związku z opracowaniem systemu Linux, w ramach projektu GNU powstała nowa wersja shella

Bourne'a o nazwie bash (Bourne Again Shell). Wersja ta zaadaptowała większość najkorzystniejszych

cech wcześniej istniejących powłok i obecnie jest prawdopodobnie najbardziej rozpowszechniona na

świecie.

Poszczególne powłoki posiadają swoje charakterystyczne znaki zgłoszenia (prompt), po których łatwo

jest je rozpoznać. Znakiem zgłoszenia bash'a jest znak $ .

Zbiór poleceń dowolnej powłoki może nie tylko służyć do interaktywnej pracy z systemem, ale

również służyć jako interpretowany język programowania. Oznacza to, że użytkownik ma możliwość

tworzenia plików tekstowych zwanych skryptami (script), które zawierają ciągi poleceń powłoki

wykonywane po uruchomieniu skryptu jako kolejne instrukcje programu. Skrypty nie podlegają

kompilacji (tak jak na przykład programy w C), ale kolejne polecenia są z nich wybierane i oddzielnie

interpretowane przez system.

Polecenia wydawane przez użytkownika mogą być poleceniami wewnętrznymi shella, czyli polecenia-

mi realizowanymi przez sam program powłoki, lub poleceniami zewnętrznymi, czyli oddzielnymi

programami wykonywalnymi, których wykonanie interpreter jedynie inicjuje. Jednym z założeń

projektowych Uniksa jest zatarcie różnicy (z punktu widzenia użytkownika) pomiędzy wywołaniami

poleceń wewnętrznych, wywołaniami programów skompilowanych (binarnych) i wywołaniami

skryptów, co ułatwia szybkie konstruowanie większych programów z pewnej liczby mniejszych.

Użytkownik ma możliwość zmiany powłoki w trakcie pracy na inną, wywołując jej nazwę (na przykład

ksh). Zakończenie pracy z daną powłoką i powrót do poprzedniej uzyskujemy podając polecenie exit.

W przypadku skryptu również istnieje możliwość określenia, która powłoka powinna go wykonać.

Jednym z rodzajów obiektów abstrakcyjnych, na których operują systemy uniksowe, są użytkownicy

(user). Użytkownicy są zarejestrowani w systemie, mając przyporządkowane nazwy (login name)

i hasła (password). Wśród użytkowników wyróżniony jest użytkownik uprzywilejowany, którego

nazwą zawsze jest root. Użytkownik uprzywilejowany ma szczególne prawa - ma dostęp do wszystkich

zasobów systemowych, w szczególności do plików innych użytkowników i do przestrzeni adresowych

uruchamianych przez nich procesów. Użytkownik ten pełni rolę administratora systemu.

Użytkownicy, chcąc współpracować z systemem uniksowym, muszą otworzyć na nim sesję pracy.

W tym celu muszą zgłosić się do systemu (zalogować), podając swoją nazwę i hasło. System przepro-

wadza autoryzację użytkownika, sprawdzając odpowiednią pozycję w pliku haseł. W przypadku

pomyślnym przydziela zgłoszonemu użytkownikowi terminal i uruchamia związany z danym termina-

lem proces interpretera komend (jest to tak zwany shell zgłoszony). Proces ten w pierwszej kolejności

wykonuje swój skrypt startowy, ustalający różne szczegóły związane ze sposobem współpracy

z użytkownikiem (na przykład tworzący ścieżki dostępu do częściej używanych katalogów lub

rozbudowujący znak zgłoszenia).

Sesja pracy kończy się w przypadku podania przez użytkownika polecenia exit shellowi zgłoszonemu.

Klasycznym trybem otwierania sesji jest logowanie się w trybie tekstowym, przebiegające według

wyżej opisanego scenariusza. Użytkownik ma prawo otwierania wielu sesji jednocześnie (chyba, że

administrator systemu wprowadzi ograniczenie liczby sesji), przy czym może je otwierać zarówno

z różnych terminali fizycznych, jak też (jeśli oprogramowanie lokalnego komputera na to pozwala),

z jednego komputera - na przykład w wielu oknach tekstowych otwartych w trybie graficznym, lub

w pełnoekranowym trybie tekstowym - wtedy do przełączania pomiędzy sesjami służą odpowiednie

kombinacje kluczy.

Współczesne systemy uniksowe umożliwiają również otwieranie graficznej sesji pracy - w takim

przypadku oprogramowanie stacji roboczej użytkownika musi zawierać program X serwera, który

komunikuje się z komputerem uniksowym przy użyciu X protokołu. Logowanie odbywa się w trybie

graficznym, przy czym X serwer „zagarnia” na czas pracy cały ekran monitora stacji roboczej

i przekształca ją w uniksowy terminal graficzny. Odpowiednikiem shella zgłoszonego jest wtedy

zarządca sesji (session manager).

Uwaga

Zazwyczaj możliwe jest również zalogowanie się do systemu uniksowego w trybie tekstowym, a nas-

tępnie wykorzystywanie X serwera jedynie do odbioru wyjścia graficznego programów uniksowych.

Programy uniksowe generujące wyjście graficzne wyświetlają je na terminalu graficznym wyspecy-

fikowanym przez zmienną środowiska DISPLAY (jeśli sam program nie definiuje swojego terminala).

Zawartość tej zmiennej:

adres_sieciowy_komputera : numer_terminala

Adres sieciowy jest nazwą (adresem) w protokole IP lub innym protokole, na którym oparty jest

protokół graficzny. Numer terminala jest zazwyczaj zerem (chyba, że komputer obsługuje wiele

zestawów do pracy w grafice). Adres sieciowy może być pominięty w przypadku pracy bezpośrednio

na konsoli komputera uniksowego.

W przypadku zalogowania się w trybie graficznym zmienna DISPLAY jest jest ustawiana automatycz-

nie. W przypadku zalogowania się w trybie tekstowym może ona być ustawiona przez skrypt startowy

lub „ręcznie”. Użytkownik ma możliwość przyporządkowania zmiennej DISPLAY innego adresu

terminala graficznego, niż swój własny, a tym samym skierowania wyjścia graficznego swojego

programu do innego komputera (jeśli użytkownik tamtego komputera wyrazi na to zgodę odpowiednim

poleceniem).

Ważną cechą wszystkich systemów uniksowych jest utrzymywanie informacji wbudowanej (manual)

zawierającej opisy wszystkich programów zainstalowanych w systemie oraz wszystkich funkcji syste-

mowych. Informacja ta jest precyzyjniejsza, niż w innych systemach operacyjnych, ma bardziej

techniczny charakter i ściśle określony format. Jest zorganizowana w postaci kilku tomów, przy czym

pierwszy z nich zawiera opisy standardowych poleceń systemowych (polecenia wewnętrzne są często

zgrupowane w jednym opisie programu interpretera komend), a dalsze - opisy funkcji systemowych

i programów niestandardowych.

Polecenie wyświetlenia informacji:

man program

W przypadku, gdy polecenie (program) i funkcja systemowa mają tę samą nazwę, należy podać numer

tomu, na przykład

man read

wyświetli opis polecenia systemowego read, natomiast

man 2 read

wyświetli opis funkcji systemowej read.

Opisy poleceń podają:

- nazwę polecenia;

- składnię jego wywołania, w tym wykaz możliwych opcji;

- liczbę i rodzaj argumentów;

- zwracaną wartość;

- opis działania;

- ewentualny wpływ na zmienne środowiska i zależność od zmiennych środowiska.

Funkcje systemowe są opisane zgodnie z ich interfejsem w języku C. W opisie podane są następujące

elementy:

- nazwa funkcji;

- nazwa pliku nagłówkowego biblioteki, w której jest umieszczona funkcja;

- typ wyniku;

- liczba, kolejność i typy argumentów funkcji;

- opis działania;

- wykaz możliwych sytuacji błędnych wraz z ewentualnymi wartościami zmiennej globalnej errno.

Do każdego systemu uniksowego dołączona jest pewna liczba standardowych programów, których

nazwy i działanie (z punktu widzenia użytkownika) są takie same we wszystkich wersjach uniksów.

Poza wspomnianymi wcześniej powłokami (wywoływanymi przez podane polecenia, przykładowo csh)

i kompilatorem języka C (wywoływanym przez polecenie cc) w każdym systemie uniksowym występuje

edytor tekstu vi (jego interfejs jest uznawany przez wielu użytkowników za prymitywny i niewygodny,

ale posiada on zaskakująco szerokie możliwości edycji).

We współczesnych systemach uniksowych poza standardowymi programami działającymi w trybie

tekstowym dostępnych jest zwykle bardzo dużo programów użytkowych o interfejsie graficznym -

edytory tekstowe działające w trybie graficznym, edytory graficzne, przeglądarki internetowe, gry

komputerowe, odtwarzacze muzyki i filmów itd. - nie są one jednak zestandaryzowane, a ich obecność

w systemie zależy od decyzji administratora i posiadanych zasobów.

12. SYSTEM PLIKÓW SYSTEMU OPERACYJNEGO UNIX

Logiczny obraz systemu plików z punktu widzenia użytkownika Uniksa jest jednym grafem

acyklicznym o korzeniu oznaczonym znakiem / . Ten logiczny obraz nie jest zależny od

umiejscowienia plików na konkretnych urządzeniach fizycznych - dyskach twardych (ich partycjach),

dyskietkach itp., a nawet na urządzeniach innych komputerów w sieci - administrator systemu dokonuje

montowania (mount) poszczególnych fizycznych systemów plików w jednym dużym grafie, wspólnym

dla wszystkich użytkowników systemu, który może być widziany jako sieciowy system plików.

Oznacza to, że interfejs użytkownika nie pozwala mu na stwierdzenie, gdzie (na którym komputerze

w sieci, na którym jego urządzeniu) przechowywane są jego pliki. Oznacza to również, że zwykły

użytkownik nie ma uprawnień do posługiwania się dyskietkami i płytkami CD w systemach uniksowych

i w razie potrzeby musi zwrócić się z taką sprawą do administratora. Typowym sposobem ominięcia

tego problemu jest przesyłanie plików pomiędzy indywidualnym komputerem użytkownika a jego

serwerem uniksowym przy użyciu protokołu ftp lub innego równoważnego.

W systemie plików przechowywane są zarówno pliki zawierające dane i oprogramowanie systemowe,

jak i pliki należące do poszczególnych użytkowników. Katalogi najwyższego poziomu mają zwykle

standardowe nazwy - poniżej naszkicowany jest fragment typowego grafu:

/

bin dev etc home lib tmp ..................

bash cat .... tty1 .... passwd .... anna jan ..... libcurses libXt ...... ...............

Katalogi domowe poszczególnych użytkowników są umieszczane w katalogu home (czasem user).

W pozostałych katalogach umieszczone są pliki systemowe, na przykład w bin programy binarne

(skompilowane), w dev pliki specjalne urządzeń (device), w lib biblioteki funkcji (library) itd.

W różnych systemach uniksowych ogólne struktury grafów katalogów są podobne, ale w szczegółach

mogą się różnić.

Zgodnie z przyjętą filozofią systemu Unix, poza zwykłymi plikami w grafie katalogów są też

umieszczone pliki specjalne będące logicznymi obrazami urządzeń zewnętrznych (na przykład

terminali), środków komunikacji międzyprocesowej (kanałów komunikacyjnych) i innych obiektów.

W interfejsie użytkownika (na przykład w poleceniu ls -l ) wyróżnione są następujące rodzaje plików

(i przyporządkowane tym rodzajom symbole):

d oznacza katalog (directory)

l oznacza dowiązanie symboliczne (miękkie) (symbolic link)

c oznacza urządzenie znakowe (character device)

b oznacza urządzenie blokowe (block device)

n oznacza urządzenie sieciowe (network device)

s oznacza gniazdo (socket)

p oznacza łącze nazwane (named pipe)

Poza rodzajem plik uniksowy posiada wiele innych atrybutów, które są uwidocznione w interfejsie

użytkownika. Ważnymi atrybutami są prawa dostępu wyświetlane przez polecenie ls -l

w następującym formacie:

* * * * * * * * * *

u g o

rodzaj pliku

u - prawa właściciela (user)

(według podanych oznaczeń)

g - prawa grupy (group)

o - prawa innych użytkowników (other)

Każdy plik ma swojego właściciela (użytkownika, którego proces utworzył dany plik). Użytkownik

może należeć do jednej lub więcej grup użytkowników (z których jedna jest wyróżniona dla każdego

użytkownika jako jego grupa główna). Grupy są z kolei podzbiorami wszystkich użytkowników

danego systemu uniksowego.

Uwaga

Prawa właściciela, prawa jego grupy i prawa innych użytkowników stanowią oddzielne, niezależnie

ustalane zbiory atrybutów (na przykład właściciel może odebrać sobie prawa, ale pozostawić je grupie).

W powyższym 10-znakowym zestawieniu pierwszy znak określa rodzaj pliku, natomiast ciąg

pozostałych znaków jest traktowany jako trzy trójki, odpowiednio określające prawa dostępu dla

właściciela pliku, jego grupy i pozostałych użytkowników. W każdej trójce na każdej pozycji może

wystąpić litera (prawa nadane) lub znak - (prawa odebrane).

Na pierwszej pozycji:

r prawo do czytania (read)

Na drugiej pozycji:

w prawo do pisania (write)

Na trzeciej pozycji:

x prawo do wykonywania (execute)

s

S interpretacja zależna

t od położenia w ciągu

T

Ostatni rodzaj praw związany jest z uruchamianiem procesów na podstawie pliku wykonywalnego.

Proces może posiadać prawa użytkownika, który go uruchomił, lub właściciela pliku wykonywalnego

(nie musi to być ten sam użytkownik). Podobnie może posiadać prawa grupy użytkownika, który go

uruchomił, lub grupy właściciela pliku (mieć nadany identyfikator grupy właściciela).

Uwaga

Nie wszystkie rodzaje praw odnoszą się do wszystkich rodzajów plików, ponadto w przypadku

niektórych rodzajów mają odmienną interpretację. W szczególności w przypadku katalogów:

- prawo do czytania oznacza prawo do przeglądania zawartości katalogu (np. wyświetlenia jej na

ekranie terminala);

- prawo do pisania oznacza prawo do zmiany zawartości katalogu (tworzenia i usuwania w nim plików

i podkatalogów);

- prawo do wykonywania oznacza prawo do przeszukiwania („wejścia” do danego katalogu).

W przypadku programu, który jest przeznaczony do częstego wykonywania, jego tekst (zawartość pliku)

może być pozostawiony w pamięci operacyjnej, aby zaoszczędzić czasu na ponownym ładowaniu

programu z pliku. Atrybut pliku, który to ustala, nazywany jest bitem lepkości (sticky bit).

Zestawienie interpretacji atrybutów x, s, S, t, T w zależności od położenia w ciągu:

* * x * * * * * * prawo właściciela do wykonywania (brak ustanowienia ID właściciela dla

grupy i dla innych)

* * s * * * * * * prawo właściciela do wykonywania (ustanowienie ID właściciela dla grupy i dla

innych)

* * S * * * * * * brak prawa właściciela do wykonywania (ustanowienie ID właściciela dla grupy

i dla innych)

* * * * * x * * * prawo grupy do wykonywania (brak ustanowienia ID grupy dla innych)

* * * * * s * * * prawo grupy do wykonywania (ustanowienie ID grupy dla innych)

* * * * * S * * * brak prawa grupy do wykonywania (ustanowienie ID grupy dla innych)

* * * * * * * * x prawo do wykonywania dla innych (bez ustawienia bitu lepkości)

* * * * * * * * t prawo do wykonywania dla innych (i ustawienie bitu lepkości)

* * * * * * * * T brak prawa do wykonywania dla innych (i ustawienie bitu lepkości)

Szczególną postacią grafu acyklicznego jest drzewo. W drzewie każdy katalog (poza korzeniem)

ma dokładnie jeden nadkatalog i każdy plik należy do dokładnie jednego katalogu. Ogólnie, w grafie

acyklicznym nie musi to zachodzić - można tworzyć dodatkowe dowiązania zarówno plików, jak

i katalogów do katalogów innych, niż te, w których już są. W interfejsie użytkownika służy do tego

polecenie ln .

Uwaga

Zwykły użytkownik systemu ma jedynie prawo tworzenia dowiązań do plików. Prawo tworzenia

dowiązań do katalogów ma jedynie administrator systemu, ze względu na niebezpieczeństwo

zapętlenia w ten sposób grafu katalogów (co może wiązać się z nieodwracalnym uszkodzeniem

systemu plików).

Jeśli plik (lub katalog) jest dowiązany w kilku miejscach, to jego usunięcie w jednym miejscu nie

powoduje jego fizycznego skasowania, a jedynie usunięcie dowiązania do tego miejsca. Dopiero

usunięcie ostatniego istniejącego dowiązania powoduje fizyczne usunięcie z systemu plików.

Wyżej omówione dowiązania noszą nazwę dowiązań twardych (hard link). Ich tworzenie i usuwanie

wiąże się bezpośrednio ze zmianą informacji zapisanej w systemie na temat danego pliku. Użytkownik

ma prawo tworzenia również dowiązań symbolicznych (symbolic link). W interfejsie użytkownika są

one uwidocznione w podobny sposób, jak dowiązania twarde, ale w rzeczywistości są jedynie małymi

plikami zawierającymi informację na temat usytuowania pliku, do którego zostało stworzone takie

dowiązanie.

Z punktu widzenia użytkownika najbardziej istotną różnicą pomiędzy dowiązaniami twardymi

a miękkimi (symbolicznymi) jest to, że dowiązanie twarde może być utworzone tylko do istniejącego

pliku lub katalogu (system musi sprawdzić jego istnienie, modyfikując zapis w odpowiedniej

strukturze systemowej), zaś dowiązanie miękkie jest tylko plikiem z zapisaną informacją „na życzenie

użytkownika”, nie weryfikowaną przez system operacyjny. Odpowiednio, usunięcie wszystkich

twardych dowiązań do danego pliku spowoduje jego fizyczne skasowanie, bez względu na to, czy

gdzieś istnieją jakieś dowiązania symboliczne do tego pliku.

Łącza nazwane (kolejki FIFO) są specjalnym rodzajem plików przeznaczonych do umożliwiania

komunikacji międzyprocesowej. Mogą one być traktowane jako „pliki z nietrwałą zawartością”

w tym sensie, że każdy odczyt porcji informacji z łącza przez pewien proces jednocześnie usuwa tę

informację z łącza. Pojedyncze łącze umożliwia komunikację jednokierunkową - jeżeli chcemy

ustanowić komunikację dwukierunkową pomiędzy dwoma procesami, musimy użyć do tego pary łącz.

Łącza wymuszają synchronizację dwóch komunikujących się procesów - proces, który otworzył łącze

do zapisu, zostaje zawieszony aż do otwarcia łącza przez inny proces do odczytu (i na odwrót).

Podobnie proces, który wywołał funkcję zapisu do łącza, zostaje zawieszony aż do wywołania przez

drugi proces funkcji odczytu z łącza (i na odwrót).

Łącza nazwane są uwidocznione dla użytkownika w systemie plików jako pliki specjalne

o wielkości 0. Do ich utworzenia służy polecenie systemowe mkfifo. Poza łączami nazwanymi Unix

dysponuje również łączami nienazwanymi (bezimiennymi), które nie są uwidocznione dla

użytkownika. Są to tymczasowe pliki nietrwałe (a właściwie ich bufory), które są tworzone w katalogu

bieżącym na czas przekazywania strumienia danych z jednego procesu do drugiego wskutek użycia

operatora | .

Gniazda (socket) są alternatywnym medium komunikacji międzyprocesowej, charakterystycznym dla

Uniksów BSD. Zostały zaprojektowane jako obiekty interfejsu do protokołów komunikacyjnych

(w szczególności do protokołu internetowego IP). Mogą służyć zarówno do komunikacji wewnętrznej

(między procesami w tym samym systemie uniksowym), jak i do komunikacji pomiędzy procesami

na różnych komputerach.

W katalogu /dev umieszczone są pliki specjalne będące logicznymi obrazami urządzeń fizycznych -

terminali użytkowników, urządzeń dyskowych, drukarek i innych. Sposób ich obsługi zależy od

konkretnych programów obsługi (sterowników) tych urządzeń. Tekstowe terminale użytkowników

traktowane są jako pliki tekstowe, do których (jeśli prawa dostępu na to pozwalają) można bezpośrednio

pisać, i z których można bezpośrednio czytać. Związana z tym jest możliwość przesyłania sobie

wzajemnie komunikatów przez użytkowników systemu uniksowego, jak również możliwość

„bronienia się” przed otrzymywaniem niepożądanych komunikatów.

Uwaga

Administrator systemu zawsze ma możliwość przesłania komunikatu dowolnym użytkownikom.

Wewnętrzna organizacja systemu plików

Z każdym plikiem w systemie skojarzona jest systemowa struktura danych zawierająca informacje na

temat tego pliku, nazywana jego węzłem indeksowym (index node) lub, krótko i-węzłem (i-node).

I-węzeł zawiera następujące informacje:

- identyfikator właściciela pliku;

- typ pliku;

- prawa dostępu;

- czasy: ostatniego dostępu, ostatniej modyfikacji pliku i ostatniej modyfikacji i-węzła;

- liczba twardych dowiązań;

- tablica adresów bloków dyskowych, w których zapisana jest zawartość pliku;

- rozmiar pliku.

Tablica wszystkich i-węzłów zapisana jest na dysku, a jej kopia - w pamięci operacyjnej. Kopie

i-węzłów zawierają dodatkowo informacje o założeniu blokady na plik przez pewien pewien proces,

o modyfikacji i-węzła od czasu skopiowania do pamięci i o tym, czy plik jest punktem montowania.

Jeśli jakikolwiek proces chce czytać dane z pliku lub zapisywać do niego dane, musi najpierw ten plik

otworzyć, a po wykonaniu na nim operacji zamknąć go. W systemie przechowywane są w związku

z tym następujące struktury danych:

- struktura globalna jądra systemu nazywana tablicą plików, w której każda pozycja odpowiada

jednemu otwarciu pliku (zatem w przypadku, gdy dwa różne procesy otworzą ten sam plik, w tablicy

będą zapisane dwie pozycje);

- dla każdego procesu w jego indywidualnej strukturze danych przechowywana jest jego tablica

deskryptorów plików, która zawiera indeksy (numery pozycji) w globalnej tablicy plików związane

z otwarciami plików dokonanymi przez ten proces.

Tablice deskryptorów Tablica plików Tablica i-węzłów

plików (po jednej na proces) (globalna) (globalna)

Uwaga

Z powyższego sposobu zapisu informacji o plikach otwartych wynika, że w każdej chwili zarówno

liczba plików otwartych przez pojedynczy proces, jak i łączna liczba wszystkich plików otwartych

w systemie jest ograniczona. Maksymalne wielkości tych liczb są ustalane przez administratora

w czasie konfiguracji systemu operacyjnego.

Każdy proces w momencie swojego uruchomienia ma automatycznie otwarte trzy deskryptory plików:

0 - standardowe wejście (standard input);

1 - standardowe wyjście (standard output);

2 - standardowe wyjście błędów (standard error output).

Przez domniemanie te trzy deskryptory są związane z terminalem właściciela procesu (może to być

zmienione przez przekierowania) i służą odpowiednio do czytania danych, wyświetlania wyników

i wyświetlania komunikatów o błędach. Użytkownicy mogą rozdzielić mieszające się na ekranie

terminala strumienie wyjściowe: wyników i błędów, poprzez przekierowanie ich do dwóch różnych

plików.

Jedną z najważniejszych informacji zapisanych w każdej pozycji tablicy plików jest aktualne

położenie wskaźnika pliku. Ponieważ różne pozycje w tablicy plików mogą wskazywać na ten sam

i-węzeł, a ponadto w tablicach deskryptorów procesów mogą występować jednakowe deskryptory,

stwarza to bogactwo możliwości współpracy wielu procesów za pośrednictwem współdzielonych

plików. Jest możliwe zarówno niezależne przemieszczanie swoich wskaźników po jednym pliku przez

dwa procesy (w typowym zastosowaniu jeden z nich zapisuje dane, a drugi asynchronicznie te dane

odczytuje), jak też korzystanie (zwykle przez grupę spokrewnionych procesów) ze wspólnego

deskryptora, co umożliwia im na przykład naprzemienne zapisywanie danych do wspólnego pliku.

W systemach uniksowych procesy potomne dziedziczą po swoich procesach rodzicielskich między

innymi wszystkie deskryptory otwartych plików. Oznacza to, że strumienie wejściowe i wyjściowe

tych procesów będą mieszały się (jest zatem możliwe zjawisko wzajemnego „podkradania sobie”

danych przez proces rodzicielski i potomny) i programista powinien zadbać, aby w jego programach

nie występowały zjawiska hazardu (niedeterminizmu).

13. ZARZĄDZANIE PROCESAMI W SYSTEMIE OPERACYJNYM UNIX

Z punktu widzenia organizacji systemu Unix procesy są obiektami logicznymi posiadającymi swoje

atrybuty (zapisane w odpowiednich systemowych strukturach danych), na których można wykonywać

operacje przy użyciu odpowiednich funkcji systemowych.

Najważniejsze atrybuty procesów:

- identyfikator procesu PID (Process ID)

- identyfikator procesu rodzicielskiego PPID (Parent Process ID)

- rzeczywisty identyfikator użytkownika UID (User ID)

- efektywny identyfikator użytkownika EUID (Effective User ID)

- rzeczywisty identyfikator grupy użytkowników GID (Group ID)

- efektywny identyfikator grupy użytkowników EGID (Effective Group ID)

Proces może mieć swój terminal sterujący, z którym może komunikować się, pobierając z niego

dane (na przykład polecenia użytkownika) i wyświetlając wyniki. Jednym z atrybutów procesu jest

wskaźnik do struktury terminala - systemowej struktury danych związanej z danym terminalem.

Struktura terminala zawiera między innymi bieżący identyfikator grupy terminala (Current

Terminal Process Group) - nie ma ustalonego skrótu, na potrzeby wykładu przyjmijmy CTPGRP.

Jeśli proces nie ma swojego terminala sterującego, jego wskaźnik ma wartość NULL.

W większości przypadków procesy użytkowników mają swój terminal sterujący (ten, z którego

zostały uruchomione), natomiast procesy systemowe nie mają swojego terminala (wykonują one

swoją pracę przez cały czas, niezależnie od tego, czy nadzorca systemu jest aktualnie zalogowany).

Procesy systemowe, mające za zadanie dostarczanie określonych usług na rzecz innych procesów,

nazywane są często serwerami tych usług lub demonami (daemon).

Zwykły użytkownik systemu również ma możliwość uruchamiania procesów „pozbawionych

terminala sterującego”, które mogą być wykonywane nawet po zakończeniu przez użytkownika sesji

pracy i wyłączeniu terminala (na przykład mogą wykonywać przez całą noc skomplikowane

obliczenia i zapisywać wyniki do pliku).

Proces interpretatora komend, który zostaje uruchomiony w wyniku zalogowania się do systemu,

nazywany jest shellem zgłoszonym. Na początku pracy jest on procesem sterującym terminala,

z którego nastąpiło zalogowanie, czyli przywódcą grupy terminala (zachodzi dla niego

PID = PGRP = CTPGRP).

Proces nazywamy pierwszoplanowym (foreground process), jeżeli jego PGRP jest równy CTPGRP

wskazywanego przez niego terminala. W przeciwnym przypadku proces nazywamy drugoplanowym,

lub wykonującym się w tle (background process). Procesy pierwszoplanowe mogą odczytywać znaki

z terminala i zapisywać znaki do terminala (to jest pliku specjalnego odpowiadającego terminalowi).

Procesy drugoplanowe nie mogą odczytywać znaków z terminala (są zawieszane przy próbie czytania),

natomiast zazwyczaj mogą zapisywać znaki do terminala (jest to zależne od systemu i aktualnych

ustawień w strukturze terminala).

Uwaga.

Zazwyczaj w każdej chwili istnieje dokładnie jeden proces pierwszoplanowy terminala.

Pojedynczą komendę lub pojedynczy potok komend uruchomiony z linii poleceń shella nazywamy

pracą (job). Shell ma własność sterowania pracami (job control), jeżeli posiada możliwość zmiany

CTPGRP terminala, z którego został uruchomiony.

Polecenia:

komenda - uruchamia proces pierwszoplanowy;

komenda & - uruchamia proces drugoplanowy

(komenda &) - uruchamia proces drugoplanowy korzystając z podshella

Ctrl-Z - zawiesza proces pierwszoplanowy

Ctrl-Y - zawiesza proces pierwszoplanowy w chwili rozpoczęcia czytania

bg praca (lub praca &) - uruchamia zawieszony proces w tle

fg praca (lub praca ) - uruchamia zawieszony proces na pierwszym planie

kill -9 PID - powoduje zakończenie procesu (działającego lub zawieszonego)

Uwaga. Można uruchomić kilka procesów jednocześnie z jednej linii poleceń (pisząc na przykład

komenda_1 & komenda_2 & ).

Do uzyskiwania informacji o aktualnie wykonywanych procesach (numerach, właścicielach, stanach

procesów itp.) służy polecenie zewnętrzne ps. W zależności od podanych opcji może wyświetlać

jedynie informacje o aktywnych procesach lub o wszystkich (też zawieszonych), informacje jedynie

o procesach użytkownika wydającego polecenie, lub o wszystkich procesach w systemie (ta ostatnia

opcja zwykle nie jest udostępniana dla użytkowników nieuprzywilejowanych).

Uwaga

Użytkownik może badać stan swoich procesów związanych z bieżącą sesją przy użyciu interpretera

komend bieżącej sesji (w takim przypadku proces interpretera musi być pierwszoplanowy, a wszystkie

inne procesy drugoplanowe lub zawieszone), lub też otwierając nową sesję (i uruchamiając w ten

sposób nowy proces interpretera) i z niej sprawdzając stan procesów pierwszej sesji. Jest to możliwe,

bo użytkownik jest właścicielem wszystkich procesów zarówno pierwszej, jak i drugiej sesji.

Drugi sposób jest lepszy, jeśli z jakichś powodów nie jest wskazane czasowe przerywanie pracy

procesu pierwszoplanowego pierwszej sesji.

Pełny diagram stanów procesu (według [ M. Bach ] ):

Wykonywany w trybie użytkownika

wywołanie

funkcji systemowej, powrót do trybu

przerwanie przerwanie powrót użytkownika

i powrót wywłaszczenie

Wykonywany w trybie jądra

wyjście Wywłaszczony

Zombie ponowne

zasypia uszeregowanie

Gotowy do wykonania w pamięci

Uśpiony budzi się

w pamięci dość pamięci

usunięcie usunięcie sprowadzenie Utworzony

z pamięci z pamięci do pamięci fork

Uśpiony budzi się za mało pamięci

poza pamięcią Gotowy do wykonania poza pamięcią

Uwaga. Powyższy diagram nie uwzględnia mechanizmu stronicowania pamięci.

Jądro utrzymuje globalną strukturę nazywaną tablicą procesów, której zajęte pozycje odpowiadają

identyfikatorom procesów aktualnie istniejących w systemie (typowa wielkość tablicy procesów -

32768 pozycje). Każda zajęta pozycja zawiera między innymi:

- pole stanu procesu;

- identyfikator właściciela procesu (UID);

- wskaźnik do tablicy segmentów procesu;

- wskaźnik do u-obszaru (u-area, user area) procesu. Tablica segmentów

U-obszar

Tablica segmentów

procesu

Tablica procesów Pamięć operacyjna

Tablica procesów jest strukturą globalną, do której dostęp ma jedynie jądro systemu. Z każdym

wykonywanym procesem związany jest jego u-obszar, do którego dostęp ma zarówno jądro, jak

i sam proces (gdy wykonywany jest w trybie jądra). U-obszar zawiera między innymi:

- wskaźnik do odpowiadającej mu pozycji w tablicy procesów;

- deskryptory plików otwartych przez dany proces;

- nazwę bieżącego katalogu;

- argumenty aktualnego wywołania funkcji systemowej, zwracane wartości i kod błędu.

Z każdym wykonywanym procesem związana jest też tablica segmentów procesu, która zawiera

opisy segmentów przydzielonych procesowi (kodu, danych, stosu, pamięci wspólnej) wraz ze

wskaźnikami do odpowiednich pozycji w globalnej tablicy segmentów. Tablica segmentów,

utrzymywana przez jądro segmentów, zawiera z kolei deskryptory wszystkich istniejących segmentów

w pamięci operacyjnej. Taki dwustopniowy system adresowania segmentów w pamięci umożliwia

współdzielenie segmentów przez wiele procesów, przy jednoczesnym zachowaniu możliwości ich

ochrony (na przykład jeden proces może mieć prawo do zapisu w danym segmencie, a pozostałe tylko

do odczytu).

Każdy proces, poza procesem rozpoczynającym pracę systemu, powstaje w wyniku utworzenia przez

inny proces. Proces uruchamiający jądro systemu jest tworzony „bezpośrednio” i otrzymuje numer 0.

Jest on procesem wykonywanym w trybie jądra. Po uruchomieniu systemu przyjmuje on rolę procesu

wymiany. Tworzy on proces init o numerze 1, który jest procesem wykonywanym w trybie użytkow-

nika, i który zajmuje się dalej tworzeniem procesów potomnych. Init jest zatem przodkiem wszystkich

procesów w systemie poza procesem 0.

Każdy proces ma prawo tworzyć swoje procesy potomne (ich liczba jest ograniczona pojemnością

tablicy procesów oraz, ewentualnie, maksymalną liczbą procesów należących do jednego użytkownika).

W klasycznych systemach uniksowych proces potomny powstawał zawsze wskutek wywołania przez

proces rodzicielski funkcji systemowej fork (obecnie takich funkcji jest więcej, co jest między innymi

związane z implementacją wątków, czyli procesów lekkich). Utworzony proces potomny jest wpisywany

do tablicy procesów na kolejnej wolnej pozycji (modulo rozmiar tablicy), której numer staje się jego

identyfikatorem. Każdy proces „pamięta” identyfikator swojego procesu rodzicielskiego, natomiast

spośród identyfikatorów procesów potomnych pamięta jedynie ostatni (najpóżniej przydzielony).

Każdy nowo utworzony proces dziedziczy identyfikator grupy procesów (PGRP) po swoim procesie

rodzicielskim. Może w tej grupie pozostać lub wywołać funkcję systemową setpgrp, która powoduje

zmianę jego dotychczasowego PGRP na wartość jego identyfikatora (PID). Proces, który wywołał

setpgrp, staje się przywódcą grupy procesów.

Proces potomny dziedziczy po swoim procesie rodzicielskim większość jego atrybutów - UID, GID,

deskryptory przydzielonych zasobów systemowych (w szczególności deskryptory otwartych plików)

i zbiór zmiennych środowiska. Dziedziczy również segment kodu programu oraz otrzymuje kopie

segmentu danych i stosu. Ponieważ zazwyczaj celem utworzenia nowego procesu jest powierzenie

mu wykonywania innego programu, niż wykonuje jego proces rodzicielski, proces potomny powinien

wywołać funkcję systemową exec, której argumentem jest nazwa pliku z innym programem.

Proces potomny, kończąc działanie (wywołując funkcję systemową exit) przesyła informację o tym

do swojego procesu rodzicielskiego, przekazując mu jednocześnie swój kod zakończenia (exit code),

będący niedużą liczbą całkowitą (0 w przypadku traktowanym jako pomyślny). Jeśli proces rodziciel-

ski zakończył działanie przed zakończeniem wszystkich jego procesów potomnych, automatycznie

„przybranym rodzicem” dla niezakończonych procesów potomnych staje się proces init.

Priorytet procesu, decydujący o częstości wybierania go do wykonywania przez procesor, zależny jest

od kilku wielkości, między innymi od współczynnika nice (nazywanego też poziomem uprzejmości

procesu) umieszczonego w odpowiadającej danemu procesowi pozycji tablicy procesów.

Współczynnik ten może mieć wartość z zakresu 0 ... 39 , przy czym niższa wartość współczynnika

oznacza podwyższenie priorytetu.

Standardowa wartość nice wynosi 20 - wartość tę dziedziczą między innymi wszystkie shelle

zgłoszone pośrednio od procesu init (przez domniemanie każdy proces dziedziczy wartość nice po

swoim procesie rodzicielskim). Użytkownicy nieuprzywilejowani mogą uruchamiać procesy ze

współczynnikiem nice równym 20 lub więcej (do jego zwiększania służy polecenie systemowe nice).

Tylko nadzorca systemu może uruchamiać procesy z dowolnym współczynnikiem nice.

Uwaga

W dawniejszych systemach uniksowych wartość nice mogła być zmieniana jedynie przez proces,

którego ta wartość dotyczyła i nawet nadzorca systemu nie mógł wpływać na priorytety procesów

użytkowników. Obecnie możliwość taka istnieje (polecenie renice).

Procesy mogą korzystać z pomiaru czasu prowadzonego przez system i ustalać pory wykonywania

różnych czynności (na przykład tworzenia nowych procesów, wysyłania komunikatów itd.) poprzez

zapisywanie ich w tablicach crontab (związanych z poszczególnymi użytkownikami systemu).

Tablice te są sukcesywnie przeglądane przez proces demona zegarowego cron, który w podanych

porach inicjuje wykonywanie podanych komend.

Do operowania na zawartości tablicy crontab służy polecenie o takiej samej nazwie. Innymi

poleceniami związanymi z pomiarem czasu i wykonywaniem pewnych czynności w ustalonych

porach są polecenia at oraz leave.

Polecenia związane z pomiarem czasu ogólnie są uważane za polecenia niebezpieczne z punktu

widzenia systemu operacyjnego (nie są starannie opracowane pod kątem zabezpieczenia przed

włamaniami). W systemach uniksowych są zazwyczaj przechowywane listy użytkowników (ustalane

przez administratora), którzy albo a) mają prawo do wykonywania konkretnych poleceń związanych

z pomiarem czasu, albo b) mają zakaz wykonywania konkretnych poleceń.

14. KOMUNIKACJA MIĘDZYPROCESOWA W SYSTEMIE OPERACYJNYM UNIX

Poniższa seria slajdów jest w trakcie opracowywania.

W systemie Unix istnieje wiele środków komunikacji międzyprocesowej - zarówno przeznaczonych

do komunikacji między procesami na tym samym komputerze (zaprojektowanych glownie w AT&T),

jak i przeznaczonych do komunikacji w sieci komputerowej (gniazda BSD zaprojektowane

w Berkeley). Wszystkie te środki dostępne są w postaci struktur danych i obsługujących je funkcji

systemowych w interfejsie programisty w języku C. Najprostsze środki, takie jak przekazanie kodu

zakończenia procesu potomnego procesowi rodzicielskiemu, współdzielenie plików oraz łącza

(nazwane i nienazwane) dostępne są również poprzez polecenia powłoki (shella).

Proste mechanizmy koordynacji procesów w shellu:

1) polecenia exit i wait

exit liczba - kończy działanie procesu i przesyła jednobajtową liczbę (kod wyjścia)

do jego procesu rodzicielskiego

wait praca - powoduje zawieszenie procesu w oczekiwaniu na zakończenie jego procesu

potomnego i odbiera jego kod wyjścia

2) polecenia kill i trap

kill sygnał praca - powoduje wysłanie sygnału do procesu (z tej samej grupy)

trap komenda sygnał - powoduje przechwycenie przysłanego sygnału (jeśli to możliwe)

i wykonanie komendy

3) łącza nienazwane (bezimienne)

komenda1 | komenda2 | ... | komendan - potok

(może być utworzony przy założeniu, że komendy korzystają ze standardowego wejścia/wyjścia)

4) łącza nazwane (kolejki FIFO)

mkfifo nazwa (lub mknod nazwa p) - tworzy kolejkę FIFO

Zapis/odczyt oraz usunięcie odbywają się dla kolejki FIFO tak samo, jak dla zwykłego pliku

(mogą to robić wszystkie procesy, które mają odpowiednie prawa dostępu do kolejki). Musi

zachodzić synchronizacja operacji zapisu i odczytu.

Uwaga.

1) Łącza są realizowane jako bufory plików i mają ograniczoną pojemność. Mogą być widziane jako

„pliki nietrwałe” - odczyt porcji informacji z łącza usuwa ją jednocześnie z łącza.

2) Łącza nazwane są uwidocznione w systemie plików jako szczególny rodzaj plików (o wielkości 0).

Łącza nienazwane nie są uwidocznione (istnieją tylko w czasie wykonywania potoku).

Funkcje operujące na identyfikatorach.

Każdy proces poza procesem o numerze 0 powstaje wskutek utworzenia przez inny proces. Numery

procesów są liczbami naturalnymi przydzielanymi rosnąco modulo rozmiar tablicy procesów (zwykle

32 K) z pominięciem numerów aktualnie używanych. Każdy proces pamięta swój PID i PPID, ale

nie zapamiętuje w sposób automatyczny identyfikatorów tworzonych potomków (programista może

spowodować przechowywanie ich w zmiennych). Jeśli proces kończy działanie wcześniej, niż jego

(niektóre) procesy potomne, wszystkie procesy potomne otrzymują PPID=1 (jest to PID procesu Init)

i kontynuują działanie.

int getpid(void); - zwraca PID procesu

int getppid(void); - zwraca PPID procesu

int getpgrp(void); - zwraca PGRP procesu

int setpgrp(void); - odłącza proces od dotychczasowej grupy i ustanawia go przywódcą

nowej grupy (PGRP = PID)

Uwaga. Istnieją też odpowiednie funkcje dla identyfikatorów użytkowników i ich grup.

Funkcje związane z tworzeniem i kończeniem procesów.

Tworzenie nowego procesu:

int fork(void); zwraca -1 w przypadku niepowodzenia (na przykład brak zasobów)

zwraca 0 utworzonemu procesowi potomnemu

zwraca PID utworzonego potomka procesowi rodzicielskiemu

Wykonanie funkcji fork przez jądro systemu wiąże się z szeregiem skomplikowanych czynności

(przydział zasobów, wpisanie do tablicy procesów, kopiowanie środowiska itp.) i jest czasochłonne.

Segment instrukcji nie jest kopiowany, segment danych jest zwykle kopiowany dopiero w przypadku

próby dokonania zapisu przez nowy proces.

Zamiana kontekstu procesu:

Funkcja systemowa exec ma sześć interfejsów w języku C (różniących się sposobem przekazywania

parametrów i zmiennych środowiska). Jej zadaniem jest zamiana kontekstu procesu (przy zachowaniu

tożsamości procesu), to jest spowodowanie, żeby proces zaczął wykonywać inny program.

int execl (char *ścieżka, char *arg0, char *arg1, ... , char *argn, NULL);

ścieżka - pełna nazwa ścieżkowa pliku z nowym programem;

arg0 - powtórzona sama nazwa pliku z nowym programem;

arg1 ... argn - lista parametrów dla nowego programu zakończona znakiem pustym (NULL).

int execv (char *ścieżka, char *argv [ ] );

int execle (...);

int execve (...);

int execlp (...);

int execvp (...);

Funkcje fork i exec zazwyczaj współpracują ze sobą.

Kończenie wykonywania procesu:

void exit (int kod);

Kończy działanie procesu, wysyła sygnał do procesu rodzicielskiego oraz jednobajtowy kod wyjścia.

Oczekiwanie na zakończenie działania potomka:

int wait (int *wsk);

Zawiesza proces w oczekiwaniu na zakończenie któregokolwiek procesu potomnego. Zwraca PID

zakończonego potomka lub -1 w przypadku błędu. wsk zwraca dwa bajty:

- jeśli prawy bajt ma wartość 0, to lewy bajt zwraca kod wyjścia potomka;

- jeśli prawy bajt ma wartość niezerową, to określa, jaki sygnał spowodował zakończenie potomka,

oraz czy nastąpił zrzut pamięci do pliku core.

Uwaga. Obecnie istnieje też funkcja pozwalająca czekać na zakończenie określonego potomka.

Funkcje związane z operowaniem na sygnałach.

Wysłanie sygnału:

int kill (int pid, int sig);

Umożliwia wysłanie określonego sygnału do określonego procesu / grupy procesów.

Przechwycenie sygnału:

void (*signal (int sig, void (*func) (int))) (int);

Umożliwia przechwycenie określonego sygnału (jeśli to możliwe) i wykonanie wskazanej funkcji

obsługi.

Polecenia shella kill i trap są obudowami funkcji systemowych kill i signal.

Funkcje związane z operowaniem na łączach nienazwanych.

Pierwotnie łącza nienazwane mogły być używane jedynie jako jednokierunkowe:

P Q

zapis odczyt

kolejka prosta

Funkcja tworząca łącze:

int pipe (int fd [2] );

fd [0] - deskryptor pliku służący do odczytu z łącza

fd [1] - deskryptor pliku służący do zapisu do łącza

Do zapisów / odczytów stosujemy funkcje systemowe write i read (są wykonywane niepodzielnie).

Łącze ma pojemność zależną od ustawień systemowych (co najmniej pół KB, zazwyczaj 4 KB).

Zazwyczaj bezpośrednio po wywołaniu funkcji pipe wywoływana jest funkcja fork (proces potomny

dziedziczy deskryptory plików), a następnie, w zależności od zamierzonego kierunku przesyłania,

zamykane są niepotrzebne deskryptory (po jednym w każdym procesie).

...

pipe (fd);

if (fork ( ) = = 0) fd [1] fd [1]

{

close (fd [0] );

...

} fd [0] łącze fd [0]

else

{ proces proces

close (fd [1] ); potomny rodzicielski

...

}

Główną wadą łącz nienazwanych jest to, że mogą łączyć tylko procesy spokrewnione (zazwyczaj

pary rodzic - potomek, ale mogą też być dziadek - wnuk, dwóch potomków itp.).

W nowszych wersjach Unixa łącza są implementowane jako dwukierunkowe (full duplex).

W starszych mogły być tylko jednokierunkowe (half-duplex) - chcąc uzyskać łączność

dwukierunkową należało skorzystać z dwóch par deskryptorów i dwukrotnie wywołać funkcję pipe.

Uwaga.

1) W przypadku próby odczytu z pustego łącza lub próby zapisu do pełnego łącza procesy są czasowo

zawieszane.

2) W przypadku łącz dwukierunkowych może być potrzebna synchronizacja operacji zapisu i odczytu

po obu stronach łącza (na przykład za pomocą semaforów).

3) Na zakończenie działania programu należy pozamykać wszystkie otwarte deskryptory.

Funkcje związane z operowaniem na łączach nazwanych (FIFO).

Łącza nazwane są uwidoczniane w systemie plików jako specjalny rodzaj plików o zerowym

rozmiarze. Mogą być tworzone i usuwane zarówno w programach, jak i przy użyciu komend shella.

Z łączami nazwanymi mogą współpracować dowolne procesy (niekoniecznie spokrewnione), które

posiadają odpowiednie prawa dostępu.

Funkcja tworząca kolejkę FIFO:

int mknod (const char *ścieżka, int tryb);

ścieżka - pełna nazwa ścieżkowa kolejki FIFO

tryb - słowo trybu, którego bity informują między innymi o prawach dostępu do kolejki

Przed użyciem łącze nazwane musi być otwarte (open), a przed zakończeniem wykonywania programu

zamknięte (close) przez każdy proces współpracujący z łączem. Jest wymuszona synchronizacja

otwarcia łącza do zapisu i otwarcia łącza do odczytu przez dwa procesy chcące korzystać z łącza.

Samo korzystanie z łącza wygląda podobnie, jak w przypadku łącz nienazwanych (funkcje write i read).

11. PAKIET IPC

Narzędzia z pakietu IPC (InterProcess Communication) służą do koordynacji procesów wykonywa-

nych na jednym komputerze (nie są przeznaczone do komunikacji sieciowej). W skład tego pakietu

wchodzą biblioteki funkcji obsługujących kolejki komunikatów (message queue), pamięć dzieloną

(shared memory) i semafory (semaphore). Z wszystkimi trzema rodzajami obiektów związane są

odpowiednie struktury danych tworzone przez jądro systemu, do których dostęp jest możliwy jedynie

poprzez wywoływanie przeznaczonych do tego funkcji systemowych.

Zestawienie głównych funkcji pakietu IPC (wg W.R. Stevensa):

Kolejki komun. Semafory Pamięć dziel.

Plik nagłówkowy < sys/msg.h > < sys/sem.h> < sys/shm.h>

Funkcja systemowa tworzenia lub otwierania msgget semget shmget

Funkcja systemowa operacji sterujących msgctl semctl shmctl

Funkcje systemowe przesyłania msgsnd semop shmat

msgrcv shmdt

Ze względu na to, że struktury kontrolne obiektów pakietu IPC są przechowywane w jądrze systemu

i mogą być widoczne dla wszystkich procesów, muszą mieć klucze unikalne w obrębie całego systemu.

Zalecane jest stosowanie funkcji ftok generującej unikalne klucze na podstawie ścieżek dostępu do

plików z programami wykonywanymi przez procesy tworzące obiekty pakietu IPC. Dopuszczalny

zakres kluczy zależy od ustawień systemowych - odpowiada mu typ key_t zdefiniowany w nagłówku

< sys/types.h >.

Kolejki komunikatów

Kolejki komunikatów nie są kolejkami prostymi (komunikaty mogą być z nich wybierane w innej

kolejności, niż zostały umieszczone). Komunikaty posiadają pewną strukturę (nie są tylko ciągami

bajtów, jak w przypadku łącz):

struct msgbuf { long mtype ;

char mtext[1] ; }

Uwaga. Typ char zawartości komunikatu jest zdefiniowany tylko pro forma - może być rzutowany.

int msgget (key_t klucz, int flagi);

Zwraca: identyfikator kolejki w przypadku sukcesu ;

-1 w przypadku błędu.

Flagi określają prawa dostępu, oraz czy ma być zwrócony błąd, jeśli kolejka o danym kluczu już istnieje.

Działanie: tworzy kolejkę o podanym kluczu, jeśli taka kolejka jeszcze nie istnieje.

int msgsnd (int ident, struct msgbuf *kom, int rozmiar, int flagi) ;

Zwraca: 0 w przypadku sukcesu ;

-1 w przypadku błędu.

ident - identyfikator kolejki (zwrócony przez msgget)

kom - wskaźnik do struktury przechowującej typ komunikatu i sam komunikat (bufora komunikatu)

rozmiar - rozmiar komunikatu „netto” (nie licząc typu)

flagi - 0 lub IPC_NOWAIT (decydują, czy w sytuacji przepełnienia kolejki proces ma być zawieszony)

Działanie: wstawia komunikat wraz z podanym typem na koniec kolejki.

int msgrcv (int ident; struct msgbuf *kom, int rozmiar, long mtype, int flagi);

Zwraca: liczbę faktycznie pobranych bajtów z kolejki w przypadku sukcesu ;

-1 w przypadku błędu.

ident - identyfikator kolejki

kom - wskaźnik do bufora komunikatu

rozmiar - rozmiar struktury komunikatu (nie licząc typu)

mtype - typ komunikatu, jaki chcemy pobrać z kolejki (może być 0)

flagi - można ustawić IPC_NOWAIT i / lub MSG_NOERROR (powoduje odpowiednie zachowanie,

jeśli komunikat jest większy, niż przewiduje rozmiar)

Działanie: pobiera z kolejki najdawniej wstawiony komunikat o danym typie (jeśli istnieje), zaś

jeśli został podany typ 0, pobiera najdawniej wstawiony komunikat (o dowolnym typie).

int msgctl (int ident, int polecenie, struct msgqid_ds *struktura);

Zwraca: 0 w przypadku sukcesu ;

-1 w przypadku błędu.

ident - identyfikator kolejki

polecenie - kod czynności do wykonania na strukturze kontrolnej kolejki

struktura - wskaźnik do bufora struktury kontrolnej

Działanie: może wykonywać mnóstwo różnych czynności (w tym również takich, które mogą

pozbawić programistę kontroli nad kolejką) - zmieniać prawa dostępu, odczytywać

informacje o ostatnio wykonanej operacji na kolejce itp. Najczęściej jest wykorzystywana

do usunięcia kolejki:

msgctl (ident, IPC_RMID, 0);

Kolejki komunikatów są narzędziem bardziej skomplikowanym w użyciu, a jednocześnie oferującym

bogatsze możliwości, niż kolejki FIFO. W typowym zastosowaniu - implementacji par programów

typu klient / serwer - stosując kolejki FIFO musimy otworzyć oddzielną kolejkę dla serwera i oddzielne

dla wszystkich klientów (gdyż nie ma możliwości testowania danych umieszczonych w jednej kolejce

dla wielu odbiorców).

Dane od klientów powinny na początku zawierać informację o swojej długości oraz adres zwrotny

(to jest adres kolejki odbiorczej klienta).

W przypadku stosowania kolejek komunikatów wystarczają dwie takie kolejki (związane z serwerem),

gdyż umożliwiają demultipleksowanie odpowiedzi serwera do różnych klientów.

Klienci muszą mieć unikalne adresy kodowane jako typy komunikatów. Odpowiedzi serwera są

opatrywane tymi samymi typami, jakie miały przysyłane przez klientów pytania - klienci mogą je

selektywnie wybierać z kolejki zwrotnej.

Uwaga. Jest również możliwe rozwiązanie przy użyciu tylko jednej kolejki (dla pytań i odpowiedzi).

Pamięć dzielona

Poza własnym segmentem danych przydzielonym w momencie utworzenia, proces może mieć

przydzielony dynamicznie (w trakcie wykonywania) jeden lub więcej segmentów pamięci z ogólnych

zasobów systemowych. Takie segmenty są dołączane do przestrzeni adresowej procesu i można na

nich operować bezpośrednio (na przykład wykonując operacje przypisania). Jeśli programista ustanowi

odpowiednie prawa dostępu, segmenty takie mogą być niezależnie przydzielane wielu procesom

jednocześnie. Komunikowanie się przez pamięć dzieloną jest zdecydowanie najszybszym sposobem

komunikowania się procesów (choć najtrudniejszym do synchronizacji).

Uwaga.

1) Podobnie jak w przypadku plików i dowiązań do nich, segment pamięci dzielonej jest zwracany do

puli wolnych zasobów systemowych dopiero wtedy, gdy ostatni z użytkujących go procesów

zrzeknie się jego dalszego używania (odłączy go od swojej przestrzeni adresowej).

2) Nie ma możliwości operowania w segmencie pamięci dzielonej inaczej, niż za pomocą zmiennych

dynamicznych (wskaźników). W gruncie rzeczy segmenty pamięci dzielonej są widziane przez

programy jako dodatkowe (współdzielone) sterty.

3) Jeden i ten sam segment pamięci dzielonej może być dołączony do przestrzeni adresowej procesu

w wielu różnych miejscach. W ten sposób możemy dysponować wieloma kopiami jednej i tej

samej zmiennej, zmiana wartości jednej kopii jest natychmiast widoczna w pozostałych miejscach.

4) Proces potomny dziedziczy utworzony (i przyłączony) segment pamięci dzielonej.

int shmget (key_t klucz, int rozmiar, int flagi);

Zwraca: identyfikator segmentu w przypadku sukcesu;

-1 w przypadku błędu.

klucz, flagi - pełnią podobną rolę, jak dla kolejek komunikatów

rozmiar - rozmiar tworzonego segmentu w bajtach (argument nieistotny, jeśli segment już istnieje)

Działanie: tworzy nową pozycję w tablicy segmentów, jeśli segment wcześniej nie istniał.

int shmat (int ident, char *adres, int flagi);

Zwraca: wskaźnik do miejsca, gdzie rzeczywiście został dołączony segment, w przypadku sukcesu;

-1 w przypadku błędu.

ident - identyfikator segmentu (zwrócony przez funkcję shmget)

adres - wskaźnik do miejsca, gdzie programista proponuje dołączyć segment (może być 0)

flagi - rozmaite role ( na przykład mogą nakazać dołączenie segmentu tylko do odczytu)

Działanie: dołącza segment (i zwiększa jego licznik dowiązań o 1) pod podanym adresem (w miarę

możności) jeśli adres jest większy od 0, zaś pod adresem wybranym przez system, jeśli

podany adres jest równy 0 (najczęściej stosowane i najbardziej zalecane postępowanie).

int shmdt (char *adres);

Zwraca: 0 w przypadku sukcesu;

-1 w przypadku błędu.

adres - adres, pod którym był dołączony (przez funkcję shmat) segment pamięci dzielonej

Działanie: segment jest odłączany od przestrzeni adresowej procesu, a jego licznik dowiązań zmniej-

szany o 1. Jeżeli stan licznika dowiązań zmniejszył się w wyniku tego do 0, a segment był

oznaczony do usunięcia, w tym momencie następuje jego usunięcie z tablicy segmentów.

int shmctl (int ident, int polecenie, struct shmid_ds *struktura);

Zwraca: 0 w przypadku sukcesu;

-1 w przypadku błędu.

ident - identyfikator segmentu (zwrócony przez funkcję shmget)

polecenie - kod polecenia do wykonania na strukturze zarządzającej segmentem

struktura - wskaźnik do bufora struktury zarządzającej segmentem

Działanie: podobnie, jak w przypadku kolejek komunikatów, może wykonywać wiele różnych

czynności, a najczęsciej wykonywaną jest oznaczenie segmentu do usunięcia:

shmctl (ident, IPC_RMID, 0);

Uwaga. Zalecane jest odłączenie segmentu (wykonanie funkcji shmdt) przed oznaczeniem segmentu

do usunięcia.

Semafory

Semafory są uważane za najbardziej skomplikowane w użyciu obiekty pakietu IPC. Ich najbardziej

typowym zastosowaniem jest synchronizacja dostępu różnych procesów do zmiennych w pamięci

dzielonej. Semafory zaimplementowane w pakiecie IPC są podobne do semaforów Agerwali.

Występują nie jako pojedyncze obiekty, ale jako elementy tablic semaforów, na których można

wykonywać jednocześnie (niepodzielne) operacje. Maksymalny rozmiar tablicy semaforów zależy od

ustawień systemowych. Zakres wartości przyjmowanych przez pojedynczy semafor jest zakresem

wartości typu ushort (czyli od 0 do 255).

int semget (key_t klucz, int liczbasem, int flagi);

Zwraca: identyfikator tablicy semaforów w przypadku sukcesu;

-1 w przypadku błędu.

klucz, flagi - jak dla kolejek komunikatów i pamięci dzielonej

liczbasem - liczba semaforów w tablicy (argument nieistotny, jeśli tablica już istnieje)

Działanie: tworzy nową tablicę semaforów, jeśli wcześniej nie istniała.

int semop (int ident, struct sembuf *oper, unsigned liczbaoper);

gdzie struct sembuf

{ ushort sem_num; numer semafora w tablicy

short sem_op; operacja na semaforze

short sem_flg; } flagi operacji

Zwraca: 0 w przypadku sukcesu;

-1 w przypadku błędu.

ident - identyfikator tablicy semaforów (zwrócony przez funkcję semget)

oper - wskaźnik do początku tablicy operacji (tablicy struktur sembuf)

liczbaoper - liczba elementów w tablicy wskazywanej przez oper

Działanie: system wykonuje niepodzielnie wszystkie operacje nakazane w tablicy struktur wskazywanej

przez oper - albo nie wykonuje żadnej, jeśli choć jedna z nich jest w danej chwili niemożliwa.

Pojedyncza operacja na pojedynczym semaforze wygląda następująco:

- jeżeli wartość sem_op jest dodatnia, wartość semafora zwiększa się o nią (zatem, w przeciwieństwie

do tego, co jest podane w klasycznej definicji semafora, wartość semafora może wzrosnąć o więcej,

niż 1), a jednocześnie jest budzona odpowiednia liczba procesów śpiących pod tym semaforem (jeśli

są takie);

- jeżeli wartość sem_op jest ujemna, wartość semafora odpowiednio się zmniejsza, jeśli to możliwe,

a jeśli niemożliwe, zmniejszenie nie jest wykonywane, a proces albo zasypia czekając na zaistnienie

takiej możliwości, albo od razu następuje powrót z funkcji z błędem (w zależności od ustawienia

flagi IPC_NOWAIT);

- jeżeli wartość sem_op wynosi zero, proces zasypia, jeśli wartość semafora nie jest zerem (lub wraca

od razu z błędem, jeśli jest ustawiona flaga IPC_NOWAIT) i budzi się dopiero po osiągnięciu

wartości zero przez semafor.

int semctl (int ident, int numer, int polecenie, union semun argument);

gdzie union semun

{ int val; do ustawienia wartości pojedynczego semafora

struct semid_ds *buf; bufor struktury zarządzającej tablicą semaforów

ushort *array; wskaźnik do tablicy ustawień wartości całej tablicy sem.

struct seminfo *__buf; specyficzne dla Linuxa, używane przez

void *__pad; } jądro systemu operacyjnego

Zwraca: liczbę dodatnią będącą wynikiem wykonania polecenia - w przypadku sukcesu

-1 w przypadku błędu

ident - identyfikator tablicy semaforów (zwrócony przez funkcję semget)

numer - numer semafora w tablicy (istotny w przypadku, gdy polecenie dotyczy pojedynczego semafora)

polecenie - kod polecenia do wykonania

argument - argument jednego z typów wchodzących w skład unii, zależnego od polecenia

Działanie: może wykonywać mnóstwo różnych czynności (najwięcej z wszystkich funkcji IPC) na

pojedynczych semaforach, na całej ich tablicy lub na strukturze zarządzającej. Najczęściej

używanymi poleceniami są:

IPC_RMID usunięcie tablicy semaforów

GETALL odczytanie wartości wszystkich semaforów w tablicy

SETALL nadanie wartości wszystkim semaforom w tablicy

GETVAL odczytanie wartości pojedynczego semafora

SETVAL nadanie wartości pojedynczemu semaforowi

Uwaga. Operacje nadania lub odczytania wartości semaforów nie wiążą się z możliwością wstrzymania

procesu wykonującego daną operację.



Wyszukiwarka

Podobne podstrony:
ściąga chemia wykład, Studia, Sem 1,2 +nowe, ALL, szkoła, Chemia
zachomikowane notatki i wyklady, wykład z estetyki 04.01.2008, Estetyka o żywiole apollińskim i di
handlowe-prominska, wykład z dnia 16.01.2008[1], 16
handlowe-prominska, wykład z dnia 16.01.2008[1], 16
wyklad 13 21.01.2008, wyklady - dr krawczyk
wyklad 12 14.01.2008, wyklady - dr krawczyk
AiR 11 12 wyklad 15 27 01 2012 Nieznany (2)
11 bankowosc wyklad 11 27 01 2015
handlowe-prominska, wykład z dnia 17.01.2008[1], Wykład 17
PK, wykład 15, 27 01 2017
Sciągi do egzaminu, Ściąga - systemy operacyjne, Procedura przesyłania danych
Ściąga na drugie koło z wykładów
GEOGRAFIA EKONOMICZNA WYKŁAD 22.01.2011, SZKOŁA, szkola 2011
wyklad 11 7.01.2008, wyklady - dr krawczyk
wykład 23.11.2008, SZKOŁA, SZKOŁA, PRACA LICENCJACKA, notatki, wykład
wyklad 06[1].01.2008, Zarządzanie studia licencjackie, Finanse publiczne

więcej podobnych podstron