Системные и пользовательские службы

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

В этой процедуре загрузки в случае IBM совместимых компьютеров есть неприятность: она связана с огромным кол-вом легаси. И вот, производители железа решили поменять эту процедуру загрузки на EFI.

Как выглядит процедура загрузки через обычный биос мы в прошлый раз даже смотрели. Что там произошло: в начале - загрузка первичного загрузчика, потом загрузится вторичный загрузчик и предложит нам меню. Его задача - загрузить операционную систему. Он также умеет загружать другой загрузчик.  Давайте теперь подсунем ему вместо обычного биоса то, что называется EFI.

Один из минусов биоса: процессор во время начальной загрузки работает в 16-разрядном режиме как в DOS. И пользуется 16-разрядными функциями. Конечно, это из прошлого тысячелетия. Соответственно, я подозреваю, что популярность EFI в качестве загрузчика в немалой степени диктовалась тем, что у них был баланс между легаси и новым (ново). EFI в режиме супервизора, даже можно по-хитрому драйверы писать. А с другой стороны, в EFI полно легаси, например, файловая система там - FAT-32.

Так вот. Сейчас на сидюке обнаружится EFI раздел, там будут какие-то дополнительные программы и вся картина будет выглядеть так: загружается код EFI, вот он попытался загрузить какой-то загрузчик по умолчанию - загрузчика по умолчанию нет, но он поискал среди имеющихся устройств те, с которых можно загрузить что-то по стандарту EFI, нашел загрузчик нашего дистрибутива.

EFI выгодно отличается от других стандартов тем, что там дохрена напихано. Не знаю зачем все это нужно, но для красоты, наверное.

Squashfs - упаковщик, предоставляющий доступ к данным в режиме «только для чтения». 

Итак, вернемся к загрузке, с которой загружается каталог EFI.

Загружается картинка, где есть 2 пункта, один связан с линуксом, второй с собственно EFI, а снизу всякие подпункты.  EFI Shell: не знаю, говорил я или нет, он - загрузчик EFI - операционная система такая, она поддерживает выполнение бинарников, эти бинарники - windows executable. Сам раздел должен быть FAT-32, поддерживается какое-то количество комманд, протокол работы примерно такой, как в досовской комадной строке. На этой машине есть еще один раздел EFI - FS0, и там есть приложенька заранее записанная, которую мы можем запустить.

$ hello.efi
> Hello, world!

Так вот, маленький секрет. Эмулятор, который называется QEMU - я с удивлением узнал, что можно сказать так: "мне лень класть образ диска, у тебя есть каталог, в этом каталоге есть файлы, а твоя виртуальная машина пускай думает, что это устройство отформатировано под FAT-32». QEMU умеет из каталога сделать устройство FAT-32.

Есть специальное сдк, которое позволяет такие программы делать. Собственно, вот.

У нас есть EFI shell, который прямо забит в фирмварь. Он другой. Вендоры разные. Это великая фича: мы придумали стандарт, а вы сами рисуйте как хотите реализацию. Поскольку пишут ее производители железа, то, чаще всего, она написана криво.

Точно так же запускаается линуксовый загрузчик.

Что происходит дальше: дальше происходит загрузка ядра, ядро загружает какие-то процессы, выполяет какие-то команды и, в конце концов, система должна быть в штатном режиме. Проблемы выхода в штатный режим мы пытались в прошлый раз обсудить.

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

Такая последовательность загрузки имеет 2 недостатка:

Первый(главный): управление операционной системой - это не только вывод ее в штатный режим и выключение. Управление операционной системой, например, включает в себя такую штуку, как обработка изменения аппаратных конфигураций. Воткнули сканер, новый монитор… Например, если воткнуть монитор - открывается программа настройки монитора. Кто на это должен отреагировать? Со всей очевидностью это делает операционная система. Программа настройки монитора глубоко прикладная. Понятное дело, что на самом деле этим должна заниматься операционная система, она не должна рисовать красивые картинки. Она не должна вообще думать о том, какие изображения выводить на экран - это прикладные штуки. Но определение изменения железяки должна сделать операционная система. Другая картина очень важная: появился новый сетевой интерфейс. Например, подключили вайфай. Кто этим должен заниматься? Этим должна заниматься сетевая подистема, которая занимается собственно определением того, как наш компьютер реагирует на сетевые взаимодействия. Но откуда она узнает, что появился сетевой интерфейс? Этим должна заниматься операционная система. 

