Przejdź do treści
Lekcja 24 z 117

Kurs › Moduł 2 › Lekcja 24

Programy w tle

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

Po tej lekcji: kursant uruchamia programy w tle przez &, zarządza nimi przez jobs, fg, bg, Ctrl+Z i Ctrl+C, rozumie, czemu zamknięcie terminala zabija jego zadania, i chroni długą pracę przez nohup i disown.

Wstęp

Odpalasz przez SSH kopię zapasową bazy. Ma trwać dwie godziny. Po czterdziestu minutach pociąg wjeżdża w tunel, Wi-Fi znika, SSH się zrywa. Łączysz się ponownie, a kopii… nie ma. Proces zginął razem z połączeniem. Czterdzieści minut w plecy, a kopia nadal nie istnieje.

W lekcji 18 był tmux, który to załatwia. Ale nie zawsze go masz, nie zawsze pamiętasz, a czasem już odpaliłeś robotę i dopiero potem przypomniałeś sobie o tunelu. Dziś poznasz narzędzia wbudowane w samego Basha: jak wysłać robotę w tło, jak ją zatrzymać, przywołać i jak uchronić ją przed zamknięciem terminala. Nawet jeśli już leci.

Pierwszy plan i tło: kucharz przy stoliku i na zapleczu

Pamiętasz z lekcji 20? Proces pierwszoplanowy to kucharz, który gotuje przy Twoim stoliku. Dopóki nie skończy, nic innego nie zamówisz. Proces w tle to kucharz na zapleczu.

Znak & na końcu komendy wysyła ją na zaplecze:

sleep 700 &
[2] 616

Dwie liczby. W nawiasie numer zadania, po angielsku job. To numer w notesie Twojego Basha, ważny tylko w tym jednym terminalu. Druga liczba to PID, znany całemu systemowi. Numerek z restauracji i PESEL. Do zadania odwołujesz się przez procent: %2.

jobs: notes kelnera

jobs
jobs -l
jobs -p
[1]-   579 Running                 sleep 600 &
[2]+   616 Running                 sleep 700 &

jobs pokazuje zadania tego terminala. -l dopisuje PID, -p wypisuje same PID-y, świetne do skryptów. Plus oznacza zadanie bieżące, czyli to, którego dotyczy fg albo bg bez numeru. Minus to poprzednie.

Sposób zapisu zadania:

  • %1, %2 — po numerze,
  • %% albo %+ — bieżące, %- — poprzednie,
  • %sleep — zadanie, którego komenda zaczyna się od „sleep”,
  • %?700 — zadanie, w którego komendzie gdziekolwiek występuje „700”.

Ważne: jobs widzi tylko dzieci tego Basha. Zadania z drugiego okna albo z innej sesji SSH są dla niego niewidzialne. Do nich masz ps i pgrep.

Ctrl+C, Ctrl+Z, fg i bg

Ctrl+C wysyła SIGINT do tego, co jest na pierwszym planie. Kucharz od Twojego stolika odkłada nóż i wychodzi. Kod wyjścia 130, czyli 128 plus 2.

Ctrl+Z wysyła SIGTSTP, zatrzymanie. Kucharz zamiera w pół ruchu, jak w „Baba Jaga patrzy”. Nie pracuje, ale też nie wychodzi. Terminal od razu wraca do Ciebie.

bg od „background” wznawia zatrzymane zadanie w tle. Kucharz idzie dokńczyć na zaplecze.

fg od „foreground” sprowadza zadanie na pierwszy plan. Kucharz wraca do Twojego stolika.

Cały taniec na żywo:

sleep 600
# Ctrl+Z
jobs
bg
sleep 700 &
jobs -l
fg %1
# Ctrl+C
echo "kod: $?"
$ sleep 600
^Z
[1]+  Stopped                 sleep 600
$ jobs
[1]+  Stopped                 sleep 600
$ bg
[1]+ sleep 600 &
$ sleep 700 &
[2] 616
$ jobs -l
[1]-   579 Running                 sleep 600 &
[2]+   616 Running                 sleep 700 &
$ fg %1
sleep 600
^C
$ echo "kod: $?"
kod: 130

