Создание initrd с unionfs

Материал из Belgorod Linux User Group - Белгород

Перейти к: навигация, поиск

Содержание

Цель

Загрузка операционной системы с использованием unionfs в качестве корневой файловой системы (rootfs).

О unionfs

Стековая файловая система. Призвана объединять несколько смонтированных файловых систем в одну логическую, где каждый слой имеет свои приоритеты и права на чтение / запись.

Мы будем использовать ее для объединения файловой системы доступной только для чтения (RO) с перезаписываемой файловой системой (RW). Такой подход используется на многих LiveCD и USB загрузочных образах, когда необходим образ Linux в режиме RO (иногда даже физически, к примеру на CD) и перезаписываемая часть, с возможностью сохранения изменений (USB flash к примеру или RW часть на CD). Этот же метод используется компанией ASUS для своих ноутбуков Eee PC с операционной системой Xandros, позволяя хранить заводской образ всегда доступным и неизменным (RO) и пользовательский изменения, обновления, накладываемые на перезаписываемую часть.

При таком использовании файловые системы накладываются как "калька" - одна на другую. Мы видим доступную файловую систему (физически являющуюся RO образом (squashfs, cramfs и т.п.) или даже располагаемую на RO носителе) и можем вносить в нее изменения, которые физически накладываются на перезаписываемый носитель.

О initrd

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

initrd по сути представляет из себя образ файловой системы с минимальным набором необходимых исполняемых файлов и библиотек

Мы будем использовать initrd для подготовки объединенной файловой системы (unionfs) в качестве корневой для системы (root file system).

Создание

Образ файловой системы init ram disk-а будет храниться в памяти. По этому он должен быть как можно меньше. Но в тоже время он должен хранить необходимый для выполнения своего функционала минимум. Образ может быть как сжатым в gzip так и в cpio или любым другим способом, доступным ядру - это на ваш выбор. Я буду использовать файловую систему ext2, находящуюся внутри файла, зажатого gzip-ом.

Ядро

unionfs

В ядре целевой файловой системы необходимо включить поддержку блочных устройств, initrd и наложить патч поддержки unionfs.

CONFIG_BLK_DEV_INITRD=y
CONFIG_RD_GZIP=y

CONFIG_BLK_DEV=y
CONFIG_BLK_DEV_LOOP=y
CONFIG_BLK_DEV_RAM=y
CONFIG_BLK_DEV_RAM_SIZE=16384

Патч unionfs можно скачать от сюда: http://www.filesystems.org/project-unionfs.html, потом наложить его:

cd linux
wget http://download.filesystems.org/unionfs/unionfs-2.x/unionfs-2.5.3_for_2.6.31.diff.gz
zcat unionfs-2.5.3_for_2.6.31.diff.gz | patch -p1

Ну и включить unionfs в ядре:

CONFIG_UNION_FS=y

yaffs2

Скачиваем:

cvs -q -f -z1 -d ":pserver:anonymous@cvs.aleph1.co.uk:/home/aleph1/cvs" co -dP yaffs2


Патчим ядро:

cd yaffs2
./patch-ker.sh c /home/clfs/linux/linux

Выставляем поддержку:

CONFIG_YAFFS_FS=y
CONFIG_YAFFS_YAFFS1=y
CONFIG_YAFFS_YAFFS2=y
CONFIG_YAFFS_AUTO_YAFFS2=y
CONFIG_YAFFS_SHORT_NAMES_IN_RAM=y

Образ

Образ будем монтировать в директорию /mnt/initrd, по этому ее надо создать на машине, на которой будет изготавливаться образ.

Описание порядка создания образа initrd будет максимально приближено к готовому скрипту создания этого образа.

Подготавливаем:

#/bin/bash
pwd=$(pwd)
DEV_TAR_GZ=$pwd/dev.tar.gz # образ с директорией /dev системы
FS=/home/clfs/tmpfs-bu/ # тут хранятся копии файлов настоящей системы
RDSIZE=15000 # размер рамдиска. 

ВНИМАНИЕ! На целевой системе в ядре надо установить размер ram диска не менее этого! (параметр CONFIG_BLK_DEV_RAM_SIZE=16384 конфигурационного файла ядра).

BLKSIZE=1024
if [ "$(whoami)" != "root" ]; then
echo "Need root"
exit 1
fi
umount /mnt/initrd

Создаем файл образ нашего рамдиска (initrd), сначала просто сгенерировав пустой файл заданного размера, а потом отформатировав его в ext2:

dd if=/dev/zero of=/tmp/ramdisk.img bs=$BLKSIZE count=$RDSIZE
/sbin/mke2fs -F -m 0 -b $BLKSIZE /tmp/ramdisk.img $RDSIZE

