Proces jest działającą w danym momencie instancją programu. Wielokrotne uruchomienie programu powoduje powstanie oddzielnych procesów do każdego uruchomienia. Procesy są podstawowym mechanizmem separacji działających programów, każdy proces posiada własne:
Nowe procesy w systemie UNIX tworzone są poprzez klonowanie istniejących. Wywołanie funkcji fork() w procesie powoduje powstanie drugiego egzemplarza procesu z identycznymi cechami, tylko innym id. Klon od procesu bazowego może zostać odróżniony poprzez wartość zwróconą przez fork().
Inna funkcja – exec() powoduje zastąpienie kodu binarnego wykonywanego przez dany proces kodem programu podanego jako parametr funkcji exec(). Oznacza to, że uruchamianie nowego programu w systemie jest dwuetapowe: najpierw proces wykonuje operację fork(), a następnie klon powstały w ten sposób wywołuje exec(). Ten mechanizm jednoznacznie wyjaśnia jakie zmienne środowiskowe, wartości umask, itp będzie posiadał program uruchomiony np. z powłoki.
Tworzone w ten sposób procesy tworzą drzewo – każdy proces ma swojego rodzica – proces, który go utworzył. Proces tworzący (macierzysty) jest odpowiedzialny za odpowiednie obsłużenie zakończenia potomka, w przeciwnym wypadku proces potomek po zakończeniu programu przechodzi w stan zombie – zajmuje niewielką ilość zasobów z informacjami, które powinien odczytać proces-rodzic. W przypadku, gdy rodzic zakończy się wcześniej niż potomek, potomek jest adoptowany przez proces od PID 1 (init lub systemd).
Linux, (a także kilka innych systemów uniksowych), udostępnia specjalny system plików zawierający informacje o procesach w systemie, montowany w /proc. Uwaga: to nie jest przenośne, np. większość systemów z rodziny BSD nie ma odpowiednika /proc.
$ ls /proc # zawiera podkatalogi z id procesów
Dla każdego z procesów dostępny jest szereg informacji, m.in:
Szczegółowy opis można znaleźć w podręczniku systemowym:
$ man 5 proc
Istnieje wiele narzędzi, które podają informacje o procesach, część z nich zostanie omówiona w ćwiczeniu:
Sygnały są najprostszym mechanizmem komunikacji między procesami. Standard POSIX definiuje zbiór sygnałów, które można przesłać do dowolnego procesu. W reakcji na określony sygnał działający proces wykona pewną akcję zdefiniowaną przez programistę. Dużą grupę sygnałów stanowią mniej lub bardziej grzeczne żądania zakończenia procesu. Pełna lista sygnałów dostępna jest w podręczniku systemowym: man 7 signal, najważniejsze z nich:
Uwaga: programista może napisać własne funkcje obsługi dla prawie wszystkich sygnałów, wyjątkami są SIGKILL i SIGSTOP, których działania nie da się zmienić. W przypadku, gdy programista nie przewidział obsługi danego sygnału, domyślnym zachowaniem jest zakończenie lub zignorowanie, szczegóły w manualu. Zazwyczaj użytkownik może wysyłać sygnały tylko do uruchomionych przez siebie procesów.
Są dwa podstawowe programy służące do wysyłania sygnałów do procesów:
Procesy mogą być organizowane w grupy, które ułatwiają wysłanie sygnału do całej grupy. Typową sytuacją, gdy mamy do czynienia z grupą procesów są procesy działające w obrębie jednego terminala (powłoka, np. bash i wszystkie programy uruchomione z tej powłoki).
Większość systemów operacyjnych udostępnia metody śledzenia interakcji procesu z systemem operacyjnym (działania takie jak otwieranie, zamykanie plików, odczyty, zapisy, połączenia sieciowe, …). Jest to narzędzie pomocne w debugowaniu programów, również tych, dla których niedostępny jest kod źródłowy. Operacja ta nazywana jest śledzeniem procesu (process tracing). W zależności od systemu operacyjnego mogą być dostępne różne narzędzia do śledzenia procesów. W typowym GNU/Linuksie jest to strace.
Wywołać pstree bez parametrów. Zaobserwować hierarchię procesów. Pstree wyświetla również wątki – nazwy w nawiasach klamrowych. Są to mniejsze jednostki wykonania wewnątrz procesów, proszę je zignorować.
Zwrócić uwagę na istnienie procesu o identyfikatorze 1 (tradycyjnie o nazwie init, w niektórych linuksach, np. w Arch jest to proces o nazwie systemd). Jedną z funkcji procesu o id 1 jest przejmowanie (adoptowanie) wszystkich tych procesów, których proces macierzysty już się zakończył. Przykład:
$ echo $$ # drukuje PID bieżącego procesu powłoki
$ nohup mousepad & # uruchomienie w tle procesu edytora tekstu
$ pstree -p # drzewo procesów -- znaleźć powłokę i mousepad
Zamknąć bieżące okno terminala i otworzyć nowe. W tym momencie edytor tekstu dalej działa, mimo że zakończył się jego proces-rodzic. Otworzyć ponownie pstree i odnaleźć proces edytora tekstu. Jaki proces jest teraz rodzicem?
$ pstree -p
Program ps – lista procesów.
$ ps # lista procesów przypisanych do bieżącego terminala
Standard POSIX specyfikuje kilka opcji kontrolujących które procesy zostaną wyświetlone na liście. Ze względów historycznych większość implementacji ps, poza opcjami POSIX akceptuje również opcje w stylu pochodzącym ze starych systemów BSD. W tym ćwiczeniu należy użyć opcji w stylu POSIX, proszę jednak mieć na uwadze, że szukając informacji w internecie mogą Państwo znaleźć opcje w innych konwencjach. Przykład – wyświetlenie wszystkich procesów w systemie:
$ ps -e # w opcje w stylu POSIX
$ ps aux # opcje w stylu BSD
Policzyć ile procesów działa w systemie i ile z nich jest uruchomionych przez Państwa użytkownika. Użyć programu wc i operatora potoku |
ps -U login # lista procesów utworzonym prze użytkownika login
Nie zawsze jest tak, że jedna aplikacja widziana przez użytkownika działa jako dokładnie jeden proces. Uruchomić przeglądarkę chrome (niekoniecznie z terminala) i otworzyć kilka stron w kartach. Wyświetlić listę procesów. Czy jest tylko jeden proces chrome?
System plików /proc. Za pomocą $$ można sprawdzić PID powłoki:
$ echo $$
$ ps # czy PID wykazany przez ps jest taki sam?
$ cd /proc/$$ # wejście do katalogu opisującego proces powłoki
$ ls -l cwd exe # symlinki do katalogu roboczego procesu i wykonywanego programu
$ less environ # zmienne środowiskowe procesu
Inną użyteczną informacją są pliki otwarte przez dany proces. Otworzyć drugi terminal i uruchomić program less do przejrzenia jakiegoś pliku:
less /etc/passwd
Pozostawić otwarty less, w innym terminalu za pomocą ps ustalić PID uruchomionego less. Wejść do odpowiedniego katalogu w /proc i obejrzeć pliki otwarte przez less (
$ cd /proc/<pid>/fd
$ ls -l # symlinki do otwartych plików
Poza otwartym plikiem passwd, widać trzy deskryptory: 0 (STDIN), 1 (STDOUT), 2 (STDERR), prawdopodobnie wszystkie wskazują na urządzenie pseudoterminala (/dev/pts/..)
top – interaktywny monitor procesów.
$ top # program top można zakończyć wciskając q
Szczegóły działania programu top są specyficzne dla każdego systemu operacyjnego. W trakcie działania programu top można przełączać widoki i tryby wyświetlania z użyciem komend, na przykład wciśnięcie V (wielkie v, inaczej: trzeba wcisnąć Shift+V), spowoduje przełączenie między widokiem płaskim a widokiem lasu (zbioru drzew) procesów. Informacje o widokach i komendach dostępnych w danej wersji top należy szukać w manualu
$ man top
Uruchomić obciążające procesor zadanie, na przykład wyliczanie pierwiastka z 2 z dokładnością do 100 000 miejsc po przecinku:
$ bc # kalkulator ustalonej precyzji
scale=100000
sqrt(2)
quit
W tym samym czasie w drugim terminalu uruchomić top. Przełączyć widok na płaską listę procesów. Domyślnie procesy sortowane są według zużycia czasu procesora.
Nie czekając na zakończenie bc przejść do kolejnego ćwiczenia.
Sygnały i program kill. Program kill służy do wysyłania sygnałów do procesów.
SIGSTOP and SIGCONT zatrzymanie i wznowienie procesu.
Otworzyć trzecie okno terminala. Jeśli obliczenie bc się już zakończyło, ponowić. W programie top można odczytać PID procesu bc. Wysłać do tego procesu sygnał stop:
$ kill -s STOP <pid> # wysłanie SIGSTOP do procesu
Wrócić do programu top. Czy bc dalej intensywnie liczy? Czy obliczenie się zakończyło? Proces bc jest w tej chwili zatrzymany. Można go wznowić wysyłając SIGCONT
$ kill -s CONT <pid>
Zaobserwować kontynuację zużycia procesora. Sygnały SIGSTOP i SIGCONT można też wysłać za pomocą kombinacji klawiszy do procesu działającego w danym terminalu. Przełączyć okno na to, w którym działa bc. Wcisnąć Ctrl-s – spowodowało to wysłanie SIGSTOP. Wcisnąć Ctrl-q – kontynuacja. Proszę zwrócić uwagę, że zatrzymany proces nie reaguje również na działania użytkownika, na przykład wciśnięcie Ctrl-s w trakcie przeglądania manuala powoduje brak reakcji do momentu wciśnięcia Ctrl-q.
Kończenie procesów. Najważniejsze sygnały używane do kończenia procesów to SIGKILL, SIGTERM, SIGINT, SIGQUIT. Przypomnienie: Trzy sygnały – SIGINT, SIGQUIT i SIGTERM mogą zostać obsłużone przez programistę, na przykład program może zapisać zmiany na dysk i zakończyć się po odebraniu sygnału tego typu. SIGKILL nie możne być zignorowany ani obsłużony przez proces.
SIGINT jest zazwyczaj wysyłany z terminala, poprzez kombinację klawiszy Ctrl-c. Spróbować wysłać sygnał SIGINT do procesu bc. Sygnał został zignorowany. Uruchomić:
$ tail -f /etc/passwd
Wcisnąć Ctrl-c – ten program nie ignoruje SIGINT. Kolejnym sygnałem, który można wysłać z użyciem kombinacji klawiszy w terminalu jest SIGQUIT, wysyłany z użyciem Ctrl+\. Uruchomić kolejny raz obliczenie w bc i wysłać SIGQUIT – zaobserwować, że bc nie ignoruje tego sygnału.
Destrukcyjny eksperyment: program kill pozwala wysłać sygnał do wszystkich procesów, do których uprawnienia pozwalają użytkownikowi wysyłać sygnały. Operacja ta osiągana jest przez podanie -1 jako identyfikatora procesu. Zapisać wszystkie dane, których nie chcą Państwo stracić i wykonać:
$ kill -s KILL -1
Sygnał SIGHUP – wysyłany do procesów, gdy ich terminal kontrolujący zostaje wyłączony. Zazwyczaj oznacza to zakończenie wszystkich procesów z nim skojarzonych.
Uruchomić program mousepad z terminala. Zamknąć okno terminala i zaobserwować efekt.
Istnieje program, który pozwala uruchomić zadany program poza kontekstem terminala, przez co nie zostanie on zakończony (ale taki program nie może już używać terminala jako wejścia/wyjścia:
$ nohup mousepad # nie zakończy się po zamknięciu okna terminala
Uwaga: program top może również posłużyć do wysyłania sygnałów, należy w tym celu użyć polecenia k.
Sprawdzić uprawnienia: spróbować wysłać sygnał SIGKILL (używając top lub kill) do jakiegoś procesu użytkownika root. Czy jest to dopuszczalne?
Zarządzanie zadaniami w powłoce. Powłoka pozwala na przełączanie się między kilkoma programami działającymi w obrębie terminala.
Zatrzymywanie zadań z użyciem ctrl-z: Uruchomić bc i rozpocząć długie obliczenie. Wcisnąć ctrl-z. Ponownie pojawił się znak zachęty powłoki, ale program bc nie zakończył się, można go zobaczyć w liście zadań:
$ jobs
$ top # zobaczyć, że bc nie używa procesora
Można wznowić zatrzymane zadanie na dwa sposoby – przywołując je na pierwszy plan – fg lub kontynuując w tle – bg. Parametrem poleceń bg i fg jest numer zadania z listy jobs
$ bg 1 # jeśli 1 to numer zadania bc w jobs
$ jobs # teraz zadanie 1 działa
Uruchomić manual do bc i również zatrzymać ctrl-z. Teraz jobs powinien wyświetlić dwa zadania.
Przywołać wybrane z nich na pierwszy plan (zastąpić i numerem zadania):
$ fg i
Można też uruchomić zadanie tak, aby od razu działało w tle, używając operatora &
$ bc &
$ jobs
Uwaga: zadania działające w tle mogą wypisywać komunikaty na standardowe wyjście. W takim układzie efekt jest podobny do tego, który znają Państwo z programu wrtie.
Śledzenie procesów. Porównać wyjście:
$ strace ls -f
$ strace ls -l
Zwrócić uwagę, że ls -l wymaga znacznie większej liczby wywołań systemowych do wypisania wyjścia. Nazwy wypisywane przez strace są tożsame nazwami funkcji systemowych, które poznają Państwo na przedmiocie Systemy Operacyjne.