View on GitHub

Linux Practic Usage

Practical materials for learning Linux

На этой лекции будут затронуты некоторые аспекты управления процессами. Рассматривается структура процесса, его жизненный цикл, мониторинг и управление процессами.

Быстрый поиск


Что такое процесс?

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

Определим для начала значение этого термина. Если придерживаться строгих технических определений, процесс — это совокупность машинных команд и данных, которая обрабатывается в рамках вычислительной системы и обладает правами на владение некоторым набором ресурсов. Постараемся развернуть это определение в набор простых формулировок. Непосредственно программа на компьютере — это просто текст. Если быть более точным, последовательность байт. Сама по себе эта последовательность байт, хранящаяся где-то на носителе ничего из себя не представляет. При её исполнении создаётся специальный виртуальный объект, обладающий своими уникальными свойствами и содержащий в качестве исполняемого кода ту самую последовательность байт программы. Этот объект и называется процессом.

Состав процесса

Кроме исполняемого кода процесс содержит множество информации: от метаданных, определяющих сам процесс, до аргументов программы, открытых ею файлов и так далее.

Основной параметр, определяющий процесс — его идентификатор, 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 ~]#

Здесь становится отчётливо видно некоторые параметры процесса:

Вся информация о существующих в системе процессах доступна напрямую из файловой системы в «виртуальной файловой системе» /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:

[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 ~]#