Теперь монтируем его как блочное устройство в систему, и записываем путь в переменную ROOT, далее будем плясать от нее, имея ввиду, что $ROOT - это наша корневая файловая система образа initrd

mount /tmp/ramdisk.img /mnt/initrd -t ext2 -o loop
ROOT="/mnt/initrd"

dev

Необходима директория /dev/, с минимально необходимыми устройствами.

Создать ее можно к примеру так:

mkdir -p $ROOT/dev
cd $ROOT
mknod -m 600 dev/console c 5 1
mknod -m 666 dev/null c 1 3
mknod -m 666 dev/tty c 5 0
mknod -m 666 dev/tty0 c 4 0
mknod -m 666 dev/tty1 c 4 1
mknod -m 666 dev/tty2 c 4 2
mknod -m 666 dev/tty3 c 4 3
mknod -m 666 dev/tty4 c 4 4
# Create the RAM disk device:
mknod -m 600 dev/ram0 b 1 0
ln -s /proc/self/fd dev/fd
ln -s /proc/self/fd/0 dev/stdin
ln -s /proc/self/fd/1 dev/stdout
ln -s /proc/self/fd/2 dev/stderr
ln -s /proc/kcore dev/core
mkdir dev/pts
mkdir dev/shm

Но можно взять с готового загруженного образа, просто запаковав её tar-ом, а потом распаковав при создании initrd, я так и сделал, указав в начале файл этого архива, как переменную $DEV_TAR_GZ

# /dev
echo "Make /dev/"
tar -xxf $DEV_TAR_GZ -C $ROOT

rootfs

Теперь подготовим директории на нашей файловой системе:

# rootfs
echo "Make clear rootfs"
mkdir -pv ${ROOT}/{bin,usr,var,lib,sys,dev,proc,mnt,etc,root,initrd}
mkdir -pv ${ROOT}/mnt/{ro,rw,union}
ln -sv /bin ${ROOT}/sbin
ln -sv /bin ${ROOT}/usr/bin
ln -sv /bin ${ROOT}/usr/sbin
ln -sv /lib ${ROOT}/var/lib
ln -sv /bin/bash ${ROOT}/bin/sh

ПО

Все. Теперь необходимо положить нужное ПО (исполняемые файлы и библиотеки) на файловую систему. Обычно это делают используя busybox, так как он является очень маленьким и удобным и содержит в себе весь минимально необходимый функционал. Но я не стал заморачиваться с busybox и взял готовые файлы целевого устройства. Копии этих файлов лежат в директории, описанной в переменной $FS (выше). Мне потребуется

  • bash - для интерпретации моего сценария
  • mount, umount - для монтирования файловых систем
  • chroot, pivot_root - для перемещения точек монтирования
  • blockdev - для работы с блочным устройством
  • сюда еще можно поместить insmod и модуль ядра с unionfs, но я вкомпилировал его в ядро [y]


Просто копируем нужные файлы и библиотеки в будущий образ (необходимость библиотеки можно посмотреть командой ldd у соответствующего исполняемого файла):

# bash
echo "Make bash"
cp -v $FS/lib/libncurses.so.* \
     $FS/lib/libdl.so.* \
     $FS/lib/libc.so.* \
     $FS/lib/ld-linux.so* \
     $FS/usr/lib/libgcc_s.so* \
     ${ROOT}/lib/

cp -v $FS/bin/bash ${ROOT}/bin/

# mount
echo "Make mount"
cp -v      $FS/lib/libblkid.so.* \
          $FS/lib/libuuid.so.* \
          ${ROOT}/lib/

cp -v $FS/bin/mount \  
     $FS/bin/umount \ 
     ${ROOT}/bin/

# blockdev
echo "Make blockdev"
cp -v   $FS/sbin/blockdev ${ROOT}/bin/

# pivot, chroot
echo "Make pivot_root, chroot"
cp -v $FS/usr/sbin/chroot \
     $FS/sbin/pivot_root \
     ${ROOT}/bin

init

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

Итак, после распаковки образа initrd в оперативную память ядро будет исполнять файл /sbin/init (по умолчанию, если не передан параметр init= ядру загрузчиком или параметром ядра CONFIG_CMDLINE). Так же он известен как linuxrc. Этот файл может представлять из себя простой sh-сценарий, последовательно исполняющий то, что нужно нам для подготовки целевой файловой системы операционки.

