Programy i procesy

Uruchamianie programów jest podstawową czynnością wykonywaną przez użytkownika komputera; warto więc od początku zrozumieć parę pojęć z tym związanych.

Program rozumiemy tu jako plik wykonywalny, a więc taki, który system potrafi uruchomić, np. w reakcji na działanie użytkownika. Działaniem tym może być np. wypisanie nazwy tego pliku w linii komend shella (interpretera komend) lub (w środowisku okienkowym) kliknięcie na ikonę powiązaną z danym programem.

Procesem z kolei jest instancja działającego programu, która powstała w wyniku jego uruchomienia. W danej chwili może istnieć i działać wiele różnych procesów, będących wynikiem uruchomienia tak tego samego, jak i różnych programów, przez wielu różnych użytkowników. Dlatego m. in. należy rozróżniać pomiędzy procesem a programem. Każdy proces posiada przydzielony przez system, jednoznaczny (w danej chwili czasu) numeryczny identyfikator (PID) i wykonuje się w zasadzie niezależnie od innych procesów, działających na tym samym komputerze; może się jednak z nimi komunikować, za pośrednictwem całego szeregu mechanizmów komunikacji międzyprocesowej zapewnionych przez system operacyjny.

Programy

Aby plik mógł być uruchomiony jako program, muszą być spełnione dwa warunki:

Pierwszy warunek został omówiony w rozdziale o plikach; drugi warunek jest spełniony, gdy plik programu powstał w wyniku kompilacji i linkowania kodu źródłowego (napisanego w jakimś języku programowania, takim jak C, Fortran, Pascal, ...) przez odpowiedni kompilator (mówimy wówczas o programie binarnym). Jest on spełniony również, jeżeli plik zawiera tekst instrukcji w języku interpretowanym (takim jak język shella sh, Perl, Python, ...), program interpretujący ten język jest właściwie zainstalowany na systemie, a treść pierwszej linijki pliku zbudowana jest według wzoru

#! /bin/sh

tj. zawiera jako dokładnie dwa pierwsze znaki sekwencję #!, a po nich następuje pełna ścieżka dostępu do programu interpretującego. Taki program nazywa się często skryptem.

