Процессы, сигналы и код возврата
Дополнительный материал к лекции 4 «Переменные, окружение и процессы»: процессы, сигналы и код возврата подробно, с настоящими выводами с сервера. Уроки Дарьи - короткая выжимка отсюда.
6. Процессы и сигналы
Процесс
Процесс - запущенный экземпляр программы. Внешняя команда (ls, python3, ps) запускается отдельным процессом; сам shell - тоже процесс. Встроенные команды shell (cd, echo, export) отдельного процесса не создают - их shell выполняет сам.
У процесса есть:
- PID - номер процесса, PPID - номер родителя;
- окружение и текущий каталог - свои у каждого (поэтому они копируются от родителя к ребёнку, а не «общие»);
- владелец (UID) и состояние: работает, спит, остановлен.
Кто чей родитель
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.py | PID вашего процесса, в команде которого есть 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 нет.
Сигналы
| Сигнал | № | Как отправить | Что значит | Можно перехватить |
|---|---|---|---|---|
SIGINT | 2 | Ctrl+C | прервать | да |
SIGTERM | 15 | kill PID | вежливо завершиться | да |
SIGKILL | 9 | kill -9 PID | убить немедленно | нет |
SIGTSTP | 20 | Ctrl+Z | приостановить из терминала | да |
SIGSTOP | 19 | kill -STOP PID | приостановить | нет |
SIGCONT | 18 | kill -CONT PID, fg | продолжить | - |
SIGHUP | 1 | закрыли терминал | терминал отключился | да |
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 |
130 | Ctrl+C (128+2) |
137 | kill -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.