Programy w tle
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] 616Dwie 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: 130Najczę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) catcat 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 grep23325 1 S sleep 3002
23329 1 S sleep 3003Zwykł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 700Po 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:
disownbez argumentów — bieżące zadanie.disown -a— wszystkie zadania.disown -h %1— zostawia zadanie wjobs, ale oznacza je, żeby nie dostałoSIGHUP. 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ób | Przeżyje zamknięcie terminala | Można wrócić i popatrzeć | Kiedy |
|---|---|---|---|
komenda & | nie | tak, fg, dopóki terminal żyje | krótkie rzeczy, kiedy i tak siedzisz przy terminalu |
nohup komenda > log 2>&1 & | tak | tylko log w pliku | długi skrypt bez potrzeby podglądu |
Ctrl+Z, bg, disown | tak | nie | ratunek, gdy już leci |
| tmux lub screen | tak | tak, attach | długa praca interaktywna przez SSH |
| usługa systemd | tak, przetrwa nawet restart | logi w dzienniku | rzeczy, 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/bashPGID— numer grupy. Każdysleepma 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 samps. I tylko on ma wSTATplusik.
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
| Komenda | Flaga / zapis | Co robi |
|---|---|---|
komenda & | uruchamia w tle | |
jobs | -l | zadania z PID-ami |
jobs | -p | same PID-y zadań |
jobs | -r / -s | tylko działające / tylko zatrzymane |
fg | %N | zadanie N na pierwszy plan |
bg | %N | wznów zatrzymane zadanie N w tle |
| Ctrl+C | SIGINT do pierwszego planu, kod 130 | |
| Ctrl+Z | SIGTSTP, zatrzymanie | |
kill | %N | sygnał do zadania N |
%%, %+, %- | bieżące, bieżące, poprzednie | |
%nazwa, %?tekst | zadanie po początku komendy / po fragmencie | |
nohup | > log 2>&1 & | ignoruj SIGHUP, wyjście do pliku |
disown | %N | wykreśl zadanie z listy Basha |
disown | -h %N | zostaw na liście, ale bez SIGHUP |
disown | -a | wszystkie zadania |
⚠ Typowy błąd
nohup ./kopia.shBez & 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: 130I 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.