А нужно нам сделать вот что:

  • Подмонтировать RO раздел с образом операционной системы
  • Подмонтировать RW раздел, куда будут записываться измененные данные
  • Смонтировать оба этих образа (RO + RW) в единую систему (union) посредством unionfs
  • Перейти в новую файловую систему
  • Отмонтировать файловую систему initrd и вычистить занимаемую им память
  • Запустить init целевой операционной системы, который уже загрузит саму ОС с файловой системы

Итак, приступим:

Монтируем файловые системы. RO в /mnt/ro, RW в /mnt/rw. У меня они обе находятся на MTD блочных устройствах (/dev/mtdblock?), просто на RO находится файловая система CRAMFS (сжатая файловая система с фозможностью только чтения), а на RW находится YAFFS2 (перезаписываемая файловая система для памяти Flash, NAND, идет тоже как патч ядра). Для LiveCD это может быть squashfs и к примеру tmpfs.


# init
echo "Make init"
cat > ${ROOT}/bin/init <<EOFF
#!/bin/bash
echo "Starting initrd..."
/bin/mount -t cramfs /dev/mtdblock1 /mnt/ro -o ro
/bin/mount -t yaffs2 /dev/mtdblock2 /mnt/rw -o rw

Теперь монтируем эти две файловые системы в одну /mnt/union посредством unionfs. Важно первой указать перезаписываемую ФС, т.к. она будет первым слоем.

/bin/mount -t unionfs -o dirs=/mnt/rw=rw:/mnt/ro=ro unionfs /mnt/union

Все. Теперь новая файловая система с образом операционной системы доступна в директории /mnt/union. По идеи в нее надо перейти (chroot) и запустить там init. Но нам еще нужно будет отмонтировать (umount) образ initrd и высвободить занимаемую им память. Особенно это актуально для встраиваемых (embedded) операционных систем.

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

Переходим в новую файловую систему и делаем ее корнем, а текущую переносим в директорию mnt/initrd: ВАЖНО! Тут именно перейти (cd) в новую директорию, чтобы процесс ушел с предыдущий директории и не занимал ее, а то мы не сможем ее отмонтировать. Так же пути желательно указать относительные.

cd /mnt/union
./sbin/pivot_root . ./mnt/initrd

Все. В корне у нас теперь новая файловая система, а в директории mnt/initrd - старая, с образом init ram disk.

Теперь очень важный момент! Необходимо перенести и старые точки монтирования. Старую корневую ФС от initrd мы уже перенесли в /mnt/initrd, но нужно не забывать, что у нас были еще подмонтированы ro и rw разделы в директории /mnt/ro и /mnt/rw файловой системы образа initrd, но после подмены (pivot_root) они теперь находятся в /mnt/initrd/mnt/ro и /mnt/initrd/mnt/rw. И мы бы из-за них не смогли отмонтировать mnt/initrd и высвободить память. По этому переместим их в новую файловую систему используя команду mount --move:

mount --move /mnt/initrd/mnt/ro/ /mnt/ro/
mount --move /mnt/initrd/mnt/rw/ /mnt/rw/

Почти все готово чтобы отмонтировать наш корень (initrd), но еще необходимо отвязать дескрипторы консоли, которую использует текущий процесс:

exec </dev/console >/dev/console 2>&1

После этого у вас все-таки останется один процесс, использующий /mnt/initrd/dev/console, и не дающий отмонтировать /mnt/initrd, но он уйдет. Это текущий процесс, с PID=1. А уйдет он, когда мы запустим exec с вызвом нового init системы.

Грубо говоря можно сделать так: exec /usr/sbin/chroot . /sbin/init. А потом, в начале загрузки отмонтировать /mnt/initrd и чистить память, но это надо залезать в init-скрипты целевой системы, по этому мы поступим умнее, и отмонтирование запишем непосредственно в момент вызова нового init-а:

echo "Chroot..."
exec /usr/sbin/chroot . /bin/sh <<- EOF >dev/console
umount /mnt/initrd
/sbin/blockdev --flushbufs /dev/ram0
echo "Startng init!"
exec /sbin/init
EOF

Закрываем скрипт генерирующий init и ставим ему бит исполняемости:

EOFF
chmod +x ${ROOT}/bin/init

Все. Наш init готов.

# Done
echo "Done"

Отмонтируем образ initrd и запаковываем его.

umount /mnt/initrd
gzip -9 /tmp/ramdisk.img
# copy
mv -v /tmp/ramdisk.img.gz /home/clfs/my/


initrd готов, и доступен в виде файла ramdisk.img.gz, который можно уже скармливать системе, указывая параметром ядру initrd=. Да! Еще надо указать root=/dev/ram0.

Ссылки

Личные инструменты