Linux First: Загрузка ядра
linux linux-first boot mbr kernel grub qemu initrdПосле предыдущего поста “Кратко о linux-ядре” есть общее представление как устроено ядро. Дальше поговорим про загрузку компьютера в общем и ядра в частности.
Попутно создадим образ жесткого диска, на базе которого продолжим разбираться с linux в следующих статьях.
Загрузка от включения ПК до устройства #
- После включения код BIOS загружается в оперативную память (ОЗУ) из постоянной памяти (ПЗУ)
- После загрузки в ОЗУ код BIOS выполняет тест оборудования Power-On Self-Test (POST-тест)
- Читает настройки BIOS из ПЗУ
- Ищет и загружает в оперативную память код загрузчика
- Передает управление загрузчику
Кроме этого BIOS предоставляет API для работы с устройствами еще до загрузки Операционной Системы
Подробно останавливаться на этом этапе смысла нет, мы будем собирать образ диска с linux и загружать его используя эмулятор QEMU
Подробнее про QEMU
Эмулятор аппаратных платформ, позволяет эмулировать разные процессоры.
Мы будем использовать qemu для x86_64
Загрузка с устройства #
Все команды выполнялись на ubuntu 16.04
BIOS в зависимости от своих настроек выбирает устройство для загрузки или последовательно проверяет несколько устройств.
в случае с QEMU это выглядит так:
$ qemu-system-x86_64 \ # запускаем эмулятор с архитектурой x86_64
-nographic \ # весь вывод будет в консоль, иначе откроется gui-окно
-m 128m # оперативки будет 128 Мб - нам хватит
...
Booting from Hard Disk...
Boot failed: could not read the boot disk
Booting from Floppy...
Boot failed: could not read the boot disk
Booting from DVD/CD...
Boot failed: Could not read from CDROM (code 0003)
Booting from ROM...
...
Ни CDROM, ни диск не указан, поэтому и загрузки не происходит.
Исправляем это и создаем маленький по современным меркам жесткий диск на 256 mb.
Перед созданием образа стоит поговорить про стандарты таблиц разделов на диске. Сейчас их два:
- MBR (Main Boot Record) - старый стандарт таблицы разделов (из 1983 года)
- GPT (Guid Partition Table) - современный стандарт разделов, является частью стандарта EFI (Extensible Firmware Interface), разработанного Intel для замены BIOS
Подробнее про стандарты будет ссылка в конце статьи. Сейчас сделаем образ с MBR, а как-нибудь в другой серии с GPT, потому что с ним все немного сложнее
$ dd if=/dev/zero of=./mbr_hdd.img bs=1024k count=256
256+0 records in
256+0 records out
268435456 bytes transferred in 0.362737 secs (740027899 bytes/sec)
немного про dd
dd
позволяет копировать файлы блоками заданного размера
в данном случае мы копируем нули из /dev/zero блоками по 1024 килобайт (1Mb) 256 раз
про dd и устройство файловой систему будем разбираться в следующих статьях
Если сразу попытаться подсунуть образ диска в qemu, то ничего не поменятся, так как на диске нет таблицы разделов, а только нули.
Создаем один загрузочный linux-раздел, например, с помощью fdisk (или cfdisk или parted)
Должно получиться так:
$ fdisk -l ./mbr_hdd.img
Disk ./mbr_hdd.img: 256 MiB, 268435456 bytes, 524288 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000
Device Boot Start End Sectors Size Id Type
./mbr_hdd.img1 * 2048 524287 522240 255M 83 Linux
Теперь на диске есть таблица разделов, пробуем загружаться:
$ qemu-system-x86_64 -nographic -m 128m \
-boot c \ # загружаться с жесткого диска
-hda ./mbr_hdd.img # указываем образ первого жесткого диска
...
Booting from Hard Disk...
У нас есть жесткий диск, на нем есть таблица разделов, есть загрузочный раздел - пришло время поговорить про загрузчики.
Загрузчик (bootloader) #
Для linux по большому счету существует два загрузчика: LILO и GRUB.
LILO считается устаревшим, он не умеет работать с файловыми системами и для его конфигурации нужно каждый раз обновлять загрузочную запись на диске. Его рассматривать не будем.
GRUB умеет работать с разными файловыми системами, multiboot, grub-shell, консоль восстановления и много чего еще.
Конфиги GRUB, архивы с ядрами и initrd можно смотреть и править в каталоге (разделе) /boot
.
Для начала нужно создать файловую систему в разделе нашего hdd.
Для реальных hdd разделы диска будут отображатся с номерами в /dev и в выводе команды lsblk
выглядят примерно так:
# lsblk показывает блочные устройства в системе
$ lsblk
...
sda 8:0 0 10G 0 disk
└─sda1 8:1 0 10G 0 part /mnt
...
Видим что примонтирован диск sda с одним разделом sda1. Для нашего образа диска нам нужно как-то подключить файл образа как устройство.
Один из способов - команда losetup
, с помощью нее можно подключать образы как блочные устройства.
$ sudo losetup \
--find \ # ищет свободный id для блочного устройства
--partscan \ # ищет на устройстве разделы, а нас как раз один раздел
./mbr_hdd.img
$ lsblk
...
loop0 7:0 0 256M 0 loop # видим наш диск с одним разделом
└─loop0p1 259:0 0 255M 0 loop
...
Теперь можем форматировать раздел в ext4:
$ sudo mkfs.ext4 /dev/loop0p1
mke2fs 1.42.13 (17-May-2015)
...
Writing superblocks and filesystem accounting information: done
Примонтируем раздел и посмотрим что на нем есть.
$ mkdir /mnt/hdd # создаем точку монтирования
$ mount /dev/loop0p1 /mnt/hdd # монтируем раздел
$ ls -l /mnt/hdd # внутри только lost+found
total 12
drwx------ 2 root root 12288 May 13 16:45 lost+found
Вот мы плавно и подошли к установке загрузчика - как говорил ранее, будем ставить grub.
При установке загрузчик будет записывать файлы непосредственно в файловую систему и изменять MBR на самом диске.
sudo grub-install \
--root-directory=/mnt/hdd \ # тут нужно указывать полный путь до примонтированного раздела
/dev/loop0 # тут указываем сам диск, а не раздел
Installing for i386-pc platform.
Installation finished. No error reported.
На диске появилась директория boot
$ ls -l /mnt/hdd/boot/grub/
total 12
drwxr-xr-x 2 root root 1024 May 13 16:50 fonts
-rw-r--r-- 1 root root 1024 May 13 16:50 grubenv
drwxr-xr-x 2 root root 9216 May 13 16:50 i386-pc
drwxr-xr-x 2 root root 1024 May 13 16:50 locale
# а команда file говорит так
$ file mbr_hdd.img
mbr_hdd.img: DOS/MBR boot sector
Загрузчик установлен, пробуем загружаться в QEMU
$ qemu-system-x86_64 -nographic -m 128m -boot c -hda ./mbr_hdd.img
...
Booting from Hard Disk...
GNU GRUB version 2.02
Minimal BASH-like line editing is supported. For the first word, TAB
lists possible command completions. Anywhere else TAB lists possible
device or file completions.
grub>
Видим приветствие GRUB и grub shell.
В шелле можно посмотреть диски, информацию по разделам, поставить классные картинки на boot-screen, но то что нам пригодится сейчас - это возможность указать путь до образа ядра и ram-диска.
Загрузка ядра #
самый простой вариант получить образ ядра это скопировать его уже из готовой системы
ls -l /boot
total 31584
-rw-r--r-- 1 root root 217458 Apr 22 18:31 config-4.15.0-99-generic
drwxr-xr-x 5 root root 4096 May 11 15:58 grub
-rw-r--r-- 1 root root 19659681 May 11 15:57 initrd.img-4.15.0-99-generic
-rw------- 1 root root 4071696 Apr 22 18:31 System.map-4.15.0-99-generic
-rw------- 1 root root 8380056 Apr 22 18:32 vmlinuz-4.15.0-99-generic
$ cp /boot/vmlinuz-4.15.0-99-generic /mnt/hdd/boot
Запускаем QEMU и в консоли grub грузим ядро:
тут поменял -nographic на -curses, так лучше отображается в консоли, но этот ключ можно вообще убрать и работать в отдельном окне.
$ qemu-system-x86_64 -curses -m 128m -boot c -hda ./mbr_hdd.img
grub> ls /boot
grub/ vmlinuz-4.15.0-99-generic # вот ядро в директории /boot
grub> linux /boot/vmlinuz-4.15.0-99-generic # указываем какое ядро загружать
grub> boot # загружаемся
... # тут лог загрузки
# и в итоге получаем ошибку kernel panic
[ 2.159628] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
После загрузки ядро должно иметь временную файловую систему и из нее запустить первый процесс в пользовательском пространстве с pid=1.
Временная файловая система (ram-диск) это и есть initrd и этот образ нужно будет собрать.
После загрузки ядра при запуске пользовательского пространства нужны хоть какие-то команды и есть такой набор инструментов - busybox.
busybox - это один бинарный файл, который содержит в себе минимальный набор программ для работы с системой. Добавим busybox в сборке ram-диска.
минимальный initrd #
mkdir initrd
cd initrd
# создаем основные директории
mkdir bin sys dev proc
# загружаем busybox и расставляем симлинки
wget https://busybox.net/downloads/binaries/1.30.0-i686/busybox -O bin/busybox
# обязательно нужно добавить права на выполнение для busybox
chmod +x bin/busybox
# симлинки для некоторых команд, остальные добавим позже
ln -s busybox bin/echo
ln -s busybox bin/ash
ln -s busybox bin/ls
ln -s busybox bin/cat
# копируем файлы устройств, про файлы в linux поговорим в других статьях
cp -a /dev/console ./dev
cp -a /dev/null ./dev
cp -a /dev/tty1 ./dev
cp -a /dev/tty2 ./dev
# создаем скрипт с которого начнется запуск пользовательского пространства
cat >> ./init << EOF
#!/bin/ash
/bin/ash --login
EOF
# делаем скрипт исполняемым
chmod +x init
# из этой же директории пакуем initrd в cpio-архив
find . | cpio -o -H newc | gzip -9 > ../initrd.img
копируем полученный initrd.img в /boot на нашем hdd
cd .. && cp ./initrd.img /mnt/hdd/boot
и запускаем qemu:
$ qemu-system-x86_64 -curses -m 128m -boot c -hda ./mbr_hdd.img
grub> ls /boot
grub/ vmlinuz-4.15.0-99-generic initrd.img
grub> linux /boot/vmlinuz-4.15.0-99-generic # задаем ядро
grub> initrd /boot/initrd.img # задаем образ initrd
grub> boot # грузимся
...
/ # ls
bin dev init proc root sbin sys
Видим процесс загрузки ядра, после загрузки видим шелл, выполняем ls
и видим содержимое диска - самый минимум грузится осталось немного автоматизировать процесс загрузки.
Настроим grub чтобы система стартовала без ручного ввода ядра и ram-диска.
Для этого достаточно в /boot/grub
создать файл grub.cfg
(для grub2 нужно именно расширение .cfg
, а не .conf
)
set default=0
set timeout=5
menuentry 'linux' {
linux /boot/vmlinuz-4.15.0-99-generic
initrd /boot/initrd.img
}
если снова запустить qemu - увидим меню выбора системы
потом загрузку ядра и шелл
Итого #
В общих чертах разобрались с процессом загрузки и создали образ, в котором загружается ядро и первым процессом стартует shell (ash). Это только начало, дальше будем разбираться с файловой системой и с тем процесс должен быть запущен первым, да и вообще с процессами и не только.
Примеры #
Готовый образ из статьи можно взять тут:
https://github.com/rootaround/examples/tree/master/linux-first/2020-05-13-boot-mbr
Ссылки #
- Про BIOS: https://ru.wikipedia.org/wiki/BIOS
- Про стандарты таблиц разделов MBR, GPT https://losst.ru/chem-otlichaetsya-mbr-ot-gpt
- Форматирование диска: https://losst.ru/formatirovanie-diska-v-linux
- GRUB (wikipedia) https://ru.wikipedia.org/wiki/GNU_GRUB
- GNU GRUB официальная дока https://www.gnu.org/software/grub/manual/grub/grub.html
- IBM: Подробности процесса загрузки Linux https://www.ibm.com/developerworks/ru/library/l-linuxboot/