Przejdź do treści
Lekcja 19 z 117

Kurs › Moduł 2 › Lekcja 19

Co to jest proces? ps od środka

⏱ ok. 10 min czytania· moduł 2: Procesy i jak działa system

Po tej lekcji: kursant rozumie różnicę między programem a procesem, wie, co to PID i PPID, jak powstaje nowy proces (fork i exec), i umie znaleźć dowolny proces przez ps, pgrep, pstree i folder /proc.

Wstęp

Piątek, 16:40. Serwer zwalnia. Klikać nie ma gdzie, bo serwer nie ma myszki ani okienek. Ale jest jedna linijka:

ps -eo pid,user,%cpu,%mem,comm --sort=-%cpu | head -5
  PID USER     %CPU %MEM COMMAND
 2214 eryk     38.5  0.1 gzip
 3526 root      4.3  1.2 apt-get
 5959 root      1.0  0.2 python3
    1 root      0.2  0.1 systemd

Pięć najbardziej zapracowanych procesów, od największego zjadacza procesora. W trzy sekundy wiesz, kogo zapytać „co ty robisz?”. Dziś nauczysz się czytać taką listę i zbudujesz ją sam.

Program a proces: przepis i kucharz

Wyobraź sobie kuchnię w restauracji. Na półce leży książka z przepisami. Przepis na pierogi sam z siebie nic nie gotuje. Leży i czeka.

Dopiero kiedy kucharz bierze przepis i zaczyna lepić, coś się dzieje. Kucharz ma swój stół, swoje garnki, swoje zamówienie i swój numerek na fartuchu.

W Linuksie przepis to program: plik na dysku, na przykład /usr/bin/sleep. Kucharz to proces: uruchomiony program, który dostał od systemu pamięć, numer, właściciela i otwarte pliki.

Jeden przepis, wielu kucharzy. Jeśli trzy razy uruchomisz sleep, masz jeden program i trzy procesy. Każdy z własnym numerem, każdy niezależny.

PID i PPID: numer na fartuchu i kto cię zatrudnił

Każdy proces dostaje od jądra numer. To PID, od „process ID”, identyfikator procesu. Jak numerek z automatu w urzędzie: unikalny, dopóki sprawa trwa. Kiedy proces się kończy, numer wraca do puli i kiedyś dostanie go ktoś inny.

Drugi numer to PPID, „parent process ID”, czyli numer rodzica. Każdy proces ktoś uruchomił. Kucharza zatrudnił szef kuchni, szefa kuchni kierownik zmiany.

echo $$
bash -c 'echo "ja: $$, rodzic: $PPID"'
4900
ja: 5137, rodzic: 4900

$$ to PID Twojego Basha. Potem uruchamiamy drugiego Basha w środku pierwszego i pytamy go o PID i o rodzica. Rodzic to 4900, czyli my. Rodzinka się zgadza.

A kto jest na samej górze? Proces numer 1. Na każdej współczesnej maszynie z Ubuntu i z RHEL to systemd, czyli kierownik zmiany. Jądro uruchamia go jako pierwszego przy starcie, a on uruchamia całą resztę. Lekcja o starcie systemu i o systemd jeszcze przed nami.

fork i exec: jak rodzi się proces

Teraz rzecz, która brzmi dziwnie, ale wyjaśnia pół tego modułu. W Linuksie nowy proces nie powstaje z niczego. Powstaje w dwóch krokach.

Krok 1: fork, czyli klonowanie. Kucharz staje przy stole i robi swoją kopię. Identyczną: ten sam fartuch, te same garnki, nawet to samo zamówienie w ręku. Jedyna różnica: klon dostaje nowy numer na fartuchu, a jego rodzicem jest oryginał.

Krok 2: exec, czyli wymiana przepisu. Klon wyrzuca stary przepis i bierze nowy. Numer na fartuchu zostaje, ale robota jest już zupełnie inna.

Kiedy wpisujesz w Bashu ls, Bash robi forka, czyli swoją kopię, a ta kopia robi exec na program ls. Dlatego rodzicem ls jest Twój Bash.

