星 «Звезда» 1999

Процессы, сигналы и код возврата

Дополнительный материал к лекции 4 «Переменные, окружение и процессы»: процессы, сигналы и код возврата подробно, с настоящими выводами с сервера. Уроки Дарьи - короткая выжимка отсюда.


6. Процессы и сигналы

Процесс

Процесс - запущенный экземпляр программы. Внешняя команда (ls, python3, ps) запускается отдельным процессом; сам shell - тоже процесс. Встроенные команды shell (cd, echo, export) отдельного процесса не создают - их shell выполняет сам.

У процесса есть:

Кто чей родитель

student@unix-server:~$ echo $$
19
student@unix-server:~$ ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
student     19    13  0 14:38 pts/0    00:00:00 -bash
student     28    19  0 14:38 pts/0    00:00:00 ps -f
student@unix-server:~$ bash
student@unix-server:~$ ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
student     19    13  0 14:38 pts/0    00:00:00 -bash
student     29    19  0 14:38 pts/0    00:00:00 bash
student     36    29  0 14:38 pts/0    00:00:00 ps -f

$$ - PID текущего shell. Новый bash (PID 29) - ребёнок нашего (PPID 19), а ps - ребёнок нового bash.

ps и компания

КомандаЧто показывает
psпроцессы этого терминала
ps -fподробно: владелец, PID, PPID, время старта, команда
ps -e, ps -efвсе процессы системы
ps -u $USERтолько ваши процессы
ps -ef --forestдеревом (Linux; на macOS нет)
pgrep -u "$USER" -f counter.pyPID вашего процесса, в команде которого есть counter.py
top, htopв реальном времени; выход - q

На общем сервере всегда добавляйте -u "$USER": без него pgrep найдёт и чужие процессы с таким же именем.

Долгий процесс для опытов

import signal
import time

def on_sigterm(signum, frame):
    print("Получил SIGTERM: сохраняю результаты и выхожу")
    raise SystemExit(0)

signal.signal(signal.SIGTERM, on_sigterm)
i = 0
while True:
    print(i, flush=True)
    i += 1
    time.sleep(1)

Это counter.py: раз в секунду печатает число, на SIGTERM «убирает за собой» и выходит. Запускаем в окне 1: python3 counter.py, а команды набираем в окне 2. (В квесте его брат - zv-counter.)

/proc/PID - процесс в виде файлов

student@unix-server:~$ PID=$(pgrep -u "$USER" -f counter.py)
student@unix-server:~$ echo $PID
52
student@unix-server:~$ head -n 7 /proc/$PID/status
Name:	python3
Umask:	0022
State:	S (sleeping)
Tgid:	52
Ngid:	0
Pid:	52
PPid:	44

/proc - виртуальная файловая система ядра Linux: файлов на диске нет, данные создаются на лету. Там же /proc/$PID/environ - окружение процесса. На macOS /proc нет.

Сигналы

Сигнал№Как отправитьЧто значитМожно перехватить
SIGINT2Ctrl+Cпрерватьда
SIGTERM15kill PIDвежливо завершитьсяда
SIGKILL9kill -9 PIDубить немедленнонет
SIGTSTP20Ctrl+Zприостановить из терминалада
SIGSTOP19kill -STOP PIDприостановитьнет
SIGCONT18kill -CONT PID, fgпродолжить-
SIGHUP1закрыли терминалтерминал отключилсяда

kill не убивает, а отправляет сигнал. Полный список - kill -l. Номера - для Linux; на macOS у STOP/CONT/TSTP другие. SIGHUP - причина, по которой программа умирает вместе с окном; от него спасают tmux и nohup (урок tmux).

Остановить и продолжить

Окно 2:

student@unix-server:~$ kill -STOP $PID
student@unix-server:~$ ps -o pid,stat,cmd -p $PID
  PID STAT CMD
   52 T    python3 counter.py
student@unix-server:~$ kill -CONT $PID
student@unix-server:~$ ps -o pid,stat,cmd -p $PID
  PID STAT CMD
   52 S    python3 counter.py

Окно 1:

student@unix-server:~$ python3 counter.py
0
...
5

[1]+  Stopped        python3 counter.py
student@unix-server:~$ 6
7
8

T - остановлен, S - спит (наш time.sleep). В окне 1 bash увидел, что программа остановилась, и вернул приглашение - как после Ctrl+Z. После CONT счётчик продолжил, но уже в фоне. Это не баг: остановка активной программы для bash - то же, что Ctrl+Z.

kill и kill -9

Окно 2: kill $PID. Окно 1:

student@unix-server:~$ python3 counter.py
0
...
4
Получил SIGTERM: сохраняю результаты и выхожу
student@unix-server:~$ echo $?
0

Программа перехватила SIGTERM, убралась и вышла сама.

Окно 2: kill -9 $PID. Окно 1:

student@unix-server:~$ python3 counter.py
0
...
4
Killed
student@unix-server:~$ echo $?
137

SIGKILL перехватить нельзя: процесс исчез сразу, ничего не сохранив. Сначала kill, и только если процесс не реагирует - kill -9.

Ctrl+C и Ctrl+Z

student@unix-server:~$ python3 counter.py
0
1
2
3
^CTraceback (most recent call last):
  File "/home/student/counter.py", line 16, in <module>
    time.sleep(1)
KeyboardInterrupt
student@unix-server:~$ echo $?
130

Ctrl+C - SIGINT. Python превращает его в исключение KeyboardInterrupt.

student@unix-server:~$ python3 counter.py
0
1
2
^Z
[1]+  Stopped        python3 counter.py
student@unix-server:~$ jobs
[1]+  Stopped        python3 counter.py
student@unix-server:~$ fg
python3 counter.py
3
4

Ctrl+Z - приостановить, fg - вернуть. Частая ошибка: нажать Ctrl+Z вместо Ctrl+C и думать, что программа завершилась. Она висит остановленной - jobs покажет.


7. Код возврата $?

$? - чем закончилась последняя команда

student@unix-server:~$ ls /no/such/file
ls: cannot access '/no/such/file': No such file or directory
student@unix-server:~$ echo $?
2
student@unix-server:~$ command_not_exist
-bash: command_not_exist: command not found
student@unix-server:~$ echo $?
127
student@unix-server:~$ grep nobody_here /etc/passwd
student@unix-server:~$ echo $?
1

grep слово файл - найти строки со словом; не нашёл - код 1.

КодСмысл
0успех
другое числоне успех; что именно - решает программа (у grep 1 - «не нашёл», у ls 2 - «серьёзная проблема»)
127команда не найдена (это говорит shell)
128+Nубит сигналом N
130Ctrl+C (128+2)
137kill -9 (128+9)

Код 137 мы видели после kill -9, 130 - после Ctrl+C. После нашего обработчика SIGTERM - 0: программа вышла сама. echo $? показывает код последней команды, поэтому его запускают сразу.

&& и ||

student@unix-server:~$ mkdir data && cd data
student@unix-server:~/data$ pwd
/home/student/data
student@unix-server:~/data$ cd ..
student@unix-server:~$ mkdir data && cd data
mkdir: cannot create directory ‘data’: File exists
student@unix-server:~$ pwd
/home/student
student@unix-server:~$ cd nowhere || echo "такой папки нет"
-bash: cd: nowhere: No such file or directory
такой папки нет

A && B - B, только если A вернула 0. A || B - B, только если A вернула не 0.