Все это происходит не при докстарте и останове, но и в режиме реального времени. Ничего этого нам система из шелл скриптов не дает. 

Второй недостаток, связанный с оформлением сценариев: большая часть того, действий, которые требуются от обычного сервиса, однотипные. Например, при организации сетевого подключения, нужно запиниться на сокет, сделать листенер, сделать кучу всяких действий. Вся вот эта вот механика. Любой сетевой сервер делает так. Очевидно - написать универсальный сервер, который будет это делать и на уровне протокола предоставлять ему программы со стандартного ввода. Конечно, хотелось бы, чтобы это было встроено в общее дерево. Было бы неплохо научить нашу управлялку запускать несколько экземпляров.  Кто обеспечит изоляцию от случайного доступа, чтобы не прибили ваш процесс случайно? Все это делается, но требует дополнительных усилий от разработчика и главное почти в любом сервисе эти действия все равно будут производиться. Так почему бы не сделать виртуалку, которая предоставляет эту возможность, а сам сервис - маленькая тупая программа.

Еще одна проблема - время выполнения скриптов.

Еще одна вещь - совершенно непонятно, а какие, собственно, процессы, если их больше одного, породил ваш сервис? Потому что нет никакого способа сказать «убей эту программу и все которые ты запустил». Приходится складывать в специальные файлы. Очень неудобно, не говоря о том что на эту группу хотелось бы наложить ограничения.

Вернёмся к сегодняшней лекции. Бешеными темпами развивающаяся управлялка системы, которая называется systemd. Изначально systemd преследовала в большей степени именно эту цель. Организовать параллельный запуск служб и тем самым построить дерево зависимостей и убыстрить загрузку системы. Но довольно быстро стало понятно, что главная проблема именно в том, что многие фунцкии системы - изменение профиля системы потребует централизванного управления. Кто-то один должен этим заниматься, и эта штука - systemd.

Не в порядке значимости, но в порядке, который мне пришел в голову: решается в systemd вопрос жруналирования. Ваша управлялка systemd - такой демон, написанный на си, настоящий. Вы можете под ним запустить программу, которая читает со стандартного ввода, пишет на стандартный вывод и ошибки направляет в поток ошибок. И stderr будет попадать в журнал.

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

Пример:

Запускаем нечто под названием memcached.

Обратите внимание на его зависимости: перед тем, как запустить демон, но после того, как будет достигнут чекпойнт - контрольная точка. Она будет запущена с какими-то параметрами окружения, файл настройки - если есть - есть, если нету - ну значит нету.

И наконец запускаем инсталл.

systemctl start memcached

Мы сейчас продемонстрировали ситуацию, когда мы запустили штуку руками.

Обратите так же внимание, что в systemd используется контрольная точка. У нас есть службы, но тот факт что она - всего лишь символьная ссылка на уровень мультиюзер таргет.

Как видите, при достижении контрольной точки мультиюзер таргет администратором сказано что, вот эти службы должны подниматься потому что администратор так захотел. Это относительно служб.

Вторая фишка от systemd: нам не нужно знать никакие PID файлы, потому что каждый сервис у нас заворачивается в отдельную группу (контрол-группу) cgroup - отдельная, уже не новая тема для линуксов, которая в отличие от наследования процессов не доверяет самим процессам.  Тем самым проблема отслеживания ресурсов процессов и их группировка снимается.

1.2.png

Ну, кстати, к вопросу об этой картинке : systemd-cgls

Если нам нужно выделить группу, где надо проконтролировать выделение ресурсов, мы делаем слайс. Слайсы бывают вложенные. И там запущенные сервисы: для них отдельно нарезан систем слайс.

Что еще умеет systemd

Запускать, как вы видели, сервисы потому что они запущены руками, разрешены админу, требуются для запуска других сервисов. Можно посмотреть состояние. Поскольку есть такое понятие, как зависимость, есть возможность построить дерево загрузок.

systemd-analyze: показывает статистику

2.1.png

3.1.png

4.1.png

