Приветствую! Меня зовут Валерий, я инженер операционных систем в секторе клиентской и мобильной ОС.
Сегодня хочу затронуть фундаментальные вопросы:
- процесс загрузки операционной системы в автоматизированное рабочее место (АРМ),
- распаковку начального образа оперативной памяти,
- подробный разбор initrd, что это такое и с чем его едят.
Этот материал поможет понять как природу загрузки ОС Astra Linux Special Edition в частности, так и загрузку GNU/Linux в целом.
1. Что такое initrd?
Начальный RAM-диск для загрузки Linux (initrd) — это блочное устройство, в операционной системе представленное образом, который монтируется в процессе загрузки системы в оперативную память для поддержки 2-уровневой модели загрузки. Initrd состоит из различных исполняемых файлов и драйверов, которые позволяют смонтировать настоящую корневую файловую систему, после чего initrd размонтируется и освободится память.
Определим, как загружается initrd в систему. Для этого выполним команду:
sudo dmesg | grep -i 'initramfs'
[0.322851] Trying to unpack rootfs image as initramfs...
[0.651110] Freeing initrd memory: 75824K
[2.320381] systemd[1]: systemd-fsck-root.service - File System Check on Root Device was skipped because of an unmet condition check (ConditionPathExists=!/run/initramfs/fsck-root).
Из первой строки сообщений видно, что на этапе загрузки системы ядро пытается распаковать образ корневой файловой системы, такой как initramfs, для начальной инициализации файловой системы.
Второе сообщение говорит об освобождаемой памяти, занятой initrd. Также оно явно указывает, что начальная загрузка использовала initrd, который после завершения своей задачи высвобождает занятые ранее аппаратные ресурсы.
Третье сообщение подтверждает, что используется все-таки initramfs, а systemd сообщает о пропущенной проверке файловой системы systemd-fsck-root.service из-за того, что не существует пути /run/initramfs/fsck-root.
Логика этой проверки такова: проверить файловую систему, если /run/initramfs/fsck-root не существует.
А поскольку проверка была пропущена, то данный путь существует в момент запуска операционной системы, ведь других сообщений касательно initramfs не появлялось.
1.1. Как initrd загружается в память?
Когда мы нажали кнопку включения компьютера или сервера, начался процесс загрузки BIOS/UEFI. Сначала он определяет устройство для загрузки с помощью проверки таблиц разделов устройств MBR или GPT.
Затем, используя информацию о разделах и загрузочных данных, загружается начальная часть загрузчика (например, GRUB), которая отвечает за загрузку второй стадии grub, отвечающей за загрузку ядра операционной системы и переход на её выполнение.
Когда ядро загрузилось в оперативную память, GRUB передает ему управление через прямой переход (например, через функции jmp или call на x86_64 архитектуре):
jmp [адрес начала ядра]
После чего ядро начинает настройку таблиц страниц в оперативной памяти, кеширование и настройку других системных структур.
Так как в загрузчике указан initrd, ядро будет искать и загружать образ в оперативную память. GRUB в этом случае передает ядру путь к initrd через параметры командной строки. Ядро использует свои встроенные драйверы для разархивации и монтирования initrd. В зависимости от типа сжатия используется соответствующий декомпрессор (в ОС Astra Linux SE используется метод сжатия gzip). Initrd распаковывается в область оперативной памяти согласно параметрам, передаваемым от GRUB, через структуру boot_params. Она описана в заголовках ядра Linux и содержит такие поля, как:
__u32 ext_ramdisk_image; /* 0x0c0 */
__u32 ext_ramdisk_size; /* 0x0c4 */
__u32 ext_cmd_line_ptr; /* 0x0c8 */
Данная информация содержится по пути:
/usr/src/linux-headers-$(uname -r)/arch/x86/include/uapi/asm/bootparam.h
Это происходит на ранних стадиях инициализации, еще до запуска init-процесса.
Но поскольку мы изучаем initrd в данной статье, давайте рассмотрим структуру каталогов и файлов, распаковав initrd в операционной системе:
mkdir initrd_content && cp /boot/initrd.img-6.1.90-1-generic ~/initrd_content/initrd.img-6.1.90-1-generic.xz
cd ~/initrd_content/
unzstd initrd.img-6.1.90-1-generic.xz && cpio -id < ./initrd.img-6.1.90-1-generic
rm initrd.img-6.1.90-1-generic*
Так выглядит распакованный образ initrd.
administrator@astra-02841:~/initrd_content$ ls -la
итого 48
drwxr-xr-x 9 administrator administrator 4096 сен 3 17:43 .
drwxr-x---+ 20 administrator administrator 4096 сен 3 17:43 ..
lrwxrwxrwx 1 administrator administrator 7 сен 3 17:43 bin -> usr/bin
drwxr-xr-x 3 administrator administrator 4096 сен 3 17:43 conf
drwxr-xr-x 2 administrator administrator 4096 сен 3 17:43 cryptroot
drwxr-xr-x 11 administrator administrator 4096 сен 3 17:43 etc
-rwxr-xr-x 1 administrator administrator 6556 сен 3 17:43 init
lrwxrwxrwx 1 administrator administrator 7 сен 3 17:43 lib -> usr/lib
lrwxrwxrwx 1 administrator administrator 9 сен 3 17:43 lib64 -> usr/lib64
drwxr-xr-x 2 administrator administrator 4096 сен 3 17:43 run
lrwxrwxrwx 1 administrator administrator 8 сен 3 17:43 sbin -> usr/sbin
drwxr-xr-x 10 administrator administrator 4096 сен 3 17:43 scripts
drwxr-xr-x 9 administrator administrator 4096 сен 3 17:43 usr
drwxr-xr-x 3 administrator administrator 4096 сен 3 17:43 var
В Astra Linux многие действия и механизмы защиты содержатся в директории скриптов.
1.2. Вперед, изучать скрипты! Скрипт init
Посмотрим в содержимое каталога scripts:
administrator@astra-02841:~/initrd_content/scripts$ ls -lah
итого 64K
drwxr-xr-x 10 administrator administrator 4,0K сен 3 17:43 .
drwxr-xr-x 9 administrator administrator 4,0K сен 3 17:43 ..
-rw-r--r-- 1 administrator administrator 12K сен 3 17:43 functions
drwxr-xr-x 2 administrator administrator 4,0K сен 3 17:43 init-bottom
drwxr-xr-x 2 administrator administrator 4,0K сен 3 17:43 init-premount
drwxr-xr-x 2 administrator administrator 4,0K сен 3 17:43 init-top
-rw-r--r-- 1 administrator administrator 5,2K сен 3 17:43 local
drwxr-xr-x 2 administrator administrator 4,0K сен 3 17:43 local-block
drwxr-xr-x 2 administrator administrator 4,0K сен 3 17:43 local-bottom
drwxr-xr-x 2 administrator administrator 4,0K сен 3 17:43 local-premount
drwxr-xr-x 2 administrator administrator 4,0K сен 3 17:43 local-top
-rw-r--r-- 1 administrator administrator 3,1K сен 3 17:43 nfs
drwxr-xr-x 2 administrator administrator 4,0K сен 3 17:43 panic
Мы наблюдаем скрипты разных уровней, которые загружаются в определенной последовательности. Так какой же скрипт загружается первым?
Самым "шустрым" оказался скрипт ./init, расположенный в корне inird:
Что он делает?
Как работает скрипт init, начальная отработка
Для начала он настраивает окружение:
export PATH=/sbin:/usr/sbin:/bin:/usr/bin
Потом скрипт монтирует директории /dev /root /sys /proc /tmp. Далее монтирует файловые системы sysfs и proc, которые предоставляют информацию о системе и процессах.
[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t proc -o nodev,noexec,nosuid proc /proc
Чуть не забыли о создании каталога:
mkdir -p /var/lock
Он создает lock-директорию для хранения файлов блокировок. В основном каталог предназначен для предотвращения гонок и коллизий, для обеспечения ресурсной синхронизации и для управления доступом к ресурсам уже в пользовательском режиме.
Дальше идет обработка параметров командой строки, которые задаются в меню GRUB.
for x in $(cat /proc/cmdline); do
case $x in
initramfs.clear)
clear
;;
quiet)
quiet=y
;;
esac
done
Этот кусочек скрипта не так важен, он просто реагирует на параметр quiet и выводит на экран надпись Loading, please wait...:
if [ "$quiet" != "y" ]; then
quiet=n
echo "Loading, please wait..."
fi
export quiet
а после этого скрипт:
mount -t devtmpfs -o nosuid,mode=0755 udev /dev
монтирует виртуальную файловую систему devtmpfs, которая позволяет автоматизировать создание файлов устройств (потому что в Linux все есть файл!).
Смысл параметров заключается в доступе root на чтение, запись и выполнение, остальным пользователям на чтение и выполнение, в том числе атрибут nosuid запрещает выполнение файлов с setuid и setgid битами, чтобы повысить безопасность. Это еще не корневая ФС, если не будут использованы udev-скрипты, о чем говорит комментарий:
# Note that this only becomes /dev on the real filesystem if udev's scripts
# are used; which they will be, but it's worth pointing out
Скрипты udev потом выполняют действия для окончательного переноса файлов устройств на корневую файловую систему.
Это уточнение важно для понимания того, как файловая система и каталоги устройства интегрируются на разных этапах загрузки системы. Временная файловая система используется на ранних этапах загрузки, а затем передаёт контроль основной файловой системе, где и происходит окончательная настройка и управление устройствами через udev. Но об этом как-нибудь позже.
А вот и потоки ввода-вывода:
# Prepare the /dev directory
[ ! -h /dev/fd ] && ln -s /proc/self/fd /dev/fd
[ ! -h /dev/stdin ] && ln -s /proc/self/fd/0 /dev/stdin
[ ! -h /dev/stdout ] && ln -s /proc/self/fd/1 /dev/stdout
[ ! -h /dev/stderr ] && ln -s /proc/self/fd/2 /dev/stderr
Четыре строчки обеспечивают возможность ввода-вывода через потоки!
Первая часть каждой строчки проверяет, есть ли файл. Если его не существует или он не является символической ссылкой (симлинком), возвращает true, после чего линкуется с соответствующим файлом в /dev. Каждый линуксоид когда-нибудь делал что-то вроде такого:
somecommand 2>> error.log
Именно про это и сделаны скрипты для создания потоков ввода-вывода.
Переходим к терминалам. Вернее, к псевдотерминалам!
mkdir /dev/pts
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true
Этими командами initrd создает каталог для псевдотерминалов, а теперь к параметрам его монтирования!
noexec запрещает выполнение файлов в этой ФС.
nosuid, как и в /dev, запрещает выполнение файлов с setuid и setgid битами, устанавливает доступ для группы c gid 5 на запись. В любом linux стандартно это группа tty (tty:x:5:).
Root имеет в данном случае право на чтение и запись, devpts — стандартное имя файловой системы псевдотерминалов, а /dev/pts || true — точка монтирования и интересные пайплайны, которые обеспечат продолжение работы скрипта, если монтирование будет неуспешным.
Экспортирование переменных
После монтирования устройств идёт экспортирование переменных:
# Export the dpkg architecture
export DPKG_ARCH=
. /conf/arch.conf
Первый экспорт архитектуры DPKG берет свое значение из /conf/arch.conf и равен для x86_64 архитектур amd64. Думаю, здесь ничего непонятного нет, не так ли?
# Set modprobe env
export MODPROBE_OPTIONS="-qb"
Опции для команды modprobe, говорящие ей: "Не выводи сообщения о статусе и игнорируй ошибки".
# Export relevant variables
export ROOT=
export ROOTDELAY=
export ROOTFLAGS=
export ROOTFSTYPE=
export IP=
export DEVICE=
export BOOT=
export BOOTIF=
export UBIMTD=
export break=
export init=/sbin/init
export readonly=y
export rootmnt=/root
export debug=
export panic=
export blacklist=
export resume=
export resume_offset=
export noresume=
export drop_caps=
export fastboot=n
export forcefsck=n
export fsckfix=
Теперь пройдемся по определенным переменным окружения:
init=/sbin/init — указывает, какой иницилизационный процесс следует запустить после загрузки. Стандартно это, конечно же, init, который запускает с 0 PID и вызывает systemd.
readonly=y: — указывает, что корневая файловая система должна монтироваться только для чтения. Установлено значение "Да".
rootmnt=/root — указывает на то, что мы определили корневой монтированный каталог.
В результате выполнения экспорта настроены переменные окружения для дальнейшей работы initrd.
Но об этом я расскажу в следующей части.
В качестве промежуточного анонса могу добавить, что дальше остановимся на конфигурационном файле initramfs.conf, а также доразберем init-скрипт. Продолжение следует.