Делаем из 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 в каталоге со скриптом и инсталлятор готов!

Комментарии
]]> ipv6 ready Kiev LUGLinux4MeНостальгияЛичный сайт skeletora ]]>