Делаем из Linux From Scratch свой универсальный дистрибутив | ||
Дистрибутивы > Прочее Разработки линуксовой части велись (да и ведутся) под Ubuntu, в среде Code::Blocks. Но, как показала практика, для качественной работы нужно что-то гораздо более легкое с гарантированным временем отклика. Для работы было достаточно консоли, так как задачи организации пользовательского интерфейса решались на подключаемом по TCP/IP удаленном компьютере. Разработки линуксовой части велись (да и ведутся) под Ubuntu, в среде Code::Blocks. Но, как показала практика, для качественной работы нужно что-то гораздо более легкое с гарантированным временем отклика. Для работы было достаточно консоли, так как задачи организации пользовательского интерфейса решались на подключаемом по TCP/IP удаленном компьютере.
Тогда и пришла идея использовать дистрибутив Linux собственной сборки, чем (сборкой дистрибутива), собственно, в свободное время я и занялся. Выбор пал на LFS. Про то что такое LFS уже неоднократно писали даже на Хабре, я же опишу решение нескольких дополнительных (кроме простенького Linux'а) задач, вставших передо мной в нашем конкретном случае. Поначалу такая задача была одна — использовать real-time ядро. Однако дальше, когда идея USB-флешки с дистрибутивом, пришлась всем по душе, то появились задачи размножения флешек и запуска системы на различных компьютерах (тестовых стендов много, имея свою флешку суешь в карман и иди к любому). Вот тут и появились проблемы — LFS не обладает 100% переносимостью с одного компьютера на другой. Для ее адаптации к конкретному компьютеру нужно править некоторые скрипты, что в условиях команды вчерашних Windows-кодеров проблематично (на виртуалку с Ubuntu некоторые пересели, но консоль и скрипты — это беда). Размножение системы также требует повторения некоторых манипуляций, проделываемых в процессе сборки (тот же GRUB установить). Естественно, решение всех задач есть на просторах интернета, но, думаю, сбор некоторой информации в одном месте никому не помешает.
Итак, конкретные задачи были следующие… 1. Использование ядра Linux с real-time патчем Это была одна из самых легких задач. Процесс сборки прошел по книге LFS с единственным исключением — вместо штатного для книги ядра было взято 2.6.33.9 и RT-патч для него. Везде, где происходили манипуляции с ядром (установка Linux Headers и ядра непосредственно), работаем с нашей пропатченной версией. Также не лишним будет сказать, что дистрибутив собирался без подключения swap-раздела (2Гб ОЗУ это в нашем случае — выше крыши, наличие swap не желательно по причине его негативного влияния на гарантированное время отклика, да и для флешки он крайне губителен) и представлял собой один единственный раздел ext2fs. 2. Автоматический вход в систему (в условиях разработки безопасность нам не важна, да и систему пускаем под рутом по ряду причин) Идея автоматического входа была взята отсюда. Был создан файл autologin.c следующего содержания: int main() { execlp("login", "login", "-f", "root", 0); } Далее файл был скомпилирован командой: gcc autologin.c -o /usr/local/sbin/autologin Далее было решено, что двух консолей с автоматическим входом хватит (одна для запуска системы, другая для всего остального, если понадобится). В файле /etc/inittab строчки: 1:2345:respawn:/sbin/agetty tty1 9600 2:2345:respawn:/sbin/agetty tty2 9600 были заменены на: 1:2345:respawn:/sbin/agetty -n -l /usr/local/sbin/autologin tty1 9600 2:2345:respawn:/sbin/agetty -n -l /usr/local/sbin/autologin tty2 9600 После этого никаких проблем с автоматическим входом не было, поэтому никаких дополнительных манипуляций со скриптами не было. 3. Отвязывание системы от порядка подключения дисков У разных BIOS свои заморочки. Одни, например, считают, что первым является тот диск, с которого мы загружаемся. В этом случае наша флешка будет sda. Другие считают, что сначала должны идти жесткие диски, а потом другие устройства. В этом случае наша флешка будет иметь имя sdb, sdc и так далее. В результате, то система не может загрузиться с диска, которого нет, то корневой каталог не может быть смонтирован по той причине, что в /etc/fstab указан не тот диск. Все решается либо исправлением /boot/grub/grub.cfg и /etc/fstab под конкретную машину или использованием для загрузки и монтирования не имени диска (sda, sdb и т. д.), а UUID файловой системы, который для данной файловой системы на флешке будет уникален и, что самое главное — постоянен. Проблема в том, что GRUB работать с UUID умеет, а ядро — нет, то есть напрямую монтировать корневую систему по UUID (не зная имени устройства) невозможно. Это не баг, а следствие идейных соображений Линуса Торвальдса, поэтому на такую возможность и в будущем надеяться не стоит. Тем не менее пути обхода есть — это initramfs. Initramfs — временная файловая система, помогающая в загрузке и монтировании файловых систем настоящей системы. В стандартную сборку LFS initramfs не входит, поэтому для ее построения воспользуемся рекомендациями из Gentoo Wiki и некоторыми собственными соображениями (вариант из Gentoo Wiki без изменений в моем случае проблему с именами дисков не решил, да и не заработал толком). Для создания простейшей initramfs системы, монтирующей нашу основную по UUID нужна простейшая командная оболочка (shell) и скрипт init. Полный набор утилит командной строки достаточно громоздок для initramfs, поэтому часто для этой цели применяется busybox, который при скромных размерах и требованиях реализует некоторые, наиболее часто используемые утилиты. Забираем последнюю версию busybox: cd sources wget http://busybox.net/downloads/busybox-1.18.4.tar.bz2
Распаковываем и конфигурируем: tar jxf busybox-1.18.4.tar.bz2 cd busybox-1.18.4 make menuconfig Конфигурирование производится при помощи меню (наподобие ядра Linux). В принципе, стандартной конфигурации хватает для наших нужд, но, на всякий случай, стоит проверить, что подключены следующие возможности: Support for devfs — поддержка devfs для работы с /dev. Build Busybox as a static library (no shared libraries) — статическая компоновка, чтобы не тянуть за собой кучу so-библиотек. Support version 2.6.x Linux kernels — поддержка ядер линейки 2.6. А также поддержка функциональности утилит: sh, cat, cut, findfs, mount, umount, sleep, echo, switch_root. Компилируем: make Теперь собираем дерево каталогов нашей файловой системы: mkdir /usr/src/initramfs cd /usr/src/initramfs mkdir -p bin lib dev etc mnt/root proc root sbin sys cp -a /dev/{null,console} /usr/src/initramfs/dev/ Копируем busybox и создаем линки на утилиты: cp /sources/busybox-1.18.4/busybox ./bin/ cd bin ln -s busybox sh ln -s busybox cat ln -s busybox cut ln -s busybox findfs ln -s busybox mount ln -s busybox umount ln -s busybox sleep ln -s busybox switch_root cd .. Осталось написать скрипт init: #!/bin/sh # Монтируем файловые системы proc и sysfs mount -t proc none /proc mount -t sysfs none /sys # USB-устройства инициализируются ядром не сразу # На всякий случай ждем 10 секунд, иначе корневой раздел не будет найден sleep 10 # Монтируем файловую систему устройств mount -t devtmpfs none /dev # Ищем по UUID имя корневого диска и монтируем систему for cmd in $(cat /proc/cmdline) ; do case $cmd in root=*) uuid=$(echo $cmd | cut -d= -f3) mount -o ro $(findfs UUID="$uuid") /mnt/root ;; esac done # Размонтируем все примонтированные файловые системы umount /dev umount /proc umount /sys # Загружаем основную систему exec switch_root /mnt/root /sbin/init Делаем наш скрипт исполняемым: chmod +x /usr/src/initramfs/init Собираем нашу временную файловую систему в архив: cd /usr/src/initramfs find . -print0 | cpio --null -ov --format=newc | gzip -9 > /boot/initrd.img-2.6.33-rt31 Обратим внимание на имя файла — оно должно соответствовать имени ядра (это нужно, чтобы GRUB его правильно подцепил). То есть, если ядро имеет имя vmlinux-2.6.33-rt31, то initramfs должен иметь имя initrd.img-2.6.33-rt31. Теперь при выполнении grub-mkconfig GRUB обнаружит initramfs, а также включит в конфигурацию UUID корневой системы. Для проверки можно поправить /boot/grub/grub.cfg руками. Например конфигурацию: menuentry "Linux 2.6.33-rt31" --class gnu-linux --class gnu --class os { insmod ext2 set root='(hd0,1)' search --no-floppy --fs-uuid --set 47029df8-8567-417d-b813-eedfe1ff8b0f linux /boot/vmlinux-2.6.33-rt31 root=/dev/sda1 ro } исправим на: menuentry "Linux 2.6.33-rt31" --class gnu-linux --class gnu --class os { insmod ext2 set root='(hd0,1)' search --no-floppy --fs-uuid --set 47029df8-8567-417d-b813-eedfe1ff8b0f linux /boot/vmlinux-2.6.33-rt31 root=UUID=47029df8-8567-417d-b813-eedfe1ff8b0f ro initrd /boot/initrd.img-2.6.33-rt31 } UUID файловой системы можно узнать так (например, для /dev/sdb1): blkid -p -o udev /dev/sdb1 Осталось поправить /etc/fstab, заменив строчку: /dev/sda1 / ext2 defaults 1 1 на UUID= 47029df8-8567-417d-b813-eedfe1ff8b0f / ext2 defaults 1 1 Также следует заметить, что для всех манипуляций выше необходимо, чтобы в ядре была включена поддержка devtmpfs (CONFIG_DEVTMPFS=y) и initramfs (CONFIG_BLK_DEV_INITRD=y). 4. Отвязывание системы от сетевой карты Если в компьютере установлено более одной сетевой платы, то при параллельной загрузке модулей ядра не гарантируется постоянное назначение имен этим платам. Например, есть две платы. Плата_1 имеет имя интерфейса в системе eth0, Плата_2 — eth1. При очередной перезагрузке может получиться так, что Плата_1 станет eth1, а Плата_2 — eth0. С этой целью в LFS производится привязка имени к конкретной плате. При загрузке на другом компьютере очень велика вероятность, что сеть не поднимется. В моем конкретном случае плата на всех компьютерах одна и IP — статический (связь только с терминальным компьютером напрямую). Поднятие сетевого интерфейса в LFS осуществляется скриптом /etc/rc.d/init.d/network. Допишем скрипт так, чтобы каждый раз при загрузке генерировался конфигурационный файл /etc/udev/rules.d/70-persistent-net.rules и при завершении работы этот файл удалялся. Подозреваю, что есть метод проще, но найденный метод заработал, а копаться в принципах работы Udev времени и особого желания на момент сборки системы не было. В начало раздела start команды case добавляем: for NIC in /sys/class/net/* do INTERFACE=${NIC##*/} udevadm test --action=add $NIC done
А в конец секции stop (непосредственно перед ;;) добавляем: rm /etc/udev/rules.d/70-persistent-net.rules
Теперь при загрузке на любой системе имя сетевого интерфейса будет eth0 (кроме самых экзотических случаев) и сеть будет подниматься. Разумеется, каталог /etc/sysconfig/network-devices/ifconfig.eth0 с файлом ipv4 должен существовать. Содержимое этого файла описано в книге LFS. 5. Написание скрипта, производящего инсталляцию LFS на любую флешку Осталось последнее — сделать архив системы и скрипт, который будет ее устанавливать на произвольный носитель. Загружаемся в другой системе (не с флешки), монтируем флешку, например в /mnt/usb-os. Архивируем содержимое: cd /mnt/usb-os tar -cvjf ~/pack.tar.bz2 * Пишем скрипт для установки install_usb-os.sh. В качестве параметра скрипт принимает имя устройства, на котором необходимо развернуть систему (например /dev/sdb). Скрипт сам создаст необходимый раздел и файловую систему (/dev/sdb1, если указано имя /dev/sdb), распакует архив и установит GRUB. Запускаться он должен с правами root и, на самом деле, очень опасен. В случае неверного указания имени устройства могут быть уничтожены все данные на рабочем диске! #!/bin/sh # Проверяем, существует ли параметр с именем устройства if [ "x${1}" = "x" ] ; then echo "Usage: install_usb-os device_name" exit fi # Размонтируем раздел, если вдруг был примонтирован umount ${1}1 # Создаем временный каталог для монтирования if [ ! -e /mnt/USBOSTmp ]; then mkdir /mnt/USBOSTmp fi # Создаем MBR, первичный раздел размером с диск echo "Building partitions..." parted -s ${1} mklabel msdos parted -s ${1} unit % mkpart primary ext2 0 100 # Форматируем раздел и монтируем его в наш временный каталог echo "Preparing filesystem..." mkfs -t ext2 ${1}1 mount -t ext2 ${1}1 /mnt/USBOSTmp # Распаковываем архив echo "Unpacking distributive..." tar -xvf ./pack.tar.bz2 -C /mnt/USBOSTmp # Создаем правильные ноды console и null # (те, что распаковываются из архива, почему-то оказываются неверными) mknod -m 600 /mnt/USBOSTmp/dev/console c 5 1 mknod -m 666 /mnt/USBOSTmp/dev/null c 1 3 # Монтируем виртуальные файловые системы (устройств и т.д.) echo "Mounting necessary file systems..." mount -v --bind /dev /mnt/USBOSTmp/dev mount -vt devpts devpts /mnt/USBOSTmp/dev/pts mount -vt tmpfs shm /mnt/USBOSTmp/dev/shm mount -vt proc proc /mnt/USBOSTmp/proc mount -vt sysfs sysfs /mnt/USBOSTmp/sys # Добываем UUID нашего раздела на флешке и записываем не нее # файл fstab uu=`blkid -p -o value ${1}1 | grep -` echo "Writing fstab for uuid=$uu..." cat > /mnt/USBOSTmp/tmp/fstab << "EOF" proc /proc proc defaults 0 0 sysfs /sys sysfs defaults 0 0 devpts /dev/pts devpts gid=4,mode=620 0 0 tmpfs /dev/shm tmpfs defaults 0 0 EOF echo "UUID=$uu / ext2 defaults 1 1" >> /mnt/USBOSTmp/tmp/fstab # Записываем на флешку скрипт, который должен быть запущен # когда корневым каталогом является корень флешки # В этом скрипте происходит конфигурирование и установка GRUB cat > /mnt/USBOSTmp/tmp/update.sh << "EOF" echo "Starting internal update script for $H_DEV ..." mv -v /tmp/fstab /etc/fstab grub-mkconfig -o /boot/grub/grub.cfg echo "Updating mbr $H_DEV2..." grub-setup $H_DEV2 EOF # Делаем его исполняемым chmod +x /mnt/USBOSTmp/tmp/update.sh # Запускаем скрипт в другом root-окружении, # когда корневым каталогом является корень флешки echo "Running update..." chroot /mnt/AxiOMATmp/ /usr/bin/env -i HOME=/root TERM="$TERM" PS1='\u:\w\$ ' \ PATH=/bin:/usr/bin:/sbin:/usr/sbin H_DEV=${1}1 H_DEV2=${1} /tmp/update.sh # Убираем все лишнее, размонтируем файловые системы rm -v /mnt/USBOSTmp/tmp/update.sh umount -v /mnt/USBOSTmp/dev/pts umount -v /mnt/USBOSTmp/dev/shm umount -v /mnt/USBOSTmp/dev umount -v /mnt/USBOSTmp/proc umount -v /mnt/USBOSTmp/sys umount ${1}1 rm -rvf /mnt/USBOSTmp echo "Installation finished!" Теперь сделаем наш скрипт исполняемым: chmod +x install_usb-os.sh Размещаем архив pack.tar.bz2 в каталоге со скриптом и инсталлятор готов! |
||
Комментарии | ||