Помимо, собственно, желания администратора и зависимостей и это собственно главное, почему неудобно на современном этапе пользоваться классической юникс схемой. Есть много событий, которые происходят с объектами операционной системы, которые требуют запуска, перезапуска… Самый примитивный: создавать и поддерживать в порядке какой-нибудь каталог.

5.1.png

find /lib/systemd/ -name \*.path - юниты которые заканчиваются на .path

cat /lib/systemd/system/cups.path

systemctl status cups - работает.

Он должен проверять, что он существует и в зависимости от этого что-то делать. Активирует соответствующий юнит.  Тогда зачем он нужен? Если туда что-то положили, то запускается CUPS.

6.1.png

Слава богу, в линуксе есть системные вызовы, которые мониторят, могут изменения анализировать. Это очень дешевая операция. Каждое изменение вызывает событие.

Запуск события по таймеру. Еще со времен ранних юниксов есть в составе любой юникс системы демон по имени cron, оно произошло от того, что американские программисты плохо разбирались в греческой мифологии и перепутали крона с хроносом. Но, опять таки, он совершенно отдельный. Крон - таблица в которой прописаны программы (чаще - шелл скрипты) и написано когда они запускаются.

И, да, у нас есть юнит, который запускается по таймеру. Например, юнит, который время от времени может удалять временные файлы. Причем настройка этого юнита лежит не здесь. Мы просто берем и запускаем нечто под названием systemd-tmpfiles-clean раз в 15 минут. А в service уже описано, что он делает -  systemd-tmpfiles-clean.service

Mount - монтирование файловых систем, вот еще забавная штука. Все хорошо, пока файловые системы доступны сразу после включения кнопки питания. Мир усложнился, помимо простых файловых систем есть те, которые в действительности являются сетевыми: это реально другое устройство, чтобы оно запустилось, нужно загрузить какие-то фирмвари, ну не важно. Есть ли у нас службы .mount? Да дохрена.

/srv - каталог, в котором лежит всякий треш для сервиса. Там веб может лежать, файловые системы для экспорта, ну, всякое. Каталог /run - очередная специфическая файловая система, необычная, в которой на самом деле tmpfs.

mount | grep run

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

find /run/systemd/ -name \*.mount

7.1.png

8.1.png

cat /run/systemd/generator/tmp.mount

Более хитрые штуки оформляются как вот такой юнит и включаются-выключаются когда им нужно.

Сокет активация. Это довольно хитрая вещь, очень активно используется повсеместно. Я уже говорил, что мы можем написать программу, которая реализует текстовый протокол: со стандартного вывода читает - в стандартный вывод пишет, например, http сервер, который ничего не знает про сеть. Потом оформить его как сетевую службу в systemd, systemd сам откроет этот сокет где лежит эта служба, сам будет принимать подключения, сам будет преобразовывать поток в поток данных, выходной поток данных в сенд и отсылать обратно. А мы будем писать программу, которая будет обрабатывать этот поток.

find /lib/systemd/system/
cat /lib/systemd/system/cups-lpd.socket

старинный демон, который организует доступ к принтеру.

cat /lib/systemd/system/cups-lpd@.service

отдает соединение сервису: для этого запускается программа, вводит она - у нее стандарт инпут из сокета, под управлением пользователя lp. Собственно, и все. Сетевое соединение установлено, обработчик соединения запущен, ну и все.

Обратите внимание на лягушку: @ - она означает, что сервис может запуститься параметрически - их можно запустить много и там будут разные параметры. Для чего это нужно? Если у нас много серверов. У нас таких параметрических сервисов щас запущено несколько штук. Для каждого пользователя запущен отдельный собственный systemd, управляется все юзер сервисом параметрически. В системе как минимум 2 пользователя: я и пользователь, от которого запускается логин скрин. (ему выдаются ограниченный набор прав, например, проверять пароли и логиниться в кого-нибудь, называется LightDM)

Поскольку времени совсем нет, я вас пошлю на страничку - тут Арсений нашел человека, который на гитхаб выложил всевозможные пользовательские службы, которые запускаются при логине пользователя в систему.

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

LecturesCMC/LinuxSoftware2017/09_Services/Conspect (последним исправлял пользователь AslanAshabokov 2017-12-05 14:30:44)