Najczęstsze użycie w prawdziwej pracy: odpalasz coś i dopiero po chwili widzisz, że potrwa dłużej, niż myślałeś. Nie przerywasz. Ctrl+Z, bg i terminal jest Twój, a robota leci dalej.

I druga, sprytna rzecz: Ctrl+Z działa też na edytory. Jesteś w vi, chcesz szybko coś sprawdzić w terminalu? Ctrl+Z, sprawdzasz, fg i jesteś z powrotem w tym samym miejscu pliku. Jak pauza w grze.

Zadanie w tle chce coś od Ciebie

Kucharz na zapleczu może zawołać „ile soli?”. Ale Ty siedzisz przy innym stoliku i go nie słyszysz. Co wtedy?

cat &
jobs -l
[1] 32756
[1]+  Stopped                 cat
[1]+ 32756 Stopped (tty input)     cat

cat bez pliku chce czytać z klawiatury. Proces w tle nie ma prawa czytać z terminala, więc system go zatrzymuje: Stopped (tty input). Kucharz czeka, aż podejdziesz. Wystarczy fg i możesz mu odpowiedzieć.

Pisać na ekran proces w tle już może, i to bywa irytujące: jego komunikaty wskakują Ci w środek tego, co akurat piszesz. Dlatego przy pracy w tle prawie zawsze przekierowujesz wyjście do pliku. Dwa głośniki z lekcji 6 wracają: > plik.log 2>&1.

Zamykasz terminal i co wtedy

Tu jest sedno lekcji. Kiedy zamykasz okno terminala albo zrywa się SSH, Twój Bash dostaje SIGHUP, czyli „odłożono słuchawkę”. I zanim sam się zakończy, przekazuje SIGHUP wszystkim swoim zadaniom, działającym i zatrzymanym. Tak opisuje to man bash, w części o sygnałach. A domyślna reakcja na SIGHUP to zakończenie procesu.

Czyli: kelner idzie do domu i zamyka wszystkie swoje stoliki, także te na zapleczu. Dlatego kopia z haka zginęła, mimo że była w tle.

Sprawdźmy to. W nowym oknie terminala uruchamiam cztery procesy na cztery sposoby, a potem zamykam okno:

sleep 3001 &
nohup sleep 3002 &
sleep 3003 & disown
sleep 3004 &

Z innego okna sprawdzam, kto przeżył:

ps -eo pid,ppid,stat,cmd | grep 'sleep 300[1-4]' | grep -v grep
23325     1 S    sleep 3002
23329     1 S    sleep 3003

Zwykłe &, czyli 3001 i 3004, zginęły. Przeżyły tylko dwa: ten z nohup i ten z disown. Oba stały się sierotami i przejął je proces numer 1. Kierownik zmiany adoptuje, pamiętasz?

nohup: zatyczki do uszu

nohup od „no hang up”. Uruchamia program tak, że ten ignoruje SIGHUP. Kucharz dostał zatyczki do uszu: kelner wychodzi i krzyczy „zamykamy”, a on gotuje dalej.

nohup sleep 100 &
[3] 300
nohup: ignoring input and appending output to 'nohup.out'

nohup robi przy okazji porządek z wejściem i wyjściem. Klawiaturę odcina, bo terminala za chwilę może nie być. A wszystko, co program wypisze, dopisuje do pliku nohup.out w bieżącym folderze, bo ekranu też może zaraz nie być.

Lepiej samemu wskazać plik z logiem:

nohup ./kopia.sh > kopia.log 2>&1 &

To jest wzorzec do zapamiętania: nohup, komenda, przekierowanie obu głośników do pliku i & na końcu. Komunikat nohup wówczas też ląduje w logu, a w terminalu jest cisza.

