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

О книге

Сов­сем недав­но в изда­тель­стве БХВ выш­ло вто­рое изда­ние кни­ги Дмит­рия Кетова «Внут­реннее устрой­ство Linux». Я про­чел пре­дос­тавлен­ный изда­тель­ством экзем­пляр и сде­лал свои выводы о качес­тве кни­ги, ее плю­сах и целевой ауди­тории. В при­дачу мы пуб­лику­ем отры­вок кни­ги, который поз­волит тебе сос­тавить собс­твен­ное мне­ние.

Нес­мотря на гром­кое наз­вание, эта кни­га — не об устрой­стве Linux. Из нее ты не узна­ешь, как работа­ет сис­тема управле­ния вир­туаль­ной памятью ядра Linux или фай­ловая сис­тема Btrfs. Эта кни­га — ско­рее учеб­ник «GNU/Linux для про­дол­жающих», то есть тех, кто понял, что такое коман­дный интер­пре­татор и пра­ва дос­тупа, но хотел бы коп­нуть глуб­же и понять, почему этот интер­пре­татор имен­но такой, с какой целью появил­ся инс­тру­мент sudo и почему, нес­мотря на весь хейт, менед­жер Systemd стал стан­дартом.

Глав­ное дос­тоинс­тво кни­ги — гра­мот­ное балан­сирова­ние меж­ду теорией и прак­тикой. Автор соп­ровож­дает текст боль­шим количес­твом исто­ричес­ких спра­вок и деталей, которые быва­ют нез­накомы даже мне, умуд­ренно­му двад­цатилет­ним опы­том линук­соиду. При этом прак­тичес­ки каж­дое выс­казыва­ние авто­ра соп­ровож­дает­ся при­мером, пояс­няющим теорию. Хочешь уви­деть, как работа­ют управля­ющие пос­ледова­тель­нос­ти тер­минала? Вот тебе лис­тинг команд с отметка­ми, где, что и как надо нажать.

Вто­рой плюс — пол­нота информа­ции. Нес­мотря на мас­су под­робнос­тей о реали­зации ком­понен­тов типич­ного дис­три­бути­ва и срав­нитель­но неболь­шой объ­ем, кни­га охва­тыва­ет прак­тичес­ки все, что может быть инте­рес­но поль­зовате­лю, начиная от эму­лято­ра тер­минала и коман­дно­го интер­пре­тато­ра и закан­чивая гра­фичес­кой сис­темой Wayland, которая толь­ко готовит­ся занять мес­то стан­дар­тной в дис­три­бути­вах Linux, пла­ниров­щиком BFQ и кон­тей­нер­ной сис­темой Docker, о которых ты не узна­ешь из более «ака­деми­чес­кой» и «про­верен­ной вре­менем» литера­туры.

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

Ев­гений Зоб­нин

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.012092 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

4 комментария

  1. Аватар

    Smart_Fox

    08.03.2021 в 18:13

    Инфа ценная :)) Спасибо ооооо что надо

  2. Аватар

    Роман

    16.03.2021 в 18:26

    Очень смущает 32 или 48 бит… 48?

  3. Аватар

    p.zybkin

    09.04.2021 в 12:11

    Книгу приобрел и не пожалел, не копал глубоко в linux`е раньше. Единственная печалька — не нашёл адреса для возможных вопросов к автору)
    Например, непонятен такой момент. Отрывок другой главы не про память, а про назначение доступов:
    «Для просмотра и назначения внеядерных (пользовательских) расширенных атрибутов используются утилиты getfattr(1) и setfattr(1)…» далее по тексту: «Ядерные (системные) обычно управляются специально предназначенными командами: например, getfacl(1) и setfacl(1) …» пример приведённый в главе 3.7 только по setfattr.
    Я из man`ов понял, что setattr — расширенные атрибуты объектов файловой системы, а setfacl — это блин, тоже список доступа к тем же объектам фс(( а в чём разница между данными командами и как понять какую из них использовать для каких случаев — совсем не понял. Может кто знает?

Оставить мнение