Начальный RAM диск для загрузки Linux (initrd) это временная корневая файловая система, которая монтируется в процессе загрузки системы в оперативную память для поддержки 2х уровневой модели загрузки. Initrd состоит из различных исполняемых файлов и драйверов, которые позволяют смонтировать настоящую корневую файловую систему, после чего initrd
размонтируется и освобождается память. Во многих встраиваемых системах initrd так и остаётся корневой файловой системой. В этой статье исследуется загрузочный RAM диск для ядра Linux 2.6, включая процесс его создания и использования в ядре линукса.
Содержание
Что такое загрузочный RAM диск?
Загрузочный RAM диск (Initrd) это образ корневой файловой системы, который монтируется до того как настоящая корневая фс будет доступна. Initrd связан с ядром и загружается как часть ядра в процессе загрузки системы. Ядро монтирует образ initrd в котором находятся необходимые модули для монтирования корневой фс и уже дальнейшего перехода в этот корень как основной.
В initrd содержится минимальный набор директорий и исполняемых файлов для загрузки модулей, например insmod для загрузки модулей ядра.
В случае настольной системы или сервера, initrd временная файловая система. Её время жизни коротко и служит лишь связующим звеном к корневой фс. Во встраиваемых системах нет записываемых устройств хранения данных, поэтому initrd является постоянной корневой фс. В этой статье исследуются оба варианта.
Внутреннее устройство initrd
В образе initrd содержится необходимый минимум исполняемых и системных файлов для осуществления второй стадии загрузки linux. В зависимости от версии linux которую вы используете, различаются методы создания initrd.
Начиная с Fedora Core 3, по умолчанию образ initrd это сжатый cpio архив. Вместо монтирования файла с использованием loop device, нужно использовать программу cpio. Чтобы исследовать содержимое cpio архива, используйте следующую последовательность команд:
Листинг 2. Исследование initrd (FC3 и более поздние версии)
# mkdir temp ; cd temp
# cp /boot/initrd-2.6.14.2.img initrd-2.6.14.2.img.gz
# gunzip initrd-2.6.14.2.img.gz
# cpio -i —make-directories < initrd-2.6.14.2.img
В итоге мы имеем маленькую корневую фс, содержимое которой показано в листинге 3. Маленький, но необходимый набор приложений присутствует в каталоге /bin, включая nash (not a shell, интерпретатор скриптов), insmod для загрузки модулей ядра и lvm (утилита для управления lvm).
Листинг 3. Структура initrd по умолчанию (для FC3)
# ls -la # drwxr-xr-x 10 root root 4096 May 7 02:48 . drwxr-x— 15 root root 4096 May 7 00:54 .. drwxr-xr-x 2 root root 4096 May 7 02:48 bin drwxr-xr-x 2 root root 4096 May 7 02:48 dev drwxr-xr-x 4 root root 4096 May 7 02:48 etc -rwxr-xr-x 1 root root 812 May 7 02:48 init -rw-r—r— 1 root root 1723392 May 7 02:45 initrd-2.6.14.2.img drwxr-xr-x 2 root root 4096 May 7 02:48 lib drwxr-xr-x 2 root root 4096 May 7 02:48 loopfs drwxr-xr-x 2 root root 4096 May 7 02:48 proc lrwxrwxrwx 1 root root 3 May 7 02:48 sbin -> bin
drwxr-xr-x 2 root root 4096 May 7 02:48 sys
drwxr-xr-x 2 root root 4096 May 7 02:48 sysroot
#
Интерес в листинге 3 представляет файл init в корне. Это файл, как и в традиционном процессе загрузки linux, запускается когда образ initrd распаковывается в память. Мы разберем этот процесс позже в этой статье.
Утилиты для создания образа initrd
Давайте вернемся в начало и формально разбремся как создается образ initrd. Для традиционных linux систем образ initrd содается в процессе установки. Огромное количество программ, таких как mkinitrd, могут быть использованны для создания initrd с необходимыми библиотеками и модулями для связи с реальной корневой фс. Mkinitrd это по сути обычный shell
скрипт, так что вы можете посмотреть каким образом достигается нужный нам результат. Есть так же YAIRD (Yet Another Mkinitrd) — программа, которая позволяет настроить практически любой параметр в initrd.
Команда cpio Используя команду cpio, вы можете манипулировать cpio файлами. Файл Cpio это по сути простая конкатенция файлов с заголовками. Формат файла cpio позволяет работать как с ascii файлами, так и с бинарными. Для совместимости используйте ascii, для уменьшения размера — бинарную версию.
Создание вручную индивидуального образа initrd
По причине того что на многих встраиваемых системах, основанных на linux нет жесткого диска, initrd так же является и постоянной фс. Листинг 4 показывает как создавать образ initrd. Я использую стандартный десктоп с linux’ом, так что вы можете опробовать эти действия не имея под рукой встраиваемой системы с linux’ом. Не считая компиляции под другую
платформу, концепция создания образа initrd одинакова для встраиваемых систем и обычных компьютеров (включая сервера).
Листинг 4. Утилита (mkird) для создания индивидуального образа initrd
#!/bin/bash
# Housekeeping…
rm -f /tmp/ramdisk.img
rm -f /tmp/ramdisk.img.gz
# Ramdisk Constants
RDSIZE=4000
BLKSIZE=1024
# Create an empty ramdisk image
dd if=/dev/zero of=/tmp/ramdisk.img bs=$BLKSIZE count=$RDSIZE
# Make it an ext2 mountable file system
/sbin/mke2fs -F -m 0 -b $BLKSIZE /tmp/ramdisk.img $RDSIZE
# Mount it so that we can populate
mount /tmp/ramdisk.img /mnt/initrd -t ext2 -o loop=/dev/loop0
# Populate the filesystem (subdirectories)
mkdir /mnt/initrd/bin
mkdir /mnt/initrd/sys
mkdir /mnt/initrd/dev
mkdir /mnt/initrd/proc
# Grab busybox and create the symbolic links
pushd /mnt/initrd/bin
cp /usr/local/src/busybox-1.1.1/busybox .
ln -s busybox ash
ln -s busybox mount
ln -s busybox echo
ln -s busybox ls
ln -s busybox cat
ln -s busybox ps
ln -s busybox dmesg
ln -s busybox sysctl
popd
# Grab the necessary dev files
cp -a /dev/console /mnt/initrd/dev
cp -a /dev/ramdisk /mnt/initrd/dev
cp -a /dev/ram0 /mnt/initrd/dev
cp -a /dev/null /mnt/initrd/dev
cp -a /dev/tty1 /mnt/initrd/dev
cp -a /dev/tty2 /mnt/initrd/dev
# Equate sbin with bin
pushd /mnt/initrd
ln -s bin sbin
popd
# Create the init file
cat >> /mnt/initrd/linuxrc << EOF #!/bin/ash echo echo «Simple initrd is active» echo mount -t proc /proc /proc mount -t sysfs none /sys /bin/ash —login EOF chmod +x /mnt/initrd/linuxrc # Finish up… umount /mnt/initrd gzip -9 /tmp/ramdisk.img cp /tmp/ramdisk.img.gz /boot/ramdisk.img.gz
Листинг 4. Скрипт mkird для автоматического создания образа
#!/bin/bash # Housekeeping... rm -f /tmp/ramdisk.img rm -f /tmp/ramdisk.img.gz # Ramdisk Constants RDSIZE=4000 BLKSIZE=1024 # Create an empty ramdisk image dd if=/dev/zero of=/tmp/ramdisk.img bs=$BLKSIZE count=$RDSIZE # Make it an ext2 mountable file system /sbin/mke2fs -F -m 0 -b $BLKSIZE /tmp/ramdisk.img $RDSIZE # Mount it so that we can populate mount /tmp/ramdisk.img /mnt/initrd -t ext2 -o loop=/dev/loop0 # Populate the filesystem (subdirectories) mkdir /mnt/initrd/bin mkdir /mnt/initrd/sys mkdir /mnt/initrd/dev mkdir /mnt/initrd/proc # Grab busybox and create the symbolic links pushd /mnt/initrd/bin cp /usr/local/src/busybox-1.1.1/busybox . ln -s busybox ash ln -s busybox mount ln -s busybox echo ln -s busybox ls ln -s busybox cat ln -s busybox ps ln -s busybox dmesg ln -s busybox sysctl popd # Grab the necessary dev files cp -a /dev/console /mnt/initrd/dev cp -a /dev/ramdisk /mnt/initrd/dev cp -a /dev/ram0 /mnt/initrd/dev cp -a /dev/null /mnt/initrd/dev cp -a /dev/tty1 /mnt/initrd/dev cp -a /dev/tty2 /mnt/initrd/dev # Equate sbin with bin pushd /mnt/initrd ln -s bin sbin popd # Create the init file cat >> /mnt/initrd/linuxrc << EOF #!/bin/ash echo echo "Simple initrd is active" echo mount -t proc /proc /proc mount -t sysfs none /sys /bin/ash --login EOF chmod +x /mnt/initrd/linuxrc # Finish up... umount /mnt/initrd gzip -9 /tmp/ramdisk.img cp /tmp/ramdisk.img.gz /boot/ramdisk.img.gz
Чтобы создать образ initrd, начнем с создания пустого файла, используя
псевдоустройство /dev/zero (просто поток нулей) в качестве входящего
потока в файл ramdisk.img. В результате получим файл размером 4
мегабайта (4000 блоков по 1ому килобайту). Потом, используя команду
mke2fs, создадим в этом файле файловую систему ext2. После того как файл
отформатирован в ext2, смонтируем его в каталог /mnt/initrd как
устройство loop. В точке монтирования у вас теперь есть директория,
которая отражает ext2 файловую систему файла и там вы уже можете
собирать свой образ initrd.
Следующим шагом является создание необходимых подкаталогов: /bin, /sys,
/dev и /proc. Нужно только необходимое, для обеспечения требуемой
функциональности, например не нужно никаких библиотек (это условие верно
когда все исполняемые файлы находящиеся в образе initrd скомпилированны
статически).
Чтобы такая корневая фс предоставляля необходимый функционал используйте
BusyBox. Это приложение является единым образом для многих утилит,
которые обычно используюся в linux системах (такие как ash, awk, sed,
insmod etc.) . Приемущество BusyBox в том что он, включая в себя
функциональность многих необходимых утилит, имеет куда меньший размер.
Это идельный выход для встраиваемых систем. Скопируйте BusyBox в каталог
/bin в корневой файловой системы вашего образа initrd. Создайте
необходимые символические ссылки (на те же ash, awk, sed и т.д.) в
каталоге /bin на BusyBox. BusyBox выясняет какая утилита была запущена и
предоставляет необходимые функции. Небольшое количество ссылок создаётся
в этом каталоге чтобы заработал init скрипт (ссылки эти указывают опять
же на BusyBox).
Следующий шаг - это создание необходимых файлов устройст. Я копирую их
прямо из каталога /dev со своей рабочей системы используя опцию -a (для
cp) чтобы сохранить атрибуты файлов.
Предпоследний шаг - создание файла linuxrc. После того как ядро
монтирует ram диск, оно ищет init скрипт для выполнения. Если файл init
не найден, ядро выполняет файл linuxrc вместо init. В этом файле
выполняются основные операции для установки окружения, например
монтируется файловая система /proc. В дополнении к /proc у меня так же
монтируется файловая система /sys и выводит сообщение в консоль. В конце
запускается ash (клон Bourne shell) чтобы можно было взаимодействовать с
корневой фс. Файлу linuxrc ставится флаг исполняемости (+x) используя
команду chmod.
В итоге мы получаем готовую корневую фс. Образ размонтируется и
сжимается с помощью gzip. Результирующий файл (ramdisk.img.gz)
копируется в каталог /boot для того чтобы можно было загрузить GRUB'ом
или lilo.
Чтобы создать начальный RAM диск, можно просто запустить mkinitrd и он
автоматически соберет образ и скопирует его в каталог /boot
Дистрибутив linux в initrd Был разработан интересный open
source проект, в котором весь дистрибутив linux помещался в образ
initrd, называется он MiniMax. Он занимает 32 мегабайта , включает в
себя BusyBox и uClibc для меньшего размера. Несмотря на маленький
размер, это дистрибутив на основе ядра 2.6 и несет в себе довольно
большое количество полезных утилит.
Альтернатива файловой системе ext2 Ext2 это стандарт
де-факто для linux, но если альтернативы, которые позволяют уменьшить
размер итогового образа initrd. К примеру romfs (файловая система ROM),
cramfs (сжатая файловая система ROM) и squashfs (сильно сжатая файловая
система только для чтения). Если вам нужно что то писать на фс, ext2
вполне для этого подходит. Напоследок есть еще e2compr - это расширения
для драйвера ext2, которое поддерживает сжатие.
Тестирование индивидуального образа initrd
Итак, ваш новый образ initrd лежит в каталоге /boot, следующим шагом
нужно оттестировать его с вашим ядром. Перезагрузите систему и когда
появится приглашение GRUB, нажмите клавишу C, это переведет GRUB в
режим командной строки. Вы можете взаимодествовать с GRUB'ом например
чтобы передать ядру специфические параметры или загрузить свой образ
initrd. Команда kernel определить какое ядро загружать, а команда
initrd выбрать образ initrd. Когда эти параметры определны, наберите
boot и загрузчик загрузит указанные ядро и initrd.
Листинг 5. Ручная загрузка ядра и initrd используя GRUB
grub> kernel /bzImage-2.6.1
[Linux-bzImage, setup=0x1400, size=0x29672e]
grub> initrd /ramdisk.img.gz
[Linux-initrd @ 0x5f2a000, 0xb5108 bytes]
grub> boot
Uncompressing Linux... OK, booting the kernel.
После того как ядро запущенно, оно проверяет доступен ли initrd
(подробнее об этом позже) и образ initrd доступе, ядро загружает его в
память и монтирует как корневую фс. Вы можете посмотреть на процесс
загрузки в листинге 6. Когда initrd загружен, становится доступна
оболочка ash. В этом примере я исследую корневую фс и опрашиваю
виртуальную файловую систему /proc. Я так же демонстрирую возможность
писать на файловую создавая файл командой touch. Обратите внимание на то
что первый созданный процесс это linuxrc (обычно init).
Листинг 6. Загрузка ядра linux с простым initrd
...
md: Autodetecting RAID arrays
md: autorun
md: ... autorun DONE.
RAMDISK: Compressed image found at block 0
VFS: Mounted root (ext2 file system).
Freeing unused kernel memory: 208k freed
/ $ ls
bin etc linuxrc proc sys
dev lib lost+found sbin
/ $ cat /proc/1/cmdline
/bin/ash/linuxrc
/ $ cd bin
/bin $ ls
ash cat echo mount sysctl
busybox dmesg ls ps
/bin $ touch zfile
/bin $ ls
ash cat echo mount sysctl
busybox dmesg ls ps zfile
Процесс загрузки с начальным RAM диском
Теперь вы знаете как собрать свой образ initrd, в этой часте астатьи мы
рассмотрим как ядро идентифицирует и монтируется initrd как корневую фс.
Я прошелся через этот процесс останавливаясь на некоторых важных
функциях в цепочке загрузки, объясняя что происходит.
Загрузчик, например GRUB, идентифицирует ядро, которое нужно загрузить и
копирует образ ядра и initrd в память. Вы можете найти описание всех
необходимых функций в каталоге /init в дереве исходных кодов ядра.
После того как ядро и initrd распакованы и скопированны в память,
исполняется ядро. В этом месте происходит много разных инициализационных
процедур, в конечном счете вы оказываетесь в функции init/main.c:init()
(поддиректория/файл:функция). Эта функция как раз и представляет собой
подсистему инициализации. Отсюда идет вызов функции
init/do_mounts.c:prepare_namespaces(), которая подготавливает рабочее
пространство (монтирует файловую систему dev, RAID или md устройства и,
в итоге, сам initrd). Загрузка initrd выполняется вызовом
init/do_mounts_initrd.c:initrd_load().
Функция initrd_load() вызывает init/do_mounts_rd.c:rd_load_image()
которая определяет браз RAM диска, чтобы загрузить его в
init/do_mounts_rd.c:identify_ramdisk_image(). Это функция проверяет
"магическое число" образа чтобы определить какая фс в этом образе:
minux, ext2, romfs,cramfs или формат gzip. Возвраящаясь к функции
initrd_load_image, происходит вызов init/do_mounts_rd:crd_load(). Эта
функция выделяет в памяти место под RAM диск и считает контрольную сумму
(CRC), после этого распаковывает и загружает RAM диск в память. Теперь
ваш образ initrd находится в подходящем для монтирования блочном
устройстве.
Монтирование блочного устройства в качестве корневого раздела начинается
в функции init/do_mounts.c:mount_root(). Создаётся корневой раздел и
происходит вызов init/do_mounts.c:mount_block_root(). Отсюда вызывается
init/do_mounts.c:do_mount_root(), который вызывает
fs/namespace.c:sys_mount(), чтобы уже смонтировать корневую фс и перейти
в неё. Это то место где выводится в консоль знакомое нам по листингу 6
сообщение VFS: Mounted root (ext2 file system).
В конце, вас возвращает в функцию init и происходит вызов
init/main.c:run_init_process. Это происходит в результате вызова execve
чтобы запустился процесс init (в данном случае linuxrc). Linuxrc может
быть как исполняемым файлом, так и скриптом (если для скрипта есть
интерпретатор).
В листинге 7 показана иерархия функций. Не все функции учавствующие в
процессе копирования и монтирования инициализационного RAM диска
показаны здесь, но общее представление об общем процессе здесь дано.
Листинг 7. Иерархия основных функций участвующих в процессе загрузки и
монтирования initrd
init/main.c:init
init/do_mounts.c:prepare_namespace
init/do_mounts_initrd.c:initrd_load
init/do_mounts_rd.c:rd_load_image
init/do_mounts_rd.c:identify_ramdisk_image
init/do_mounts_rd.c:crd_load
lib/inflate.c:gunzip
init/do_mounts.c:mount_root
init/do_mounts.c:mount_block_root
init/do_mounts.c:do_mount_root
fs/namespace.c:sys_mount
init/main.c:run_init_process
execve
Загрузка бездисковых систем
Почти как во встраиваемых системах, локальный диск (флоппи или CD-ROM)
не обязательны чтобы загрузить ядро и корневую фс в RAM. Протокол
динамического назначения адресов или просто DHCP может быть использован
для того чтобы определить такие параметры как IP адресс и маску подсети.
Упрощенный протокол передачи данных (TFTP) может быть использован для
передачи образа ядра и образа initrd на локальную машину. После
передачи, ядро может быть загружено, а initrd смонтирован, так как будто
это загрузка с локальной машины.
Уменьшение размера образа initrd
Когда вы создаёте встраиваемую систему и хотите добится наименьшего
размера от образа initrd, если несколько хитростей чтобы это сделать.
Первый совет - используйте BusyBox (описано в статье). BusyBox включает
в себя несколько мегабайт утилит и сжимает их всего до нескольких сотен
килобайт.
В этом примере BusyBox скомпилирован статически, т.е. ему не нужно для
работы никаких библиотек. Однако, еесли вам нужны стандартные библиотеки
языка C (для дополнительных программ), есть нескольно путей решить эту
проблемму не используя массивный glibc. Первая маленькая библиотека
подходящая для наших целей это uClibc, которая является минимизированной
версией стандартной библиотеки C для систем с ограниченным местом.
Другая библиотека, идеальная с точки зрения занимаемого дискового
пространства это dietlib. Не забывайте что для того чтобы ваши программы
работали с урезанными версиями, эти программы нужно перекомпилировать
используя эти библиотеки, потребуется некоторая дополнительная работа,
но оно того стоит.