Niezbędnym elementem oprogramowania podstawowego każdego komputera jest system operacyjny. Jego zadaniem jest tworzenie środowiska operacyjnego, w którym użytkownik może wygodnie i bezpiecznie opracowywać, uruchamiać lub eksploatować programy. System operacyjny ukrywa przed użytkownikiem pewne cechy i funkcje sprzętowe, a dostarcza mu w zamian narzędzi wygodniejszych do rozwiązywania problemów. W szczególności system operacyjny umożliwia:
-przechowywanie informacji przez dłuższy czas w pamięci,
-wspólne wykorzystywanie urządzeń komputerowych przez grupę osób, jednoczesne wykonywanie różnych czynności przez system komputerowy,
-korzystanie z różnorodnych pakietów oprogramowania.
1.1 Filozofia systemu
UNIX od swojego powstania w 1969 roku zyskał znaczną popularność głównie dzięki temu, iż jest dostępny jest na komputerach różnej mocy — od mikrokomputerów po komputery klasy mainframe i zapewnia na nich jednakowe środowisko pracy. Mimo, iż system ten powstał ponad 30 lat temu, ciągle jest rozbudowywany.
Zasadniczo system ten składa się z dwóch części:
-programy i usługi - dzięki którym środowisko UNIX stało się takie popularne (shelle, programy pocztowe, kompilatory,...);
-system operacyjny wspierający te programy i usługi.
W połowie lat 70-tych system UNIX został udostępniony uniwersytetom i uzyskał dużą popularność w społeczności akademickiej, ponieważ:
-był mały — pierwsze systemy (na PDP-11) wymagały dysku o rozmiarach 512kB, wykorzystując l6kB na system, 8kB na programy użytkownika i 64kB na plik;
-był elastyczny — dostępny był kod źródłowy napisany w języku wysokiego poziomu, co ułatwia przenoszenie systemu operacyjnego na inne komputery;
-był tani — uniwersytety mogły uzyskać licencje systemu UNIX w cenie nośnika — taśmy, na której był nagrany.
Już pierwsze wersje systemu dawały duże możliwości, takie, jakie miały wówczas tylko systemy operacyjne pracujące na znacznie droższych maszynach.
Powyższe zalety przeważały nad ówczesnymi wadami systemu, takimi jak:
-System nie miał opieki informatycznej — AT&T przeznaczył tak znaczne środki na system MULTICS, że nie był już zainteresowany rozwijaniem systemu operacyjnego UNIX.
-Miał wiele błędów, a ponieważ nie było opieki informatycznej, nie było tez pewności, ze błędy te zostaną usunięte.
-Niewiele lub tez wcale nie było dokumentacji — jednakże zawsze można było sięgnąć do wersji źródłowej.
1.2 Własności systemu UNIX
System UNIX obejmuje system operacyjny pracujący z podziałem czasu (ang. time sharing operating system), zarządzający pracą oraz zasobami komputera, a także interakcyjne, elastyczne środowisko operacyjne. Został tak zaprojektowany, aby równolegle mogło pracować wiele procesów, oraz aby zapewnić obsługę wielu użytkowników, umożliwiając wspólne korzystanie z danych. Środowisko operacyjne zostało zaprojektowane w architekturze modułowej na wszystkich poziomach. Podczas procesu instalacji systemu UNIX można włączyć tylko wybrane elementy, odpowiednie do potrzeb użytkowników, pomijając pozostałe elementy. Dla przykładu, system UNIX zawiera duży zestaw programów użytkowych służących opracowywaniu oprogramowania. W przypadku, gdy użytkownicy nie będą opracowywać oprogramowania, wystarczy zainstalować kompilator w minimalnej konfiguracji, bez tych programów. Interfejs użytkownika jest również zgodny z filozofią modułową. Polecenia zupełnie niezależne od siebie mogą być w prosty sposób łączone przez potoki (ang. pipes), umożliwiając wykonanie złożonych operacji.
1.2.1 System operacyjny
Właściwym systemem operacyjnym jest jądro (ang. kernel). Jest ono odpowiedzialne za zarządzanie zasobami i dostęp do elementów sprzętu komputerowego. Jądro zawiera odpowiednie moduły dla każdego elementu sprzętu komputerowego, z którym współpracuje. Moduły te umożliwiają oprogramowaniu dostęp do jednostki centralnej, pamięci, dysków, terminali, sieci komputerowej i innych. Przy instalacji nowych elementów sprzętu komputerowego nowe moduły mogą być włączone w skład jądra.
1.2.2 Środowisko operacyjne
Narzędzia i aplikacje
Modułowa struktura środowiska systemu UNIX jest najbardziej widoczna w warstwie narzędzi aplikacji. Filozofia poleceń systemu jest następująca: każde pojedyncze polecenie wykonuje dobrze jedna czynność, natomiast zastaw poleceń tworzy pewien komplet narzędzi. W celu wykonania konkretnego zadania dobiera się odpowiednie narzędzie lub narzędzia, a złożone zadania mogą być wykonane przez odpowiednie złożenie narzędzi. Od samego początku zastaw „narzędzi” systemu UNIX zawierał znacznie więcej niż tylko proste polecenia, służące porozumiewaniu się z systemem. UNIX zapewnia również cały szereg programów użytkowych jak np. pocztę elektroniczna (mail, mailx), edycje plików (ed,ex,vi),przetwarzanie tekstów (sort, grep, wc, awk, sed), formatowanie tekstów (nroff), opracowywanie oprogramowania (cc, make, lint, lex), zarządzanie wersjami kodu źródłowego programów(SCCS, RCS), komunikacje miedzy systemami (uucp, ftp, telnet), monitorowanie stanu procesowi kont użytkowników (ps, du, acctom). Interakcyjna, programowalna, modułowa implementacja środowiska użytkownika systemu sprawia, ze nowe programy użytkowe mogą być łatwo przygotowane i dołączone do zestawu narzędzi użytkownika, a narzędzia które nie są niezbędne, mogą być pominięte bez zakłócenia pracy systemu.
Duża popularność systemu UNIX można głównie przypisać:
-kompletności i elastyczności systemu, pozwalającym dopasować go do wielu środowisk aplikacyjnych;
-licznym programom użytkowym, zawartym w środowisku operacyjnym, podnoszącym produktywność użytkowników;
-dostępności systemu na wielu platformach sprzętowych i jego przenaszalności.
Program Shell
Program Shell jest interakcyjnym interpretatorem poleceń. Polecenia wprowadzane są po znaku gotowości programu Shell i wykonywane bezpośrednio po ich wydaniu. Użytkownik komunikuje się z komputerem właśnie za pomocą programu Shell. Program Shell przyjmuje na wejściu to, co użytkownik wprowadzi z klawiatury i sprowadza polecanie użytkownika do takiej postaci, która może być zrozumiana przez jądro systemu. Wtedy system wykonuje polecenie. Należy zauważyć, ze program Shell jest nie zależny od jądra systemu. Jeśli użytkownikowi nie odpowiada interfejs dostarczonego programu Shell, program ten może być łatwo zastąpiony innym programem Shell. Obecnie dostępnych jest wiele programów Shell. Niektóre wykorzystują system poleceń, ale są również takie, które zapewniają interfejs w formie menu.
1.2.3 Hierarchiczny system plików
Informacja przechowywana jest na dysku w pojemnikach zwanych plikami (ang. files). Każdy plik ma przyporządkowaną nazwę. Użytkownik posługując się ta nazwą może uzyskać dostęp do określonego pliku. Pliki zawierają zwykle dane, teksty programów, itd. System zawiera setki plików. W związku z tym istnieje w nim także inny rodzaj pojemnika zwany katalogiem (ang.directory), który pozwala użytkownikom organizować ich pliki w logiczne grupy. W UNIX-sie katalog może być wykorzystany do grupowania plików lub innych katalogów. Struktura systemu plików jest bardzo elastyczna.
1.2.3 Wielozadaniowość(multi tasking)
W systemie UNIX kilka zadań może być wykonywanych w tym samym czasie. Jeden użytkownik z pojedynczego terminala może wykonać kilka programów i będzie to wyglądało tak, jakby programy te działały jednocześnie. Oznacza to na przykład, ze użytkownik może edytować pewien plik z tekstem, podczas gdy inny plik jest formatowany, a jeszcze inny drukowany. W rzeczywistości jednostka centralna może wykonywać tylko jedno zadanie w danej chwili, ale system operacyjny ma zdolność dzielenia czasu jednostki centralnej miedzy wiele procesów skierowanych do wykonania w tym samym czasie. Z punktu widzenia użytkownika wygląda to tak, jakby wszystkie programy były wykonywane równocześnie.
1.2.5 Wielodostępność(multi user)
Wielodostępność oznacza, ze więcej niż jeden użytkownik może rozpocząć prace i użytkować system w tym samym czasie. Wiele terminali lub x-terminali może być dołączonych do tego samego komputera. Jest to naturalne rozszerzenie wielozadaniowości. Jeśli system może wykonywać wiele programów równocześnie, to niektóre z tych programów mogą obsługiwać sesje innych użytkowników. Dodatkowo, jeden użytkownik może w tym samym systemie komputerowym rozpocząć prace wiele razy z różnych terminali. Dużą zaletą takiej architektury jest możliwość dostępu wielu członków grupy roboczej do tych samych danych w tym samym czasie.
1.3 Krótka historia UNIXa
W 1965 roku podjęto próby skonstruowania systemu operacyjnego MULTICS (MULTI-plexed lnformation and Computing System) przez: Bell Telephone Laboratories, General Electric Company oraz Massachusetts Institute of Technology.
Założenia tego systemu:
- równoczesny dostęp do komputera dużej ilości użytkowników;
- dostarczenie im znacznej mocy obliczeniowej i dużej pamięci operacyjnej;
- wygodne współdzielenie danych.
Pierwsza wersja systemu działała przed 1969 rokiem.
W 1969 r. zaprzestano prace nad Multicsem — implementacja nie spełniała oczekiwań. Ken Thomson i Denis Ritchie w Laboratoriach Bella naszkicowali zarys systemu wzorowanego na Multicsie. Chcąc stworzyć grę komputerową przenieśli swój projekt na popularny wówczas komputer DEC PDP-7. Ich współpracownik z laboratorium Brian Kernighan ochrzcił nowy system nazwa UNIX (później oznaczony jako wersja 1 ).
W 1971 r. zaimplementowano UNIX na bardzo popularnym wówczas DEC PDP-11. Była to pierwsza wersja, która opuściła laboratorium (start UNIXa, wg. funkcji dostarczanych przez system nastąpił 1 stycznia 1970 roku — patrz ctime(3)).
Rozmiar systemu:
- system: 16 KB;
- programy użytkowe: 8 KB;
- dysk twardy: 512 KB;
- pliki do 64 KB.
1972 Denis Ritchie stworzył język C.- używanych jest 10 systemów UNIX powstaje druga edycja systemu.
1973 Przepisano UNIXa z asemblera na C.- 25 instalacji UNIXa Czwarta edycja systemu. System jest po raz pierwszy, nieoficjalnie dystrybuowany na uniwersytetach.
1974 pierwsza poważna wzmianka o UNIXie w prasie fachowej (Communications of ACM- UNIX wersja 6).Piąta edycja — System jest oficjalnie dostępny dla uniwersytetów, ale tylko do celów edukacyjnych
1975 Szósta edycja — Licencje systemu są dostępne dla instytucji rządowych i komercyjnych.
1977 - ok. 500 instalacji UNIXa, w tym 125 na uniwersytetach. Pierwsza firma zaczyna handlować UNIXem.
1978 — Siódma edycja.
1982 w Bell Laboratories połączono kilka wersji UNIXa powstałego w AT&T w jeden system komercyjnie znany jako UNIX System III.
1983 Bell Labs. dodało do Systemu III kilka udogodnień - powstał UNIX System V (co się stało z Systemem IV?). AT&T oficjalnie zaczęło wspierać UNIX System V. Pracownicy Uniwersytetu Kalifornijskiego w Berkeley dodali ciekawe udogodnienia i zaimplementowali je na komputery VAX - powstał BSD (Berkeley Software Distribution) UNIX 4.3.
1984 ok. 100.000 instalacji od mikrokomputerów do dużych maszyn.
þ 1985 System V Interface Definition (SVID) — określa standardowe funkcje systemowe
(ang. system calls).
1989 System V Release 4 (SVR4) — system zgodny z POSIX.l.
1.4 Powody popularności
Główne powody popularności systemu UNIX to:
system jest napisany w języku wysokiego poziomu, co ułatwia przenoszenie na nowe platformy sprzętowe;
prosty interfejs użytkownika (zarówno tekstowy, jak i graficzny);
wygodny system plików;
dostępność wielu narzędzi;
wielodostępność i wieloprocesowość;
stabilność i bezpieczeństwo w użytkowaniu systemu.
1.5 System UNIX i standardy
System otwarty — modne jest ostatnio to hasło — najogólniej jest to system komputerowy pozostający w zgodzie z uznanymi międzynarodowymi standardami i współpracujący z innymi produktami spełniającymi wymagania tych standardów. W takim systemie pochodzące z różnych źródeł: sprzęt, oprogramowanie, oraz systemy operacyjne mogłyby ze sobą dowolnie współpracować na przykład w sieci komputerowej. Dzięki standaryzacji wszelkich połączeń miedzy nimi.
Otwarty system operacyjny — system prawdziwie przenośny i uniwersalny bez bliższych związków z jakakolwiek architekturą, czy konfiguracją o precyzyjnie zdefiniowanych i znormalizowanych regułach współpracy z oprogramowaniem użytkowym i użytkownikami. Proces rozwoju takiego systemu również powinien być otwarty, a więc jawny i oparty na współpracy wszystkich zainteresowanych. UNIX dzisiaj jest najbliższy pod tymi względami ideałowi.
Filozofia systemów otwartych, czy otwartych systemów operacyjnych nie jest czymś nowym. Użytkowników i programistów zawsze pociągały takie idee, nie miało to jednak większego wpływu na strategię rynkową producentów.
Możliwość łatwego przenoszenia aplikacji była jednym z celów rozwoju systemu UNIX od samych jego początków. Ponieważ większość systemu operacyjnego i programów użytkowych została napisana w języku C, a nie w asemblerze, system UNIX nie był ograniczony do jednego tylko procesora, czy tez jednej platformy sprzętowej. Z drugiej strony, ponieważ system UNIX jest napisany w języku wysokiego poziomu, łatwo go modyfikować. Świadczy o tym ponad 100 firm oferujących implementacje zbudowane na podstawie systemu UNIX, licencjonowane przez AT&T, oraz wiele implementacji naśladujących system UNIX, które nie wymagają licencji AT&T. Chociaż większość implementacji wywodzi się z systemu UNIX firmy AT&T, systemu UNIX BSD lub z ich kombinacji, każda implementacja może zawierać pewne szczególne rozszerzenia systemu operacyjnego, takie jak np. przetwarzanie w czasie rzeczywistym, które mogą zniweczyć zgodność różnych implementacji systemu. Obecnie system V zawiera już w sobie wiele popularnych własności systemu BSD.
W celu zapewnienia zgodności różnych implementacji, formułuje się standardy środowiska systemu operacyjnego UNIX. Celem tych standardów jest promowanie:
-Przenaszalności — możliwości łatwego przenoszenia aplikacji z jednej implementacji systemu na inną.
-Współpracy aplikacji — możliwości wymiany informacji miedzy aplikacjami pracującymi na różnych implementacjach systemu.
-Skalowalności — oferowania całej rodziny opcji sprzętowych, poczynając od małych do dużych systemów, po to, by użytkownik mógł wybrać z tej oferty opcje odpowiednia do jego potrzeb aplikacyjnych. Ponadto — możliwości elastycznej rozbudowy sprzętu i powiększania jego możliwości wraz z rozbudowa aplikacji.
1.5.1 Cele organizacji standaryzujących(normalizujących)
Określić interfejs, a nie implementacje
Standardy nie mają na celu zbudowanie zupełnie nowego interfejsu, ale określenie interfejsu na podstawie już istniejących implementacji systemu UNIX; takiego, który byłby dobrze zdefiniowany i przenaszalny. Istotne jest zrozumienie, ze standardy maja na celu określenie interfejsów środowiska systemu operacyjnego UNIX, a nie tego jak standard powinien być zaimplementowany. Standardy systemu UNIX nie wymagają, aby wszystkie komputery z systemem UNIX były swoimi kopiami, ale raczej, aby wszystkie zapewniły pewien wspólny zestaw funkcji. Konkretna implementacja powinna natomiast spełnić te funkcje. Dobrym przykładem jest samochód. Podstawowy interfejs określony przez „standard” samochodu jest następujący:
Jazda — naciśnij pedał gazu.
Stop — naciśnij pedał hamulca.
Zmiana kierunku — obróć kołem kierownicy.
Uruchomienie silnika — przekręć kluczyk stacyjki.
Samochody spełniające standard tego interfejsu, mogą być budowane na wiele różnych sposobów. Samochód może mieć, na przykład, silnik elektryczny albo silnik benzynowy, ale naciśniecie pedału gazu w obu przypadkach spowoduje jazdę samochodu. Zgodnie z powyższa filozofia również inne systemy operacyjne będą zgodne ze standardem, jeśli tylko spełnia warunki opisanego interfejsu.
Modułowość
Środowisko obliczeniowe ciągle się zmienia i rozwija. Standardy powinny być rozszerzalne. Powinny być możliwe do zachowania, mimo rozwoju technologii i rosnących wymagań użytkowników. Standardy są określane w sposób modułowy, tak ze pewne moduły mogą być dodane lub ewentualnie wymienione, gdy pojawia się potrzeba lepszego interfejsu.
Praca z systemem
Rozdział ten przedstawia w sposób systematyczny podstawowe wiadomości dotyczące koncepcji i pojęć systemu UNIX. Opisuje jak rozpocząć prace z systemem, oraz podstawowe, najczęściej używane polecenia systemu.
2.1 Sesja przy terminalu
Przed rozpoczęciem pierwszej sesji, należy udać się do lokalnego administratora UNIXa, aby założył konto dla nowego użytkownika w systemie.
2.1.1 Rozpoczęcie sesji
Sesja jest określeniem pracy z systemem, od chwili zgłoszenia się do niego (log in), do momentu jej zakończenia (log out). Komunikat ten nazywa się monitem systemowym (system login prompt). System oczekuje wówczas na wpisanie nazwy użytkownika. Tradycja nakazuje stosować małe litery, bez względu na rangę i powagę przezwanego w ten sposób użytkownika. Następnie pojawia się pytanie o hasło (password), wprowadzane hasło nie jest wyświetlane na ekranie. Po zaakceptowaniu „danych osobowych” system jest gotowy do pracy, co sygnalizuje znak zachęty (monit, prompt), oznaczony zwykle przez $ lub %, znak zachęty administratora systemu to tradycyjnie #. Bardzo często na początku sesji są wyświetlane komunikaty pochodzące od administracji systemu. Należy mieć na względzie, ze postać pojawiających się napisów, komunikatów, znaków zachęty, itp. nie jest ustalona raz na zawsze i może być modyfikowana przez administratorów, bądź w pewnym zakresie przez samego użytkownika.
2.1.2 Zakończenie pracy
W większości współczesnych instalacji sesje UNIXową kończy wykonanie komendy logout, lub exit. Tradycyjnie do zakończenia sesji można tez użyć kombinacji klawiszy Ctrl-D, choć niejednokrotnie instalacja zabrania tego, aby uniknąć przypadkowego wyjścia z systemu. Znak Ctrl-D jest w ogólności znakiem końca pliku, a więc łatwo o pomyłkę.
2.1.4 Klawiatura
Omawiając klawiaturę terminala należy pamiętać, ze UNIX rozróżnia małe i duże litery, generalnie preferowane są litery małe.
Należy pamiętać, ze klawiszy sterujących używa się poprzez wciśniecie klawisza Ctrl i przytrzymując go wciśnięcia wybranego klawisza. Odmiennie jest z klawiszami używanymi wraz z klawiszem Esc. Klawisze z tej grupy używa się wciskając i zwalniając klawisz Esc, a następnie wybrany klawisz.
2.2 Pliki i katalogi - operacje podstawowe
Rozdział ten wprowadza podstawowe koncepcje dotyczące plików. Podaje czym jest pełna i skrócona nazwa pliku, co rozumiane jest przez katalog domowy. Prezentowane są również komendy pozwalające manipulować plikami.
2.2.1Czym jest plik
Plik jest podstawowym elementem w systemie UNIX. Prawie wszystko traktowane jest jako plik, włączając:
-dokumenty - pliki zawierające tekst, takie jak listy, pliki ze źródłami programów lub wszystko inne co użytkownik może napisać;
-komendy - wiele komend jest plikami wykonywalnymi, tzn. plikami które można uruchamiać, np. pisząc komendę lynx można przeglądać tekstowo WWW, jest to plik wykonywalny;
-urządzenia - system operacyjny traktuje terminal, drukarkę, czy urządzenie dyskowe jako plik;
katalogi - katalogi są plikami zawierającymi inne pliki, w dalszej części niniejszej publikacji zostanie to dokładniej wyjaśnione.
2.2.2 System plików
System plików w UNIXie ma hierarchiczną strukturę drzewiastą. Korzeń tego drzewa (root ) oznaczony jest symbolem /. Węzłami drzewa są katalogi, mogące zawierać dalsze pliki i katalogi. Pełna nazwa pliku (absolute pathname) jest drogą prowadzącą do tego pliku od korzenia, czyli tzw. ścieżka dostępu. I tak dla pliku who ścieżka dostępu jest /bin/who. Jeżeli używane nazwy nie rozpoczynają się od symbolu /, są nazwami relatywnymi (relative pathname), używanymi w bieżącym katalogu.
2.2.3 Metaznaki - maskowanie nazw
Mówiąc o nazwach plików, warto wspomnieć o niektórych znakach specjalnych, zwanych metaznakami. Nazwy zapisane z użyciem metaznaków określa się mianem nazw uogólniających lub wzorców nazw. W UNIXie dostępne są następujące metaznaki:
*(gwiazdka) - oznacza dowolny ciąg znaków, być może pusty;
?(znak zapytania) - dowolny pojedynczy znak;
[... ] (grupa znaków w nawiasach kwadratowych) - oznacza dowolny pojedynczy znak spośród wymienionych w nawiasie.
Wszystkie te operacje kopiują pliki z katalogu bieżącego do katalogu /tmp. W pierwszym przykładzie kopiowane są pliki zaczynające się od a i kończące literą b. Drugie polecenie kopiuje pliki, których trzecim znakiem jest x, a nazwa pliku musi być co najmniej trzyznakowa. W trzecim przykładzie kopiowane są pliki ala i Ala, o ile takie istnieją. Następny przykład kopiuje wszystkie trzyznakowe pliki. Ostatni przykład ilustruje możliwość wykorzystania [] w nazwach plików, nie tylko do prostego wyliczania znaków, lecz także do określania zakresów liter rozdzielonych kreską -. Zakresy należy rozumieć jako spójne grupy wyznaczone przez porządek znaków w kodzie danej maszyny (zazwyczaj jest to kod ASCII ). Metaznaki mogą być stosowane w każdym poleceniu UNIXa, gdzie podaje się nazwy plików. Można uniknąć ich specjalnego znaczenia stosując znak \ (backslash) działający dla pojedynczego znaku lub znaki apostrofu ' bądź cudzysłowu " dla całego ciągu. Rozsądnie jest jednak unikać takich sytuacji.
Należy być ostrożnym w stosowaniu metaznaków, np. sama gwiazdka oznacza właściwie dowolna nazwę, skutki wydania polecenia rm * mogą być więc tragiczne. Jedynym ograniczeniem nałożonym na * jest to, ze „nie pasuje” ona do kropki, jeśli ta jest pierwszym znakiem nazwy (w ten sposób oznaczane są pliki ukryte).
2.2.4 Katalogi
Każdy katalog zawiera dwie stałe pozycje o nazwach:
.(kropka) — oznaczenie . odnosi się do niego samego, czyli jest synonimem pojęcia „katalog bieżący”;
.. (dwie kropki) — oznacza katalog nadrzędny wobec bieżącego.
Często stosuje się polecenia z użyciem tych nazw, np.:
cd ..
cp /home/jarek/teksty/* .
Pliki znajdują się w katalogach, które tworzą prawie drzewiastą strukturę systemu plików. Określenie „prawie drzewiasta” jest zupełnie uzasadnione, albowiem w UNIXie mogą istnieć dowiązania (linki ) pliku do różnych katalogów. Niemniej mechanizm ten jest wykorzystywany dość oszczędnie i dlatego pozostanę przy określeniach typu „drzewo katalogów i plików”. Dowiązania tworzy się poleceniem ln. UNIX jest systemem przeznaczonym dla wielu użytkowników, stad bierze się konieczność zadbania o to, by każdy miał w systemie plików swoje miejsce. Każdy użytkownik dostaje katalog domowy (home directory), jest to katalog w którym jego właściciel może wykonywać wszelkie operacje plikowe. Do szybkiego odwoływania się do katalogu domowego wykorzystuje się znak ~ (tylda)
2.2.5 Manipulowanie plikami i katalogami
cd-zmiana katalogu
pwd-podaje bieżącą ścieżkę
mkdir-tworzy katalog
rmdir-usuwa katalog
ls-podaje zawartość katalogu
file-podaje typ pliku
more-wyświetla zawartość pliku
cat-wyświetla zawartość pliku, pozwala łączyć pliki
cp-kopiuje pliki
rcp-kopiuje pliki ze i do zdalnego hosta
mv-przenosi pliki
rm-usuwa pliki
touch-tworzy plik lub uaktualnia daty dostępu i ostatniej modyfikacji pliku
chmod-zmienia prawa dostępu
chown-zmienia właściciela pliku
chgrp-zmienia identyfikator grupy, która może mięć dostęp do pliku
umask-ustawia domniemane prawa dostępu do plików
head-wyświetla początkowe wiersze pliku
tail-wyświetla końcowe wiersze pliku
find-wyszukuje pliki
grep-wyszukuje w tekście podanego wzorca
tar-archiwizuje pliki
compress-pakuje lub rozpakowuje pliki
diff-porównuje dwa pliki
wc-podaje ilość znaków, słów i linii w podanym tekście
mount-dołącza (montuje) nowy system plików
umount-odłącza (odmontowuje) system plików
df-podaje ilość miejsca w systemie plików
du-określa ile miejsca zajmują dane
dd-służy do konwersji i archiwizacji plików
ln-tworzy nowe dowiązanie (link) do istniejącego pliku
2.2.6 Prawa dostępu
Prawa dostępu determinują możliwości wykorzystania pliku lub katalogu przez użytkownika systemu. Następujące prawa skojarzone są z plikami i katalogami:
Prawo czytania (r)
Plik — można podglądać i kopiować.
Katalog — można oglądać jego zawartość.
Prawo pisania (w)
Plik — można modyfikować, usuwać i zmieniać jego nazwę.
Katalog — można dodawać i usuwać pliki.
Prawo wykonywania (x)
Plik — można uruchamiać (dotyczy to tylko programów wykonywalnych i skryptów) .
Katalog — można przeszukiwać, oglądać jego zawartość, tworzyć w nim pliki i je usuwać.
Polecenie chmod
Do zmiany praw dostępu służy polecenie chmod (change mode). Składnia polecenia jest następująca:
chmod who op prmission filename
gdzie:
who — oznacza użytkowników, którym prawa maja zostać nadane/odebrane: (a) wszystkim,
(u) właścicielowi, (g) grupie, (o) pozostałym użytkownikom;
op — operacja jaka użytkownik chce wykonać, tzn.: (=) nadanie, (+) dodanie lub (-)
odebranie praw dostępu;
permission — prawa: (r) czytania, (w) pisania i (x) wykonywania pliku lub przeszukiwania katalogu;
filename — nazwa pliku, którego polecenie dotyczy.
Można używać polecenia chmod z trzycyfrową wartością praw dostępu:
chmod ugo filename
Kazda z cyfr odpowiada prawom dostępu dla poszczególnej grupy użytkowników: (u) właściciel, (g) grupa, (o) pozostali użytkownicy i może przyjmować wartości.
Polecenie umask
Podczas tworzenia nowego pliku lub katalogu system operacyjny nada mu automatycznie domyślne prawa dostępu. Zmianę domyślnych ustawień praw dostępu dla bieżącej sesji dokonuje się poleceniem:
umask ugo
ugo —oznacza trzycyfrowa wartość praw domyślnych. Każda z cyfr odpowiada prawom dostępu dla poszczególnej grupy użytkowników: (u) właściciel, (g) grupa, (o) pozostali użytkownicy i może przyjmować wartości.
Przykład 2.2.11
Przykład użycia polecenia umask.
umask 2 oznacza domyślne prawa dostępu dla plików -rw-rw-r--
umask 002 oraz domyślne prawa dostępu dla katalogów drwxrwxr-x.
2.3 Procesy, strumienie i potoki
UNIX jest systemem wielodostępnym o wielu zasobach. W każdym momencie z jednego komputera UNIXowego (tzw. hosta) może korzystać wielu użytkowników, a każdy użytkownik może uruchomić więcej niż jeden proces. Procesem nazywamy załadowany do pamięci, działający w danej chwili program, wraz z jego danymi i środowiskiem. Pojecie współbieżnego wykonywania wielu procesów jest umowne i oznacza dla maszyn jednoprocesorowych podział czasu pomiędzy wykonywane procesy. Niemniej, z punktu widzenia użytkownika nie ma specjalnej różnicy miedzy prawdziwa równoległością a tak rozumiana współbieżnością. Każdy proces w systemie ma swój numer, unikatowy w skali danego komputera. Numer ten, zwany identyfikatorem procesu, oznaczany jest skrótem PID (process identyfication number, lub process ID). Każdy proces ma swojego właściciela i zazwyczaj jest to użytkownik, który dany proces uruchomił. Polecenie ps (process status) pozwala śledzić wykonywane procesy w systemie.
2.3.1Przeadresowanie wejścia i wyjścia
Proces w momencie uruchomienia ma otwarte trzy pliki, są to:
stdin — standardowy plik wejściowy, zazwyczaj powiązany z klawiatura;
stdout — standardowy plik wyjściowy, zwykle ekran;
stderr — standardowy plik diagnostyczny, również połączony z ekranem.
Użytkownicy mogą przeadresowywać (zmieniać) te pliki, np.:
sort < who > pracujacy.srt
Przeadresowanie wejścia (<)
Polecenie < plik
Uruchamia polecenie z wejściem ze wskazanego pliku (plik ) zamiast z klawiatury.
Przeadresowanie wyjścia (>lub>>)
Polecenie > plik
Uruchamia polecenie z wyjściem do wskazanego pliku (plik ) zamiast na ekran.
Polecenie >> plik
Uruchamia polecenie z wyjściem do wskazanego pliku (plik ) zamiast na ekran, wyniki polecenia dopisywane będą do już istniejących w pliku.
Przeadresowanie wyjścia diagnostycznego( >& lub 2>)
Polecenie >& plik
lub
Polecenie 2> plik
Uruchamia polecenie ze skierowaniem wyjścia diagnostycznego do wskazanego pliku (plik ).
Przykład 2.3.12
Rozdzielenie standardowego wyjścia (plik outfile i standardowego wyjścia diagnostycznego (plik errorfile):
(polecenie > outfile) >& errorfile
Przykład 2.3.13
Wykonanie polecenia bez zachowania wyników i danych o błędach:
Polecenie >& /dev/null
Należy uważać, używając przekierowania >, gdyż jeśli podany plik istnieje, system najpierw go usunie, a następnie uruchomi polecenie. Można zabezpieczyć się przed tym za pomocą zmiennej środowiska noclobber. Wówczas aby zapisać dane wyjściowe w miejscu istniejących należy wydać polecenie:
polecenie >! Plik
2.3.2 Potoki
W UNIXie istnieje mechanizm pozwalający na przekazywanie danych wyjściowych jednego procesu na wejście innego. Można oczywiście użyć do tego mechanizmu przeadresowywania:
proces1 > dane
proces2 < dane
Ale ten sposób tworzy zupełnie niepotrzebny plik z przekazywanymi danymi. Efektywnym rozwiązaniem tego problemu jest mechanizm potoków.
Operator |
Potok jest połączeniem typu producent-konsument. System zapewnia synchronizacje procesów w potoku. Potok zapisuje się następująco:
Polecenie1 | Polecenie2
np.: who|sort|lpr
Pewien rodzaj programów jest bardzo często wykorzystywany w potokach, są to filtry, programy czytające dane z wejścia, obrabiające je i wysyłające na wyjście. Najpopularniejsze z nich to more i grep.
Operator |&
Operator |& umożliwia połączenie stdout i stderr jednego procesu do stdin innego, np.:
make |& lpr
Polecenie tee
Przydatnym poleceniem służącym do rozwidlania potoków jest tee. Polecenie to czyta dane ze standardowego wejścia i przepisuje je na standardowe wyjście, jednocześnie zapisując je do wskazanego pliku. Polecenie to zazwyczaj używane jest do podglądania tego, co dzieje się w potoku.
Polecenie1 | tee plik | polecenie2
Dane pochodzące z polecenia1, zostaną przekazane do polecenia2 oraz zapisane zostaną w pliku plik.
Przykład 2.3.15
Wyjście z programu make wysyłane jest na ekran i na drukarkę:
make |& tee /dev/tty | lpr
Tabela 2.7 Symbole przekierowania strumieni i symbole potoków
<-pobieranie strumienia wejściowego ze wskazanego pliku
>-przekierowanie strumienia wyjściowego do wskazanego pliku
>!-jw., nadpisanie pliku, jeśli istnieje
>&-przekierowanie strumienia diagnostycznego do wskazanego pliku
>>-przekierowanie strumienia wyjściowego i dopisywanie danych wyjściowych na końcu wskazanego pliku
>>!-jw., nadpisanie pliku, jeśli istnieje
>>&-dołączanie danych z wyjścia diagnostycznego do wskazanego pliku
|-potok łączący wyjście jednego procesu z wejściem innego
|&-jw., dołączając wyjście diagnostyczne do potoku
2.2.3 Sterowanie procesami (job control)
UNIX jest środowiskiem wielozadaniowym. Użytkownicy mogą korzystać z tej cechy wykonując w systemie równocześnie wiele zadań — procesów. Dostępne są mechanizmy pozwalające sterować procesami, tzn. wykonywać dany proces jako pierwszoplanowy lub jako proces wykonywany w tle. System dostarcza wielu poleceń, które umożliwiają kontrole nad uruchomionymi procesami: ps, bg, fg, jobs, kill, stop, suspend, wait.
Wykonanie poleceń w tle - operator &
Normalnie po wydaniu polecenia powłoka (Shell ) rozpoczyna wykonywanie następnego polecenia dopiero po zakończeniu procesu rozpoczętego w wyniku wykonywania bieżącego polecenie. Istnieje możliwość rozpoczęcia procesu jako procesu drugoplanowego (tzw. w tle), jeżeli polecenie zakończy się znakiem &:
polecenie argumenty &
Powłoka wyprowadzi przydzielony takiemu procesowi numer PID na standardowe wyjście i znakiem zachęty zgłosi gotowość do przyjmowania dalszych poleceń.
Przykład 2.3.16
Rozpoczęcie sortowania pliku plik jako procesu drugoplanowego. Po uruchomieniu PID procesu wynosi 171, numer zadania, tzn. numer procesu wykonywanego w tle w bieżącej sesji wynosi 1.
$ sort plik >plik posortowany &
[1] 171
$ _
Polecenie ps
Do oglądania jakie procesy są aktualnie uruchomione w systemie służy polecenie ps. Pokazuje ono PID procesu, terminal z jakiego został dany proces uruchomiony, aktualny status procesu (lub stan), oraz czas działania.
Polecenie kill
Polecenie kill służy do zakończenia procesu, nad którym użytkownik stracił kontrolę, uruchomił z innego terminala lub z innego okna. Aby „zabić” proces należy znać jego PID. Użycie polecenia kill:
kill numer_PID
Jeśli powyższe wywołanie polecenia kill nie dało oczekiwanego rezultatu, powinno pomóc wywołanie:
kill -9 numer_PID
Przerwanie działania procesów wykonywanych w tle można dokonać poprzez wywołanie, w którym należy podąć numer zadania (numer procesu wykonywanego w tle dla danej sesji):
kill %numer_zadania
Przełączanie zadań
Użytkownik może zawiesić wykonywanie dowolnego procesu. Wysyłając sygnał przerwania ^Z. Proces zawieszony może zostać wznowiony zarówno jako proces wykonywany w tle (polecenie bg), lub jako proces pierwszoplanowy (polecenie fg).
2.4 Edytor vi
Vi (visual editor ) jest standardowym edytorem w systemie UNIX. To, że jest edytorem tekstu, a nie procesorem tekstu oznacza, ze posiada wiele narzędzi do manipulowania tekstem (wstawianie, usuwanie, przenoszenie, wyszukiwanie, itp.), ale nie posiada np. możliwości zmiany odstępu miedzy liniami czy zmiany kroju czcionek. Edytor ten pracuje w dwóch trybach: trybie komend i trybie edycji. W trybie edycji naciśnięcia klawiszy interpretowane są jako wprowadzane do tekstu znaki i pojawiają się na ekranie. W trybie poleceń naciśnięcia klawiszy interpretowane są jako komendy, które wykonują takie operacje jak kasowanie słów, wstawianie nowej linii, wyszukiwanie wzorca, itp. Większość komend edytora nie ukazuje się na ekranie podczas wprowadzania z klawiatury. Wykonują się one natychmiast po wpisaniu ostatniego znaku komendy, bez naciskania klawisza <enter>. Wyjątek stanowią komendy poprzedzone dwukropkiem. Tylko te wyświetlane są u dołu ekranu i musza być potwierdzone klawiszem enter. Po uruchomieniu edytor znajduje się w trybie poleceń. Kilka poleceń (np. <a>, <i>, <o>) umożliwia przejście do trybu edycji. Klawisz <esc> powoduje powrót do trybu poleceń. Działa on jednak tylko „w jedna stronę” - ponowne naciśniecie <esc> nie spowoduje przejścia do trybu edycji. Spowoduje to albo anulowanie poprzedniego polecenia, albo rozlegnie się dźwięk informujący, ze edytor jest już w trybie poleceń. Ponieważ tryb w jakim znajduje się edytor nie jest sygnalizowany na ekranie, więc takie przypomnienie może być czasami przydatne. Niektóre linie mogą być reprezentowane na ekranie w sposób specjalny. Jeżeli edytor został uruchomiony z pustym buforem, na ekranie znajda się linie, które zaczynają się od znaku ~ (tylda). Oznacza to, ze linie te znajdują się „za końcem” pliku. Aby móc pracować z edytorem vi, należy nauczyć się jak go uruchamiać, jak zmieniać tryb i poznać przynajmniej kilka podstawowych poleceń. Definicje pojęć użytych w dalszej części tego rozdziału:
-bieżąca linia - linia, w której znajduje kursor;
-bufor edytora - bufor przechowujący aktualnie edytowany tekst;
-bufor tymczasowy - bufor przechowujący tekst ostatnio wyrzucony, zmieniony lub kopiowany za pomocą komendy <Y>;
-bufory nazwane - bufory (do 26 równocześnie), oznaczone małymi literami od <a> do <z>, przechowujące ostatnio przesłany tam tekst.
2.4.3 Wprowadzanie tekstu
Po uruchomieniu edytora znajdujesz się w trybie komend, aby rozpocząć wprowadzanie tekstu wciśnij klawisz <i> (przejście w tryb edycji) i można rozpocząć pisanie. Dopóki wprowadzany jest tekst, możną pisać wiele linii (wciskając <enter>, za każdym razem aby przejść do nowej linii), jeśli popełniono drobny błąd można użyć klawisza <backspace>, do poruszania się po tekście można używać klawiszy ze strzałkami. Aby skończyć pisanie i przejść do trybu komend należy wcisnąć <esc>. Używając klawisza <i> można wstawiać tekst przed znakiem wyróżnionym przez kursor (oznaczony jako _ ), aby wstawić tekst za kursor należy użyć klawisza <a>. Klawisz <o> pozwala wstawić nowa linie.
2.4.4 Usuwanie tekstu
Z trybu komend można usuwać poszczególne litery z tekstu używając klawisza <x>, usuwa on literę zaznaczoną kursorem. Po wciśnięciu <x> usunięta zostanie litera <o>. Usunięcia całej linii dokonuje się wciskając dd (dwukrotnie d), usunięta zostanie linia zawierająca kursor. Do usuwania pojedynczego słowa, w którym znajduje się kursor służy kombinacja dw.
Powłoka (Shell)
3.1 Charakterystyka ogólna
Po zarejestrowaniu się w systemie użytkownik ma dostęp do systemu poprzez interpretator poleceń zwany powłoką (ang. shell ). Z tego poziomu może uruchamiać programy aplikacyjne bądź ogólnie dostępne programy systemowe. Powłoka w systemie UNIX zapewnia komunikacje miedzy użytkownikiem a systemem. Jest ona w stosunku do jądra systemu zwykłą aplikacja, często dopasowywaną do indywidualnych potrzeb i preferencji użytkownika. W działającym systemie zazwyczaj dostępnych jest kilka różnych powłok, np. Bourne Shell —sh, Berkeley C Shell —csh, czy bardzo ostatnio popularny Bourne Again Shell —bash. Różnice miedzy nimi polegają głównie na sposobie programowania w języku powłoki, różnią się również możliwościami oferowanych usług i udogodnień związanych z pracą w systemie. Powłoka jest inicjowana automatycznie po zarejestrowaniu się użytkownika w systemie. Skoro jest to zwykła aplikacja, to jej nowy egzemplarz można uruchomić w dowolnej chwili poleceniem np. sh. Powłoka przyjmuje polecenie od użytkownika, dekoduje je i przekazuje do jądra w celu wykonania. Pracę powłoki można przedstawić algorytmiczne jako ciąg następujących czynności:
1. wysłanie znaku gotowości na terminal (zazwyczaj: $, % lub #);
2. przyjęcie polecenia od użytkownika;
3. zdekodowanie polecenia, odszukanie w katalogach odpowiadającego mu programu;
4. przekazanie polecenia do jądra, w celu uruchomienia odpowiedniego procesu (lub procesów)
realizującego polecenie i oczekiwanie na jego zakończenie;
5. przyjęcie odpowiedzi od jądra i przekazanie tych wyników użytkownikowi, powrót do punktu
pierwszego .
Czynności te wykonywane są w pętli aż do napotkania polecenia kończącego działanie powłoki lub odebrania znaku Ctrl-D, oznaczającego koniec pliku (wejściowego). W niniejszej pracy skoncentrowano się głównie na shellu Bourne'a, ponieważ jest to powłoka najczęściej spotykana, część przykładów podano również dla bash'a, gdyż jest to w tej chwili chyba najwygodniejsza powłoka.
3.2 Programowanie w języku powłoki
Powłoka umożliwia konstrukcje bardziej złożonych struktur programowych niż podane we wcześniejszym rozdziale. Są to:
-operowanie zmiennymi;
-tworzenie procedur powłoki (tzw. skryptów);
-używanie struktur sterujących;
-wykorzystywanie poleceń wewnętrznych.
3.2.1 Grupowanie poleceń
Polecenia można zagnieżdżać za pomocą nawiasów okrągłych () lub grupować w nawiasach klamrowych {}. Nawiasy okrągłe służą do grupowania poleceń, które będą wykonywane jako samodzielny proces, np. proces wykonywany w tle, wówczas znak & lub przeadresowujące wejscie/wyjscie są obowiązujące dla całej grupy poleceń. Stan końcowy (exit status) jest wynikiem powstałym po wykonaniu ostatniego polecenia. Nawiasy klamrowe służą do grupowania poleceń wykonywanych w shellu bieżącym. Znaki {} traktowane są przez powłokę jak polecenia, tzn. po nich koniecznie musi wystąpić znak spacji, a ostatnie polecenie w klamrach musi zostać zakończone średnikiem. Nawiasy klamrowe mogą również służyć do oddzielenia zmiennej od innych znaków.
Ciąg poleceń zawarty w nawiasach okrągłych jest wykonywany jako samodzielny, podrzędny proces powłoki. Zmiany w otoczeniu powstałe w tym procesie nie będą więc oddziaływały na aktualny proces powłoki. Natomiast polecenie zawarte w nawiasach klamrowych wykonywane jest w aktualnym Shellu. Efekty powstałe poprzez zastosowanie nawiasów () i {} przedstawia przykład 3.2.2.
3.2.2 Zmienne powłoki
Nazwy zmiennych stanowią ciągi znaków zaczynające się od litery i mogą składać się z liter, cyfr i znaków podkreślenia (_). Nazwy zmiennych muszą zawierać co najmniej jeden znak. Przypisanie wartości zmiennej wykonuje się w następujący sposób:
zmienna =wartość
Domyślnym typem zmiennych jest typ napisowy i w zasadzie nie używa się innych typów do określenia zmiennych. Dodatkowo shell bash oferuje komendę typset, za pomocą której można deklarować typ zmiennej. Oto wybrane możliwości tej komendy:
-i - typ integer;
-u - wielkie litery;
-l - małe litery;
-x - zmienna eksportowalna;
-r - tylko do czytania.
Aby odwołać się do wartości zmiennej należy poprzedzić ja znakiem $. Do wyświetlania wartości zmiennych globalnych służy polecenie env, natomiast do wyświetlenia zmiennych lokalnych służy polecenie set. Poleceniem echo można wyświetlić wartości pojedynczych zmiennych. Dodatkowo bash oferuje do wyświetlania zawartości zmiennych polecenie print, a do podstawienia wyniku polecenia jako wartości zmiennej oferuje konstrukcje: $(polecenie).
Zmienne predefiniowane
Powłoka posiada szereg zmiennych o wartościach zdefiniowanych pierwotnie (predefiniowanych). Należą do nich miedzy innymi zmienne PS1 i PS2 (monity systemowe), HOME (nazwa katalogu osobistego), PATH (zbiór katalogów przeszukiwanych przez system operacyjny w celu znalezienia programów) i wiele innych. Wartość tych zmiennych może być zmieniana przez użytkownika. Jeśli pewne zmienne są używane w każdej sesji użytkownika, najwygodniej zapisać je w pliku .profile, który powinien znajdować się w katalogu osobistym użytkownika. Plik ten definiuje prywatne środowisko użytkownika, a jest wykonywany zawsze w momencie uruchomienia sesji. Plik ten może zawierać oprócz definicji zmiennych również polecenia, które będą wykonywane zawsze po zarejestrowaniu się użytkownika.
Zakres ważności zmiennych
Zakresem ważności zmiennych jest standardowo aktualny proces powłoki. Jeżeli zakres ten ma zostać rozszerzony, to zmienna musi zostać przesłana do procesów podrzędnych poleceniem:
export zmienna
Instrukcja readonly
Instrukcja readonly służy do określenia zmiennych, których wartości nie mogą być zmieniane. Zmiennych deklarowanych jako readonly nie można usunąć ze środowiska. Atrybut readonly nie jest przenoszony do shelli potomnych. Wydanie polecenia readonly bez argumentów spowoduje wyświetlenie listy zmiennych z ustawionym atrybutem readonly.
3.3 Substytucja tekstów
Przed wykonaniem polecenia powłoka dokonuje substytucji parametrów, następnie substytucji nazw i dopiero tak rozszerzone argumenty przekazywane są do wykonania wywołanemu poleceniu. Jeśli substytucja parametrów lub substytucji nazw ma nie zostać dokonana, to odpowiedni łańcuch znaków należy ująć w znaki cudzysłowu, wzgl. pojedynczy znak w łańcuchu poprzedzić symbolem lewego ukośnika \. Substytucja tekstów zawartych w apostrofach, cudzysłowach i akcentach w literaturze nosi nazwę mechanizmów klamrowania (ang. quoting mechanisms). Mechanizm ten używa następujących znaków:
\ — lewy ukośnik — zabiera znaczenie występującemu po nim znakowi.
' — apostrof — blokuje znaki specjalne do wystąpienia następnego apostrofu.
" — cudzysłów — blokuje specjalne znaczenie wszystkich znaków poza $ (substytucja zmiennej) i ` (substytucja wyników polecenia).
` — akcent — łańcuch zawarty miedzy dwoma akcentami traktowany jest jak komenda i
jest wykonywany.
Skrypty Shell'a
4.1 Wprowadzenie
Często używane sekwencje poleceń można zapisać w pliku i poddać ten plik powłoce do interpretacji, zamiast wpisywać każdorazowo je z klawiatury. Pliki te nazywane są skryptami shella (shell scripts).
Kilka rad dotyczących pisania skryptów:
Dobrym zwyczajem jest umieszczanie swoich skryptów w katalogu ~/bin.
Pierwsza linia skryptu powinna wyglądać następująco: #!/bin/sh ułatwia to interpretację powłoce, pierwsze dwa znaki oznaczają skrypt, dalej podana jest pełna nazwa programu wraz ze ścieżką, który ma dany skrypt interpretować.
Wiele dobrych przykładów skryptów można znaleźć w katalogach: /bin, /usr/bin, /sbin, ... Ciekawe są również skrypty sekwencji bootujacej umieszczonej w katalogu /etc/rc.d/. Aby zorientować się, które pliki z zawartych w przeszukiwanych katalogach są skryptami shella należy wydąć polecenie: $ file * | grep Bourne Wypisane wówczas zostaną wszystkie pliki zinterpretowane przez polecenie file jako skrypty shella Bournea.
4.2 Podstawy
4.2.1 Uruchamianie skryptów
Skrypty można uruchamiać, wywołując:
$sh skrypt — skrypt wykonywany jest w Shellu potomnym, nie wymagane są prawa wykonania skryptu;
$ skrypt — skrypt wykonywany jest w shellu potomnym, wymagane są prawa wykonania skryptu;
$. skrypt — skrypt wykonywany jest w shellu bieżącym, nie wymagane są prawa wykonania skryptu, nie można w ten sposób uruchamiać programów binarnych, nie można podawać dodatkowych argumentów wywołania;
$ exec skrypt — program zamazuje istniejący proces shella, można w ten sposób wykonywać skomplikowane programy.
Określenie „wykonywanie w shellu potomnym” oznacza, ze przed uruchomieniem skryptu uruchomiony zostanie nowy proces shella, będący potomkiem shella, w którym skrypt został wywołany, w tym nowym shellu skrypt zostanie uruchomiony. Natychmiast po zakończeniu skryptu, zakończony zostanie shell potomny.
4.2.4Zmienne
Odczyt wprowadzanych wartości
Do odczytu wartości zmiennych ze standardowego wejścia służy polecenie read. Odczytuje ona linie ze standardowego wejścia, dzieli ja na słowa i przypisuje je do kolejnych zmiennych dostarczanych jako argumenty. W bash dodatkowo jeśli po słowie read nie jest określona żadna zmienna, to odczytana wartość zostanie przypisana zmiennej REPLY.
Użycie zmiennej REPLAY:
$ read
> moja zmienna
$ echo $REPLAY
moja zmienna
Dodatkowe możliwości konstrukcji zmiennych
Uzyskanie wartości zmiennej powiększonej o dodane dowolne znaki uzyskuje się za pomocą konstrukcji:
$ {zmienna}dodatki
Przekazywane parametrów do skryptu
Parametry pozycyjne służą do przekazywania argumentów z linii komend do skryptu (programu). Bezpośrednio można zaadresować 9 parametrów, od $1 do $9, którym przypisane są argumenty według schematu:
$ polecenie arg1 arg2 arg3 ...
| | | |
$0 $1 $2 $3 ...
Dodatkowo bash pozwala na bezpośrednie zaadresowanie większej ilości parametrów, parametry dwucyfrowe należy podawać w postaci:
$
${nr_parametru}
$polecenie arg1 arg2 arg3 ... arg10 arg11 ...
| | | | | |
$0 $1 $2 $3 ... ${10} ${11} ...
Polecenie shift i set
Polecenie shift pozwala przesuwać parametry pozycyjne w lewo, tzn. pierwszy jest tracony, drugi zostaje pierwszym, trzeci staje się drugim, czwarty trzecim itd. Nie można przesuwać parametrów w prawo. Do przypisania nowych wartości parametrom pozycyjnym służy polecenie set, która jest wykorzystywana również m.in. do odzyskiwania parametrów pozycyjnych, straconych po wykonaniu komendy shift.
Zmienne związane z linią poleceń
Wartość niżej podanych zmiennych jest ustalana przez shell, nie może być ustalana przez użytkownika:
$0 - nazwa wykonywanego polecenia.
$# - liczba argumentów polecenia. Po wykonaniu polecenia shift, zmniejszana jest wartość $#.
$* - wszystkie argumenty z linii polecenia w postaci jednego ciągu znaków.
$@ - wszystkie argumenty z linii polecenia w postaci osobnych ciągów znaków. Zmienna $@, jeśli nie występuje w cudzysłowie, jest identyczna jak zmienna $*.
$$ - numer aktualnie wykonywanego procesu (PID), czyli numer PID skryptu, zmienna ta często jest używana do tworzenia plików tymczasowych, np. tmp=/tmp/moj.$$;
$! - numer procesu (PID) ostatniej komendy wykonywanej w tle;
$? - status wyjścia ostatnio wykonywanej komendy;
Dobrze napisany skrypt weryfikuje liczbę parametrów z jaką jest wywoływany, oto kilka przykładów:
W przypadku zerowej wartości zmiennych należy pamiętać o ujmowaniu ich w cudzysłowy. Parametry i zmienne można usuwać poleceniem unset.
Eksportowanie zmiennych
Eksportowanie zmiennych wykonuje się używając polecenia export:
$ export zmienna
4.2.5 Status wyjścia
W skryptach shella przyjęte jest, że jeśli program kończy się sukcesem wówczas do procesu wywołującego zwracana jest wartość 0, w przeciwnym wypadku zwracana jest wartość różna od zera. Status wyjścia można odczytać korzystając z parametrów $? i $! (rozdział 4.2.4)
Polecenie exit
exit [ n ]
Wyjście ze skryptu ze statusem (np. exit 1). Jeżeli n nie jest podane, status wyjściowy będzie taki, jak ostatnio wykonanego polecenia.
Polecenia true i false
Oba polecenia generują status wyjścia, przy czym true generuje zawsze status wyjścia 0, a
false generuje zawsze status wyjścia 1.
4.2.6 Polecenie test
Służy do testowania wyrażeń numerycznych, plikowych i tekstowych, zwraca 0 (prawda) lub wartość różną od zera (fałsz).
test wyrażenie lub [wyrażenie ]
Należy pamiętać, aby wpisywać spacje po obu stronach wyrażenia, używać cudzysłowów jeśli używa się dodatkowych spacji w wyrażeniu. Możliwe jest grupowanie komend w nawiasach objętych cudzysłowami.
Operatory instrukcji test:
! not
-a and
-o or
Testowanie typów plików:
-s plik plik istnieje i jest niezerowy;
-f plik plik istnieje i jest zwykłym plikiem;
-d plik plik istnieje i jest katalogiem;
-r plik plik istnieje, a wywołujący użytkownik posiada prawo czytania;
-w plik plik istnieje, a wywołujący użytkownik posiada prawo zapisu;
-x plik plik istnieje, a wywołujący użytkownik posiada prawo wykonywania;
p1 -nt p2 plik p1 jest nowszy niz plik p2;
p1 -ot p2 plik p1 jest starszy niz plik p2.
4.3.3 Funkcje w skryptach
Powłoka pozwala definiować nie tylko zmienne, ale i funkcje, które mogą zawierać dowolny ciąg poleceń. Funkcje mogą być wywoływane z argumentami, z taką samą składnią jaka obowiązuje przy przekazywaniu parametrów do skryptu.
Nazwa_funkcji ()
{
polecenia
}
Przykład 4.3.27
Przykład funkcji wczytaj:
wczytaj ()
{
echo -n ${1:-'Podaj nazwę: '}
read nazwa
echo Wybrano ${nazwa:=$2}
}
4.3.4 Reagowanie na sygnały zewnętrzne -
polecenie trep
Poruszona teraz zostanie kwestia reakcji wykonywanego skryptu na działania zewnętrzne. W systemie UNIX użytkownik może wysłać procesowi sygnał np. sygnał przerwania. Można wymagać od skryptu, aby na taki sygnał reagował w określony sposób, np. wyświetlając komunikat. Do obsługi mechanizmu sygnałów w skryptach służy polecenie trap, o składni:
trap [polecenie ][ nr_sygnału ]
Oba parametry są opcjonalne. Jeśli zamiast polecenia podany zostanie pusty argument, np. '', sygnał będzie ignorowany. Natomiast użycie trap bez numeru sygnału spowoduje, ze wybrane polecenie zostanie wywołane po odebraniu dowolnego sygnału. Wadą sygnałów jest miedzy innymi to, ze proces otrzymujący sygnał nie wie, skąd go dostał. Wysłanie dowolnego sygnału do procesu można uzyskać za pomocą polecenia kill:
kill -nr PID gdzie nr oznacza numer wysyłanego sygnału, a PID identyfikuje proces do jakiego sygnał ma zostać wysłany. Poniżej przedstawiony został przykład ukazujący możliwość wykorzystania sygnałów w skryptach powłoki. Jakie może być praktyczne wykorzystanie tych mechanizmów na poziomie języka powłoki, skoro wiadomo, ze ten sam efekt można uzyskać w sposób doskonalszy za pomocą np. języka C. Otóż, może to być przydatne tam, gdzie potrzeba jest nietypowego i szybkiego zastosowania programów już istniejących w systemie, szczególnie w dziedzinie administracji, np. skrypt badający statystykę wykorzystania systemu, generujący raporty w określonych godzinach, oraz na sygnał administratora lub skrypt, który na sygnał przekaże zebrane wyniki.
4.4 Przydatne polecenia i programy
4.4.1 Operacje na liczbach całkowitych - expr
Polecenie expr traktuje zmienną tekstową jako wartość numeryczną i wykonuje na niej operacje. Argumentami polecenia expr mogą być tylko liczby całkowite.
expr argument1 operator argument2
Możliwe operatory (operacje):
+ dodawanie,
- odejmowanie,
* mnożenie,
/ dzielenie,
% modulo,
: porównanie argumentów.
Po obu stronach operatora należy umieścić spacje. Wynik nie będący liczba całkowitą jest obcinany do wartości całkowitej. Aby przypisać wartość wyrażenia zmiennej, komendę expr należy umieścić w akcentach.
Jądro systemu
Jądro stanowi te część systemu Unix, która faktycznie nadzoruje przydział czasu komputera, obszaru pamięci i kanałów komunikacyjnych różnym zadaniom, które mogą być równocześnie wykonywane w danej chwili na rzecz wielu użytkowników. W skład jądra wchodzi centralny program nadzorujący wspierany przez procedury usługowe troszczące się o takie ważne szczegóły, jak pobieranie znaków z klawiatury, współpraca z pamięcią zewnętrzna i nadzór nad zegarem systemowym. Zasadnicza część użytecznych prac w systemie Unix wykonują oddzielne, autonomiczne programy. Większość z nich jest bezpośrednio widoczna dla użytkowników. (chociaż istnieją wyjątki).
5.1 Krótka charakterystyka jądra
System Unix, oglądany z zewnątrz, wygląda tak, jakby się składał z dwóch części: dużego zbioru programów, odpowiadających poleceniom oraz interpretatora, przyjmującego polecenia użytkownika i inicjującego wykonywanie programów. Wewnętrzną część Unixa również można podzielić na dwie składowe: duży zbiór procedur usługowych wykonujących funkcje ściśle związane z obsługą sprzętu i oprogramowania oraz jądro, troszczące się o współprace procedur z pracującymi procesami. Procedury usługowe wywołuje się jedynie wówczas, gdy są potrzebne, większość kodu programu nadzorującego znajduje się natomiast na stałe w pamięci. Program ten tworzy podstawowe środowisko dla wszystkiego, co dzieje się w systemie, a równocześnie zajmuje mało miejsca w pamięci, pozostawiając tyle, ile tylko możliwe procesom użytkowym.
5.1.1 Funkcje jądra
Przeciętny użytkownik komputera nie interesuje się zbyt dogłębnie budową sprzętu, którym posługuje się przy rozwiązywaniu swojego problemu ani szczegółami oprogramowania systemowego. System Unix dostosowano do tego braku zainteresowania umieszczając osłonę shell pomiędzy użytkownikiem a komputerem, aby użytkownik komunikował się z wirtualna maszyna UNIXową, której jedyna widoczna częścią jest właśnie program shell.
Jądro Unixa odgrywa podobną rolę, ale na niższym poziomie: ukrywa ono maszynę fizyczną przed programami i zaawansowanymi użytkownikami, chcącymi od czasu do czasu korzystać z usług niskiego poziomu systemu. Jądro definiuje maszynę wirtualną o własnościach przypominających własności dużego zbioru maszyn fizycznych. Rzeczywiste komputery opisuje się zatem poprzez maszyny wirtualne, wstawiając pomiędzy użytkownikiem i maszyną program. Własności maszyny fizycznej są rzadko bezpośrednio widoczne. Taka struktura pozwala na przenoszenie oprogramowania. Ponieważ nawet shell korzysta z maszyny wirtualnej, można go zainstalować na nowym komputerze wymieniając jedynie programy najbliższe sprzętowi, przekształcające maszynę fizyczną w maszynę wirtualną .
Maszyna wirtualna zdefiniowana przez jądro pełni trzy podstawowe funkcje, które są ściśle związane ze strukturą sprzętu komputerowego:
1. szereguje, koordynuje i zarządza wykonywaniem procesów;
2. zapewnia usługi systemowe, takie jak operacje wejścia - wyjścia i zarządzania plikami;
3. obsługuje wszystkie inne operacje związane ze sprzętem.
5.1.2 Struktura jądra
Istotna część jądra dotyczy zarządzania pamięcią, kolejkowania użytkowników i szeregowania procesów, a także nadzoru nad stosem, rejestrami oraz innymi elementami środowiska podczas ładowania procesu do pamięci. Odpowiada ponadto za przerwania spowodowane np. sprzętowymi błędami pamięci. Tę główną część jądra, napisano w języku C. Korzysta się w niej prawie wyłącznie z maszyny wirtualnej, nie powinno być zatem problemu z przenoszeniem jej na dowolną maszynę, dla której istnieje kompilator języka C. Duża część kodu jądra jest więc identyczna, w różnych wersjach i mutacjach systemów przeznaczonych do pracy na zbliżonych rodzajach komputerów.
Zarządzanie pamięcią i sterowanie wykonaniem procesów wymagają szybkiego reagowania. Dlatego odpowiadające za nie części jądra rezydują na stałe w pamięci. Z kolei procedury usługowe, których jest bardzo dużo, nigdy nie są potrzebne natychmiast, można je więc ładować do pamięci tylko w miarę potrzeby.
Podprogramy obsługi urządzeń, bezpośrednio sięgające do ich rejestrów sterujących, tworzą kolejną istotną część jądra. Obsługują przerwania zgłaszane przez urządzenia zewnętrzne i troszczą się o obsługę błędów. Podprogramy obsługi są całkowicie zależne od sprzętu — w końcu całe ich zadanie polega na przesyłaniu właściwych danych do właściwych rejestrów urządzenia. Większość unixowych podprogramów obsługi napisano w języku C. Trzecia istotna część jądra, a przy tym jedyna, która musi być napisana w asemblerze, stanowi zbiór elementarnych procedur obsługi sprzętu. Tworzą one właściwą maszynę wirtualną. Procedury te wpisują znaki do bufora drukarki, blokują i odblokowują przerwania, czytają informacje z dysku, itp.
5.1.3 Funkcje systemowe
Shell oraz duże części jądra unixowego, zostały napisane w języku wysokiego poziomu. W programach tych, a także w innych tworzonych przez użytkowników, czasami trzeba posłużyć się różnymi operacjami, dla których nie istnieją standardowe instrukcje w języku wysokiego poziomu.
Są to na przykład:
zainicjowanie nowego procesu;
otwarcie pliku z zamiarem czytania;
zapisanie informacji w pliku;
odczytanie zegara systemowego;
zakończenie procesu;
zmiana praw dostępu do pliku.
Na ogół takie operacje zależne od maszyny są z natury proste, a te tworzące maszynę wirtualną zdefiniowana przez jądro Unixa należą do najprostszych. Dostęp do nich uzyskuje się za pomocą funkcji systemowych(ang. System calls), których działanie polega na pobieraniu informacji z systemu, zapisywaniu informacji do rejestrów sprzętowych lub sięganiu do tablic systemowych. Wywołania funkcji systemowych można traktować jako instrukcje, wykonywane przez maszynę wirtualną —można tez powiedzieć, ze to zbiór funkcji stanowi maszynę wirtualną.
Wywołania funkcji systemowych są poleceniami dla jądra. Można odwoływać się do nich z programów pisanych w języku C, Fortranie, Pascalu czy wielu innych językach programowania, dokładnie tak, jakby były zwykłymi funkcjami danego języka.
Lista funkcji systemowych rośnie stosunkowo wolno w miarę poprawiania i rozszerzania Unixa. Niektóre operacje będące wynikiem wywołania funkcji można także wykonać wydając odpowiednie polecenie shellowi. Program wykonujący takie polecenie jest na ogół bardzo krótki i ogranicza się do przeformułowania zadania i przekazania go do jądra w postaci wywołania funkcji systemowej. Naturalnymi przykładami są: chdir, kill, mount, sleep, umask, wszystkie polecenia wykonania prostych operacji na katalogach, tablicach procesów, urządzeniach zewnętrznych i zegarze.
Zainteresowani dokładniejszą budową jądra, oraz algorytmami używanymi do sterowania UNIXem powinni zajrzeć do książki M. J. Bacha [1].
5.2 Zarządzanie procesami
W systemie wielodostępnym równocześnie może wykonywać się wiele programów różnych użytkowników. Ponieważ komputer zazwyczaj jest wyposażony tylko w jeden procesor, więc w rzeczywistości tylko jeden program może działać w danej chwili. Określenie równocześnie w poprzednim zdaniu oznacza, ze wykonywanie programów przeplata się w czasie, a centralny procesor jest przyznawany kolejno każdemu programowi na krótki czas. Mimo to w pamięci, o ile jest ona wystarczająco pojemna, może równocześnie przebywać kilka programów. Jądro odpowiada za sposób wykorzystania przez programy czasu procesora i obszarów pamięci, innymi słowy odpowiada za szeregowanie procesów i zarządzanie pamięcią. To ostatnie obejmuje nie tylko dzielenie dostępnych części pamięci, lecz także decydowanie, czy i kiedy usunąć proces z pamięci na dysk i sprowadzić go z powrotem do pamięci.
5.2.1 Inicjowanie procesu
W systemie Unix rozróżnią się proces od programu, przy czym proces można identyfikować jako program wykonywany w ustalonym środowisku. Przez środowisko rozumie się zbiór otwartych plików, praw dostępu do nich, wartości przypisane zmiennym interpretatora, nazwę właściciela procesu, a także szereg innych danych, niezbędnych do wykonywania programu, ale nie stanowiących jego integralnej części.
Zainicjowanie procesu w systemie Unix jest zawsze wynikiem działania innego procesu — jeden proces uruchamia drugi. Naturalnym efektem takiej koncepcji jest hierarchiczna struktura procesów. Hierarchia ta powstaje dzięki mechanizmowi rozwidlania (ang. fork ). Polega on na tym, ze jądro zastępuje istniejący proces dwoma innymi. Jednym jest oryginalny proces, drugim proces zupełnie nowy. Oryginalny proces nazywa się przodkiem, nowo dodany zaś potomkiem. Potomek zazwyczaj korzysta ze wszystkich plików swego przodka. Po rozwidleniu oba procesy działają niezależnie, chyba ze przodek jawnie zechce czekać na zakończenie pracy potomka. Jeśli potomek (proces 2) musi uruchomić jeszcze jeden proces, może to uczynić również rozwidlając się. Wynikiem są w tym przypadku trzy procesy działające równocześnie. Najnowszy proces (proces 3) jest traktowany jako potomek procesu 2. Będzie on miał dostęp do plików otwartych przez dwa poprzednie procesy, choć one mogą nie mięć dostępu do plików otwartych przez niego. Ogólnie pliki są dostępne dla procesów położonych niżej w hierarchii.
Po rozpoczęciu sesji, jądro uruchamia proces shella, pracujący jako proces użytkownika (np. proces1, rys. 5.1c). Proces 2 powstaje wtedy, gdy użytkownik wydaje pewne polecenie interpretatorowi, proces ten może uruchomić proces 3. Tak więc proces 3 wykonuje się równocześnie nie tylko z procesem 2, lecz także z procesem interpretatora. Interpretator może zresztą ponownie rozwidlić się (np. Jeśli procesy 2 i 3 są procesami drugoplanowymi), tworząc proces 4. W ten sposób łatwo można tworzyć złożone hierarchie procesów.
5.2.2 Hierarchia procesów
Bieżącą strukturę hierarchii procesów można obejrzeć za pomocą polecenia ps. Po wyborze odpowiednich opcji polecenie to wypisuje w zasadzie wszelkie informacje o procesach znanych w danej chwili systemowi.
Lista zawiera informacje o stanie każdego procesu (S — ang. sleeping — śpiący, R —runing — działający), numer identyfikacyjny użytkownika (UID), numer identyfikacyjny procesu (PID), numer identyfikacyjny procesu przodka (PPID), priorytet procesu (PRI), jego rozmiar (SZ) w blokach, numer terminala (TTY), z którym proces jest związany, czas (TIME) zużyty do tej chwili przez proces, wreszcie polecenie (CMD), które spowodowało uruchomienie procesu.
W pierwszej chwili liczba równocześnie istniejących procesów może się wydąć duża, ponadto może dziwić fakt, ze niektóre z nich nie są związane z terminalami. Takie procesy należą do systemu i znajdują się na samej górze hierarchii procesów. Lepszy obraz struktury procesów otrzymuje się rysując drzewo zależności przodek-potomek. Początkowe przygotowania poczynione przez system maja na celu zapewnienie możliwości inicjowania procesów, przesyłania ich miedzy pamięcią a dyskiem oraz uaktualnianiem informacji systemowej. Program lpsched zajmuje się szeregowaniem informacji przesyłanych na drukarkę. Program cron jest demonem zegarowym, pilnującym czasu i inicjującym akcje związane z jego upływem. Do systemu jest dołączony jeszcze czwarty terminal, w danej chwili nieaktywny. Jednak program getty od czasu do czasu sprawdza, czy ktoś nie próbuje rozpocząć z niego sesji.
5.2.3 Przydzielanie pamięci
Unix jest systemem wieloprogramowym, który umożliwia równoczesna pracę wielu procesów, wykonujących wiele programów znajdujących się równocześnie w pamięci. W normalnych warunkach każdy program jest ładowany do innego obszaru pamięci operacyjnej. Operacje związane z podziałem czasu mogą się wówczas odbywać bez zapisywania obrazu pamięci na dysk i z powrotem. Każdy program wykonuje się w przyznanym przedziale czasu, w pozostałych okresach pozostając milcząco w pamięci. Jedynie rejestry procesora musza być dzielone miedzy programami, a więc program zarządzający wymienia tylko zawartość tych rejestrów. Jądro systemu śledzi procesy w danej chwili aktywne oraz decyduje czy i kiedy należy dokonać wymiany, a także gdzie w pamięci umieścić nowo zainicjowane programy. Po zakończeniu procesu bada, czy zakończył się on powodzeniem i ustawia odpowiednio kod zakończenia.
Programy unixowe mogą mięć jedną z dwóch postaci: wielowchodliwą (ang. Re-entrant) lub zwykłą. W przypadku tej pierwszej wszystkie instrukcje programu (a także rozmaite stałe) grupuje się w pamięci osobno oddzielając je od modyfikowalnych struktur danych. W drugim przypadku oba rodzaje informacji mogą być przemieszane. Podczas wykonywania procesów, których kody są wielowchodliwe, przydziela się im trzy odrębne obszary pamięci: segment kodu, segment danych i segment stosu. Pierwszy z nich zawiera kod programu, którego nie wolno modyfikować. Drugi —wszystkie struktury danych, zmienne itp. W trzecim natomiast gromadzi się informacje, niezbędne do zachowania stanu procesu przy przyznawaniu mu i odbieraniu zasobów (procesora, pamięci operacyjnej, itp.). W programach nie mających postaci wielowchodliwej nie można oddzielić stosu i danych od treści programu.
Kod i dane programu warto rozdzielać z dwóch powodów. Po pierwsze zmniejsza się w ten sposób rozmiar informacji, przesyłanej miedzy pamięcią a dyskiem, gdyż segment kodu nie musi być kopiowany z pamięci na dysk. Ponieważ segment ten nigdy nie ulega modyfikacjom, to jego zawartość zawsze pozostaje taka, jak w inicjalnym obrazie zapisanym na dysku. Po drugie, w przypadku niektórych często używanych programów, takich jak edytor vi, w dużych systemach zdarza się, ze wielu użytkowników korzysta z nich w tej samej chwili. Dla samego interpretatora musi istnieć co najmniej tyle procesów, ilu jest użytkowników systemu, gdyż dla każdego użytkownika w chwili rozpoczynania sesji powstaje nowy proces. Jeśli kilku użytkowników chce korzystać z tego samego kodu programu, nie trzeba tworzyć kilku kopii segmentu kodu, gdyż wszystkie one będą identyczne. Wystarczy utworzyć jeden segment danych i jeden segment stosu dla każdego użytkownika i w razie potrzeby przesyłać je miedzy pamięcią a dyskiem. Wielkość obu tych segmentów rzadko jest znacząca w porównaniu z kodem dużych programów.
5.2.4 Podział czasu i innych zasobów
W wieloprogramowym systemie operacyjnym na ogół nie pozwala się pojedynczym procesom pracować aż do końca bez przerwy, lecz przydziela im się jedynie kwanty czasu procesora według jakiejś cyklicznej reguły. Co więcej zazwyczaj nie przydziela się kolejno równych kwantów, zapotrzebowanie różnych procesów może się bowiem znacząco różnic.
Długość kwantów czasu przydzielonych pojedynczym procesom zależy od wielu czynników, wśród nich od priorytetu procesu, dostępności jego danych wejściowych oraz urządzeń wyjściowych. O niektórych czynnikach decyduje się na podstawie analizy samego procesu. Na inne mają wpływ potrzeby sąsiednich procesów (np. w przypadku oczekiwania na dane utworzone przez inny program) albo stan sprzętu (np. drukarka może być zajęta). Unix, tak jak większość systemów operacyjnych, przydziela kwanty czasu w taki sposób, aby zmaksymalizować wykorzystanie zasobów systemu dając równocześnie pierwszeństwo zadaniom o najsurowszych wymaganiach.
Czas współzawodniczącym procesom przydziela zgodnie z ich priorytetami jądro Unixa. Priorytety wyraża się liczbowo, przy czym im większa liczba, tym niższy priorytet. Zadaniom najważniejszym przypisuje się wartości najmniejsze. Listę bieżących wartości priorytetów można uzyskać wydając polecenie ps. Wartości te są zmieniane cyklicznie, na ogół w odstępach kilkusekundowych. Procesy z wysokim stosunkiem zużytego czasu procesora do czasu rzeczywistego przesuwa się w dół, natomiast te wymagające mniej obliczeń — w górę. W ten sposób użytkownicy, którzy maja do wykonania dużo pracy interakcyjnej (np. wprowadzający program z klawiatury) otrzymują wysoki priorytet, co zapewnia im prawie natychmiastową odpowiedz systemu. Równocześnie można przyjąć, ze w przypadku zadania z duża ilością obliczeń użytkownik i tak będzie musiał trochę poczekać na odpowiedź, a więc niewielkie wydłużenie czasu oczekiwania nie będzie dla niego utrudnieniem. Z podobnych powodów zadania systemowe maja wyższy priorytet niż zadania użytkowników. Ponieważ priorytety są regularnie aktualizowane, procesy zmieniające dynamicznie swój charakter będą miały niewłaściwy priorytet bardzo krótko. Na przykład zadaniu obliczeniowemu uruchomionemu z wysokim priorytetem system wkrótce obniży priorytet zwiększając jego wartość. Na odwrót, proces mający początkowo niski priorytet, jeśli mało korzysta z procesora, natomiast dużo z terminala, będzie się przesuwał w gorę.
Użytkownicy mogą dwojako wpływać na wysokość priorytetu procesu: deklarując proces jako drugoplanowy lub żądając zwiększenia wartości priorytetu (zwanego w slangu użytkowników. nice number ). Służy do tego polecenie nice. Normalny użytkownik może tylko zwiększyć wartość priorytetu, przywilej zmniejszania jej jest zastrzeżony dla administratora systemu.
Po upływie czasu przyznanego procesowi, gdy nadchodzi kolej na następny proces, może się okazać, ze aby pomieścić w pamięci operacyjnej drugi, trzeba usunąć z niej ten pierwszy. Usuniecie procesu polega na zapisaniu jego obrazu w pliku dyskowym. Umieszczając proces ponownie w pamięci, kopiuje się do niej zawartość owego pliku i odtwarza taki stan komputera jaki był w chwili usuwania procesu. W ten sposób proces może wznowić wykonywanie dokładnie od miejsca, w którym je przerwał, jedynie z pewnym opóźnieniem. Obraz procesu zapisany w pliku obejmuje wszystkie modyfikowalne obszary pamięci, zawartość rejestrów maszynowych, nazwę bieżącego katalogu, listę otwartych plików i trochę innych istotnych informacji.
5.2.5 Funkcje fork, exec i wait
Aby wyjaśnić sposób funkcjonowania praw dostępu i ustalania priorytetów procesów unixowych, należy przyjrzeć się nieco dokładniej procedurze rozwidlania. Funkcja fork tworzy nowy proces, przy czym po rozwidleniu zarówno przodek, jak i potomek są aktywni. Terminów przodek i potomek użyto w znaczeniu bardzo umownym, gdyż oba procesy są takie same. Inaczej mówiąc funkcja fork powołuje do istnienia parę procesów, wyposażonych w ten sam program i w prawie takie samo środowisko — prawie, gdyż jeden z nich jest bowiem wpisany do tablic systemowych jako potomek drugiego. Procedura ta może się wydać nieco dziwna, nie jest jednak pozbawiona logiki: ponieważ oba procesy są identyczne, nie trzeba lądować do pamięci ani usuwać z niej kodu programu, tworzyć fizycznej kopii czegokolwiek z wyjątkiem modyfikowalnych obszarów danych. Do zmiany wykonywanego programu służy funkcja exec, określająca nazwę programu. Właściwie należałoby mówić o kilku funkcjach, mających podobny skutek, lecz różniących się sposobem przekazywania argumentów. Tandem fork—exec najpierw małym kosztem tworzy kopie procesu macierzystego, a następnie zastępuje kod programu procesu potomnego programem, który miał być wywołany.
Podczas normalnej interakcyjnej pracy, z klawiatury, proces jest uruchamiany natychmiast po wydaniu odpowiedniego polecenia, proces uruchamiający (zazwyczaj jest to shell) czeka na zakończenie pracy procesu potomnego. Samo rozwidlenie tu nie wystarczy. Aby przenieść proces w stan oczekiwania, trzeba użyć funkcji wait (czekaj). Powoduje ona, ze proces macierzysty zawiesza się aż do chwili, gdy jądro da znać, ze proces potomny zakończył prace.
5.2.6 Identyfikator skuteczny użytkownika
Tandem funkcji fork—exec tworzy nowy proces, dziedziczący środowisko procesu macierzystego, a następnie podmienia kod procesu. Jeśli nie dokonano świadomych zmian, to parametry środowiska pozostają takie, jak w procesie macierzystym: ten sam właściciel, ten sam katalog bieżący, ten sam terminal. Takie rozwiązanie jest dobre w większości przypadków. Czasami jednak występują problemy dotyczące praw dostępu. Przedstawiony zostanie tu krótko przykładowy problem oraz sposób jego rozwiązania. Niektóre programy zainstalowane w systemie Unix są wprawdzie ogólnie dostępne, ale po uruchomieniu muszą korzystać z plików o ściśle ograniczonych prawach dostępu. Na przykład każdy może uruchomić program paswd, ale do pliku z hasłami użytkowników. (/etc/paswd i /etc/shadow) ma normalnie dostęp jedynie administrator systemu. Pojawia się konflikt: użytkownik kazio mógłby uruchomić program paswd, lecz ten program nie miałby prawa zmiany zawartości pliku z hasłami, gdyż dostęp do niego ma tylko jego właściciel, czyli administrator.
Problem tej samej natury pojawia się w wielu zastosowaniach komercyjnych, np. gdy kilku urzędników ma prawo dopisywania informacji do bazy danych, jednak żaden z nich nie powinien mieć prawa nieograniczonego dostępu do całego pliku zawierającego bazę. Problem ten rozwiązuje się używając pewnego triku. Zazwyczaj wtedy, gdy program shell, z którego korzysta użytkownik kazio, inicjuje nowy proces wykonujący program paswd, proces potomny dziedziczy użytkownika kazio jako swojego właściciela, co grozi konfliktem przy próbie dostępu do plików stanowiących własność samego systemu. Jednak zmiana identyfikatora użytkownika w procesie potomnym na identyfikator administratora, spowodowałoby, ze proces miałby dostęp do wszystkich plików. Pozorny właściciel procesu powinien być więc różny od właściciela procesu macierzystego. W żargonie unixowym identyfikator użytkownika procesu potomnego nazywa się identyfikatorem skutecznym użytkownika , w przeciwieństwie identyfikatora faktycznego odziedziczonego z procesu macierzystego. Identyfikator skuteczny należy do środowiska procesu potomnego, a nie macierzystego, a więc jego ważność kończy się wraz z zakończeniem pracy procesu potomnego.
Każdy proces może zatem należeć albo do tego samego użytkownika, co jego proces macierzysty, albo do właściciela pliku zawierającego program, wykonywany przez proces. W chwili uruchamiania nowego procesu następuje ustawienie identyfikatora skutecznego zgodnie z identyfikatorem właściciela pliku, Jeśli plik ten ma ustawiony bit SUID (ang. Set user identity). Wówczas na liście zawartości katalogu plik jest oznaczony nie tylko jako wykonywalny, lecz także jako wykonywalny ze zmianą identyfikatora, co sygnalizuje litera a użyta zamiast litery x: Możliwość ustawiania identyfikatora skutecznego właściciela jest niebezpieczna dla mechanizmów ochrony zasobów w systemie. Jeśli np. program należący do administratora rozwidli się i utworzy nowy shell, to ów shell odziedziczy identyfikator właściciela i będzie miał nieograniczony dostęp do całego systemu!
5.2.7 Demon zegarowy
Procesy, których wykonanie jest zależne od czasu, w systemie Unix synchronizuje zegar systemowy za pośrednictwem programu cron, wykonywanego jako proces opisywany w podręcznikach pod nazwą demon zegarowy. Podczas pierwszego uruchomienia demon sprawdza w tablicach systemowych, kiedy nastąpi pierwsze zdarzenie, wymagające jego udziału. Każde zdarzenie określa proces, który należy zainicjować w zadanej chwili. Po sprawdzeniu listy demon usypia i budzi się dokładnie wówczas, gdy przychodzi pora na rozwidlenie pierwszego zapisanego procesu. Uruchomiwszy proces demon ponownie szuka w tablicy następnego zdarzenia i usypia. Administratorzy systemu intensywnie korzystają z programu cron w zadaniach administracyjnych, gdyż demon o niczym nie zapomina ani nie ma nic przeciw uruchamianiu np. programów rozliczeniowych w nocy przed świtem, gdy komputer jest słabiej obciążony. Nie tylko administrator, lecz także zwyczajni użytkownicy mogą zlecać demonowi troskę o swoje procesy. Do zapisania w odpowiednich tablicach systemowych programu, który ma być uruchamiany cyklicznie, służy polecenie crontab.
Ważną zaletą programu cron jest to, ze wszelka informacja, która uruchomiony proces wpisuje do standardowego strumienia wyjściowego w czasie, gdy zleceniodawca nie korzysta z systemu jest przesyłana mu pocztą elektroniczną. Nic nie zostanie więc stracone. Wadą natomiast to, ze wyłączanie komputera w chwili, gdy powinien być zainicjowany zlecony proces, spowoduje zapomnienie go na zawsze — nie będzie on bowiem wznowiony po ponownym włączeniu komputera.
5.3 Operacje wejścia - wyjścia
Dla większości programów (np. sh) wszelkie operacje wejścia - wyjścia wyglądają tak, jak operacje na plikach. Nic nie wiadomo w nich o istnieniu jakichkolwiek urządzeń zewnętrznych. Dla jądra natomiast zupełnie odwrotnie, jego zadanie polega właśnie na ukryciu szczegółów prawdziwych urządzeń fizycznych pod postacią pozornych plików.
5.3.1 Niezależność od urządzeń
W większości dzisiejszych systemów operacyjnych do plików znajdujących się w różnych urządzeniach fizycznych można odwoływać się w podobny sposób. O systemach takich mówi się, ze są w dużej mierze niezależne od urządzeń. Niezależność tę osiąga się definiując fikcyjne, wirtualne urządzenie, funkcjonalnie odpowiadające dyskowi, na którym umieszcza się wszystkie pliki, niezbędne do uruchomienia programów. Oczywiście żadne prawdziwe urządzenie nie ma wszystkich cech takiego urządzenia wirtualnego. Dla każdego urządzenia należy więc stworzyć specjalny podprogram obsługi (ang. device driver ), który będzie tłumaczył zadane operacje urządzenia wirtualnego na dostępne operacje urządzenia fizycznego. Programy użytkownika mogą wówczas komunikować się z dowolnymi nowymi urządzeniami dołączonymi do komputera, Jeśli tylko napisano dla nich podprogram obsługi.
Uniezależniając Unix od urządzeń wprowadzono zasadę, ze wszystkie urządzenia fizyczne w systemie maja być dla użytkownika po prostu normalnymi plikami. Ponieważ jednak takie pliki różnią się co nieco od plików stworzonych przez użytkownika, nazywa się je plikami specjalnymi Dla plików specjalnych również określa się prawa dostępu, są one zresztą niezbędne, np. drukarka nadaje się tylko do pisania i wobec tego należy wszystkim zabronić możliwość czytania z niej. Pliki specjalne znajdują się w katalogi /dev.
5.3.2 Pierwszy poziom przerwań
Wiele zdarzeń, zachodzących w wielodostępnym systemie komputerowym, ma związek z upływem czasu rzeczywistego i wymaga natychmiastowej reakcji. Takim zdarzeniem jest np. naciśniecie przez użytkownika klawisza na klawiaturze terminala. Po kilkuset milisekundach użytkownik może nacisnąć następny klawisz, a więc akcja związana z pierwszym klawiszem musi się w tym czasie zakończyć.
Zdarzenia wymagające natychmiastowej reakcji są sygnalizowane jądru przez sprzęt. Każde takie zdarzenie powoduje przerwanie: program wykonywany w danej chwili, czymkolwiek by był, zostaje wstrzymany po zakończeniu cyklu wykonywania bieżącej instrukcji maszynowej, a sterowanie jest przekazywane do innego, zazwyczaj bardzo małego podprogramu zwanego procedurą obsługi przerwania. Procedura określa rodzaj przerwania, wykonuje wszelkie niezbędne operacje stanowiące następstwo przerwania, a wreszcie przekazuje sterowanie ostatnio wykonywanemu programowi. Obsługa przerwania trochę jest podobna do wywołania podprogramu — program główny pozostaje w stanie oczekiwania, w tym czasie natomiast wykonuje się pewien inny ciąg instrukcji. Jednakże w przeciwieństwie do podprogramu wykonanie procedury obsługi przerwania jest inicjowanie przez sprzęt i przebiega asynchronicznie, w odpowiedzi na pewne zdarzenie zewnętrzne.
Ponieważ w systemie wielodostępnym może być wiele terminali a każdy z nich może przesyłać kilka znaków na sekundę, czas obsługi pojedynczego przerwania jest mały. Prawie wszystkie systemy operacyjne stosują więc dwupoziomowa obsługę znaków z klawiatury. Program pierwszego poziomu obsługi przyjmuje kolejny znak z klawiatury, sprawdza, czy nie jest to znak specjalny wymagający natychmiastowej obsługi (np. DELETE), umieszcza go w buforze klawiatury, a następnie wypisuje echo znaku na terminalu. Użytkownik może odnieść wrażenie, ze znaki są magazynowane przez terminal, program obsługi przerwań bardzo szybko bowiem wypisuje ich echo. Zresztą nawet przy kilku użytkownikach naciskających klawisze w szalonym tempie komputer będzie miał dużo czasu na wykonywanie innych zadań.
Jeśli okaże się, ze znak wprowadzony z klawiatury wymaga podjęcia jakichś akcji, program pierwszego poziomu przywołuje program drugiego poziomu. Program ten określa rodzaj koniecznej akcji i wykonuje ja. Na przykład po otrzymaniu znaku końca wiersza musza być znalezione w nim znaki usuwania i kasowania, a następnie dokonane odpowiednie poprawki. Przetwarzany tekst jest potem umieszczany w kolejce, z której przesyła się go do programu czekającego na dane. Ta operacja prawdopodobnie będzie trwała dłużej, niż samo magazynowanie podanych poleceń.
5.3.3 Pliki specjalne blokowe i znakowe
Istnieją dwa podstawowe rodzaje plików specjalnych (zdefiniowane w rozdziale 5.3.1): blokowe i znakowe. Wzorcami dla nich są odpowiednio pliki dyskowe i terminale. Innymi słowy, Unix rozpoznaje dwa rodzaje urządzeń zewnętrznych — dysko-podobne i terminalo-podohne. Wszystkie inne system upodabnia najpierw na silę do jednego z tych urządzeń, a następnie sprawia, ze wszystkie urządzenia wyglądają dla użytkownika jak pliki dyskowe.
W blokowym wejściu -wyjściu korzysta się ze zbioru buforów danych . Na ogół jest ich kilkanaście lub więcej. Podczas normalnej pracy przyznaje się, je użytkownikom w miarę potrzeby. Jeśli proces zażąda danych, jądro szuka ich w jednym z buforów. Po znalezieniu są one przekazywane procesowi bez przesyłania ich miedzy pamięcią a dyskiem. Z kolei zadanie zapisania danych jest zadaniem zapisania do buforu. Zawartość buforu przesyła się na dysk dopiero później, gdy bufor jest potrzebny do innego celu, albo gdy ktoś zażąda opróżnienia buforów. Ponieważ bufory nie maja ustalonych właścicieli, zawartość buforu wyjściowego nie jest przesyłana natychmiast po jego napełnieniu, lecz dopiero po napełnieniu wszystkich buforów, gdy jakiś proces zażąda następnych. Bufory wejściowe są zapełnione na skutek wyprzedzającego czytania danych, tworzenie wyników wiąże się natomiast z opóźnionym zapisywaniem. Operacje wejścia -wyjścia przebiegają więc asynchroniczne względem programów, programy jednak rzadko musza czekać na przesłanie danych.
Operacje wejścia -wyjścia związane z urządzeniami przetwarzającymi znaki przebiegają w sposób następujący: podprogram obsługi zajmuje się tylko pojedynczymi znakami. Dla znaków o specjalnym znaczeniu wykonuje konieczne akcje, pozostałe znaki przekazuje dalej. Na przykład znak powrotu karetki, czyli końca wiersza w pliku unixowym, musi być przesłany do terminalu jako para znaków, powrotu karetki i przejścia do następnego wiersza. Podobnie Jeśli terminal nie rozpoznaje znaku tabulacji, program obsługi musi przesłać odpowiedni ciąg spacji, a terminale i drukarki nie przesuwające kursora lub papieru do następnej strony musza zamiast tego otrzymać odpowiednia liczbę znaków przejścia do następnego wiersza. Wszystkie takie przekształcenia wykonuje podprogram obsługi.
Dla blokowych urządzeń wejscia-wyjscia podprogramy obsługi są zazwyczaj bardzo proste, gdyż wszystkie operacje dotyczą standardowych buforów. Urządzenia znakowe wymagają bar-dziej skomplikowanych podprogramów, oprogramowanie zarządzające buforami może być nato-miast mniej wyszukane.
Sposób organizacji unixowego wejscia-wyjscia jest na ogół niewidoczny dla użytkownika, któremu może się wydawać, ze operacje te wykonują się synchronicznie z programem, tzn. dane są czytane, a wyniki zapisywane dokładnie w takiej kolejności, w jakiej to wynika z tekstu programu.
Jednak w razie jakiejkolwiek awarii złożoność unixowego schematu buforowania może stać się kłopotliwa. Na przykład giną wyniki, znajdujące się w buforach — w porę nie wydrukowane lub zapisane w pliku, chociaż z punktu widzenia programu ich tworzenie zostało całkowicie zakończone.
5.3.4 Fizyczna struktura plików
Użytkownicy rzadko musza się troszczyć o fizyczną strukturę plików, ponieważ system Unix sprawia, ze każdy plik wydaje się być nieprzerwanym ciągiem bajtów (znaków). Jednak czasami choćby pobieżna znajomość fizycznej struktury plików pozwala na napisanie sprawniejszych programów użytkowych.
Pliki dyskowe w systemie Unix fizycznie dzieli się na bloki. W pierwszych wersjach Unixa bloki miały niezmiennie po 512 bajtów. Ostatnio dopuszcza się zarówno bloki 512, 1024 bajtowe i dłuższe. Każdemu plikowi przydziela się całkowita liczbę bloków, przy czym początek każdego bloku przypada na wielokrotność 512 bajtów od początku pliku. Kolejne bloki pliku nie musza koniecznie sąsiadować ze sobą na dysku, mogą leżeć gdziekolwiek. Zaletą tego rozwiązania jest to, ze nie trzeba nigdy specjalnie scalać wolnych obszarów. Równocześnie Unix stara się przydzielać plikom spójne obszary, gdyż dostęp do spójnych plików jest znacznie szybszy. Taki stopień fragmentacji pamięci dyskowej, przy którym każdy plik składa się z niewielkich kawałków rozsypanych po całym dysku zdarza się tylko wówczas, gdy dysk jest prawie całkowicie zapełniony. Zarządzanie rozproszonymi plikami wymaga jednak więcej finezji, niż tylko stworzenie prostej listy adresów dla plików ciągłych. Wiele przykładów rozwiązań dostarczają książki z zakresu teorii systemów operacyjnych np. [10].
Pliki zwykłe i katalogi można podzielić na cztery klasy ze względu na ich rozmiar: do 10, 266, 65.802 i 16.843.018 bloków (zakładając 1024 bajtowy rozmiar bloków). Przyczyną tej na pozór niezrozumiałej klasyfikacji jest zarezerwowanie w metryczce pliku miejsca na zapamiętanie numerów 13 bloków każdego. Adresowanie małych plików jest bardzo proste. Jeśli plik zajmuje nie więcej niż 10 bloków, to obszar zarezerwowany w metryczce służy do zapamiętania faktycznych numerów bloków. Na przykład niech plik zajmuje 3 bloki, wówczas 13 zapisanych pól w i-wezle ma następujące wartości: pierwsze 3 wskazują adresy fizycznych bloków na dysku, w pozostałe pola wpisywane są zera, które oznaczają nie wykorzystane pola. W ten sposób można zaadresować 10 bloków, 3 ostatnie numery są bowiem zarezerwowane do innych celów. Ten schemat jest piękny w swej prostocie, ale dotyczy tylko małych plików. Dziesięć bloków odpowiada 10 kB danych.
Większe pliki wymagają adresowania pośredniego. Pierwsze 10 z 13 pól w i-wezle służy do zaadresowania pierwszych bloków zawierających dane, dokładnie tak samo jak w przypadku małych plików. Jedenaste pole nie jest adresem danych, lecz bloku w którym zapamiętuje się adresy 256 dalszych bloków, również zawierających dane. Ponieważ może istnieć 256 dalszych bloków, plik zapisany w ten sposób będzie zajmować nie więcej niż (10+ 256) = 266 bloków, czyli 266 kilobajtów. Z tego pierwszych 10 bloków adresuje się bezpośrednio, a pozostałe — pośrednio poprzez jedenasty blok.
Do zapamiętania jeszcze większych plików wprowadza się drugi poziom pośredni. Znaczenie pierwszych jedenastu numerów bloków jest dokładnie takie, jak poprzednio; dwunasty adres wskazuje na i-wezeł zawierający do 256 adresów bloków (i-wezłów), w których z kolei umieszcza się numery 256 bloków danych. W ten sposób można zaadresować maksymalnie 65.802 bloki danych, a więc całkowity obszar adresowanych danych wynosi 10 + 256 + 256^2 =65.802 bloki, czyli z grubsza 64 megabajty.
Największe pliki są zorganizowane podobnie z tym że trzynasty numer bloku służy do adresowania trzy-poziomowego. Pozwala to zaadresować dodatkowo 16.777.216 (256*256*256) bloków, największy możliwy plik ma więc rozmiar ok. 16 gigabajtów.
Metodę te można w zasadzie rozszerzać o bloki poczwórnie-posrednie, pieciokrotnie-posrednie, itd. W praktyce adresowanie potrójnie-posrednie wystarcza w większości zastosowań Unixa. Katalogi traktuje się tak samo, jak pliki zwykłe, chociaż rzadko osiągają one bardzo duże rozmiary. W przypadku plików specjalnych pierwszy z trzynastu numerów bloków ma inne znaczenie. Jest on traktowany jako słowo, którego górna połowa zawiera identyfikator typu urządzenia fizycznego (np. stacja taśmy magnetycznej), a dolna — numer urządzenia (np. stacja numer 7). To ograniczenie rozmiaru plików specjalnych nie sprawia kłopotów, gdyż i tak nie przekraczają one kilku kilobajtów.
5.3.5 Dostęp swobodny i sekwencyjny
System Unix traktuje wszystkie urządzenia i nośniki tak samo — jako magazyny plików składających się z ciągów znaków. Różne nośniki fizyczne różnią się jednak swoimi cechami, a więc w praktyce różne są sposoby dostępu do plików.
Na przykład dysk magnetyczny jest urządzeniem o dostępie swobodnym , czas dostępu do różnych obszarów jest prawie identyczny, wszystkie znaki na dysku są zatem jednakowo dostępne. Czytanie i pisanie może odbywać się w różnej kolejności, także kilkakrotnie w tym samym miejscu. Trzeba więc pilnować, gdzie (skąd) należy przesłać kolejny znak. Służy do tego bardzo prosty mechanizm: wskaźnik pozycji, początkowo ustawiony na początek pliku, a następnie przesuwany wraz z każdym zapisem lub odczytem. System zawsze czyta i pisze dane w miejscu wskazanym przez wskaźnik pozycji, dostęp swobodny zatem polega jedynie na zmianie wartości wskaźnika.
Niektóre urządzenia, np. klawiatura czy drukarka, są ściśle sekwencyjne. Nie można odczytać znaków raz wysłanych do drukarki, czytanie (lub pisanie) z innych urządzeń zawsze odbywa się w kolejności wymuszonej przez samo urządzenie. W Unixie jednolicie traktuje się wszystkie urządzenia za cenę udawania, ze są one plikami i wyposażania ich we wskaźnik pozycji. Oczywiście wskaźnik związany z urządzeniem sekwencyjnym może być przesuwany tylko w przód.
Urządzenia o cechach pośrednich, np. taśmy magnetyczne, logicznie nie różnią się od plików dyskowych. Różna jest natomiast ich reakcja fizyczna: używanie powiedzmy taśm magnetycznych jako urządzeń o dostępie swobodnym może wymagać częstego przewijania i powodować skrajnie powolne działanie.
Oprócz swych ściśle sekwencyjnych cech, pewne urządzenia (np. klawiatury czy drukarki) narzucają również inne ograniczenia — z jednych można tylko czytać, na inne — tylko pisać. Te własności fizyczne nie powodują jednak żadnych problemów logicznych, gdyż Unix i tak korzysta z systemu praw pisania lub czytania. Urządzenie fizyczne, z którego nie można czytać jest równoważne plikowi bez prawa czytania. Tak więc urządzenia jednokierunkowe mieszczą się bez kłopotów w ogólnej strukturze Unixa.
W systemie Unix można korzystać z plików o dostępie swobodnym w programach napisanych w językach wysokiego poziomu, np. w C, a przeciętny użytkownik nigdy nie musi się troszczyć o to, jak odbywa się fizyczny dostęp.
5.3.6 Buforowanie
O buforowaniu obsługi plików wspominał już rozdział5.3.3. Z punktu widzenia zwykłego użytkownika operacje czytania i pisania plików przebiegają synchronicznie z programami. Wszystkie instrukcje typu read(), write() programu w C lub odpowiednie instrukcje w innych językach programowania, według użytkownika są wykonywane dokładnie w chwili i w miejscu pojawienia się ich w programie. Nie widać buforowania, Każda operacja czyta lub pisze dokładnie podana liczbę bajtów, niezależnie od rozmiaru bloku dyskowego.
Ponieważ pliki są fizycznie podzielone na bloki, które nie muszą znajdować się obok siebie na dysku, takie synchroniczne i niebuforowane zachowanie można osiągnąć korzystając przy czytaniu i pisaniu z faktycznego pośrednictwa buforów. Buforowanie nie jest proste. Na przykład instrukcja czytania powoduje odczytanie z dysku całego bloku do bufora w pamięci, chociaż następnie z bufora do obszaru programu kopiuje się tylko zadaną liczbę bajtów. Pisanie również polega na skopiowaniu znaków z obszaru programu do bufora, jednak dopiero po wypełnieniu bufora informacja jest faktycznie przesyłana na dysk. Przypuśćmy, ze program użytkowy czyta kolejne 64-znakowe rekordy instrukcja read() . Informacje z dysku będą przesyłane tylko raz na kilka zadań czytania, przy założeniu, ze są to zadania dostępu do kolejnych rekordów. Jeśli jednak będą to zadania przeczytania dowolnie rozmieszczonych 64-znakowych rekordów —mieszczących się każdy w innym bloku, to każde zadanie będzie wymagać osobnego dostępu do dysku. Ponieważ w wielu programach użytkowych czyta się i pisze kolejne rekordy danych, dla Unixa zaprojektowano bardziej złożony schemat buforowania, zgodnie z którym kolejny blok jest odczytywany zanim jest on potrzebny. Dane dla większości zadań czytania znajdują się już w buforze, nie trzeba zatem czekać na fizyczny ruch głowic lub taśmy. Takie przyspieszenie jest widoczne, zwłaszcza w przypadku dużych plików wymagających kilku poziomów pośredniego adresowania w celu znalezienia kolejnego bloku. Dane przechowuje się zawsze w blokach. Programy intensywnie korzystające z wejscia-wyjscia, o ile czyta się w nich i pisze dane porcjami, będącymi wielokrotnościami lub dzielnikami rozmiaru bloku, teoretycznie powinny pracować nieco szybciej. Jednak unixowy schemat buforowania zmniejsza ewentualne przyspieszenie i większość programistów nie dba o szczegóły na tym poziomie, może z wyjątkiem przypadków zupełnie swobodnego dostępu do pliku.
Buforowanie danych przesyłanych do lub z plików specjalnych — plików blokowych i znakowych — odbywa się odmiennie. Pliki blokowe, tzw. pliki specjalne odpowiadające urządzeniom o strukturze blokowej, obsługuje się tak samo, jak pliki zwykłe. Pliki specjalne o strukturze znakowej musza oczywiście manipulować pojedynczymi znakami. Ich schemat buforowania jest zatem całkiem prosty, bez wielu wymyślnych szczegółów buforowania blokowego.
5.3.7 Mechanizmy dostępu do plików
Zanim program będzie mógł odczytać lub zapisać dane w pliku, należy ów plik otworzyć albo — Jeśli dotychczas nie istniał— stworzyć. Odpowiednio, należy zamknąć plik przed zakończeniem programu, który go otworzył. Do otwierania i zamykania plików służą funkcje systemowe open() i close() . Dwie podstawowe funkcje, read(), i write(), powodują przesyłanie znaków z pliku (być może specjalnego) do podanego obszaru buforu w pamięci albo odwrotnie.
Z każdym plikiem związana jest liczba zwana deskryptorem pliku, która wiąże konkretny proces z plikiem. Niektóre deskryptory plików przypisuje się procesom automatycznie. Plik 0 odnosi się do standardowego strumienia wejściowego, czyli domyślnie do klawiatury. Plik l jest standardowym strumieniem wyjściowym i domyślnie odpowiada ekranowi terminala. Plik 2 jest standardowym strumieniem diagnostycznym, także kierowanym na ekran terminala. Służy on do przekazywania rozmaitych informacji systemowych oraz diagnostycznych. Dzięki rozdzieleniu tego strumienia od strumienia wyjściowego, można wypisywać komunikaty na ekranie terminala nawet wówczas, gdy strumień wyjściowy został skierowany na dysk lub na drukarkę. Użytkownikowi wolno zmienić przypisanie strumieni. Ponieważ normalne zasady rozwidlania odnoszą się także do programu shell, wszystkie pliki otwarte przez shell są dostępne dla każdego utworzonego przez niego programu — dla jego potomków, ich potomków, itd. Standardowe przypisanie strumieni procesu macierzystego dziedziczą więc wszystkie pokolenia potomków. Oczywiście przypisanie można zmienić z jakiegokolwiek na dowolne inne.
5.3.8 Identyfikatory plików
Pliki identyfikuje się w katalogach Unixa za pomocą indeksów (w żargonie unixowym — i-numerów). Indeks dla danego wolumenu fizycznego (dysku, taśmy, itp.) jest w rzeczywistości indeksem do tablicy, zwanej i-lista zapisanej na tym samym urządzeniu. I-liste danego urządzenia tworzy zbiór metryczek zwanych i-wezłami (ang. i-node, dlatego czasami i-lista jest nazywana tablica i-nodów). Każda metryczka opisuje jeden plik i zawiera m.in. następujące informacje:
1. numer identyfikacyjny użytkownika, który utworzył plik;
2. kod ochronny pliku (prawa dostępu);
3. trzynaście numerów bloków z danymi zajmowanymi przez plik;
4. rozmiar pliku;
5. czas ostatniej modyfikacji pliku;
6. liczbę dowiązań do pliku;
7. bity oznaczające rodzaj pliku (plik zwykły, katalog, plik specjalny).
Informacja o tym, ile dowiązań do pliku jest zapisanych w katalogach ma istotne znaczenie, gdyż pliki różne od katalogów mogą być zapisane w katalogach wielu użytkowników. Jeśli jeden z nich zechce usunąć plik ze swojego katalogu, musi ograniczyć się do usunięcia pozycji w katalogu, ponieważ plik może być nadal dowiązany do innych katalogów. Jeśli natomiast pozycje w katalogu usuwa ostatni z użytkowników pliku, powinien on również usunąć sam plik, tzn. zwolnić obszar zajęty przezeń na dysku. W niektórych sytuacjach użytkownik może zechcieć zbadać i-numery plików, np. gdy potrzebuje informacji dotyczących obszaru na dysku. Do wy-pisania i-numerów służy polecenie þþ z opcja -i. Wynik jest taki, jak zazwyczaj w przypadku tego polecenia, ale dodatkowo dla każdego pliku podaje się jego i-numer. Odwzorowanie nazwy pliku na jego i-numer nazywa się dowiązaniem (ang. link ) i stanowi podstawowe narzędzie identyfikowania plików i ich pielęgnowania. Można powiedzieć, ze polecenie mv przenosi dowiązanie, rm usuwa je, a ln tworzy nowe.
1
1