На вопрос: "Для чего нам изучать устройство системы?" мы отвечаем, что нам нужны знания, с помощью которых мы сможем добиваться от неё наилучшего результата. Если мы не понимаем, как она устроена, то мы можем лишь догадываться, как она работает, а это не является знанием. (C) Балабоба, нейросеть Яндекса.
Главной частью операционной системы является ядро Linux. Система становится доступна для работы только после его загрузки. Этот процесс состоит из нескольких этапов:
- BIOS считывает и запускает загрузчик системы
- Загрузчик системы находит образ ядра на диске, загружает его в память и запускает
- Ядро выполняет инициализацию устройств и их драйверов
- Ядро запускает команду init, с которой начинается пространство пользователя
- Команда init приводит в действие остальные системные процессы
- На завершающем этапе загрузки системы команда init запускает процесс, позволяющий войти в систему.
Название BIOS является аббревиатурой слов basic input-output system. Это программа, записанная в память материнской платы компьютера, предназначенная для правильной инициализации электронных компонентов и их взаимодействия. Одна из её важнейших функций - обеспечение начала загрузки операционной системы. Для этого она читает главную загрузочную запись диска (MBR) и запускает на выполнение инструкции, предваряющие запуск основного загрузчика системы. Кроме того, BIOS более новых плат может работать с другой архитектурой разделов - GPT (GUID Partition Table) и выполнять функции запуска основного загрузчика самостоятельно.
Традиционным загрузчиком Linux является LILO, но из за большей гибкости в новых версиях по умолчанию используется GRUB (Grand Unified Bootloader). Его параметры работы можно изменить отредактировав файл /etc/default/grub.
Например, для вывода на экран меню загрузки нужно закомментировать строчку GRUB_TIMEOUT_STYLE=hidden, убрав символ # в её начале. И установить время показа меню в строке GRUB_TIMEOUT=10.
Узнать доступные варианты загрузки можно прочитав файл /boot/grub/grub.cfg. А в каталоге /etc/grub.d содержатся скрипты, предназначенные для создания этого файла. При вводе команды upgrade-grub они используются для нахождения всех имеющихся на компьютере ядер, чтобы вписать их в меню загрузки.
Образ ядра хранится в каталоге /boot. Обычно там имеется несколько версий ядра, на любую из которых можно перейти в любой момент. Узнать текущую версию ядра можно с помощью команды uname -r. Общее слово в названии версий ядра- vmlinuz.
Узнать размер ядра можно выполнив команду du -h /boot/vmlinuz*
Размер в пределах 11 МБайт по современным меркам не впечатляет, но в этом файле сосредоточены лишь основные умения: для работы с памятью, управления процессами и т.д.
Более специфические функции, обеспечивающие взаимодействие с сетевым оборудованием, видеокартами и другими устройствами вынесены в отдельные файлы, называемые модулями. Они хранятся в каталоге /lib/modules в виде файлов с расширением ".ko" и подгружаются ядром по мере необходимости. Для каждой версии ядра создан отдельный подкаталог. Из файла modules.builtin можно прочитать список таких модулей. А в файле modules.alias перечислено для каких устройств какие модули грузить в ядро.
Для получения списка модулей текущего ядра можно воспользоваться командой: find /lib/modules/`uname -r` -name '*.ko' | less
Все эти модули работают в едином адресном пространстве ядра, поэтому Linux считается монолитным ядром.
На этапе, когда загрузчик передал управление ядру, возникает проблема с доступом к модулям, которые размещаются на устройстве хранения, драйверы для работы файловой системой которого ещё не загружены, поскольку находятся там же. Для её решения ядро разворачивает в оперативной памяти мини-образ системы, содержащий минимально необходимые модули для монтирования корневой файловой системы. Этот мини-образ обычно находится там же, где и образ ядра системы и называется initrd или initramfs.
После того как корневая файловая система смонтирована, наступает этап запуска команды init и инициализации устройств. Эта работа идёт асинхронно, а упорядочивает её менеджер устройств udev. Ядро, по мере обнаружения устройств отправляет об этом сообщение (uevent) менеджеру udev, а тот в свою очередь выполняет инициализацию устройства и создаёт несколько символических ссылок в каталоге /dev. Что именно должен делать udev при поступлении uevent, описано в правилах, которые хранятся в каталоге /usr/lib/udev/rules.d. Для администрирования udev используется команда udevadm.
Команда init - это программа, отвечающая за инициализацию пространства пользователя. Она управляет процессом запуска и останова всех других программ после того, как будет запущена ядром. И является первым процессом в этом пространстве. Существуют различные реализации init: System V init (SysV), systemd, Upstart, OpenRC, Runinit и другие. В новых версиях Ubuntu по умолчанию используется systemd. Убедиться в этом можно с помощью команды ps -p 1.
Systemd ориентируется на достижении определённого целевого состояния системы (target), запуская все необходимые для этого процессы. Также она выполняет и другие функции:
- управление службами;
- монтирование файловых систем;
- запуск по расписанию;
- реагирование на подключение устройств;
- отслеживание сетевых сокетов.
Настроить каждую из этих функций можно добавляя и редактируя конфигурационные файлы, являющиеся блоками (units) systemd. Блоки, идущие в составе системы и добавляемые при установке пакетов, находятся в каталоге /usr/lib/systemd/system, а для добавления пользовательских блоков существует каталог /etc/systemd/system. Блоки, предназначенные для выполнения определённого типа функции различаются расширением: .target, .service, .device, .timer, .socket, .mount, .slice и т.д.
Создавая блоки в любом текстовом редакторе с соблюдением синтаксиса, понятного systemd, можно добавлять новые службы и выполнять другие действия, доступные системе инициализации. Управление блоками осуществляется командой systemctl. Например, с помощью неё можно узнать общее состояние системы и список блоков, соответствующих запущенным процессам: systemctl status | less
В systemd поддерживается частичная совместимость c SysV. Если при обращении к службе systemd не находит соответствующий блок, он ищет сценарий SysV с таким же именем, но без расширения и руководствуется им при создании службы. Для этого системе созданы каталоги /etc/rc<N>.d, где N - число от 0 до 6 соответствующее уровню запуска SysV. В них хранятся символические ссылки, для которых systemd идентифицирует сценарии из каталога /etc/init.d и ассоциирует названия сценариев с соответствующими блоками.
Помимо самого systemd в каталоге /lib/systemd можно найти и ряд вспомогательных программ, некоторые из которых дублируют функции системных программ, потому что способны обрабатывать механизмы уведомления systemd - fsck, makefs, sysctl, networkd, udevd и другие. Например, systemd-networkd и systemd-resolvd - системные службы управления сетевыми настройками, реализованные как программы-демоны (daemon programs), т.е. systemd может запустить их в фоновом режиме, чтобы они выполняли свои функции без участия пользователя.
Посмотреть все запущенные программы (процессы) можно с помощью команды pstree. Запуск этой команды без параметров построит в окне терминала древовидную структуру, отображающую иерархическую зависимость процессов друг от друга. В этой структуре systemd отведено почётное место корневого элемента, расположенного в верхнем левом углу. Он является родительским процессом для всех остальных.
Ядро хранит информацию обо всех процессах, следит за памятью выделенной каждому процессу и за готовностью процессов возобновить выполнение. Каждому процессу присваивается идентификационный номер PID (Process ID) по мере их запуска в порядке возрастания. Увидеть их список можно с помощью команды ps -axu.
Поле TIME содержит объём процессорного времени, занимаемого процессом. STAT показывает информацию о текущем состоянии процесса:
- R, выполняется;
- S, приостановлен;
- T, принудительно остановлен;
- <, высокоприоритетный;
- N, низкоприоритетный;
- D, приостановлен без возможности прерывания;
- Z, недействующий процесс - "зомби" (дочерний процесс, который завершился, но не был удалён родителем).
Поля CPU и MEM показывают в процентах использование ресурсов процессора и памяти, VSZ- объём виртуальной памяти, RSS - размер страниц памяти, START - время запуска процессора.
Данная команда получает информацию из каталога /proc и предоставляет её пользователю в более удобном виде. В этот каталог смонтирована виртуальная файловая система procfs, в которую ядро помещает информацию о процессах, отсортированную по каталогам с именами, соответствующими PID этих процессов. Многие и другие команды пользуются ресурсами этого каталога. Например, команда free -m, выводящая количество свободной памяти в мегабайтах берёт эти сведения из /proc/meminfo. Там же можно получить информацию о процессоре: cat /proc/cpuinfo и времени работы системы cat /proc/uptime.
С помощью таких команд как top или ps можно узнать PID процесса, а затем по этому номеру зайти в каталог в /proc и посмотреть как он пользуется ресурсами системы.
Допустим, окно терминала bash имеет PID 42959. Тогда информацию о том, куда направлены потоки ввода/вывода этого процесса, можно прочитать с помощью команды ls -l /proc/42959/fd
Судя по информации из этого каталога, все стандартные потоки направлены в файл терминального устройства (/dev/pts/5). Именно поэтому в окне терминала отображаются результаты выполнения команд и сообщения об ошибках.
Каталог /proc/42959 содержит файлы, в которых собрана вся информация о процессе. Из файла cmdline можно прочитать команду, которой был запущен процесс. Символическая ссылка cwd указывает на текущий рабочий каталог, а exe на запущеный исполняемый файл. Файл wchan содержит последний системный вызов процесса. Из файла status можно прочитать ID владельца процесса, а по нему узнать его имя.
Подобными также являются каталоги /dev и /sys, в которые смонтированы виртуальные файловые системы devfs и sysfs. Все они нужны для того, чтобы программы из пространства пользователя могли получать информацию от ядра, потому в Linux не предусмотрено их общение напрямую. На самом деле в их основе файловая система Ramfs, поэтому они находятся в оперативной памяти, но при загрузке системы происходит их монтирование в соответствующие каталоги на диске.
В каталоге /dev находится перечень файлов, которые отвечают за взаимодействие с реальными устройствами. Многие стандартные команды можно применять к содержимому этого каталога также как обычным файлам. Например, вывести список всех подключённых устройств хранения: ls /dev/disk или отправить данные в какое-либо устройство: echo "Hello, world!" > /dev/pts/5
Linux наполняет этот каталог динамически. Когда ядро видит, к примеру, что подключён, новый диск, оно посылает сигнал udev, а тот в свою очередь читает правило, в котором прописано, что надо создать файл, связанный с этим диском в каталоге /dev. Файлы устройств в этом каталоге - абстракции реальных устройств, через которые приложения могут взаимодействовать с ними с помощью системных вызовов ввода-вывода (read, write, ioctl). Также в этот каталог помещаются файлы псевдоустройств, которые не соответствуют реально существующим аппаратным средствам компьютера, но предоставляют весьма полезные функции, например /dev/random служит источником случайных чисел.
Основные типы устройств - символьные (character) и блочные (block). На тип устройства указывает первый символ, который выводится командой ls -l: c или b.
Разница в том, в какой форме происходит обмен информацией. В символьном устройстве минимальная доступная для обмена единица информации - байт, а в блочном - сектор, размер которого как правило кратен 256 байт.
Символьные устройства - это обычно терминалы и принтеры. Блочные устройства - это, например, диски. Чисто технически можно вытащить данные с такого устройства, обращаясь к нему напрямую, но чтобы иметь дело с каталогами и файлами, нужно пользоваться драйвером файловой системы. Существуют и другие типы устройств, такие как каналы (FIFO, named pipe)- объекты, используемые для организации конвейеров и сокеты (socket) - объекты, предназначенные для взаимодействия между процессами по сети.
Отсортированный по типам список устройств, для которых загружены модули ядра, находится в файле /proc/devices.
В этом каталоге все файлы устройств создаются во время загрузки, а также udevd следит за тем, чтобы добавлять необходимые файлы в процессе подключения и отключения таких устройств как USB Flash. Однако существует возможность добавить или переинициализировать устройство и вручную, воспользовавшись командой mknod. Ей нужно передать в качестве аргументов имя устройства, а также его старший (major) и младший (minor) номера.
Старший номер указывает на общий класс устройства, например жёсткий диск, устройство ввода-вывода, виртуальная консоль и т.д. Это число использует ядро, чтобы выбрать драйвер, которому поручить дальнейшую работу с устройством. А младший номер указывает на конкретное устройство в рамках выбранного класса и оно используется драйвером для того, чтобы отличать однотипные устройства.
Узнать старший и младший номера блочных устройств, установленных в системе, можно с помощью команды lsblk. А ознакомиться с описанием полного списка возможных номеров по адресу:
https://www.kernel.org/doc/Documentation/admin-guide/devices.txt
Каталог /sys содержит структурированные данные об операционной системе и её компонентах. Каждый объект ядра, который отображается в каталоге /sys представлен отдельным каталогом. Файлы в этих каталогах представляют переменные ядра, которые экспортируются связанным объектом. Эти файлы называются атрибутами и они могут быть прочитаны или записаны.
Если какой-либо зарегистрированный в системе объект создаёт каталог в sysfs, то место создания каталога зависит от родительского элемента этого объекта, который также является объектом. Таким образом получается наглядная иерархическая зависимость. Каталоги верхнего уровня представляют общих предков иерархии объектов или подсистем, к которым принадлежат объекты.
Верхний уровень /sys - это блоки (block), шины (bus), классы (class), устройства (devices и dev), прошивки (firmware), ядро (kernel), файловые системы (fs), питание (power), модули (module).
В каталоге /sys/modules многие драйверы автоматически размещают определённые данные, которые могут быть использованы программами из пространства пользователя для выдачи информации об устройствах. Например, список загруженных модулей можно узнать с помощью команды lsmod, а информацию о модуле получить с помощью команды modinfo. Всё это хранится в каталоге /sys/module и может быть просмотрено стандартными средствами, такими как ls, tree, cat и т.д.
В /sys/block находятся каталоги всех блочных устройств, зарегистрированных в системе. Каждый из них содержит подкаталоги для разделов на устройстве.
В /sys/firmware представлено специфичное для аппаратного обеспечения системы дерево низкоуровневых подсистем, таких как ACPI, DMI и EFI .
Каталог /sys/devices даёт реальное представление о топологии устройств в системе.
В /sys/dev отображаются зарегистрированные узлы устройств в необработанном (без иерархии) виде, причём каждый из них является символической ссылкой на реальное устройство в системе.
Каталог /sys/bus содержит зарегистрированные в системе шины.
В каталоге /sys/fs перечислены файловые системы, которые используются в системе.
Каталог /sys/kernel содержит параметры конфигурации ядра и информацию о состоянии системы. Каталог /sys/power предоставляет интерфейс управления питанием системы из пользовательского пространства.
Слово "объект" в связи с каталогом /sys появилось не просто так, а потому, что виртуальная файловая система sysfs опирается в своей работе на такую структуру как kobject, реализующую базовый объектно-ориентированный механизм управления устройств. Она встроена во все объекты-контейнеры, описывающие модель устройства, такие как шина, драйверы и т.д. Атрибуты kobject в sysfs можно задавать в форме стандартных файлов. Это может пригодиться в задачах, связанных с отладкой и написанием скриптов.
Ещё один интересный, с токи зрения исследователя, каталог - /var. В нём хранятся файлы системных переменных - кэши, буферы, журналы. Если что-то пошло не так при запуске какой-нибудь программы первым делом имеет смысл заглянуть в /var/log. Глобальный системный журнал находится в /var/log/journal. Сюда собирает данные служба сбора логов системы systemd - journald и хранятся они в бинарном виде. Поэтому для их просмотра необходимо использовать специальную команду: journalctl. Введя journalctl --list-boots | less можно увидеть все запуски системы. Под цифрой 0 - текущая загрузка, -1 - предыдущая загрузка и т.д.
Ссылаясь на эту цифру можно посмотреть логи конкретной загрузки, указав её в команде: journalctl -b 0 | less.
Аналогичным образом можно получить логи, касающиеся только определённой службы. Например, NetworkManager:
journalctl -u NetworkManager | less.
Для вывода логов ядра системы: journalctl -k. Если нужно следить за логами в реальном времени, то используется параметр -f. Параметры можно комбинировать для получения более конкретного результата, например, посмотреть как прошла прошлая загрузка NetworkManager: journalctl -b -1 -u NetworkManager | less
У systemd есть также программа, собирающая статистику её самой: systemd-analyze. Если запустить её без каких-либо параметров, то мы увидим общее время загрузки системы.
А если добавить параметр blame, то будет показано время загрузки каждой из служб. Можно сократить время загрузки, отключив некоторые службы.
Логи в системе генерируются в зависимости от типа события. А программы их обрабатывают в соответствии с определённым приоритетом важности:
0: emerg - неработоспособность системы (наивысший приоритет)
1: alert - тревога (требуется немедленное вмешательство)
2: crit - критическое состояние
3: err - ошибка
4: warning - предупреждение
5: notice - уведомление
6: info - информационное сообщение
7: debug - отладочная информация
Можно указывать journalctl сообщения какого уровня выводить с помощью параметра -p. Например, вывести сообщения об ошибках при текущей загрузке: journalctl -p err -b. Также можно указать номер приоритета: journalctl -p 2 -b. В обоих случаях указанный приоритет - это точка отсчёта для показа всех сообщений такого типа и с более высоким приоритетом.
Помимо подсистемы логирования systemd многие программы занимаются подобным самостоятельно и помещают свои логи в отдельные файлы и подкаталоги внутри /var/log. В том числе там можно найти глобальный системный журнал /var/log/syslog, в котором пишутся сообщения от ядра Linux, различных служб, устройств и т.д. Эти файлы текстовые и с ними можно работать стандартными средствами, например можно вывести логи с сообщениями об ошибках командой: grep 'warning' /var/log/syslog
Также могут представлять интерес файлы /var/log/auth.log, где хранится информация об авторизации пользователей и /var/log/dmesg, где находится информация от драйверов.
Чтение логов - верный способ узнать больше о работе системы, особенно когда возникают ошибки. Поэтому важно оттачивать умение находить нужный файл журнала и искомые данные в нём.
"Как видите, работая с Linux, иногда приходится проводить массу интересных детективных расследований"! (С) Уильям Шоттс "Командная строка Linux".