Procesy

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).

Informacje o procesach

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

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:

Grupy 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).

Śledzenie procesów

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.

  1. 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
  2. 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?

  3. 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 ( zastąpić PID 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/..)

  4. 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.

  5. Sygnały i program kill. Program kill służy do wysyłania sygnałów do procesów.

    1. 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.

    2. 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 
    3. 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?

  6. 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.

  7. Ś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.