Процессы операционной системы в большинстве случаев отождествляются с выполняющимися программами, что не совсем верно, точнее — совсем неверно. В современных операционных системах, включая Linux, между программой и процессом есть очевидная взаимосвязь, но далеко не такая непосредственная, как кажется на первый взгляд.
Совсем недавно в издательстве БХВ вышло второе издание книги Дмитрия Кетова «Внутреннее устройство Linux». Я прочел предоставленный издательством экземпляр и сделал свои выводы о качестве книги, ее плюсах и целевой аудитории. В придачу мы публикуем отрывок книги, который позволит тебе составить собственное мнение.
Несмотря на громкое название, эта книга — не об устройстве Linux. Из нее ты не узнаешь, как работает система управления виртуальной памятью ядра Linux или файловая система Btrfs. Эта книга — скорее учебник «GNU/Linux для продолжающих», то есть тех, кто понял, что такое командный интерпретатор и права доступа, но хотел бы копнуть глубже и понять, почему этот интерпретатор именно такой, с какой целью появился инструмент sudo и почему, несмотря на весь хейт, менеджер Systemd стал стандартом.
Главное достоинство книги — грамотное балансирование между теорией и практикой. Автор сопровождает текст большим количеством исторических справок и деталей, которые бывают незнакомы даже мне, умудренному двадцатилетним опытом линуксоиду. При этом практически каждое высказывание автора сопровождается примером, поясняющим теорию. Хочешь увидеть, как работают управляющие последовательности терминала? Вот тебе листинг команд с отметками, где, что и как надо нажать.
Второй плюс — полнота информации. Несмотря на массу подробностей о реализации компонентов типичного дистрибутива и сравнительно небольшой объем, книга охватывает практически все, что может быть интересно пользователю, начиная от эмулятора терминала и командного интерпретатора и заканчивая графической системой Wayland, которая только готовится занять место стандартной в дистрибутивах Linux, планировщиком BFQ и контейнерной системой Docker, о которых ты не узнаешь из более «академической» и «проверенной временем» литературы.
В целом эта книга для тех, кто стремится стать тем самым гуру Linux, который не только сможет рассказать, как удалить «неудаляемый» файл или исправить терминал после вывода в него бинарного файла, но и будет понимать, почему эти инструкции работают. Но это точно книга не для тех, кто хотел бы разобраться, как работают операционные системы на уровне ядра.
— Евгений Зобнин
На сайте издательства можно купить эту книгу со скидкой 20% по промокоду linuxakep. Промокод следует ввести на этапе оформления заказа, нажав на ссылку «У вас есть купон? Нажмите здесь для введения кода».
Программа представляет собой алгоритм, записанный на определенном языке, понятном исполнителю программы. Различают машинный язык, понятный центральному процессору, и языки более высоких уровней (алгоритмические), понятные составителю программы — программисту.
Программы, составленные на языке высокого уровня, в любом случае перед исполнением должны быть транслированы (переведены) на язык исполнителя, что реализуется при помощи специальных средств — трансляторов. Различают два вида трансляторов программ — компиляторы и интерпретаторы. Компилятор транслирует в машинный код сразу целиком всю программу и не участвует в ее исполнении. Интерпретатор, наоборот, пошагово транслирует отдельные инструкции программы и немедленно выполняет их. Например, командный интерпретатор при интерактивном режиме пошагово выполняет команды, вводимые пользователем, а в пакетном режиме так же пошагово выполняет команды, записанные в файле сценария.
Алгоритм, в свою очередь, есть некоторый набор инструкций, выполнение которых приводит к решению конкретной задачи. В большинстве случаев инструкции алгоритма имеют причинно‑следственные зависимости и выполняются исполнителем последовательно. Однако если выделить «независимые» поднаборы инструкций (независимые ветви), то их можно выполнять несколькими исполнителями одновременно — параллельно. Поэтому различают последовательные и параллельные алгоритмы и соответствующие им последовательные и параллельные программы. Некоторые программы реализуют алгоритмы общего назначения, например алгоритмы сжатия или шифрования информации, алгоритмы сетевых протоколов и т. д. Такие программы, востребованные не столько конечными пользователями, сколько другими программами, называют библиотеками.
Согласно hier
, откомпилированные до машинного языка программы размещаются в каталогах /bin, /sbin
, /usr/bin
, /usr/sbin
, /usr/local/bin
, /usr/local/sbin
, а библиотеки — в каталогах /lib
, /usr/lib
, /usr/local/lib
. Программы имеют специальный бинарный «запускаемый» формат W:[ELF] executable и зависят от библиотек, что проиллюстрировано в следующем листинге при помощи команды ldd
(loader dependencies). Каждая зависимость отображается именем библиотеки ❶ (SONAME, shared object name), найденным в системе файлом библиотеки ❷ и адресом в памяти процесса ❸ (32- или 48-битным, в зависимости от платформы), куда библиотека будет загружена.
fitz@ubuntu:~$ which ls
/usr/bin/ls
fitz@ubuntu:~$ file /usr/bin/ls
/usr/bin/ls: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2f15ad836be3339dec0e2e6a3c637e08e48aacbd, for GNU/Linux 3.2.0, stripped
fitz@ubuntu:~$ ldd /usr/bin/ls
linux-vdso.so.1 (0x00007ffcb529d000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007fb02f58d000)
❶ libc.so.6 => ❷ /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb02f39c000) ❸
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007fb02f317000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fb02f311000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb02f5f1000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb02f2ee000)
fitz@ubuntu:~$ file /lib/x86_64-linux-gnu/libc.so.6
❹ /lib/x86_64-linux-gnu/libc.so.6: symbolic link to libc-2.30.so
Нужно заметить, что файла библиотеки linux-vdso.so.1
(реализующей интерфейс системных вызовов к ядру) не существует, так как она является виртуальной (VDSO, virtual dynamic shared object), т. е. предоставляется и отображается в память процесса самим ядром, «как будто» является настоящей библиотекой. Кроме того, библиотека ld-linux-x86-64.so.2
указана абсолютным путевым именем, поэтому поиск ее файла не производится.
Для большинства библиотек зависимость устанавливается при помощи SONAME вида libNAME.so.X
, где lib
— стандартный префикс (library, библиотека), .so
— суффикс (shared object, разделяемый объект), NAME
— имя «собственное», а .X
— номер версии ее интерфейса. По имени SONAME в определенных (конфигурацией компоновщика) каталогах производится поиск одноименного файла библиотеки, который на самом деле оказывается символической ссылкой ❹ на «настоящий» файл библиотеки. Например, для 6-й версии интерфейса динамической библиотеки языка с (libc.so.6
) настоящий файл библиотеки называется libc2.30.so
, что указывает на версию самой библиотеки как 2.30.
fitz@ubuntu:~$ file /lib/x86_64-linux-gnu/libpcre2-8.so.0
/lib/x86_64-linux-gnu/libpcre2-8.so.0: symbolic link to libpcre2-8.so.0.7.1
Аналогично, в приведенном выше листинге показано, что для 0-й версии интерфейса динамической библиотеки регулярных perl-выражений pcre2
(libpcre2-8.so.0
) настоящий файл библиотеки называется libpcre2-8.so.0.7.1
, а это указывает на версию самой библиотеки как 0.7.1.
Такой подход позволяет заменять (исправлять ошибки, улучшать неэффективные алгоритмы и пр.) библиотеки (при условии неизменности их интерфейсов) отдельно от программ, зависящих от них. При обновлении библиотеки libc2.30.so
, например, до libc2.32.so
достаточно установить символическую SONAME-ссылку libc.so.6
на libc-2.32.so
, в результате чего ее начнут использовать все программы с зависимостями от libc.so.6
. Более того, в системе может быть одновременно установлено любое количество версий одной и той же библиотеки, реализующих одинаковые или разные версии интерфейсов, выбор которых будет указан соответствующими SONAME-ссылками.
fitz@ubuntu:~$ file /lib/x86_64-linux-gnu/libc-2.30.so
/lib/x86_64-linux-gnu/libc-2.30.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2155f455ad56bd871c8225bcca85ee25c1c197c4, for GNU/Linux 3.2.0, stripped
fitz@ubuntu:~$ file /lib/x86_64-linux-gnu/libpcre2-8.so.0.7.1
/lib/x86_64-linux-gnu/libpcre2-8.so.0.7.1: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=815e1acbcc22015f05d62c17fe982c1b573125b1, stripped
fitz@ubuntu:~$ ldd /lib/x86_64-linux-gnu/libpcre2-8.so.0.7.1
linux-vdso.so.1 (0x00007ffe22093000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f8ec2bdd000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8ec29ec000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8ec2c99000)
Библиотеки имеют тот же бинарный формат W:[ELF], что и «запускаемые» программы, но не «запускаемый» executable, а «совместно используемый» shared object. Библиотеки, являясь пусть и незапускаемыми, но программами, естественным образом тоже зависят от других библиотек, что показано в следующем листинге. Практически «запускаемость» ELF-файлов зависит не от их типа, а от прав доступа и осмысленности точки входа — адреса первой инструкции, которой передается управление при попытке запуска. Например, библиотеку libc-2.30.so
можно запустить, в результате чего будет выведена статусная информация.
fitz@ubuntu:~$ ls –l /lib/x86_64-linux-gnu/libc-2.30.so
-rwxr-xr-x 1 root root 2025032 сен 16 17:56 /lib/x86_64-linux-gnu/libc-2.30.so
fitz@ubuntu:~$ /lib/i386-linux-gnu/libc-2.15.so
GNU C Library (Ubuntu GLIBC 2.30-0ubuntu2) stable release version 2.30.
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 9.2.1 20190909.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
Не стоит забывать, что самой главной программой операционной системы является ее ядро, которое в Linux состоит из статического стартового модуля в формате ELF executable и динамически пристыковываемых программных модулей формата ELF relocatable. Для выполнения процедуры начальной загрузки стартовый модуль упакован в «самораспаковывающийся» gzip-архив формата bzImage (big zipped image), который состоит из программы распаковки и собственно запакованного стартового модуля.
В приведенном ниже листинге проиллюстрирован процесс извлечения стартового модуля из архива /boot/vmlinuz-3.13.0-49-generic
формата bzImage ⓿, который предварительно копируется ❶ в /tmp/vmlinuz
. Для извлечения используется сценарий extract-vmlinux
❷ из пакета заголовочных файлов ядра. Распакованный ❸ стартовый модуль /tmp/vmlinux
ожидаемо оказывается статически скомпонованной (т. е. не использующей библиотеки ELF shared object) исполняемой ELF-программой.
fitz@ubuntu:~$ uname -r
5.3.0-23-generic
fitz@ubuntu:~$ file /boot/vmlinuz-5.3.0-23-generic
/boot/vmlinuz-5.3.0-23-generic: regular file, no read permission
fitz@ubuntu:~$ ls -l /boot/vmlinuz-5.3.0-23-generic
-rw------- 1 root root 11399928 ноя 12 11:51 /boot/vmlinuz-5.3.0-23-generic
fitz@ubuntu:~$ sudo file /boot/vmlinuz-5.3.0-23-generic
⓿ /boot/vmlinuz-5.3.0-23-generic: Linux kernel x86 boot executable bzImage, version 5.3.0-23-generic (buildd@lgw01-amd64-002) #25-Ubuntu SMP Tue Nov 12 09:22:33 UTC 2019, RO-rootFS, swap_dev 0xA, Normal VGA
❶ fitz@ubuntu:~$ sudo cat /boot/vmlinuz-5.3.0-23-generic > /tmp/vmlinuz
❷ fitz@ubuntu:~$ /usr/src/linux-headers-5.3.0-23/scripts/extract-vmlinux /tmp/vmlinuz > /tmp/vmlinux
fitz@ubuntu:~$ file /tmp/vmlinux
/tmp/vmlinux: ELF 64-bit LSB executable ❸, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=b23ff3f6790319ec538278e3269af619ba2ca642, stripped
Динамические модули загружаются в пространство ядра и пристыковываются к стартовому модулю позднее, уже при работе операционной системы при помощи системных утилит insmod
или modprobe
. Для отстыковки и выгрузки ненужных модулей предназначена системная утилита rmmod
, для просмотра списка ❶ загруженных модулей — lsmod
, а для идентификации свойств и параметров ❷ модулей — утилита modinfo
. Загрузка и выгрузка модулей реализуется специальными системными вызовами init_module
и delete_module
, доступ к списку загруженных модулей — при помощи файла /proc/modules
псевдофайловой системы proc
, а идентификация свойств и параметров модулей — чтением специальных секций ELF-файлов модулей.
❶ fitz@ubuntu:~$ lsmod
Module Size Used by
...
i915 1949696 4
...
btusb 57344 0
...
uvcvideo 98304 0
...
e1000e 258048 0
...
❷ fitz@ubuntu:~$ modinfo i915
filename: /lib/modules/5.3.0-23-generic/kernel/drivers/gpu/drm/i915/i915.ko
license: GPL and additional rights
description: Intel Graphics
...
fitz@ubuntu:~$ file /lib/modules/5.3.0-23-generic/kernel/drivers/gpu/drm/i915/i915.ko
/lib/modules/5.3.0-23-generic/kernel/drivers/gpu/drm/i915/i915.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=49e59590c1a718074b76b6541702f6f794ea7eae, not stripped
Динамические модули ядра зачастую являются драйверами устройств, что проиллюстрировано в листинге при помощи утилит lspci
и lsusb
, которые сканируют посредством псевдофайловой системы sysfs списки обнаруженных ядром на шинах PCI и USB устройств и обслуживающих их драйверов.
fitz@ubuntu:~$ lspci -k
...
00:02.0 VGA compatible controller: Intel Corporation 2nd Generation Core Process
or Family Integrated Graphics Controller (rev 09)
Subsystem: Dell 2nd Generation Core Processor Family Integrated Graphics
Controller
Kernel driver in use: i915
Kernel modules: i915
...
00:19.0 Ethernet controller: Intel Corporation 82579LM Gigabit Network Connection (Lewisville) (rev 04)
Subsystem: Dell 82579LM Gigabit Network Connection (Lewisville)
Kernel driver in use: e1000e
Kernel modules: e1000e
fitz@ubuntu:~$ lsusb –t
.../: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=ehci-pci/3p, 480M
|__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/6p, 480M
|__ Port 4: Dev 3, If 2, Class=Vendor Specific Class, Driver=, 12M
|__ Port 4: Dev 3, If 0, Class=Wireless, Driver=btusb, 12M
|__ Port 4: Dev 3, If 3, Class=Application Specific Interface, Driver=, 12M
|__ Port 4: Dev 3, If 1, Class=Wireless, Driver=btusb, 12M
|__ Port 5: Dev 4, If 0, Class=Video, Driver=uvcvideo, 480M
|__ Port 5: Dev 4, If 1, Class=Video, Driver=uvcvideo, 480M
Сущность процесса неразрывно связана с мультипрограммированием и многозадачностью операционной системы. Например, в однозадачных операционных системах программы существуют, а процессы — нет. В однозадачных операционных системах единовременно одна последовательная программа выполняется одним исполнителем (центральным процессором), имея возможность безраздельно использовать все доступные ресурсы (память, устройства ввода‑вывода и пр.).
В любой программе можно выделить перемежающиеся блоки инструкций, использующих или центральный процессор (ЦП), или устройства ввода‑вывода (УВВ). При этом центральный процессор вынужден простаивать при выполнении программой операций ввода‑вывода, например, при ожидании окончания записи (или чтения) блока данных на внешний носитель, или при ожидании окончания передачи (или приема) сетевого кадра, или при ожидании событий с устройств человеко‑машинного взаимодействия. С другой стороны, устройства ввода‑вывода тоже вынуждены простаивать при выполнении программой вычислительных операций, например, ожидая результата, подлежащего выводу, или ожидая возникновения у программы потребности в новых исходных данных.
Используя такую модель поведения программ, можно провести анализ потребления ими ресурсов при выполнении. Например, компрессоры gzip, bzip и xz считывают очередной блок данных исходного файла, относительно долго упаковывают его и записывают в результирующий файл, а затем повторяют процедуру до исчерпания блоков исходного файла. Количество времени, потраченного на вычислительные операции упаковки, будет много больше количества времени, потраченного на чтение исходных данных и запись результатов, поэтому нагрузка на ЦП будет высокой, а на УВВ — нет. Такой же анализ можно привести и для дубликатора dd, копировщика rsync или архиватора tar, которые, наоборот, почти не выполняют никаких вычислений, а сосредоточены на вводе‑выводе больших объемов данных, поэтому при их использовании нагрузка на ЦП будет довольно низкой, а на УВВ — высокой.
Для командного интерпретатора bash, текстовых редакторов nano и vim и других интерактивных программ, взаимодействующих с пользователем, характерны длительные ожидания ввода небольших команд, простая и недолгая их обработка и вывод короткого результата. В результате коэффициент полезного использования и ЦП, и УВВ будет приближен к нулю.
Подобный анализ и желание увеличить коэффициенты полезного использования ресурсов привели к созданию многозадачных операционных систем, основывающихся на простой идее псевдоодновременного выполнения нескольких последовательных программ одним исполнителем. Для этого вместо простоя в ожидании окончания операции ввода‑вывода, начатой некоторой программой, центральный процессор переключается на выполнение другой программы, тем самым увеличивая интегральный коэффициент его полезного использования.
С появлением мультипрограммной смеси (так называют набор программ, между которыми переключается процессор) каждая из ее программ больше не может безраздельно использовать все доступные ресурсы (например, всю память — она одновременно нужна всем программам смеси), в связи с чем операционная система берет на себя задачи диспетчеризации (распределения) ресурсов между ними. В Linux, как и во многих других операционных системах, программы изолируются друг от друга в специальных «виртуальных» средах, обеспечивающих их процесс выполнения. Каждая такая среда называется процессом и получает долю доступных ресурсов — выделенный участок памяти, выделенные промежутки процессорного времени. Процесс эмулирует для программы «однозадачный» режим выполнения, словно программа выполняется в одиночку, и «безраздельное» использование ресурсов процесса, как будто это все доступные ресурсы.
Параллельные программы, как указывалось ранее, состоят из независимых ветвей, каждая из которых сама по себе укладывается в модель поведения последовательной программы, поэтому одну параллельную программу можно выполнять в нескольких процессах в псевдоодновременном режиме. Процессы операционной системы, таким образом, являются контейнерами для многозадачного выполнения программ, как последовательных, так и параллельных.
В следующем листинге при помощи команды ps
показаны процессы пользователя, упорядоченные в дерево, построенное на основе дочерне‑родительских отношений между процессами. Уникальный идентификатор, отличающий процесс от других, выведен в столбце PID (process identifier), а имя и аргументы программы, запущенной в соответствующем процессе — в столбце COMMAND.
В столбце STAT показано текущее состояние процесса, например S (сон, sleep) или R (выполнение, running, или готовность к выполнению, runnable). Процессы, ожидающие завершения их операций ввода‑вывода, находятся в состоянии сна, в противном случае либо выполняются, либо готовы к выполнению, т. е. ожидают, когда текущий выполняющийся процесс заснет и процессор будет переключен на них. В столбце TIME показано чистое потребленное процессом процессорное время от момента запуска программы, увеличивающееся только при нахождении им в состоянии выполнения.
fitz@ubuntu:~$ ps fx
PID TTY STAT TIME COMMAND
...
17764 tty3 Ssl+ 0:00 /usr/lib/gdm3/gdm-x-session --run-script ...
17766 tty3 Sl+ 3:09 _ /usr/lib/xorg/Xorg vt3 -displayfd 3 ...
17774 tty3 Sl+ 0:00 _ /usr/lib/gnome-session/gnome-session-binary ...
...
2987 ? Ss 0:04 /lib/systemd/systemd --user
2992 ? S 0:00 _ (sd-pam)
17373 ? Ssl 0:08 _ /usr/bin/pulseaudio --daemonize=no
17444 ? Ss 0:02 _ /usr/bin/dbus-daemon --session --address=systemd: ...
...
17921 ? Ssl 10:04 _ /usr/bin/gnome-shell
...
⓿ 30192 ? Ssl 0:00 _ /usr/libexec/gnome-terminal-server
❶ 30202 pts/1 Ss 0:00 _ bash
❷ 30226 pts/1 S+ 0:00 _ man ps
30236 pts/1 S+ 0:00 _ pager
❶ 30245 pts/3 Ss 0:00 _ bash
❷ 30251 pts/3 R+ 0:00 _ ps fx
❸ 30315 ? Sl 0:04 _ /usr/lib/firefox/firefox -new-window
30352 ? Sl 0:02 _ /usr/lib/firefox/firefox -contentproc -childID 1 ...
30396 ? Sl 0:00 _ /usr/lib/firefox/firefox -contentproc -childID 2 ...
30442 ? Sl 0:00 _ /usr/lib/firefox/firefox -contentproc -childID 3 ...
Управляющий терминал процесса, показанный в столбце TTY, используется для доставки ему интерактивных сигналов (см. разд. 4.8) при вводе управляющих символов intr ^C, quit ^
и пр. у части процессов ⓿, ❸ управляющий терминал отсутствует, потому что они выполняют приложения, взаимодействующие с пользователем не посредством терминалов, а через графическую систему.
Процесс по своему определению изолирует свою программу от других выполняющихся программ, что затрудняет использование процессов для выполнения таких параллельных программ, ветви которых не являются полностью независимыми друг от друга и должны обмениваться данными. Использование предназначенных для этого средств межпроцессного взаимодействия при интенсивном обмене приводит к обременению неоправданными накладными расходами, поэтому для эффективного выполнения таких параллельных программ используются легковесные процессы (LWP, light-weight processes), они же нити (threads).
Существует еще один (неудачный, на мой взгляд) перевод понятия thread на русский язык — поток. Во‑первых, он конфликтует с переводом понятия stream — поток, а во‑вторых, в отличие от stream, thread никуда не течет. А вот процесс (process) содержит в себе нити (thread) абсолютно таким же образом, как и обычная веревка состоит из нитей.
Механизм нитей позволяет переключать центральный процессор между параллельными ветвями одной программы, размещаемыми в одном (!) процессе. Нити никак не изолированы друг от друга, и им доступны абсолютно все ресурсы своего процесса, поэтому задача обмена данными между нитями попросту отсутствует, т. к. все данные являются для них общими.
В примере из cktle.otuj
следующего листинга показаны нити процесса в BSD-формате вывода. Выбор процесса производится по его идентификатору PID, предварительно полученному командой pgrep
по имени программы, выполняющейся в искомом процессе.
В выводе наличие нитей процесса отмечает флаг l (lwp)
в столбце состояния STAT, а каждая строчка без идентификатора PID символизирует одну нить. Так как в многонитевой программе переключение процессора производится между нитями, то и состояния сна S, выполнения или ожидания R приписываются отдельным нитям.
fitz@ubuntu:~$ pgrep firefox
30315
fitz@ubuntu:~$ ps mp 30315
PID TTY STAT TIME COMMAND
PID TTY STAT TIME COMMAND
30315 ? - 0:05 /usr/lib/firefox/firefox -new-window
- - Sl 0:03 -
...
- - Sl 0:00 -
- - Sl 0:00 -
- - Sl 0:00 –
В нижеследующем листинге показаны нити процесса в SYSV-формате вывода. Выбор процесса производится по имени его программы. Общий для всех нитей идентификатор их процесса отображается в столбце PID, уникальный идентификатор каждой нити — в столбце LWP (иногда называемый TID, thread identifier), а имя процесса (или собственное имя нити, если задано) — в столбце CMD.
fitz@ubuntu:~$ ps -LC firefox
PID LWP TTY TIME CMD
30315 30315 ? 00:00:04 firefox
30315 30320 ? 00:00:00 gmain
30315 30321 ? 00:00:00 gdbus
...
30315 30328 ? 00:00:00 Socket Thread
30315 30332 ? 00:00:00 Cache2 I/O
30315 30333 ? 00:00:00 Cookie
...
30315 30371 ? 00:00:00 HTML5 Parser
30315 30373 ? 00:00:00 DNS Resolver #3
...
Несмотря на очевидные различия, историю возникновения и развития, нити и процессы объединяет общее назначение — они являются примитивами выполнения некоторого набора последовательных инструкций. Откровенно говоря, нити, в общем, появились в операционных системах раньше, чем изолированные UNIX-процессы, в которые со временем вернулись UNIX-нити.
Процессы выполняют или разные последовательные программы целиком, или ветви одной параллельной программы, но в изолированном окружении со своим «частным» (private) набором ресурсов. Нити, наоборот, выполняют ветви одной параллельной программы в одном окружении с «общим» (shared) набором ресурсов. В многозадачном ядре Linux вообще используется универсальное понятие «задача», которая может иметь как общие ресурсы (память, открытые файлы и т. д.) с другими задачами, так и частные ресурсы для своего собственного использования.
Порождение нового процесса реализуется при помощи системного вызова fork
, в результате которого ядро операционной системы создает новый дочерний (child) процесс PID2 — полную копию (COPY) процесса‑родителя (parent) PID1. Вся (за небольшими исключениями) память процесса — состояние, свойства, атрибуты (кроме идентификатора PID) и даже содержимое (программа с ее библиотеками) — наследуется дочерним процессом. Даже выполнение порожденного и порождающего процесса продолжится с одной и той же инструкции их одинаковой программы. Такое клонирование обычно используют параллельные программы с ветвями, выполняющимися в дочерних процессах.
Уничтожение процесса (например, при штатном окончании программы) производится с помощью системного вызова exit
. При этом родительскому процессу доставляется сигнал SIGCHILD
, оповещающий о завершении дочернего процесса. Статус завершения status
, переданный дочерним процессом через аргументы exit
, будет сохраняться ядром до момента его востребования родительским процессом при помощи системного вызова wait
, а весь этот промежуток времени дочерний процесс будет находиться в состоянии Z (zombie).
Родительский процесс может завершиться раньше своих дочерних процессов, тогда логично предположить, что все «осиротевшие» процессы окажутся зомби по завершении, потому как просто некому будет востребовать их статус завершения. На самом деле этого не происходит, потому что «осиротевшим» процессам назначается приемный родитель, в качестве которого выступает прародитель всех процессов init
с идентификатором PID = 1
.
Запуск новой программы (см. рис.) реализуется при помощи системного вызова exec
, в результате которого содержимое процесса PID1 полностью замещается запускаемой программой и библиотеками, от которых она зависит, а свойства и атрибуты (включая идентификатор PID) остаются неизменными. Такое замещение обычно используется программами, устанавливающими нужные значения свойств и атрибутов процесса и подготавливающими ресурсы процесса к выполнению запускаемой программы. Например, обработчик терминального доступа getty
открывает заданный терминал, устанавливает режимы работы порта терминала, перенаправляет на терминал стандартные потоки ввода‑вывода, а затем замещает себя программой аутентификации login
.
Для запуска новой программы в новом процессе используются оба системных
вызова fork
и exec
согласно принципу fork-and-exec «раздвоиться и запустить», показанного на рисунке ниже. Например, командный интерпретатор bash по командам ps fx
или man ps
порождает дочерние процессы ❷ и замещает их программами ps
и man
. Тем же образом действует ⓿ графический эмулятор терминала gnome-terminal-server — запуская новый сеанс пользователя ❶ на каждой из своих вкладок, он замещает свои дочерние процессы программой интерпретатора bash
.
Следующий листинг иллюстрирует команду интерпретатора, запущенную в «фоновом» режиме при помощи конструкции асинхронного списка. Аналогично всем предыдущим командам, интерпретатор использует fork-and-exec для запуска программы в дочернем процессе с идентификатором 23228
, но не дожидается его завершения при помощи системного вызова wait
, как обычно, а немедленно ❶ продолжает интерактивное взаимодействие с пользователем, сообщив ему PID порожденного процесса и «номер задания» [1]
команды «заднего фона». Оповещение о завершении своего дочернего процесса интерпретатор получит позже, при помощи сигнала SIGCHLD
, и отреагирует соответствующим сообщением ❷ об окончании команды «заднего фона».
fitz@ubuntu:~$ dd if=/dev/dvd of=plan9.iso &
❶ [1] 23228
fitz@ubuntu:~$ ps f
PID TTY STAT TIME COMMAND
23025 pts/1 S 0:00 -bash
23228 pts/1 R 1:23 _ dd if=/dev/dvd of=plan9.iso
23230 pts/1 R+ 0:00 _ ps f
fitz@ubuntu:~$
...
fitz@ubuntu:~$ 586896+0 записей получено
586896+0 записей отправлено
300490752 байт (300 MB, 286 MiB) скопирован, 14,6916 c, 20,5 MB/c
❷ [1]+ Завершён dd if=/dev/dvd of=plan9.iso
В следующем листинге показана конвейерная конструкция интерпретатора, при помощи которой осуществляется поиск самого большого файла с суффиксом .html
вниз по дереву каталогов, начиная с /usr/share/doc
. Эта конструкция реализуется при помощи fork-and-exec четырьмя параллельно порожденными дочерними процессами интерпретатора, в каждом из которых запущена программа соответствующей части конвейера, при этом дочерние процессы связаны неименованным каналом pipe — простейшим средством межпроцессного взаимодействия. Встроенная команда интерпретатора wait
реализует одноименный системный вызов и используется для ожидания окончания всех дочерних процессов конвейера, целиком запущенного в «фоновом» режиме.
fitz@ubuntu:~$ find /usr/share/doc -type f -name '.html' | xargs -n1 wc –l | sort -k 1 –nr | head -1 &
[1] 12827
fitz@ubuntu:~$ ps fj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
11715 11716 11716 9184 pts/0 14699 S 1006 0:01 -bash
11716 12824 12824 9184 pts/0 14699 R 1006 0:00 _ find ... -type f -name *.html
11716 12825 12824 9184 pts/0 14699 R 1006 0:00 _ xargs -n1 wc -l
11716 12826 12824 9184 pts/0 14699 S 1006 0:00 _ sort -k 1 -nr
11716 12827 12824 9184 pts/0 14699 S 1006 0:00 _ head -1
11716 14699 14699 9184 pts/0 14699 R+ 1006 0:00 _ ps fj
fitz@ubuntu:~$ wait
15283 /usr/share/doc/xterm/xterm.log.html[1]+
Завершён find /usr/share/doc -type f -name '.html' | xargs -n1 wc -l | sort -k 1 -nr | head -1
Как указывалось ранее, параллельные программы зачастую используют процессы для выполнения отдельных ветвей. В эту категорию часто попадают программы сетевых служб, например сервер баз данных W:[PostgreSQL], служба удаленного доступа W:[SSH] и подобные. Следующий листинг иллюстрирует программу postgres, выполняющуюся в шести параллельных процессах, один из которых — диспетчер ❶, четыре служебных ❷ и еще один ❸ вызван подключением пользователя fitz
к одноименной базе данных fitz
. При последующих подключениях пользователей к серверу будут порождены дополнительные дочерние процессы для обслуживания их запросов — по одному на каждое подключение.
fitz@ubuntu:~$ ps f -C postgres
PID TTY STAT TIME COMMAND
❶ 6711 ? S 0:00 /usr/lib/postgresql/11/bin/postgres -D /var/lib/postgresql...
❷ 6713 ? Ss 0:00 _ postgres: 11/main: checkpointer│ 6714 ? Ss 0:00 _ postgres: 11/main: background writer
│ 6715 ? Ss 0:00 _ postgres: 11/main: walwriter
│ 6716 ? Ss 0:00 _ postgres: 11/main: autovacuum launcher
│ 6717 ? Ss 0:00 _ postgres: 11/main: stats collector
6718 ? Ss 0:00 _ postgres: 11/main: logical replication launcher
❸ 9443 ? Ss 0:00 _ postgres: 11/main: fitz fitz [local] idle
fitz@ubuntu:~$ ssh ubuntu
fitz@ubuntu's password:
...
Last login: Sat Nov 21 13:29:33 2015 from localhost
fitz@ubuntu:~$ ps f -C sshd
PID TTY STAT TIME COMMAND
① 655 ? Ss 0:00 /usr/sbin/sshd -D
② 21975 ? Ss 0:00 _ sshd: fitz [priv]
③ 22086 ? S 0:00 _ sshd: fitz@pts/1fitz@ubuntu:~$ ^Dвыход
Connection to ubuntu closed.
Аналогично, при удаленном доступе по протоколу SSH программа sshd, работая в качестве диспетчера ① в одном процессе, на каждое подключение порождает один свой клон ②, который, выполнив аутентификацию и авторизацию пользователя в системе, порождает еще один свой клон ③, имперсонирующийся в пользователя и обслуживающий его запросы.
Для управления нитями в Linux используют стандартный POSIX-интерфейс pthreads
, реализующийся библиотекой W:[NPTL], которая является частью библиотеки libc
. Интерфейс предоставляет «нитевой» вызов создания нити pthread_create
, который является условным аналогом «процессных» fork
и exec
, вызов завершения и уничтожения нити pthread_exit
, условно аналогичный exit
, и вызов для получения статуса завершения нити pthread_join
, условно аналогичный wait
.
В качестве типичных примеров применения нитей можно привести сетевые сервисы, которые для параллельного обслуживания клиентских запросов используют нити вместо процессов. Например, WEB-сервер apache, как показано в следующем листинге, использует два многонитевых процесса по 27 нитей в каждом, что позволяет экономить память (за счет работы всех нитей процесса с общей памятью) при обслуживании большого количества одновременных клиентских подключений.
fitz@ubuntu:~$ ps f -C apache2
PID TTY STAT TIME COMMAND
10129 ? Ss 0:00 /usr/sbin/apache2 -k start
10131 ? Sl 0:00 _ /usr/sbin/apache2 -k start
10132 ? Sl 0:00 _ /usr/sbin/apache2 -k start
fitz@ubuntu:~$ ps fo pid,nlwp,cmd -C apache2
PID NLWP CMD
10129 1 /usr/sbin/apache2 -k start
10131 27 _ /usr/sbin/apache2 -k start
10132 27 _ /usr/sbin/apache2 -k start
fitz@ubuntu:~$ ps -fLC rsyslogd
UID PID PPID LWP C NLWP STIME TTY TIME CMD
syslog 606 1 606 0 4 ноя18 ? 00:00:00 /usr/sbin/rsyslogd -n -iNONE
syslog 606 1 680 0 4 ноя18 ? 00:00:00 /usr/sbin/rsyslogd -n -iNONE
syslog 606 1 681 0 4 ноя18 ? 00:00:00 /usr/sbin/rsyslogd -n -iNONE
syslog 606 1 682 0 4 ноя18 ? 00:00:00 /usr/sbin/rsyslogd -n -iNONE
Аналогично, сервис централизованной журнализации событий rsyslogd использует нити для параллельного сбора событийной информации из разных источников, ее обработки и журнализации. Одна нить считывает события ядра из /proc/kmsg
, вторая принимает события других служб из файлового сокета /run/systemd/journal/syslog
(/dev/log
в ранних, до systemd
системах), третья фильтрует поток принятых событий и записывает в журнальные файлы каталога /var/log/*
и т. д. Параллельная обработка потоков поступающих событий при помощи нитей производится с минимально возможными накладными расходами, что позволяет достигать колоссальной производительности по количеству обрабатываемых сообщений в единицу времени.
Распараллеливание используется не только для псевдоодновременного выполнения ветвей параллельной программы, но и для их настоящего одновременного выполнения несколькими центральными процессорами. В примере из следующего листинга показано, как сокращается время сжатия ISO-образа файла при использовании параллельного упаковщика pbzip2 по сравнению с последовательным bzip2. Для измерения времени упаковки применяется встроенная команда интерпретатора time
, при этом сначала измеряется время упаковки ❶ и время распаковки ❷ последовательным упаковщиком, а затем — время упаковки ① и время распаковки ② параллельным упаковщиком. Команды упаковки запускаются на «заднем фоне», оценивается наличие процессов и нитей паковщиков, после чего они переводятся на «передний фон» встроенной командой интерпретатора fg
(foreground) и оцениваются затраты времени.
fitz@ubuntu:~$ ls -lh plan9.iso
-rw-r--r-- 1 fitz fitz 287M нояб. 28 15:47 plan9.iso
❶ fitz@ubuntu:~$ time bzip2 plan9.iso &[1] 5545
fitz@ubuntu:~$ ps f
PID TTY STAT TIME COMMAND
4637 pts/0 S 0:00 -bash
5545 pts/0 S 0:00 _ -bash
5546 pts/0 R 0:12 _ bzip2 plan9.iso
5548 pts/0 R+ 0:00 _ ps f
fitz@ubuntu:~$ ps -fLp 5546
UID PID PPID LWP C NLWP STIME TTY TIME CMD
fitz 5546 5545 5546 96 1 10:50 pts/0 00:00:22 bzip2 plan9.iso
fitz@ubuntu:~$ fg
time bzip2 plan9.iso
real 0m54.780s
user 0m51.772s
sys 0m0.428s
fitz@ubuntu:~$ ls -lh plan9.iso.bz2
-rw-r--r-- 1 fitz fitz 89M нояб. 28 15:47 plan9.iso.bz2
❷ fitz@ubuntu:~$ time bzip2 -d plan9.iso.bz2
real 0m20.705s
user 0m19.044s
sys 0m1.168s
① fitz@ubuntu:~$ time pbzip2 plan9.iso &[1] 5571
fitz@ubuntu:~$ ps f
PID TTY STAT TIME COMMAND
4637 pts/0 S 0:00 -bash
5571 pts/0 S 0:00 -bash
5572 pts/0 Sl 0:03 _ pbzip2 plan9.iso
5580 pts/0 R+ 0:00 _ ps f
fitz@ubuntu:~$ ps -fLp 5572
UID PID PPID LWP C NLWP STIME TTY TIME CMD
fitz 5572 5571 5578 92 8 10:52 pts/0 00:00:43 pbzip2 plan9.iso
...
fitz 5572 5571 5579 1 8 10:52 pts/0 00:00:00 pbzip2 plan9.iso
fitz@ubuntu:~$ fg
time pbzip2 plan9.iso
real 0m24.259s
user 1m22.940s
sys 0m1.888s
fitz@ubuntu:~$ ls -lh plan9.iso.bz2
-rw-r--r-- 1 fitz fitz 89M нояб. 28 15:47 plan9.iso.bz2
② fitz@ubuntu:~$ time pbzip2 -d plan9.iso.bz2
real 0m7.384s
user 0m25.972s
sys 0m1.396s
В результате оценки оказывается, что последовательный упаковщик bzip2 использует один однонитевой процесс и затрачивает ≈54,7 с реального времени на упаковку, из них ≈51,7 с проводит в пользовательском режиме user
и лишь ≈0,4 с в режиме ядра sys
(выполняя системные вызовы, например read
или write
). Соотношение между временем режимов говорит о вычислительном характере программы, т. е. о существенном превалировании времени вычислительных операций упаковки над временем операций ввода‑вывода для чтения исходных данных и записи результатов. Это означает, что нагрузка последовательного упаковщика на центральный процессор (в случае, если бы он был единственный) близка к максимальной, и его параллельная реализация для псевдоодновременного выполнения ветвей (которые практически никогда не спят) лишена смысла.
Параллельный упаковщик pbzip2 использует один многонитевой процесс из восьми нитей и затрачивает ≈24,4 с реального времени на упаковку, при этом ≈1 мин 22,9 с (!) проводит в пользовательском режиме и ≈1,8 с в режиме ядра. Прирост производительности упаковки и, как следствие, сокращение времени упаковки достигаются за счет настоящего параллельного выполнения нитей на нескольких процессорах (разных ядрах процессора). Соотношение между реальным временем упаковки и суммарно затраченным временем режима пользователя, которое примерно в 3 раза больше, означает использование в среднем трех процессоров для параллельного выполнения вычислительных операций упаковки.
Как указывалось ранее, процессы и нити в ядре Linux сводятся к универсальному понятию «задача». Задача, все ресурсы которой (память, открытые файлы и т. д.) используются совместно с другими такими же задачами, является нитью. И наоборот, процессами являются такие задачи, которые обладают набором своих частных, индивидуальных ресурсов.
Универсальный системный вызов clone
позволяет указать, какие ресурсы станут общими в порождаемой и порождающей задачах, а какие — частными. Системные вызовы порождения POSIX-процессов fork
и POSIX-нитей pthread_create
оказываются в Linux всего лишь «обертками» над clone
, что проиллюстрировано в двух следующих листингах.
В первом примере архиватор tar PID = 11801
создает при помощи системного вызова clone
дочерний процесс PID = 11802
, в который помещает программу компрессора gzip, используя системный вызов execve
. В результате параллельной работы двух взаимодействующих процессов будет создан компрессированный архив docs.tgz
каталога /usr/share/doc
.
fitz@ubuntu:~$ strace -fe clone,fork,execve tar czf docs.tgz /usr/share/doc
execve("/usr/bin/tar", ["tar", "czf", "docs.tgz", "/usr/share/doc"], ... ) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID| ...) = 12403
tar: Удаляется начальный /' из имен объектов
[pid 12403] execve("/bin/sh", ["/bin/sh", "-c", "gzip"], 0x7ffd8dd597c0 ...) = 0[pid 12403] clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID| ...) = 12404
strace: Process 12403 attachedstrace: Process 12404 attached
[pid 12404] execve("/usr/bin/gzip", ["gzip"], 0x55e2e45bbb48 /* 35 vars */) = 0`
...+++ exited with 0 +++
В примере из следующего листинга компрессор pbzip2 создает при помощи системного вызова clone
семь «дочерних» нитей ❶...❼ PID = 12514->12520
, которые имеют общую память CLONE_VM
, общие открытые файлы CLONE_FILES
и прочие общие ресурсы.
fitz@ubuntu:~$ strace -fe clone,fork,execve pbzip2 plan9.iso
execve("/usr/bin/pbzip2", ["pbzip2", "plan9.iso"], 0x7ffd28884938 /* 35 vars */) = 0
❶ clone(child_stack=0x7f1d46d38fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|... ) = 12514
5 ...
❼ clone(child_stack=0x7f1d46537fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|...) = 12520
strace: Process 12520 attached
5 ...
strace: Process 12514 attached[pid 12514] +++ exited with 0 +++
5 ...[pid 12520] +++ exited with 0 +++
+++ exited with 0 +++
Процессы, попарно связанные дочерне‑родительскими отношениями, формируют дерево процессов операционной системы. Первый процесс init
, называемый прародителем процессов, порождается ядром операционной системы после инициализации и монтирования корневой файловой системы, откуда и считывается программа /sbin/init
(в современных системах является символической ссылкой на актуальный /lib/systemd/systemd
). Прародитель процессов всегда имеет PID = 1
, а его основной задачей является запуск разнообразных системных служб, включая запуск обработчиков алфавитно‑цифрового терминального доступа getty, менеджера дисплеев графического доступа, службы дистанционного доступа SSH и прочих (см. главу 10). Кроме того, systemd
назначается приемным родителем для «осиротевших» процессов, а также отслеживает аварийные завершения запускаемых им служб и перезапускает их.
В примере из следующего листинга показано дерево процессов, построенное при помощи специальной команды pstree
, а в листинге “Процессы ядра, демоны, прикладные процессы” — «классическое» представление дерева процессов при помощи команды ps
.
fitz@ubuntu:~$ pstree -cnAhT
systemd-+-systemd-journal
❷ |-systemd-udevd
❷ |-systemd-resolve
❷ |-rsyslogd
...
|-gdm3---gdm-session-wor-+-gdm-session-wor
| |-gdm-x-session-+-Xorg
| | -gnome-session-b
-bash
...
|-systemd-+-(sd-pam)
...
| |-gnome-terminal--+-bash---man---pager ❸
| |
...
❷ |-postgres-+-postgres
| |-postgres
| |-postgres
| |-postgres
| |-postgres
| -postgres
-apache2
❷ |-apache2-+-apache2
|
...
❷ |-sshd-+-sshd---sshd---bash
| `-sshd---sshd---bash
...
|-agetty
|-login---bash---pstree ❸
...
Процессы операционной системы принято классифицировать на системные (ядерные), демоны и прикладные, исходя из их назначения и свойств (см. листинг “Процессы ядра, демоны, прикладные процессы”).
Прикладные процессы ❸ выполняют обычные пользовательские программы (например, утилиту man
), для чего им выделяют индивидуальную память, объем которой указан в столбце VSZ вывода команды ps
. Такие процессы обычно интерактивно взаимодействуют с пользователем посредством управляющего терминала (за исключением графических программ), указанного в столбце TTY.
Демоны (daemons) ❷ выполняют системные программы, реализующие те или иные службы операционной системы. Например, cron реализует службу периодического выполнения заданий, atd — службу отложенного выполнения заданий, rsyslogd — службу централизованной журнализации событий, sshd — службу дистанционного доступа, systemd-udevd — службу «регистрации» подключаемых устройств, и т. д. Демоны запускаются на ранних стадиях загрузки операционной системы и взаимодействуют с пользователем не интерактивно при помощи терминала, а опосредованно — при помощи своих утилит. Таким образом, отсутствие управляющего терминала в столбце TTY отличает их от прикладных процессов.
fitz@ubuntu:~$ ps faxu
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 2 0.0 0.0 0 0 ? S ноя18 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? I< ноя18 0:00 _ [rcu_gp]
root 4 0.0 0.0 0 0 ? I< ноя18 0:00 _ [rcu_par_gp]
root 6 0.0 0.0 0 0 ? ❶ I< ноя18 0:00 _ [kworker/0:0H...]
root 8 0.0 0.0 0 0 ? I< ноя18 0:00 _ [mm_percpu_wq]
root 9 0.0 0.0 0 0 ? S ноя18 0:09 _ [ksoftirqd/0]
...
root 1 0.0 0.2 168400 11684 ? Ss ноя18 0:12 /sbin/init splash
...
root 333 0.0 0.1 21844 5348 ? Ss ноя18 0:07 /lib/systemd/systemd-udevd
syslog 606 0.0 0.1 224360 4244 ? Ssl ноя18 0:01 /usr/sbin/rsyslogd -n –i...
...
root 649 0.0 0.0 20320 3036 ? ❷ Ss ноя18 0:00 /usr/sbin/cron -f
daemon 675 0.0 0.0 3736 2184 ? Ss ноя18 0:00 /usr/sbin/atd –f
...
root 21545 0.0 0.0 5560 3420 tty4 Ss ноя18 0:00 /bin/login -p --
fitz 28152 0.0 0.0 2600 1784 tty4 S 01:38 0:00 _ -sh
fitz 28162 0.0 0.0 12948 3584 tty4 S+ 01:38 0:00 _ bash
finn 12989 0.2 0.012092 3988 tty4 ❸ S+ 13:47 0:00 _ man ps
finn 13000 0.0 0.0 10764 2544 tty4 S+ 13:47 0:00 _ pager
Зачастую демоны имеют суффикс d
в конце названия, например sshd — это secure shell daemon, а rsyslogd — rocket system logging daemon, и т. д.
Системные (ядерные) ❶ процессы (gравильнее — ядерные нити, т. к. выполняются они в общей памяти ядра операционной системы) выполняют параллельные части ядра операционной системы, поэтому не обладают ни индивидуальной виртуальной памятью VSZ, ни управляющим терминалом TTY. Более того, ядерные процессы не выполняют отдельную программу, загружаемую из ELF-файла, поэтому их имена COMMAND являются условными и изображаются в квадратных скобках, а кроме того, они имеют особое состояние I
в столбце STAT.
Процесс в операционной системе является основным активным субъектом, взаимодействующим с окружающими его объектами — файлами и файловыми системами, другими процессами, устройствами и пр. Возможности процесса выполнять те или иные действия по отношению к другим объектам определяются его специальными свойствами — атрибутами процесса.
Возможности процесса по отношению к объектам, доступ к которым разграничивается при помощи дискреционных механизмов (в частности, к файлам дерева каталогов) определяются значениями его атрибутов, формирующих его DAC-маркер доступа, а именно — атрибутами RUID, RGID, EUID, EGID, см. credentials
.
Эффективные идентификаторы EUID (effective user identifier) и EGID (effective group identifier) указывают на «эффективных» пользователя и группу, использующихся дискреционными механизмами для определения прав доступа процесса к файлам и другим объектам согласно назначенному им режиму или списку доступа. Атрибуты RUID (real user identifier) и RGID (real group identifier) указывают на «настоящих» пользователя и группу, «управляющих» процессом.
Первому процессу пользовательского сеанса (в случае регистрации в системе
с использованием алфавитно‑цифрового терминала — командному интерпретатору) назначают атрибуты RUID/EUID и RGID/EGID равными идентификаторам зарегистрировавшегося пользователя и его первичной группы. Последующие процессы пользовательского сеанса наследуют значения атрибутов, т. к. порождаются в результате клонирования при помощи fork
. В примере из следующего листинга при помощи команды id
показаны значения EUID/EGID пользовательского сеанса и их наследование от командного интерпретатора, что явным образом подтверждает команда ps
.
fitz@ubuntu:~$ id
uid=1006(fitz) gid=1008(fitz) группы=1008(fitz)
fitz@ubuntu:~$ ps fo euid,ruid,egid,rgid,user,group,tty,cmd
EUID RUID EGID RGID USER GROUP TT CMD
1006 1006 1008 1008 fitz fitz pts/2 -bash
1006 1006 1008 1008 fitz fitz pts/2 _ ps fo euid,uid,egid,...,tty,cmd
Изменение идентификаторов EUID/EGID процесса происходит при срабатывании механизма неявной передачи полномочий, основанном на дополнительных атрибутах SUID/SGID файлов программ. При запуске таких программ посредством системного вызова exec
атрибуты EUID/EGID запускающего процесса устанавливаются равными идентификаторам UID/GID владельца запускаемой программы. В результате процесс, в который будет загружена такая программа, будет обладать правами владельца программы, а не правами пользователя, запустившего эту программу.
В следующем листинге приведен типичный пример использования механизма неявной передачи полномочий при выполнении команд passwd
и wall
. При смене пароля пользователем при помощи программы /usr/bin/passwd
ее процесс получает необходимое право записи ① в файл /etc/shadow
в результате передачи полномочий ❶ суперпользователя root (UID=0)
. При передаче широковещательного сообщения всем пользователям при помощи /usr/bin/wall
необходимо иметь право записи ② в их файлы устройств /dev/tty*
, которое появляется ❷ в результате передачи полномочий группы tty (GID = 5)
.
fitz@ubuntu:~$ who
fitz pts/0 2019-11-22 00:52 (:0.0)
fitz pts/1 2019-11-22 00:53 (:0.0)
fitz pts/2 2019-11-22 01:06 (:0.0)
fitz@ubuntu:~$ ls -la /etc/shadow /dev/pts/*
crw--w---- 1 fitz tty 136, 2 ноя 19 12:00 /dev/pts/1
② crw--w---- 1 fitz tty 136, 3 ноя 19 13:53 /dev/pts/2
c--------- 1 root root 5, 2 ноя 17 03:30 /dev/pts/ptmx
① -rw-r----- 1 root shadow 1647 ноя 19 12:27 /etc/shadow
fitz@ubuntu:~$ ls -l /usr/bin/passwd /usr/bin/wall
-rwsr-xr-x 1 root root 67992 авг 29 16:00 /usr/bin/passwd
-rwxr-sr-x 1 root tty 35048 авг 21 16:19 /usr/bin/wall
fitz@ubuntu:~$ ls -ln /usr/bin/passwd /usr/bin/wall
-rwsr-xr-x 1 0 0 67992 авг 29 16:00 /usr/bin/passwd
-rwxr-sr-x 1 0 5 35048 авг 21 16:19 /usr/bin/wall
fitz@ubuntu:~$ ps ft pts/1,pts/2 o pid,ruid,rgid,euid,egid,tty,cmd
PID RUID RGID EUID EGID TT CMD
27883 1006 1008 1006 1008 pts/2 bash
❷ 27937 1006 1008 1006 5 pts/2 _ wall
27124 1006 1008 1006 1008 pts/1 bash
❶ 27839 1006 1008 0 1008 pts/1 _ passwd
По отношению к объектам, доступ к которым ограничивается при помощи мандатных механизмов, возможности процесса определяются значениями его МАС‑маркера доступа, а именно — атрибутом мандатной метки LABEL. Как и RUID/EUID/RGID/EGID, атрибут LABEL назначается первому процессу сеанса пользователя явным образом, а затем наследуется при клонировании процессами‑потомками от процессов‑родителей. В примере из приведенного ниже листинга при помощи команды id
показан атрибут LABEL сеанса пользователя, а при помощи команды ps
— его явное наследование от процесса‑родителя.
Аналогично изменениям EUID/EGID процесса, происходящим при запуске SUID-ной/SGID-ной программы, изменение метки LABEL процесса происходит (согласно мандатным правилам ❶) в системном вызове exec
при запуске программы, помеченной соответствующей мандатной меткой файла. Так, например, при запуске программы /usr/sbin/dhclient
с типом dhcpc_exec_t
ее мандатной метки ❸ процесс приобретает тип dhcpc_t
своей мандатной метки ❹, в результате чего существенно ограничивается в правах доступа к разным объектам операционной системы.
fitz@ubuntu:~$ ssh lich@fedora
lich@fedora's password:
Last login: Sat Nov 21 14:25:16 2015
[lich@centos ~]$ id -Z
staff_u:staff_r:staff_t:s0-s0:c0.c1023
[lich@centos ~]$ ps Zf
LABEL PID TTY STAT TIME COMMAND
staff_u:staff_r:staff_t:s0-s0:c0.c1023 31396 pts/0 Ss 0:00 -bash
staff_u:staff_r:staff_t:s0-s0:c0.c1023 31835 pts/0 R+ 0:00 _ ps Zf
staff_u:staff_r:staff_t:s0-s0:c0.c1023 31334 tty2 Ss+ 0:00 -bash[lich@centos ~]$ sesearch -T -t dhcpc_exec_t -c process
Found 19 semantic te rules:
...
❶ type_transition NetworkManager_t dhcpc_exec_t : process dhcpc_t;
...
[lich@centos ~]$ ls -Z /usr/sbin/dhclient
❷ -rwxr-xr-x. root root system_u:object_r:dhcpc_exec_t:s0 /usr/sbin/dhclient[lich@centos ~]$ ps -ZC dhclient
LABEL PID TTY TIME CMD
❸ system_u:system_r:dhcpc_t:s0 2120 ? 00:00:00 dhclient
system_u:system_r:dhcpc_t:s0 4320 ? 00:00:00 dhclient
Еще одним важным атрибутом процесса, определяющим его возможности по использованию системных вызовов, являются привилегии процесса cababilities
. Например, обладание привилегией CAP_SYS_PTRACE
разрешает процессам трассировщиков strace
и ltrace
, использующих системный вызов ptrace
, трассировать процессы любых пользователей (а не только «свои», EUID которых совпадает с EUID трассировщика). Аналогично, привилегия CAP_SYS_NICE
разрешает изменять приоритет, устанавливать привязку к процессорам и назначать алгоритмы планирования процессов и нитей любых пользователей, а привилегия CAP_KILL
разрешает посылать сигналы процессам любых пользователей.
Явная привилегия «владельца» CAP_FOWNER
позволяет процессам изменять режим и списки доступа, мандатную метку, расширенные атрибуты и флаги любых файлов так, словно процесс выполняется от лица владельца файла. Привилегия CAP_LINUX_IMMUTABLE
разрешает управлять флагами файлов i
, immutable
и a
, append
, а привилегия CAP_SETFCAP
— устанавливать «файловые» привилегии запускаемых программ.
Необходимо отметить, что именно обладание полным набором привилегий делает пользователя root (UID=0)
в Linux суперпользователем. И наоборот, обычный, непривилегированный пользователь (в смысле UID≠0
) не обладает никакими явными привилегиями (неявно он обладает привилегией владельца для всех своих объектов). Назначение привилегий процесса (здесь допущено намеренное упрощение механизма наследования и назначения привилегий при fork
и exec
без потери смысла) происходит при запуске программы при помощи системного вызова exec
, исполняемый файл которого помечен «файловыми» привилегиями.
В примере из следующего листинга иллюстрируется получение списка привилегий процесса при помощи утилиты getpcaps
. Как и ожидалось, процесс postgres (PID=6711)
, работающий от лица обычного (непривилегированного, в смысле UID≠0
) псевдопользователя postgres
, не имеет ❶ никаких привилегий, а процесс apache2 (PID=10129)
, работающий от лица суперпользователя root (UID=0)
, имеет полный ❷ набор привилегий. Однако процесс NetworkManager (PID=646)
выполняется от лица «суперпользователя», лишенного ❸ большинства своих привилегий, т. к. ему их умышленно уменьшили при его запуске (см. systemd
в главе 8) до минимально необходимого набора, достаточного для выполнения его функций (xто способствует обеспечению защищенности операционной системы).
fitz@ubuntu:~$ ps fo user,pid,cmd -C NetworkManager,postgres,apache2
USER PID CMD
root 10129 /usr/sbin/apache2 -k start
www-data 10131 _ /usr/sbin/apache2 -k start
www-data 10132 _ /usr/sbin/apache2 -k start
postgres 6711 /usr/lib/postgresql/11/bin/postgres -D /var/lib/postgresql/11/main ...
postgres 6713 _ postgres: 11/main: checkpointer
postgres 6714 _ postgres: 11/main: background writer
postgres 6715 _ postgres: 11/main: walwriter
postgres 6716 _ postgres: 11/main: autovacuum launcher
postgres 6717 _ postgres: 11/main: stats collector
postgres 6718 _ postgres: 11/main: logical replication launcher
root 646 /usr/sbin/NetworkManager --no-daemon
fitz@ubuntu:~$ getpcaps 6711
❶ Capabilities for 6711': =
10129': = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,
❷ fitz@ubuntu:~$ getpcaps 10129
Capabilities for
cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_
admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,
cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_
sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_
setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,
cap_audit_read+ep
❸ fitz@ubuntu:~$ getpcaps 646
Capabilities for `646': = cap_dac_override,cap_kill,cap_setgid,cap_setuid,cap_net_bind_service,cap_net_admin,
cap_net_raw,cap_sys_module,cap_sys_chroot,cap_audit_write+ep
В листинге ниже показан типичный пример применения отдельных привилегий там, где классически применяется неявная передача всех полномочий суперпользователя при помощи механизма SUID/SGID. Например, «обычная» утилита ping
для выполнения своей работы должна создать «необработанный» raw
сетевой сокет, что является с точки зрения ядра привилегированной операцией. В старых системах (актуально для Ubuntu до версии 18.10 включительно, начиная с 19.04 все уже «правильно из коробки») программа /bin/ping
наделялась атрибутом SUID
❶ и находилась во владении суперпользователя root
, чьи права и передавались при ее запуске. С точки зрения защищенности системы это не соответствует здравому смыслу, подсказывающему наделять программы минимально необходимыми возможностями, достаточными для их функционирования. Для создания «необработанных» raw
и пакетных packet
сокетов достаточно только привилегии CAP_NET_RAW
, а весь суперпользовательский набор привилегий более чем избыточен.
fitz@ubuntu-1804:~$ ls -l /bin/ping
❶ -rwsr-xr-x 1 root root 64424 Jun 28 11:05 /bin/ping
fitz@ubuntu-1804:~$ ping ubuntu-1804
PING ubuntu-1804 (127.0.1.1) 56(84) bytes of data.
64 bytes from ubuntu-1804 (127.0.1.1): icmp_req=1 ttl=64 time=0.074 ms^C
--- ubuntu ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.074/0.074/0.074/0.000 ms
fitz@ubuntu-1804:~$ sudo chmod u-s /bin/ping
fitz@ubuntu-1804:~$ ls -l /bin/ping
-rwxr-xr-x 1 root root 64424 Jun 28 11:05 /bin/ping
fitz@ubuntu-1804:~$ ping ubuntu-1804
ping: icmp open socket: Operation not permitted
❸ fitz@ubuntu-1804:~$ sudo setcap cap_net_raw+ep /bin/ping
fitz@ubuntu-1804:~$ getcap /bin/ping
/bin/ping = cap_net_raw+ep
fitz@ubuntu-1804:~$ ping ubuntu-1804
PING ubuntu (127.0.1.1) 56(84) bytes of data.
64 bytes from ubuntu (127.0.1.1): icmp_req=1 ttl=64 time=0.142 ms^C
--- ubuntu ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.142/0.142/0.142/0.000 ms
При отключении передачи полномочий ❷ программа /bin/ping
лишается возможности выполнять свои функции, а при назначении ей при помощи команды setcap
«файловой» привилегии CAP_NET_RAW
❸ функциональность возвращается в полном объеме, т. к. приводит к установке «процессной» привилегии CAP_NET_RAW
при запуске этой программы. Для просмотра привилегий, делегируемых при запуске программ, используется парная команда getcap
.
Аналогично, при использовании анализаторов сетевого трафика tshark и/или wireshark, вызывающих для захвата сетевых пакетов утилиту dumpcap, требуется открывать как «необработанные» raw
, так и пакетные packet
сетевые сокеты, что требует той же привилегии CAP_NET_RAW
. Классический способ применения анализаторов пакетов состоит в использовании явной передачи всех полномочий суперпользователя (при помощи su
или sudo
) при их запуске, что опять не соответствует минимально необходимым и достаточным требованиям к разрешенным возможностям программ.
fitz@ubuntu:~$ tshark
tshark: There are no interfaces on which a capture can be done
itz@ubuntu:~$ strace -fe execve tsharkexecve("/usr/bin/tshark", ["tshark"], [/* 23 vars */]) = 0
Process 8951 attached[pid 8951] execve("/usr/bin/dumpcap", ["/usr/bin/dumpcap", "-D", "-Z", "none"],...) = 0
Process 8951 detached--- SIGCHLD (Child exited) @ 0 (0) ---
tshark: There are no interfaces on which a capture can be done
fitz@ubuntu:~$ ls -la /usr/bin/dumpcap
-rwxr-xr-x 1 root root 104688 Sep 5 19:43 /usr/bin/dumpcap
fitz@ubuntu:~$ getcap /usr/bin/dumpcap
fitz@ubuntu:~$ sudo setcap cap_net_raw+ep /usr/bin/dumpcap
fitz@ubuntu:~$ getcap /usr/bin/dumpcap
/usr/bin/dumpcap = cap_net_raw+ep
fitz@ubuntu:~$ tshark -i wlan0
Capturing on wlan0
0.307205 fe80::895d:9d7d:f0b3:a372 → ff02::1:ff96:2df6 ICMPv6 86 Neighbor Solicitation
0.307460 SuperMic_74:0e:90 → Spanning-tree-(for-bridges)_00 STP 60 Conf. Root = 32768/0/00:25:90:74:0e:90 Cost = 0 Port = 0x8001
...
Для эффективного использования анализаторов трафика непривилегированными пользователями достаточно делегировать их процессам захвата пакетов привилегию CAP_NET_RAW
при помощи «файловых» привилегий CAP_NET_RAW
для программы захвата /usr/bin/dumpcap, что и проиллюстрировано в предыдущем листинге.
Необходимо заметить, что все это уже достаточно давно умеет проделывать инсталлятор при установке пакета wireshark-common (от которого зависят пакеты tshark и wireshark), если утвердительно ответить на вопрос инсталлятора ‘Should non-superusers be able to capture packets?’. Однако для более простого tcpdump такой услуги не предоставлено ☺.
Переменные окружения и текущий рабочий каталог на поверку тоже оказываются атрибутами процесса, которые можно получить при помощи команд ps
и pwdx
соответственно.
fitz@ubuntu:~$ ps fe
PID TTY STAT TIME COMMAND
21872 pts/2 S 0:00 -bash USER=fitz LOGNAME=fitz HOME=/home/fitz PATH=/usr/...
22904 pts/2 R+ 0:00 _ ps fe LANGUAGE=ru:ko:en LC_ADDRESS=ru_RU.UTF-8 ...
fitz@ubuntu:~$ ps fx
PID TTY STAT TIME COMMAND
22984 pts/0 S 0:00 -bash
23086 pts/0 S+ 0:00 _ man ps
23097 pts/0 S+ 0:00 _ pager
21872 pts/2 S 0:00 -bash
23103 pts/2 R+ 0:00 _ ps fx
fitz@ubuntu:~$ pwdx 23097 22984
23097: /home/fitz
22984: /home/fitz
Читайте также
Последние новости