Евгений Зобнин
info
На сайте издательства можно купить эту книгу со скидкой 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
Не стоит забывать, что самой главной программой операционной системы является ее ядро, которое в 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).
info
Существует еще один (неудачный, на мой взгляд) перевод понятия thread на русский язык — поток. Во‑первых, он конфликтует с переводом понятия stream — поток, а во‑вторых, в отличие от stream, thread никуда не течет. А вот процесс (process) содержит в себе нити (thread) абсолютно таким же образом, как и обычная веревка состоит из нитей.
Механизм нитей позволяет переключать центральный процессор между параллельными ветвями одной программы, размещаемыми в одном (!) процессе. Нити никак не изолированы друг от друга, и им доступны абсолютно все ресурсы своего процесса, поэтому задача обмена данными между нитями попросту отсутствует, т. к. все данные являются для них общими.
В примере из cktle.otuj следующего листинга показаны нити процесса в BSD-формате вывода. Выбор процесса производится по его идентификатору PID, предварительно полученному командой pgrep по имени программы, выполняющейся в искомом процессе.
В выводе наличие нитей процесса отмечает флаг l (lwp) в столбце состояния STAT, а каждая строчка без идентификатора PID символизирует одну нить. Так как в многонитевой программе переключение процессора производится между нитями, то и состояния сна S, выполнения или ожидания R приписываются отдельным нитям. Нити процессов, BSD-формат вывода
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. Нити процессов, SYSV-формат вывода
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/1
fitz@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
Как указывалось ранее, процессы и нити в ядре Linux сводятся к универсальному понятию «задача». Задача, все ресурсы которой (память, открытые файлы и т. д.) используются совместно с другими такими же задачами, является нитью. И наоборот, процессами являются такие задачи, которые обладают набором своих частных, индивидуальных ресурсов.
Универсальный системный вызов clone позволяет указать, какие ресурсы станут общими в порождаемой и порождающей задачах, а какие — частными. Системные вызовы порождения POSIX-процессов fork и POSIX-нитей pthread_create оказываются в Linux всего лишь «обертками» над clone , что проиллюстрировано в двух следующих листингах.
В первом примере архиватор tar PID = 11801 создает при помощи системного вызова clone дочерний процесс PID = 11802 , в который помещает программу компрессора gzip, используя системный вызов execve . В результате параллельной работы двух взаимодействующих процессов будет создан компрессированный архив docs.tgz каталога /usr/share/doc . Системный вызов clone — порождение нового процесса
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: Удаляется начальный /' из имен объектов strace: Process 12403 attached [pid 12403] execve("/bin/sh", ["/bin/sh", "-c", "gzip"], 0x7ffd8dd597c0 ...) = 0 [pid 12403] clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID| ...) = 12404strace: 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 и прочие общие ресурсы. Системный вызов clone — порождение новой нити
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 ... |-systemd-+-(sd-pam) ... | |-gnome-terminal--+-bash---man---pager ❸ | | -bash ... ❷ |-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
info
Зачастую демоны имеют суффикс 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 . DAC-маркер доступа процесса — атрибуты RUID, EUID, RGID, EGID
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) . Атрибуты файла SUID/SGID и атрибуты процесса RUID, EUID, RGID, EGID
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 своей мандатной метки ❹, в результате чего существенно ограничивается в правах доступа к разным объектам операционной системы. MAC-маркер доступа процесса — мандатная метка selinux
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то способствует обеспечению защищенности операционной системы). Привилегии (capabilities) процесса
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': = ❷ fitz@ubuntu:~$ getpcaps 10129 Capabilities for 10129': = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid, 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 , а весь суперпользовательский набор привилегий более чем избыточен. Делегирование привилегий программы ping
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 ) при их запуске, что опять не соответствует минимально необходимым и достаточным требованиям к разрешенным возможностям программ. Делегирование привилегий программе tshark
fitz@ubuntu:~$ tshark tshark: There are no interfaces on which a capture can be done itz@ubuntu:~$ strace -fe execve tshark
execve("/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, что и проиллюстрировано в предыдущем листинге.
info
Необходимо заметить, что все это уже достаточно давно умеет проделывать инсталлятор при установке пакета 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
Перейти обратно к новости
|