Programy binarne przeznaczone do wspólnego użytku zainstalowane są w katalogach ogólnosystemowych, takich jak /bin (programy niezbędne do uruchomienia systemu), /usr/bin (większość narzędzi przeznaczonych dla użytkowników), /usr/local/bin (programy spoza ,,oficjalnej" zawartości systemu operacyjnego), /usr/X11R6/bin (programy związane ze środowiskiem okienkowym X Window System), /usr/dt/bin (programy związane ze środowiskiem graficznym CDE stosowanym w systemie Sun Solaris). Każdy plik spełniający warunki opisane powyżej może być jednak uruchomiony (niezależnie od jego lokalizacji) poprzez podanie w linii komend specyfikacji ścieżki dostępu (zamiast tylko nazwy pliku); np. uruchomienie programu o nazwie np. mojprog znajdującego się w katalogu aktualnym (nie będącym jednym z katalogów systemowych) wymaga na ogół podania komendy w postaci

./mojprog

Nazwa uruchamianego pliku jest generalnie bez znaczenia, w szczególności Unix nie przywiązuje wagi do tzw. końcówki nazwy programu (nie spotyka się programów zapisanych w plikach o nazwach kończących się na .EXE, .BAT czy .COM).

Procesy i interpreter komend

Najprostszym sposobem stworzenia procesu przez użytkownika jest uruchomienie (legalnego) pliku wykonywalnego, przez podanie jego nazwy (lokalizacji) w linii komend. W wielu przypadkach, w zależności od uruchamianego programu można też (lub należy) podać opcje dla programu oraz argumenty -- będące często (choć nie zawsze) nazwami plików które mają być przetworzone. Dla większości programów obowiązuje zasada, że bezpośrednio po nazwie (lokalizacji) programu podajemy ew. opcje uruchomienia; mają one najczęściej postać skrótów jednoliterowych poprzedzonych znakiem - (minus), np. ls -l, przy czym w wypadku podawania kilku opcji jednocześnie można je grupować: np. zamiast ls -l -A można równoważnie napisać ls -lA. Ewentualne argumenty podawane są po opcjach, np. ls -Al /tmp.

Należy wiedzieć, że zawartość linii komend podlega pewnej interpretacji i ,,obróbce'' przez shell (interpreter komend) zanim opcje i argumenty zostaną przekazane uruchomionemu programowi. W dużym uproszczeniu: treść linii komend jest dzielona na ,,słowa'' (sekwencje znaków oddzielone ciągami spacji), i każde ,,słowo'' będzie potraktowane jako oddzielny argument (lub grupa opcji); po czym następuje interpretacja pewnych znaków specjalnych (metaznaków). Przytoczę tu jedynie najprostsze (i najczęściej przydatne) przykłady zastosowania rozwinięć metaznaków:

Inny użyteczny trik to komenda1 `komenda2`, działa on następująco: jako pierwsza zostanie wykonana komenda2 (może ona oczywiście zawierać również argumenty), wynik jej wykonania -- to, co normalnie pojawiłoby się na ekranie, tj. zawartość standardowego strumienia wyjściowego -- zostanie umieszczony w linii komend wywołującej komenda1, która następnie zostanie wykonana (z zastosowaniem pozostałych reguł rozwijania).

Shell umożliwia również edycję linii komend, oraz odwoływanie się do komend wpisanych uprzednio (klawisz ,,strzałka w górę''), z możliwością ich edycji (modyfikacji) przed ponownym wykonaniem. Wśród innych udogodnień ułatwiających pracę interakcyjną warto wymienić tzw. autouzupełnianie (nazw komend i argumentów); w szczegółach jego działanie różni się w zależności od używanej wersji shella i opcji konfiguracyjnych, ale zazwyczaj pozwala ono na użycie klawisza ,,Tab'' po wpisaniu części nazwy komendy, lub nazwy pliku mającej być argumentem, czego wynikiem jest uzupełnienie tej nazwy o dalsze znaki na tyle, na ile jest to jednoznacznie możliwe (tj. jeżeli do początkowych kilku znaków pasuje nazwa tylko jednego faktycznie istniejącego pliku, to pojawi się ona na linii komend w całości).

W istocie shell jest interpreterem dość zaawansowanego języka programowania, potrafiącym wykonywać programy bezpośrednio z plików tekstowych, zwanych skryptami shella. Język ten zawiera w zasadzie wszystkie elementy przydatne do tworzenia nawet dość rozbudowanych programów (zmienne, pętle, instrukcje warunkowe, możliwość definicji funkcji ...); bardziej szczegółowe jego omówienie wykracza jednak poza zakres niniejszych notatek. Wspomnę jedynie, że składnia języka shella różni się dość istotnie w zależności od typu shella; używany jako domyślny do pracy interakcyjnej w naszej sieci tcsh nie jest szczególnie godny polecenia (w dość powszechnej opinii) do tworzenia bardziej rozbudowanych skryptów; do tego celu zazwyczaj używa się bardziej standardowego sh, lub będącego domyślnym w większości instalacji Linuxa shella bash.

Z bardziej zaawansowanych właściwości shella omówię tu pobieżnie jedynie dwie: kontrolę zadań, oraz przekierowania i potoki. Kontrola zadań stwarza możliwość uruchamiania procesów ,,w tle'', tj. z natychmiastowym powrotem do shella i możliwością wydawania kolejnych poleceń bez czekania na zakończenie uruchomionego właśnie procesu, ,,zawieszania'' wykonania aktualnie działającego procesu oraz żonglowania na rozmaite sposoby grupą zadań uruchomionych z aktualnej instancji shella. Jest to szczególnie przydatne w przypadku uruchamiania długotrwałych procesów, które wykonują się bez potrzeby interakcji z użytkownikiem, oraz przy uruchamianiu programów które np. tworzą własne okna graficzne i poprzez nie komunikują się interakcyjnie z użytkownikiem. Uruchomienie procesu w tle dokonuje się przez zakończenie linii komend znakiem &, postawionym po wszystkich argumentach do polecenia i ew. przekierowaniach (p. poniżej). Do ,,zawieszenia'' procesu aktualnie działającego ,,w pierwszym planie'' służy zazwyczaj sekwencja klawiszy Ctrl-z; w odróżnieniu od procesu uruchomionego ,,w tle'', proces zawieszony wstrzymuje działanie i czeka na ewentualne ,,odwieszenie''. Listę zadań związanych z aktualnym shellem (zawieszonych i uruchomionych w tle) można obejrzeć komendą jobs; każde zadanie z tej listy będzie opatrzone numerem identyfikacyjnym, z którego można skorzystać do wydawania poleceń zmieniających stan danego zadania. Polecenie fg %n (gdzie n jest tym numerem identyfikacyjnym) przenosi dane zadanie do ,,pierwszego planu'', zarazem je wznawiając (o ile było ono zatrzymane); natomiast bg %n wznawia wykonywanie zadania zawieszonego, pozostawiając je ,,w tle''.

Mechanizm przekierowania i potoków pozwala z kolei na proste wykonywanie nawet dość złożonych operacji przetwarzania danych poprzez łączenie w ,,łańcuszek'' szeregu prostych komend, z których każda wykona jeden z etapów przetwarzania przekazując wynik następnej w celu wykonania koleknek operacji. Korzysta tu się z faktu, że z każdym procesem unixowym związane są trzy tzw. standardowe strumienie wejścia-wyjścia: standardowe wejście (stdin), standardowe wyjście (stdout) i standardowy strumień błędu (stderr). Domyślnie, stdin utożsamiony jest z klawiaturą terminala, a stdout i stderr -- z ekranem. Tzn. np. że dane wyprowadzone przez program na stdout pojawią się na ekranie użytkownika. Większość standardowych programów narzędziowych, stanowiących podstawowe wyposażenie systemu unixowego, potrafi czytać dane wejściowe zarówno z plików (z dysku), jak i ze strumienia stdin, natomiast wyniki przetwarzania wyprowadza domyślnie na strumień stdout. Strumień stderr służy natomiast do wypisywania komunikatów nie będących ,,normalnym'' wynikiem przetwarzania danych, tj. komunikatów o błędach i ew. ostrzeżeń o sytuacjach mniej lub bardziej ,,anomalnych''.

Dla przykładu, komenda sort służy do sortowania (domyślnie: alfabetycznego) linijek tekstu stanowiących dane wejściowe. Gdy się ją wywoła z argumentami będącymi nazwami plików, danymi do sortowania będzie zawartość tychże; w przypadku wywołania bez argumentów (nie będących opcjami, za pomocą których można zadać bardziej złożone kryteria sortowania), komenda sort oczekuje, że dane do przetworzenia pojawią się w standardowym strumieniu wejściowym. W obu tych przypadkach, wynik sortowania pojawi się na stdout.

Mechanizm o którym mowa polega na udostępnieniu możliwości przedefiniowania standardowych strumieni, na dwa sposoby: bądź przez połączenie ich z plikami, bądź przez połączenie stdout jednego procesu z stdin innego. Konstrukcja komenda ... > plik powoduje, że zawartość stdout wywołanej komendy, zamiast na ekranie, pojawi się w pliku o podanej nazwie (w miejscu wielokropka umieścić można dowolne opcje i inne argumenty wywołania); podobnie komenda ... < plik spowoduje, że zawartość podanego pliku będzie stanowiła stdin wywołanej komendy. Z kolei pisząc komenda1 ... | komenda2 ... sprawimy, że dane wyprowadzone przez komenda1 na jej stdout staną się zawartością stdin dla komenda2. Wszystkich tych rodzajów przekierowania można też użyć łącznie, np.: komenda1 < plik1 | komenda2 > plik2.

Uwagi:
Czytane razy.