На этой лекции будут затронуты некоторые аспекты управления процессами. Рассматривается структура процесса, его жизненный цикл, мониторинг и управление процессами.
Быстрый поиск
Что такое процесс?
При разговоре о работе вычислительных систем постоянно всплывает упоминание «процесса» как некоторой исполняемой единицы. Впервые сталкиваясь с компьютерными системами на уровне глубже, чем просто пользователь, это может показаться странным. Для обычного пользователя привычнее думать о программах, выполняющихся в системе.
Определим для начала значение этого термина. Если придерживаться строгих технических определений, процесс — это совокупность машинных команд и данных, которая обрабатывается в рамках вычислительной системы и обладает правами на владение некоторым набором ресурсов. Постараемся развернуть это определение в набор простых формулировок. Непосредственно программа на компьютере — это просто текст. Если быть более точным, последовательность байт. Сама по себе эта последовательность байт, хранящаяся где-то на носителе ничего из себя не представляет. При её исполнении создаётся специальный виртуальный объект, обладающий своими уникальными свойствами и содержащий в качестве исполняемого кода ту самую последовательность байт программы. Этот объект и называется процессом.
Состав процесса
Кроме исполняемого кода процесс содержит множество информации: от метаданных, определяющих сам процесс, до аргументов программы, открытых ею файлов и так далее.
Основной параметр, определяющий процесс — его идентификатор, PID. Это уникальный номер процесса, под которым в таблице процессов операционной системы размещена информация о нём. Кроме PID процессор также хранит уникальный идентификатор родительского процесса PPID, от которого этот был порождён. Процесс не может появиться из ниоткуда, только путём специального алгоритма порождения из родительского процесса.
Кроме идентификатора процесса в нём хранятся идентификатор пользователя UID и группы пользователя GID, от имени которого был запущен процесс. Поскольку пользователи обладают разными правами доступа к ресурсам системы (к файлам, устройствам, приложениям), процессы получают доступ сообразно своим «владельцам».
Каждый процесс получает своё виртуальное адресное пространство — место для хранения параметров и исполнения программы, исполняемым экземпляром которой он является. Здесь хранятся параметры окружения процесса, аргументы командной строки и сам код программы. Здесь заводятся системные таблицы процесса: таблица открытых файлов, таблица сигналов и так далее.
Просмотреть параметры процесса можно с помощью утилиты ps. Запустим любой процесс в фоновом режиме и выведем его параметры на экран:
[root@localhost ~]# sleep 300 &
[1] 744
[root@localhost ~]# ps -o pid,ppid,tty,stat,user,comm,args -p 744
PID PPID TT STAT USER COMMAND COMMAND
744 709 ttyS0 S root sleep sleep 300
[root@localhost ~]#
Здесь становится отчётливо видно некоторые параметры процесса:
- Идентификаторы самого процесса и его родительского процесса
- Тот терминал, через который осуществляется управление процессом
- Состояние процесса
- Выделяют пять состояний процессов:
- R — выполняется или готов к выполнению
- S — спит, ждёт событие пробуждения (буквально, основная задача утилиты
sleep N) - D — ждёт ввода данных или, наоборот, вывода для передачи его дальше в выходной файл
- T — остановлен сигналом или командой управления процессами
- Z — zombie, особое состояние завершившегося процесса: процесс никогда не удаляется сам, а ожидает родительского процесса, который освободит занимаемую сыновьим процессом память и ресурсы и уберёт его из таблицы процессов ОС.
- Выделяют пять состояний процессов:
- Владелец процесса
- Запущенная программа
- Аргументы программы (по правилам само название программы всегда присутствует в виде нулевого аргумента)
Вся информация о существующих в системе процессах доступна напрямую из файловой системы в «виртуальной файловой системе» /proc.
[root@localhost ~]# ls /proc/744
arch_status fd mountstats setgroups
attr fdinfo net smaps
autogroup gid_map ns smaps_rollup
auxv io numa_maps stack
cgroup ksm_merging_pages oom_adj stat
clear_refs ksm_stat oom_score statm
cmdline latency oom_score_adj status
comm limits pagemap syscall
coredump_filter loginuid personality task
cpu_resctrl_groups map_files projid_map timens_offsets
cpuset maps root timers
cwd mem sched timerslack_ns
environ mountinfo schedstat uid_map
exe mounts sessionid wchan
[root@localhost ~]# cat /proc/744/comm
sleep
[root@localhost ~]# cat /proc/744/environ
SHELL=/bin/bashLESS=-MMG_BROKEN_FILENAMES=1HISTSIZE=999HOSTNAME=localhostEDITOR=/usr/bin/vimPWD=/rootLOGNAME=rootXDG_SESSION_TYPE=ttyMOTD_SHOWN=pamHOME=/rootLANG=CLS_COLORS=TMPDIR=/tmp/.private/rootXDG_SESSION_CLASS=user-earlyTERM=vt220LESSOPEN=|/usr/share/less/lesspipe.sh %sUSER=rootSHLVL=0INPUTRC=/etc/inputrcSYSTEMD_PAGER=/usr/bin/less -FRXDG_SESSION_ID=1XDG_RUNTIME_DIR=/run/user/0TMP=/tmp/.private/rootPATH=/root/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/gamesHISTFILESIZE=9999DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/0/busMAIL=/var/mail/rootLESSKEY=/etc/.less_=/usr/bin/sleep
[root@localhost ~]#
Отдельное внимание стоит обратить на директорию fd. Она описывает таблицу открытых файлов процесса, через неё процесс связывает потоки ввода и вывода с терминалом или другими объектами (например, файлами при перенаправлении ввода-вывода </> из консоли). Элементы этой таблицы называются файловыми дескрипторами. По умолчанию ФД 0 отвечает за поток ввода (stdin), ФД 1 отвечает за поток вывода (stdout), ФД 2 отвечает за поток ошибок (stderr).
[root@localhost ~]# ls -l /proc/744/fd
total 0
lrwx------ 1 root root 64 Apr 12 19:12 0 -> /dev/ttyS0
lrwx------ 1 root root 64 Apr 12 19:12 1 -> /dev/ttyS0
lrwx------ 1 root root 64 Apr 12 19:11 2 -> /dev/ttyS0
[root@localhost ~]#
Всё дерево процессов можно посмотреть с помощью утилиты pstree, ключ -p также покажет идентификаторы этих процессов.
[root@localhost ~]# pstree -p
systemd(1)-+-acpid(579)
|-agetty(615)
|-crond(614)
|-dbus-daemon(580)
|-login(621)---bash(709)---pstree(813)
|-rsyslogd(597)-+-{rsyslogd}(617)
| `-{rsyslogd}(618)
|-sshd(627)
|-systemd(697)---(sd-pam)(699)
|-systemd-journal(456)
|-systemd-logind(592)
|-systemd-nsresou(511)-+-systemd-nsresou(798)
| |-systemd-nsresou(800)
| |-systemd-nsresou(801)
| |-systemd-nsresou(802)
| `-systemd-nsresou(805)
|-systemd-udevd(498)
`-systemd-userdbd(504)-+-systemd-userwor(799)
|-systemd-userwor(803)
`-systemd-userwor(804)
[root@localhost ~]# sleep 300 &
[1] 814
[root@localhost ~]# pstree -p
systemd(1)-+-acpid(579)
|-agetty(615)
|-crond(614)
|-dbus-daemon(580)
|-login(621)---bash(709)-+-pstree(815)
| `-sleep(814)
|-rsyslogd(597)-+-{rsyslogd}(617)
| `-{rsyslogd}(618)
|-sshd(627)
|-systemd(697)---(sd-pam)(699)
|-systemd-journal(456)
|-systemd-logind(592)
|-systemd-nsresou(511)-+-systemd-nsresou(798)
| |-systemd-nsresou(800)
| |-systemd-nsresou(801)
| |-systemd-nsresou(802)
| `-systemd-nsresou(805)
|-systemd-udevd(498)
`-systemd-userdbd(504)-+-systemd-userwor(799)
|-systemd-userwor(803)
`-systemd-userwor(804)
[root@localhost ~]#
Жизненный цикл процесса
Разберём по шагам, как происходит создание процесса в системе, исполнение программы, а после — его завершение.
Первый шаг — создание сыновьего процесса. При этом действии родительский процесс полностью копируется и переносится в новое адресное пространство. Меняются уникальные идентификаторы процесса и его родителя, меняется само адресное пространство*, но всё его наполнение остаётся тем же самым, что и у родителя: код исполняемой программы, её аргументы, окружение, таблицы сигналов и открытых файлов процесса, перенаправления ввода-вывода.
|
a = 5;
|
fork()
/\
0 / \ pid > 0
/ \
сын отец
a = 5; a = 5;
* Наряду с процессами (их также называют полновесными процессами) часто говорят о нитях (легковесных процессах). Нити делят одно адресное пространство процесса, выполняя одну программу. Различие в их поведении определяется условиями на основании их уникальных идентификаторов. Подробнее про легковесные процессы можно почитать здесь
Для демонстрации взаимосвязей между процессами воспользуемся программой dpidi, исходник которой приложен в дополнительных файлах. Она выполняет shell-сценарий, подаваемый ей в аргументах командной строки и после строит дерево зависимых процессов и их потоки ввода-вывода.
Для демонстрации рассмотрим три shell-сценария:
test1.sh
$ { cat | grep hello; echo Success; } < file.txt > res.log
[papillon_rouge@BaseALT-Papillon tmp]$ ./dpidi test1.sh
Дерево зависимых PID:
820 (test1.sh (shell))
├── 821 (cat)
└── 822 (grep)
Данные по вводу-выводу:
820 | test1.sh (shell) | fd 0: /dev/ttyS0 | fd 1: /dev/ttyS0
821 | cat | fd 0: /tmp/file.txt | fd 1: pipe:[354678]
822 | grep | fd 0: pipe:[354678] | fd 1: /tmp/res.log
[papillon_rouge@BaseALT-Papillon tmp]$
После выполнения отчётливо видна последовательность создания процессов. Также можно заметить, как к сыновьим процессам передаются перенаправления ввода вывода. Описание множества команд Shell в фигурных скобках не порождает отдельного процесса, но позволяет описать параметры перенаправления сразу для всех создающихся внутри процессов. Также можно заметить, что для выполнения команды echo не порождалось процесса, поскольку это внутренняя команда самого Shell.
Перейдём к следующему примеру. Здесь команды описаны в круглых скобках, Shell определяет это как необходимость создать отдельный процесс, который будет родительским для всех внутренних команд. Также здесь используется не встроенная команда echo, а утилита /bin/echo
test2.sh
$ ( cat | grep hello; /bin/echo Success; ) < file.txt > res.log
[papillon_rouge@BaseALT-Papillon tmp]$ ./dpidi test2.sh
Дерево зависимых PID:
823 (test2.sh (shell))
└── 824 (echo)
├── 825 (cat)
└── 826 (grep)
Данные по вводу-выводу:
823 | test2.sh (shell) | fd 0: /dev/ttyS0 | fd 1: /dev/ttyS0
824 | echo | fd 0: /tmp/file.txt | fd 1: /tmp/res.log
825 | cat | fd 0: /tmp/file.txt | fd 1: pipe:[359666]
826 | grep | fd 0: pipe:[359666] | fd 1: /tmp/res.log
[papillon_rouge@BaseALT-Papillon tmp]$
Здесь становится видно три уровня косвенности: процесс, связанный с самим shell-сценарием, порождает подпроцесс с другими потоками ввода и вывода, а уже от него создаются внутренние процессы.
В этом примере показывается особенность работы Shell: поскольку после выполнения /bin/echo ничего не происходит, оптимизатор не создаёт отдельный процесс, а подменяет текущий. Для того чтобы процесс всё же создавался, нужно добавить любое действие в сам подпроцесс, например, команду-пустышку :
test3.sh
$ ( cat | grep hello; /bin/echo Success; : ) < file.txt > res.log
[papillon_rouge@BaseALT-Papillon tmp]$ ./dpidi test3.sh
Дерево зависимых PID:
828 (test3.sh (shell))
└── 829 (bash (shell child))
├── 830 (cat)
├── 831 (grep)
└── 832 (echo)
Данные по вводу-выводу:
828 | test3.sh (shell) | fd 0: /dev/ttyS0 | fd 1: /dev/ttyS0
829 | bash (shell child) | fd 0: /tmp/file.txt | fd 1: /tmp/res.log
830 | cat | fd 0: /tmp/file.txt | fd 1: pipe:[353732]
831 | grep | fd 0: pipe:[353732] | fd 1: /tmp/res.log
832 | echo | fd 0: /tmp/file.txt | fd 1: /tmp/res.log
[papillon_rouge@BaseALT-Papillon tmp]$
Этот пример максимально строго иллюстрирует разделение процессов и изменение потоков ввода-вывода.
Следующий шаг после создания процесса — подмена контекста. Для подмены контекста процесс выполняет специальный системный вызов семейства exec, где указывается программа, на которую подменяется контекст процесса. После подмены процесс работает, будто был только что запущен с указанной программой.
Именно этот эффект наблюдался нами во втором тесте: подпроцесс, созданный в качестве родительского для внутренних команд, для последней команды просто подменил свой контекст, из-за чего и наблюдался странный эффект «неправильной» зависимости процессов. На самом деле зависимость не нарушилась, но изменилось «содержимое» подпроцесса.
Этот эффект можно легко повторить самостоятельно, вызвав руками exec:
- Посмотрим текущий процесс терминала —
bash(709) - Запустим другой терминал из этого —
bash(1891) - Запустим в нём
exec cal— контекст подменится, процесс выведет календарь на экран и завершится, выбросив нас во внешний bash
[root@localhost ~]# pstree -p
systemd(1)-+-acpid(579)
|-agetty(615)
|-crond(614)
|-dbus-daemon(580)
|-dhcpcd(934)-+-dhcpcd(935)---dhcpcd(1075)
| `-dhcpcd(936)
|-login(621)---bash(709)---pstree(1882)
|-rsyslogd(597)-+-{rsyslogd}(617)
| `-{rsyslogd}(618)
|-sshd(627)
|-systemd(697)---(sd-pam)(699)
|-systemd-journal(456)
|-systemd-logind(592)
|-systemd-nsresou(511)-+-systemd-nsresou(1871)
| |-systemd-nsresou(1873)
| |-systemd-nsresou(1874)
| |-systemd-nsresou(1875)
| `-systemd-nsresou(1878)
|-systemd-udevd(498)
`-systemd-userdbd(504)-+-systemd-userwor(1872)
|-systemd-userwor(1876)
`-systemd-userwor(1877)
[root@localhost ~]# ps
PID TTY TIME CMD
621 ttyS0 00:00:00 login
709 ttyS0 00:00:00 bash
1902 ttyS0 00:00:00 ps
[root@localhost ~]#
[root@localhost ~]# bash
[root@localhost ~]#
[root@localhost ~]# pstree -p
systemd(1)-+-acpid(579)
|-agetty(615)
|-crond(614)
|-dbus-daemon(580)
|-dhcpcd(934)-+-dhcpcd(935)---dhcpcd(1075)
| `-dhcpcd(936)
|-login(621)---bash(709)---bash(1891)---pstree(1892)
|-rsyslogd(597)-+-{rsyslogd}(617)
| `-{rsyslogd}(618)
|-sshd(627)
|-systemd(697)---(sd-pam)(699)
|-systemd-journal(456)
|-systemd-logind(592)
|-systemd-nsresou(511)-+-systemd-nsresou(1903)
| |-systemd-nsresou(1905)
| |-systemd-nsresou(1906)
| |-systemd-nsresou(1907)
| `-systemd-nsresou(1910)
|-systemd-udevd(498)
`-systemd-userdbd(504)-+-systemd-userwor(1904)
|-systemd-userwor(1908)
`-systemd-userwor(1909)
[root@localhost ~]# ps
PID TTY TIME CMD
621 ttyS0 00:00:00 login
709 ttyS0 00:00:00 bash
1891 ttyS0 00:00:00 bash
1900 ttyS0 00:00:00 ps
[root@localhost ~]# exec cal
April 2026
Su Mo Tu We Th Fr Sa
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
[root@localhost ~]#
[root@localhost ~]# pstree -p
systemd(1)-+-acpid(579)
|-agetty(615)
|-crond(614)
|-dbus-daemon(580)
|-dhcpcd(934)-+-dhcpcd(935)---dhcpcd(1075)
| `-dhcpcd(936)
|-login(621)---bash(709)---pstree(1901)
|-rsyslogd(597)-+-{rsyslogd}(617)
| `-{rsyslogd}(618)
|-sshd(627)
|-systemd(697)---(sd-pam)(699)
|-systemd-journal(456)
|-systemd-logind(592)
|-systemd-nsresou(511)-+-systemd-nsresou(1871)
| |-systemd-nsresou(1873)
| |-systemd-nsresou(1874)
| |-systemd-nsresou(1875)
| `-systemd-nsresou(1878)
|-systemd-udevd(498)
`-systemd-userdbd(504)-+-systemd-userwor(1872)
|-systemd-userwor(1876)
`-systemd-userwor(1877)
[root@localhost ~]# ps
PID TTY TIME CMD
621 ttyS0 00:00:00 login
709 ttyS0 00:00:00 bash
1902 ttyS0 00:00:00 ps
[root@localhost ~]#
На двух действиях — создание процесса-клона и подмена контекста — строится вся система выполнения программ в компьютере.
Управление процессами
Для управления процессами может использоваться множество способов. Один из них — явный перехват всех действий процесса и их мониторинг, подмена и так далее. Такой процесс называется трассировка. Именно с помощью трассировки, в частности, перехватывалось выполнение программ в dpidi.
Следующий способ управления — сигналы. Сами процессы обмениваются сигналами для управления или передачи каких-то реакций друг между другом. Для отправки сигналов используется утилита kill. Несмотря на своё название, она может отправлять весь доступный спектр сигналов:
- Сигналы остановки и продолжения работы процессов
- Сигнал о корректном завершении процесса
- Сигнал о немедленном завершении процесса
- И так далее
[root@localhost ~]# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
[root@localhost ~]# sleep 300 &
[1] 1934
[root@localhost ~]# kill -STOP 1934
[1]+ Stopped sleep 300
[root@localhost ~]# kill -CONT 1934
[root@localhost ~]# kill -TERM 1934
[1]+ Terminated sleep 300
[root@localhost ~]#
Некоторые сигналы могут посылаться с помощью комбинаций клавиш, например, сигнал SIGINT отправляется процессу при нажатии комбинации Ctrl+C, а SIGSTOP — Ctrl+Z.
Также для управления процессами можно использовать специализированные утилиты: fg и bg для управления фоновыми процессами, jobs для просмотра фоновых задач.
[root@localhost ~]# sleep 300
^Z
[1]+ Stopped sleep 300
[root@localhost ~]# bg
[1]+ sleep 300 &
[root@localhost ~]# fg
sleep 300
^C
[root@localhost ~]#
[root@localhost ~]# sleep 300 &
[1] 1955
[root@localhost ~]# sleep 300 &
[2] 1956
[root@localhost ~]# sleep 300 &
[3] 1957
[root@localhost ~]# jobs
[1] Running sleep 300 &
[2]- Running sleep 300 &
[3]+ Running sleep 300 &
[root@localhost ~]#