Linux First: Загрузка ядра

linux linux-first boot mbr kernel grub qemu initrd

После предыдущего поста “Кратко о linux-ядре” есть общее представление как устроено ядро. Дальше поговорим про загрузку компьютера в общем и ядра в частности.

Попутно создадим образ жесткого диска, на базе которого продолжим разбираться с linux в следующих статьях.

Загрузка от включения ПК до устройства

Кроме этого BIOS предоставляет API для работы с устройствами еще до загрузки Операционной Системы

Подробно останавливаться на этом этапе смысла нет, мы будем собирать образ диска с linux и загружать его используя эмулятор QEMU

Подробнее про QEMU

https://www.qemu.org/

Эмулятор аппаратных платформ, позволяет эмулировать разные процессоры.

Мы будем использовать 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...

...

Выход из QEMU по CTRL+a x

Ни CDROM, ни диск не указан, поэтому и загрузки не происходит.

Исправляем это и создаем маленький по современным меркам жесткий диск на 256 mb.

Перед созданием образа стоит поговорить про стандарты таблиц разделов на диске. Сейчас их два:

Подробнее про стандарты будет ссылка в конце статьи. Сейчас сделаем образ с 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)

В этом режиме чтобы выйти из виртуалки нужно перейти в QEMU monitor по Ctrl-Alt-2 и выполнить команду quit. Подробнее про хоткеи в доке раздел “2.4 Keys in the graphical frontends”

После загрузки ядро должно иметь временную файловую систему и из нее запустить первый процесс в пользовательском пространстве с 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

Ссылки


Все статьи серии “Linux First”