Czy da się zobaczyć exec na własne oczy? Da się:

bash -c 'echo "Mój PID: $$"; exec sleep 100' &
ps -o pid,ppid,cmd -p $!
[1] 5574
Mój PID: 5574
  PID  PPID CMD
 5574  4900 sleep 100

Bash pod numerem 5574 przedstawił się, a potem komendą exec zamienił się w sleep. Pod tym samym numerem siedzi już zupełnie inny program. Jak aktor, który w połowie sztuki przebrał się za kogoś innego, ale nikt nie zmienił mu nazwiska w programie teatru. $! to PID ostatniego procesu uruchomionego w tle, przyda się nam jeszcze nie raz.

ps: zdjęcie kuchni

ps to „process status”. Robi zdjęcie: kto teraz gotuje. Jedno zdjęcie, nie film. Film będzie w lekcji o top.

ps
  PID TTY          TIME CMD
 4900 pts/3    00:00:00 bash
 5104 pts/3    00:00:00 ps

Bez opcji ps pokazuje tylko procesy z Twojego terminala. Tu jest Bash i sam ps, który złapał się na własnym zdjęciu. Selfie.

TTY to terminal, do którego proces jest podpięty. pts/3 to trzecie okienko pseudo-terminala, takie, jakie dostajesz przez SSH albo w oknie terminala. TIME to nie czas od uruchomienia, tylko czas, przez który proces naprawdę używał procesora. Kucharz może stać w kuchni od rana i mieć dwie minuty faktycznego lepienia.

ps aux i ps -ef: dwa dialekty

Cały system zobaczysz na dwa sposoby. I tu Linux jest jak Polska: dwa dialekty, oba poprawne, każdy region upiera się przy swoim.

ps aux
ps -ef

ps aux to styl BSD, bez myślnika. ps -ef to styl uniksowy, z myślnikiem. Oba pokazują wszystkie procesy, tylko z innymi kolumnami.

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         2  0.0  0.0      0     0 ?        S    16:17   0:00 [kthreadd]
root         4  0.0  0.0      0     0 ?        I<   16:17   0:00 [kworker/R-rcu_gp]
  • USER — właściciel procesu, czyli czyje klucze ten kucharz nosi.
  • %CPU i %MEM — ile procesora i pamięci RAM zużywa.
  • VSZ — pamięć wirtualna w kilobajtach: wszystko, co proces sobie zarezerwował. Jak stolik zarezerwowany na dwadzieścia osób.
  • RSS — pamięć, którą naprawdę zajmuje w RAM-ie. Ilu gości faktycznie przyszło. Patrz na RSS, nie na VSZ.
  • TTY — terminal. Znak zapytania znaczy: proces nie ma terminala.
  • STAT — stan: S śpi, R pracuje i tak dalej. Cały następna lekcja jest o stanach.
  • START — kiedy wystartował. TIME — ile czasu procesora zjadł.
  • COMMAND — komenda razem z argumentami. Nazwa w nawiasach kwadratowych, jak [kthreadd], to wątek jądra. O nich też w następnej lekcji.

A ps -ef ma UID, PID, PPID, C (zużycie procesora), STIME (start), TTY, TIME, CMD. Admini lubią -ef właśnie za kolumnę PPID: od razu widać, kto jest czyim rodzicem.

-o i --sort: zdjęcie na zamówienie

Najbardziej przydatna para opcji. -o mówi, jakie kolumny chcesz zobaczyć, --sort po czym sortować.

ps -eo pid,user,%cpu,%mem,comm --sort=-%cpu | head -5
ps aux --sort=-%mem | head -4
ps -o pid,ppid,user,stat,etime,cmd -p 4900
ps -C sleep -o pid,ppid,etime,cmd
  • -e to wszystkie procesy, czyli „every”.
  • -o pid,user,... to lista kolumn po przecinku, bez spacji.
  • --sort=-%cpu sortuje po procesorze. Minus znaczy malejąco, od największego. Bez minusa rośnie.
  • -p 4900 pokazuje jeden konkretny proces po PID.
  • -C sleep pokazuje procesy o danej nazwie.
  • etime to czas od uruchomienia, czyli ile kucharz już stoi w kuchni.