Zwróć uwagę: nohup sam nie wysyła w tło. Za to odpowiada &. To dwie osobne rzeczy: nohup daje zatyczki do uszu, & wysyła na zaplecze.

disown: wypisanie z notesu

A jeśli już odpaliłeś robotę bez nohup? Nie musisz przerywać. disown wykreśla zadanie z notesu Basha. Bash o nim zapomina, więc przy zamknięciu terminala nie przekaże mu SIGHUP.

fg %2
# ojej, to potrwa godzinę, a ja zaraz wychodzę
# Ctrl+Z
bg
disown %2
jobs
ps -o pid,ppid,cmd -C sleep | grep 700
  616 17206 sleep 700

Po disown jobs milczy, bo zadania nie ma już w notesie. Ale ps pokazuje, że proces żyje dalej. Ctrl+Z, bg, disown: trzy ruchy ratunkowe na wypadek, gdy przypomnisz sobie o tunelu, zanim do niego wjedziesz.

Warianty:

  • disown bez argumentów — bieżące zadanie.
  • disown -a — wszystkie zadania.
  • disown -h %1 — zostawia zadanie w jobs, ale oznacza je, żeby nie dostało SIGHUP. Dalej możesz używać %1.

Jedna uwaga: disown nie przekierowuje wyjścia. Jeśli program pisze na ekran, a ekranu już nie ma, może dostać błąd przy zapisie i się wyłożyć. Jeśli wiesz z góry, że robota będzie długa, od razu użyj nohup z przekierowaniem albo tmuxa.

Co wybrać

SposóbPrzeżyje zamknięcie terminalaMożna wrócić i popatrzećKiedy
komenda &nietak, fg, dopóki terminal żyjekrótkie rzeczy, kiedy i tak siedzisz przy terminalu
nohup komenda > log 2>&1 &taktylko log w plikudługi skrypt bez potrzeby podglądu
Ctrl+Z, bg, disowntaknieratunek, gdy już leci
tmux lub screentaktak, attachdługa praca interaktywna przez SSH
usługa systemdtak, przetrwa nawet restartlogi w dziennikurzeczy, które mają działać zawsze

Admini do długich prac przez SSH najczęściej wybierają tmuxa. nohup przydaje się w skryptach. A to, co ma działać na stałe, robi się usługą systemd, ale to opowieść na lekcja o usługach.

Jak to działa pod spodem

Każde zadanie w Bashu to osobna grupa procesów. Rura grep | sort | uniq to trzy procesy, ale jedno zadanie i jedna grupa. A terminal ma w danej chwili dokładnie jedną grupę na pierwszym planie.

sleep 300 & sleep 400 &
ps -o pid,ppid,pgid,sid,tpgid,stat,cmd
  PID  PPID  PGID   SID TPGID STAT CMD
  313 17206   313 17206   317 S    sleep 300
  314 17206   314 17206   317 S    sleep 400
  317 17206   317 17206   317 R+   ps -o pid,ppid,pgid,sid,tpgid,stat,cmd
17206 17203 17206 17206   317 Ss   /bin/bash
  • PGID — numer grupy. Każdy sleep ma własną, bo to osobne zadania.
  • SID — numer sesji. Wszystko, co wyszło z tego terminala, należy do sesji Basha.
  • TPGID — która grupa jest teraz na pierwszym planie terminala. To 317, czyli sam ps. I tylko on ma w STAT plusik.

Kiedy naciskasz Ctrl+C, to nie Bash wysyła sygnał. Robi to sterownik terminala w jądrze: widzi znak Ctrl+C i wysyła SIGINT do całej grupy z pierwszego planu. Tak samo Ctrl+Z i SIGTSTP. Dlatego Ctrl+C przerywa całą rurę naraz, a zadania w tle nawet tego nie zauważają.

