среда, 10 июля 2013 г.

PhantomEx: Приручаем GRUB2 - установка и загрузка ядра

Теперь у нас есть скомпилированное примитивное ядро, поддерживающее однако спецификацию Multiboot. Или нам кажется что оно её поддерживает. Так давайте проверим это.



1. Инсталляция ядра


Прежде всего установим нашу "операционную систему". Для этого смонтируем виртуальный HDD, подготовленный нами ранее. Перейдем в root-терминал

# modprobe -r loop
# modprobe loop max_part=15
# losetup -f /home/user/myOSkernel/hdd/hard_disk.img
# mount /dev/loop0p1 /mnt

Теперь скопируем ядро в каталог /mnt/boot в корне виртуального диска

# cp /home/user/myOSkernel/src/kernel /mnt/boot/

Отмонтируем раздел и удалим блочное устройство и от греха переходим в обычный пользовательский shell

# umount /mnt
# losetup -d /dev/loop0
# exit

Запускаем виртуальную машину QEMU

$ qemu-system-x86_64 ~/myOSkernel/hdd/hard_disk.img -enable-kvm

и...

ну что за черт! Опять перед нами командная строка GRUB2. Ничего, это нормальное явление.

2. Первый запуск


Загрузчик GRUB2, разумеется способен найти, почти автоматически ядро ОС, особенно если она дружит с Multiboot-спецификациями. Но для этого необходимо выплнять команды автоконфигурирования, находясь в среде какой-либо ОС, либо перейти в среду установленного уже линукса с помощью chroot. У нас нет никакой другой ОС на данном компьютере (имею в виду конечно ВМ) кроме нашей "недосистемы". Поэтому воспользуемся для начала средствами командной строки GRUB2. Эти знания могут пригодится вам и при аварии вашего линукса, так что со всех сторон такое описание будет полезно.

Функционал командного интерпретатора GRUB2 поскромней чем у bash (особенно в консоли восстановления, там вообще пяток несчастных команд), но тем не менее смело набираем 

grub> ls

и видим список разделов HDD
Раздел (hd0,msdos1) или другими словами (hd0,1) - это очевидно наш раздел, в котором лежит ядро. Проверим, лежит ли?

grub> ls (hd0,1)/boot/

Таки да, лежит, там куда мы его и положили
Остается только сказать загрузчику, чтобы он его проверил и загрузил. Даем команду

grub> multiboot (hd0,1)/boot/kernel

и... ничего не произошло, курсор снова мигает
В UNIX отсутствие сообщения после команды очень часто, на деле, хорошее сообщение - в частности здесь GRUB2 сообщает нам "я проверил ваше ядро, оно действительно поддерживает спецификацию Multiboot". Если бы это было не так, он бы выдал сообщение об ошибке.

Как теперь загрузится? Очень просто

grub> boot


снова моргающий курсор. Но это от того, что наше ядро ровным счетом ничего не делает (см. Листинг 1 из предыдущей статьи там совершенно пустая функция main() ). Мы в бесконечном цикле, процессор остановлен... Что же у нас есть

  1. Заготовка ядра, с поддержкой мультизагрузки
  2. Мы уже находимся в защищенном режиме процессора (PM) с сегментной адресацией - GRUB2 уже создал нам таблицу GDT.
  3. Мы избавлены от необходимости писать и отлаживать собственный загрузчик, и можем бросить усилия на реализацию функционала ядра.
Но прежде слегка упростим себе жизнь

3. Настройка меню GRUB2


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

Настраивается он как и всё в Linux с помощью конфигурационного файла /boot/grub/grub.cfg. В комментариях к этому конфигу, сгенерированному автоматически написано: "ни в коем случает не редактируйте этот файл вручную!" Истинно так, этого не стоит делать в Linux, для изменения конфигурации загрузчика есть соответствующий инструментарий.

Но у нас этого конфига попросту нет и сгенерировать мы его не можем. Поэтому напишем его вручную. Возвращаемся в наш linux-терминал, в каталог с исходниками ядра.

$ nano grub.cfg

и пишем там что-то вот такое

Листинг 5. Конфигурационный файл GRUB2 (файл /boot/grub/grub.cfg)

# Настраиваем переменные окружения (взято из примера)
have_grubenv=true
load_env

# Пункт меню загрузки по-умолчанию
set default=0
# Задержка перед выбором пункта по умолчанию
set timeout=30


# Пункт меню загрузки
menuentry "My SuperOS kernel ver. 0.0.1" {


multiboot (hd0,1)/boot/kernel
boot
}


Сохраняем этот файл. Первые две строки здесь почерпнуты мной из вики-учебника по GRUB2, их смысл мне ясен лишь отдаленно, опускаю их.

Строчка set default=0 - это выбор пункта меню по умолчанию. У нас будет один пункт, пока что. Строка set timeout=30 - задает время ожидания (в секундах!) выбора пункта загрузки, давая вам время выбрать интересующую вас ОС. Пока, чтобы полюбоваться, оставим 30 секунд, потом в целях ускорения отладки поставим там 0, чтобы наше ядро грузилось сразу.

Далее идет структура menuentry, описывающая пункт меню и порядок его отработки. Строка "My SuperOS kernel ver. 0.0.1" - это то что мы увидим в меню, она может быть любой. В блоке фигурных скобок перчисляются построчно команды загрузки multiboot-ядра, это те самые что мы водили в ручную в командной строке загрузчика.

Снова подключаем и монтируем виртуальный диск, помещаем на него этот файл по пути /boot/grub/grub.cfg, отмонтируем диск и запускаем QEMU.

и наблюдаем меню GRUB2 с нашей ОС в качестве пункта загрузки :)

Выбрав его получим чистый экран и мигание курсора - наша ОС бездельничает. Но это, надеюсь ненадолго. Надеюсь так же, что вы побороли суеверный ужас перед командной строкой GRUB2 :)

4. Автоматизация установки ядра ОС


Следующим шагом будет автоматизация установки ядра на целевую виртуальную машину - согласитесь ужасно каждый раз вручную проделывать описанные манипуляции по установке.

Прежде всего сотворим bash-скрипт

Листинг 6. Скрипт установки ядра (файл install_kernel.sh)

#!/bin/bash
 
modprobe -r loop
sleep 1
modprobe loop max_part=15
sleep 1
losetup -f /home/user/myOSkernel/hdd/hard_disk.img
sleep 1
mount /dev/loop0p1 /mnt
sleep 1
rm /mnt/boot/kernel
cp /home/user/myOSkernel/src/kernel /mnt/boot/kernel
umount /mnt
sleep 1
losetup -d /dev/loop0

Тут перечислим все команды, необходимые для установки ядра (может излише много "слипов", но я перестраховался). Делаем скрипт исполняемым

$ chmod a+x install_kernel.sh

Теперь достаточно ввести в командной строке

$ sudo ./install_kernel.sh

и будет произведена установка ядра. Но мы пойдем ещё дальше.

Добавим в конец Makefile такую секцию

Листинг 7. Секция инсталляции ядра (файл Makefile)

install:

    -sh install_kernel.sh

это позволит нам сделать вот так

$ sudo make install

что уже совсем "по-взрослому" 8-)

Заключение


Итак, совершенно на пустом месте мы создали заготовку ядра ОС, загружающегося с HDD стандартным общепринятым загрузчиком. Теперь никто не мешает нам узнать, а что же должно быть внутри функции main()...? 

Но об этом как-нибудь позже... :)