To jest dokładnie linijka z haka. Nic magicznego, tylko kolumny na zamówienie.

pgrep: „gdzie jest ten kucharz?”

Często nie potrzebujesz zdjęcia całej kuchni. Chcesz tylko numer jednego kucharza.

sleep 300 &
sleep 200 &
pgrep sleep
pgrep -a sleep
pgrep -u root -c
pgrep -x slee; echo "kod: $?"
5738
5739
5738 sleep 300
5739 sleep 200
94
kod: 1
  • pgrep sleep wypisuje same PID-y. Idealne do skryptów.
  • -a dopisuje całą komendę, -l samą nazwę.
  • -u root tylko procesy roota, a -c zamiast listy podaje liczbę.
  • -x wymaga dokładnej nazwy. slee nie pasuje do sleep, więc nic nie ma i kod wyjścia to 1. Kciuk w dół.
  • -f szuka w całej linii komend, nie tylko w nazwie. Uwaga na nie, w lekcji o sygnałach zobaczysz, jak potrafi ugryźć.

pstree: drzewo genealogiczne

sleep 1000 &
pstree -p $$
bash(4900)-+-pstree(5989)
           `-sleep(5988)

pstree rysuje rodzinę jako drzewo, a -p dopisuje numery. Tu widać mój Bash i jego dwoje dzieci: sleep w tle i sam pstree. Z opcją -s dostajesz odwrotny kierunek: przodków aż do procesu numer 1. Na maszynie wirtualnej zobaczysz coś w stylu systemd(1) na górze, dalej serwer SSH, Twoją sesję i Twojego Basha. Cała linia rodu, jak w kronice szlacheckiej.

/proc/PID: teczka każdego kucharza

Pamiętasz /proc z lekcji o mapie Linuksa? Folder, którego nie ma na dysku. Każdy proces ma tam swój podfolder o nazwie równej PID. To jego teczka osobowa.

sleep 1000 &
grep -E '^(Name|State|Pid|PPid|Uid)' /proc/$!/status
tr '\0' ' ' < /proc/$!/cmdline; echo
ls -l /proc/$!/exe /proc/$!/cwd
Name:   sleep
State:  S (sleeping)
Pid:    5988
PPid:   4900
Uid:    1000    1000    1000    1000
sleep 1000 
lrwxrwxrwx 1 eryk eryk 0 Nov  3 16:26 /proc/5988/cwd -> /home/eryk
lrwxrwxrwx 1 eryk eryk 0 Nov  3 16:26 /proc/5988/exe -> /usr/bin/sleep
  • status — wizytówka: nazwa, stan, PID, rodzic, właściciel, pamięć.
  • cmdline — pełna komenda. Argumenty są tam oddzielone niewidocznym znakiem zerowym, dlatego tr zamienia go na spacje.
  • exe — drogowskaz do pliku programu. Który przepis ten kucharz trzyma w ręku.
  • cwd — folder, w którym proces pracuje.
  • fd — otwarte pliki. Przyda się bardzo, kiedy będziemy szukać, kto trzyma usunięty plik i zjada dysk.

Jak to działa pod spodem

W jądrze każdy proces to struktura danych, kartka w kartotece kierownika. Jest na niej numer, rodzic, stan, właściciel, pamięć, otwarte pliki. Jądro trzyma te kartki u siebie i nikomu ich nie pożycza.

Ale pokazuje je przez okienko: /proc. I tu niespodzianka: ps, pgrep, pstree i top nie mają żadnego tajnego dostępu do jądra. Po prostu czytają pliki z /proc, jak Ty cat-em. Sprawdź sam:

ls -d /proc/[0-9]* | wc -l
ps -e --no-headers | wc -l
153
153

Tyle samo folderów z numerami, ile procesów na liście. „Wszystko jest plikiem” znowu robi robotę.

Ściąga flag

KomendaFlagaCo robi
ps(bez opcji)procesy z tego terminala
psauxwszystkie procesy, styl BSD, z %CPU, %MEM, RSS
ps-efwszystkie procesy, styl uniksowy, z PPID
ps-ewszystkie procesy
ps-o kol1,kol2wybrane kolumny
ps--sort=-kolsortuje malejąco po kolumnie
ps-p PIDjeden proces po numerze
ps-C nazwaprocesy o danej nazwie
ps-u użytkownikprocesy danego użytkownika
ps--ppid PIDdzieci danego procesu
pgrep-a / -lPID z pełną komendą / z nazwą
pgrep-xdokładna nazwa
pgrep-fszuka w całej linii komend
pgrep-u / -cpo użytkowniku / tylko liczba
pstree-pdopisuje PID-y
pstree-s PIDprzodkowie procesu aż do PID 1

⚠ Typowy błąd

ps aux | grep "sleep 777"
root     28990  0.0  0.0   2740  1772 pts/3    S    16:33   0:00 sleep 777
root     28993  0.0  0.0   3572  1984 pts/3    S+   16:33   0:00 grep --color=auto sleep 777

Szukasz jednego procesu, a dostajesz dwa. Drugi to… sam grep. Rura uruchamia obie komendy jednocześnie, pamiętasz z lekcji 7? Więc kiedy ps robi zdjęcie, grep już stoi w kuchni z kartką „sleep 777” w ręku i łapie się na fotkę. Skrypt, który liczy linijki, żeby sprawdzić, czy usługa działa, zawsze dostanie co najmniej jedną. Usługa leży, a skrypt melduje, że wszystko gra.

Dwa wyjścia. Porządne: pgrep -a -f "sleep 777". Sprytne: ps aux | grep "[s]leep 777". Wzorzec [s]leep pasuje do słowa „sleep”, ale w linii komend samego grep-a stoi [s]leep, z nawiasami, i ten napis już do wzorca nie pasuje. Kucharz w przebraniu nie rozpoznaje sam siebie w lustrze.

✎ Zadanie

Uruchom sleep 777 &.

  • Znajdź jego PID trzema sposobami: przez $!, przez pgrep i przez ps.
  • Sprawdź jego PPID i porównaj z echo $$.
  • W /proc/PID sprawdź, jaki plik programu wykonuje ten proces i w jakim folderze pracuje.
  • Na koniec jedną linijką wypisz pięć procesów, które zajmują najwięcej pamięci RAM.
Pokaż rozwiązanie
sleep 777 &
echo $!
pgrep -a -f "sleep 777"
ps -o pid,ppid,cmd -p $!
echo $$
ls -l /proc/$!/exe /proc/$!/cwd
ps -eo pid,user,rss,comm --sort=-rss | head -6
[1] 30999
30999
30999 sleep 777
  PID  PPID CMD
30999 17206 sleep 777
17206
/proc/30999/cwd -> /home/eryk
/proc/30999/exe -> /usr/bin/sleep
  PID USER       RSS COMMAND
 2950 eryk     412880 firefox
 2411 eryk     287316 gnome-shell

Trzy sposoby, jeden numer. PPID równa się $$, bo to Twój Bash zatrudnił tego kucharza. exe pokazuje /usr/bin/sleep, czyli przepis, a cwd folder, w którym stałeś. A ranking pamięci sortujemy po rss, bo to prawdziwa zajętość RAM-u, a nie rezerwacja na wyrost. Jeśli w pierwszej linijce zobaczyłeś przeglądarkę — gratuluję, Twój komputer działa zgodnie z prawami fizyki.

Co dalej

Wiesz już, kto gotuje. W następnej lekcji zobaczysz, że kucharze bywają bardzo różni: tacy, co pracują przy Tobie, tacy w tle, demony bez terminala, wątki, sieroty i zombie. I zrobimy prawdziwego zombie, na żywo.