fg i bg to tylko przestawianie zwrotnicy. fg mówi terminalowi „teraz ta grupa jest na pierwszym planie” i wysyła jej SIGCONT. bg wysyła SIGCONT, ale zwrotnicy nie rusza. A nohup przed uruchomieniem programu ustawia ignorowanie SIGHUP i ta decyzja przechodzi na program przez exec. Fork i exec z lekcji 19 wracają po raz kolejny.

Ściąga flag

KomendaFlaga / zapisCo robi
komenda &uruchamia w tle
jobs-lzadania z PID-ami
jobs-psame PID-y zadań
jobs-r / -stylko działające / tylko zatrzymane
fg%Nzadanie N na pierwszy plan
bg%Nwznów zatrzymane zadanie N w tle
Ctrl+CSIGINT do pierwszego planu, kod 130
Ctrl+ZSIGTSTP, zatrzymanie
kill%Nsygnał do zadania N
%%, %+, %-bieżące, bieżące, poprzednie
%nazwa, %?tekstzadanie po początku komendy / po fragmencie
nohup> log 2>&1 &ignoruj SIGHUP, wyjście do pliku
disown%Nwykreśl zadanie z listy Basha
disown-h %Nzostaw na liście, ale bez SIGHUP
disown-awszystkie zadania

⚠ Typowy błąd

nohup ./kopia.sh

Bez & na końcu. Terminal jest zablokowany, bo zadanie siedzi na pierwszym planie. Admin się niecierpliwi i naciska Ctrl+C, żeby „odzyskać terminal”.

nohup: ignoring input and appending output to 'nohup.out'
^C
kod: 130

I kopia nie żyje. nohup daje zatyczki tylko na SIGHUP. Na Ctrl+C, czyli SIGINT, nie chroni. Kod 130 mówi wyraźnie, kto zawinił.

A kiedy już się tak stało, a robota jeszcze leci, ratunek jest prosty i nie wymaga przerywania: Ctrl+Z, potem bg. Zadanie jest w tle, ma swoje zatyczki od nohup, a terminal jest Twój. Na przyszłość: nohup, przekierowanie, &. Zawsze w tym komplecie.

I drugi grzech z tej samej rodziny: exit z terminala, w którym masz zatrzymane zadanie.

$ exit
exit
There are stopped jobs.

Bash ostrzega: „masz zamrożonych kucharzy”. Drugie exit pod rząd zamknie terminal mimo to, a zamrożone zadania zostaną zakończone. Najpierw jobs, potem decyzja: fg, bg z disown albo kill.

✎ Zadanie

Uruchom sleep 1000 &. Uruchom sleep 2000, zatrzymaj go Ctrl+Z i wznów w tle. Pokaż jobs -l. Zakończ pierwszy przez kill %1, drugi przez kill z numerem PID.

Pokaż rozwiązanie
sleep 1000 &
sleep 2000
# Ctrl+Z
bg
jobs -l
[1]-   697 Running                 sleep 1000 &
[2]+   698 Running                 sleep 2000 &

Po Ctrl+Z drugi sleep dostaje stan Stopped, a bg puszcza go dalej w tle. jobs -l pokazuje numer zadania w nawiasie i numer PID obok. Teraz sprzątamy:

kill %1
kill 698
[1]-  Terminated              sleep 1000
[2]+  Terminated              sleep 2000

%1 to numer zadania, który zna tylko Twój Bash. 698 to PID, który zna całe jądro. Jeśli napisałeś kill 1, to poprosiłeś o zamknięcie procesu numer jeden, czyli kierownika zmiany. Na szczęście jako zwykły użytkownik nie masz do tego prawa. Jako root… lepiej nie sprawdzaj.

Co dalej

W kuchni są kucharze ważniejsi i mniej ważni, a niektórym trzeba zabrać część garnków. W następnej lekcji priorytety i limity: nice, renice, ionice, ulimit, cgroups w skrócie i profile tuned na RHEL.