ФЭНДОМ


Готовим почву (создание образа HDD и установка загрузчика GRUB2)


Начинать мы будем с того, что определимся откуда будет грузится наше ядро. В большинстве руководств по написанию ОС, блуждающих в сети этот вопрос решается довольно просто: образ ядра вместе с загрузчиком жестко "вшивается" в образ дискеты, которая затем устанавливается в виртуальную машину.
Кроме того, многие HOW-TO обладают тем недостатком, что начинаются с описания создания загрузчика для самодельной ОС.

Кроме образовательного эффекта - изучения принципов работы BIOS и порядка загрузки ОС такой подход не имеет других достоинств. Кроме того, ваша ОС будет не приспособлена для совместного проживания с другими ОС (одна ОС известной фирмы тоже этим грешит ;) ).
Поэтому сделаем всё основательно - создадим образ жесткого диска, разобьем его на разделы и установим на него загрузчик GRUB2. Так чтобы наше ядро с детства училось жить в разделе жесткого диска и грузится используя спецификацию Multiboot или Multiboot2.
Что для этого понадобится?

  1. Эмулятор QEMU установленный на вашей Linux-машине.
  2. Загрузочный CD какого-нибудь 32-разрядного дистрибутива Linux. Я использовал установочный CD Arch Linux от 1 июня 2013.

Итак, в командной строке переходим в каталог где будет хранится образ HDD

$ cd ~/myOSkernel/hdd/


Далее выполняем

$ qemu-img create -f raw hard_disk.img 2G


Эта команда создаст raw-образ (не сжатый) жесткого диска объемом 2Гб, чего нам вполне достаточно для первоначальных экспериментов.

Теперь необходимо обработать этот "сырой" ещё образ винтчестера. Для этого надо создать на диске таблицу разделов и отформатировать его.


ВНИМАНИЕ! Все нижеследующие команды выполняются от имени суперпользователя. Будьте внимательны!


Прежде всего подготовим драйвер блочного устройства к работе с нашим образом.

# modprobe -r loop
# modprobe loop max_part=15
# losetup -f hard_disk.img


Тем самым мы связали наш образ с блочным устройством loop, посмотрим какое имя было ему присвоено

# losetup
NAME       SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE
/dev/loop0         0      0         0  0 /home/maisvendoo/phantom/hdd/hard_disk.img


Отлично, устройство присутствует в системе с именем loop0. Далее выполним

# parted /dev/loop0
GNU Parted 3.1
Используется /dev/loop0
Добро пожаловать в GNU Parted! Наберите 'help' для просмотра списка команд.
(parted)


Утилита parted необходима нам для создания на диске таблицы разделов. В появившемся приглашении введем команду print

(parted) print                                                          
Ошибка: /dev/loop0: метка диска не определена
Модель: Loopback device (loopback)                                      
Диск /dev/loop0: 2147MB
Размер сектора (логич./физич.): 512B/512B
Таблица разделов: unknown
Disk Flags:
(parted)


Таблица разделов - unknown, диск у нас свеженький и таблицы разделов на нем нет. Исправляем это командой mklabel msdos

(parted) mklabel msdos
(parted) print                                                  
Модель: Loopback device (loopback)
Диск /dev/loop0: 2147MB
Размер сектора (логич./физич.): 512B/512B
Таблица разделов: msdos
Disk Flags:

Номер  Начало  Конец  Размер  Тип  Файловая система  Флаги

(parted)


Отлично, теперь у нас есть таблица разделов. Дальше можно выполнить разбиение диска на разделы в том же parted, однако для меня он таки непривычен, поэтому я выхожу из этой утилиты набрав quit, и запускаю более привычный мне fdisk

# fdisk /dev/loop0


Далее создадим раздел дав комаду n. Нам последовательно предложат:

  • задать тип раздела - выбираем p (первичный раздел)
  • указать место где будет начинаться раздел в секторах. Здесь укажем число 4096 секторов, так как необходимо отступить 2 мегабайта от начала дистка, для последующей установки туда загрузчика.
  • указать размер раздела - тут жмем просто Enter, чтобы fdisk разметил весь диск до конца.


Теперь запишем изменения на диск введя команду w.

Посмотрим что у нас получилось.

# ls /dev/loop*


Среди всего списка вы увидите такое устройство - /dev/loop0p1 - это и есть созданный нами раздел. Теперь создадим на нем файловую систему ext2

# mkfs.ext2 /dev/loop0p1


Теперь мы можем смонтировать этот раздел

# mount /dev/loop0p1 /mnt
Посмотрим что у нас вышло.
10034

Смонтированный раздел на образе HDD

Великолепно - мы сделали раздел на виртуальном жестком диске. Для установки GRUB2 и хранения нашего ядра создадим на нем каталок boot

# mkdir /mnt/boot


Если у вас 32-разрядная версия Linux с загрузчиком GRUB2 то можно сразу установить его командой

# grub-install --root-directory=/mnt  /dev/loop0


У меня Arch Linux 64-разрядный, поэтому мне пришлось загрузится с установочного диска, выбрав 32-битный вариант установки. Отмонтируем блочное устройство и удалим его

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


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

$ qemu-system-x86_64 ~/myOSkernel/hdd/hard_disk.img -cdrom ~/install/archlinux/archlinux-setup.iso -boot d -enable-kvm
Здесь указываем путь к образу HDD; в ключе -cdrom указываем путь к образу загрузочного CD; в ключе -boot ставим директиву d, сообщающую эмулятору что надо грузится именно с CD; ключом -enable-kvm разрешаем использование аппаратной виртуализации (для ускорения работы виртуальной машины). Видим такой экран
10035
с вариантами установки. Выбираем 32-разрядный вариант. Грузимся....

После загрузки мы в рутовой консоли арчевского установщика, из которой выполняем монтирование раздела на виртуальном жестком диске

# mount /dev/sda1 /mnt


Просматриваем содержимое раздела, убеждаемся что созданная нами папка boot на месте.

# grub-install --root-directory=/mnt  /dev/sda


где /dev/sda - имя нашего "винта" в виртуальной системе. Всё! Отмонтируем диск и выключаем виртуальную машину

# umount /mnt
# systemctl poweroff


И запускаем её заново, только теперь используя команду

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


После загрузки мы увидим следующую картину

10036
Что это? Всё в порядке - это приглашение командной строки GRUB2. Он установлен на диск и работоспособен, только вот на этом HDD у нас ничего кроме загрузчика пока что нет, и GRUB2 просит дать команды для загрузки какой-либо ОС. Чуть позже, когда у нас будет ядро, мы рассмотрим эти команды, и сконфигурируем меню GRUB2 для загрузки нашей "игрушечной" операционной системы.

Спецификация Multiboot - делаем заготовку ядра


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

2.1. Спецификация мультизагрузки.

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

Формат заголовка Multiboot
Смещение Поле Назначение
0x00 INIT_MBOOT_HEADER_MAGIC 0x1BADB002
0x04 INIT_MBOOT_HEADER_FLAGS Флаги заголовка
0x08 INIT_MBOOT_CHECKSUM Контрольная сумма заголовка


Рассмотрим эти поля подробнее

  • INIT_MBOOT_HEADER_MAGIC - так называемое "магическое число". Найдя эту сигнатуру в файле вашего ядра, загрузчик поймет что он имеет дело с ядром, поддерживающим мультизагрузку. Приведенное значение одинаково для всех ядер, использующих спецификацию Multiboot.
  • INIT_MBOOT_HEADER_FLAGS - флаги, указывающие загрузчику специфические требования и настройки, которые необходимо выполнить при загрузке ядра. Биты 0 - 15 указывают требования к порядку загрузки. Если загрузчик их не поймет, он не будет ничего загружать и выдаст сообщение об ошибке. Биты 16 - 31 указывают дополнительные опции загрузки, и если они не распознаются загрузчиком, то он их просто игнорирует и продолжает работу.

Рассмотрим некоторые обязательные биты флагов

Таблица 2. Флаги заголовка Multiboot
Бит Назначение
0 Все загрузочные модули загружаются вместе с операционной системой и должны быть выровнены по границе страниц размером 4 Кб
1 В ядро, через поля соответствующей структуры передается информация о доступной памяти
2 Ядру передается информация о таблице видеорежимов


Информация относительно остальных флагов в спецификации GNU почему-то опущена (надо сказать эта спецификация и не блещет подробностью изложения).

Итак, нам пока требуется только флаг бита 0, то есть укажем в заголовке значение 0х00000001.

  • INIT_MBOOT_CHECKSUM - контрольная сумма заголовка. Эту контрольную сумму мы должны указать такой, что сумма всех трех полей заголовка должна давать ноль. Это дает следующую формулу для расчета контрольной суммы

контрольная сумма = -(магическое число + флаги)


Мы выполним автоматический расчет этой контрольной суммы прямо в коде ядра.

Кроме наличия вышеуказанного заголовка есть ещё одно условие - код ядра должен быть выровнен в памяти по границе страниц размером 4 Кб. Загрузчик будет искать "магическое" число именно на границах страниц, при отсутствии такового выравнивания в коде он просто ничего не найдет и выдаст сообщение об ошибке.

Где должен располагаться заголовок Multiboot? Спецификация говорит, что данный заголовок необходимо разместить как можно ближе к началу исполняемого файла ядра, то есть сразу за автоматически добавляемым компилятором заголовком выбраного формата исполняемого файла.

2.2. Код заготовки ядра

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

Что касается ассемблера, то использовать мы будем GNU Assembler (GAS). Возможно у меня появятся противники в этом вопросе, но GAS наиболее полно интегрирован в общую систему разработки ПО в *nix - подобных ОС и вызывается автоматически компилятором gcc, когда он натыкается на ассемблерный код.

Итак, открываем ваш любимый текстовый редактор (я использую kate, да простят меня аппологеты vim и emacs) и пишем там следующий код

Листинг 1. Код примитивного ядра (файл main.c)

/*-------------------------------------------------------------------
// Код примитивного ядра
//-----------------------------------------------------------------*/

int main(void)
{
  /* Тут будет расположен код ядра */

  return 0;
}


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

Листинг 2. Ассемблерная "обертка" для мультизагрузки (файл init.s)
/*-------------------------------------------------
//
// Код инициализации и мультизагрузки
//  
//-----------------------------------------------*/


/*-------------------------------------------------
//    Константы для заголовка Multiboot
//-----------------------------------------------*/

.set MBOOT_HEADER_MAGIC, 0x1BADB002
.set MBOOT_HEADER_FLAGS, 0x00000001
.set MBOOT_CHECKSUM, -(INIT_MBOOT_HEADER_MAGIC+INIT_MBOOT_HEADER_FLAGS)

/* Указываем что функция main - внешняя и расположена в другом
 объектном модуле */

.extern main

/* Секция - заголовок мультизагрузки */
.section .mboot

.int MBOOT_HEADER_MAGIC
.int MBOOT_HEADER_FLAGS
.int MBOOT_CHECKSUM
 
/* Секция кода */
.section    .text

/* Делаем точку входа глобальной, доступной для компоновщика */
.global      init

init:
        cli /* Выключаем ВСЕ прерывания */
              
        push    %ebx /* Через этот регистр передается multiboot-структура */
                     /* поглужаем её в стек, чтобы в будущем воспользоваться */
                     /* в функции main */
      
        call    main /* вызываем main */
      
        hlt /* Останавливаем процессор */
      
loop: /* Переходим в бесконечный цикл */
        jmp    loop


Это и есть код нашей заготовки. Всё что мы сделали: поместили в нужное место заголовок мультизагрузки, отключили прерывания и вызвали точку входа в код ядра - функцию main().

Следует обратить внимание на синтаксис ассемблера - он носит наименование синтаксис AT&T, и разработан создателями операционной системы UNIX. При желании использовать более привычный синтаксис Intel в начале ассемблерного кода можно поставить директиву .intel_syntax noprefix, однако особой необходимости я в этом не вижу, тем более что при написании макросов для GAS с которыми мы ещё столкнемся, может возникнуть досадная путаница. Синтаксис AT&T не так сложен в освоении, как может показаться с непривычки, за справочной информацией отсылаю к книге "Assembler для DOS, Windows и UNIX" автора Зубкова С. В.

Кроме того следует обратить внимание на стиль комментариев - это стиль языка C, тот формат сборки который мы будем использовать, не поддерживает однострочных комментариев в стиле C++, о чём компилятор обязательно вам сообщит.

2.3. Компиляция и компоновка

Теперь нам необходимо собрать наше ядро. Просто выполнить компиляцию - так не пойдет. Помните о том, что нам необходимо обеспечить генерацию необходимого формата исполняемого файла с выравниванием кода по границам 4 Кб страниц. Это можно выполнить задав правила компоновки для линковщика ld.

Листинг 3. Скрипт управления компоновкой ядра (файл link.ld)

/* Точка входа в код ядра */
ENTRY (init)

SECTIONS
{
/* Адрес, с которого будет начинаться код ядра в памяти */
    . = 0x00100000;
/* Секция кода */

    .text ALIGN (0x1000) :
    {
        *(.mboot)
        *(.text)
    }
/* Секция инициализированных данных */
    .data ALIGN (0x1000) :
    {
        *(.data)
    }
/* Секция неинициализированных данных */    

    .bss :
    {
        *(.bss)
    }
/* Место в памяти, где уже нет кода ядра */
    end = .; _end = .; __end = .;  
}


Так вот для начала мы должны указать компоновщику где находится точка входа в код ядра - она задана у нас в коде меткой init. Директива ENTRY (init) предназначена именно для этого.

Далее описываем расположение секций в исполняемом файле, и для начала указываем адрес, начиная с которого будет загружаться код ядра - 0x100000, то есть код располагается за границей первого мегабайта ОЗУ, традиционно доступного процессору из так называемого реального режима. Директиdа . - точка имеет очевидный смысл, кратко формулируемый как "это самое место". Так вот "это самое место", то есть начала кода будет расположено в памяти по адресу 0x100000.

Далее у нас секция кода, обозначенная директивой .text, и первое что мы располагаем в этой секции - заголовок Multiboot, обозначенный меткой .mboot (см. Листинг 2). Потом уже пойдет сам код ядра (метка .text после метки .mboot)

Особое внимание обратим на выравнивание, задаваемое директивой ALIGN (0x1000). Указывая её мы сообщаем компоновщику, что код (или данные) необходимо выровнять по границе блоков размером 0х1000 = 4096 байт, то есть как раз запрашиваемые GRUB2 4 Кб.

Следующей указывается секция инициализированных данных - это используемые нашим ядром константы: директива .data, опять же с выравниванием 0x1000. После нее директивой .bss размещается секция неинициализированных данных (переменные), выравнивать которую уже не требуется.

И наконец последней задаем метку end, которая указывает на место в памяти, расположенное сразу за кодом и данными ядра. Её нужно обязательно указать, она пригодится нам, когда мы будем организовывать страничную адресацию.

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

Теперь озаботимся сборкой ядра, для этого используем утилиту make, автоматизирующую сборку ПО из исходных кодов. Для утилиты make необходимо написать специальный сценарий - Makefile

Листинг 4. Makefile для сборки ядра (файл Makefile)

#------------------------------------------------------
#
#    Правила сборки кода ядра
#
#------------------------------------------------------

# Исходные объектные модули
SOURCES=init.o main.o

# Флаги компилятора языка C
CFLAGS=-nostdlib -nostdinc -fno-builtin -fno-stack-protector -m32 -g

# Флаги компоновщика
LDFLAGS=-T link.ld -m elf_i386

# Флаги ассемблера
ASFLAGS=--32

# Правило сборки
all: $(SOURCES) link

# Правило очистки
clean:
    -rm *.o kernel  

# Правило компоновки
link:
    ld $(LDFLAGS) -o kernel $(SOURCES)

Первым делом указываем какие объектные модули должны быть скомпилированы: переменная SOURCES и файлы init.o и main.o. Эти модули компилятор сформирует на основе имеющихся у нас исходных текстов init.s и make.c, вызвав при этом либо компилятор C, либо ассемблер, в зависимости от расширения исходного файла (*.s - для ассемблера, *.c - для кода на C).

Далее указываются флаги, с которыми будет произведена компиляция С-кода (CFLAGS).

-nostdlib - не используем стандартных библиотек (о Боже!)
-nostdinc - не используем стандартных заголовков (ещё лучше!!!)
-fno-builtin - не использовать встроенных функций
-fno-stack-protector - черт его знает что, но так было указано в источнике, что-то об отсутствии защиты стека.

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

-m32 - говорим компилятору, чтобы строил 32-разрядный код. Особенно актуально для разработки в среде 64-битной системы, как, допустим, у меня.
-g - добавляем отладочную информацию в код. Пригодится чуть позже, собственно при отладке.

В переменной LDFLAGS перечисляются флаги компоновщика

-T link.ld - указываем использовать сценарий компоновки из файла link.ld
-m elf-i386 - указываем что необходимо сформировать 32-разрядный исполняемый файл для процессора Intel 80386.

Переменная ASFLAGS содержит флаги ассемблера:

--32 - указывает ассемблеру создать 32-разрядный объектный модуль.

Далее идет основное правило сборки

all: $(SOURCES) link

что означает "компилировать исходные файлы в объектные модули и передать для сборки компоновщику". Правило link имеет следующий вид

link:
ld $(LDFLAGS) -o kernel $(SOURCES)

и это значит что из объектных файлов будет сформирован исполняемый файл kernel в соответствии с флагами линковки.

Правило очистки выполняется при вводе в терминал команды make clean, и просто удаляет результаты сборки с диска. Периодически это требуется, для полного перестроения кода, так как gcc не собирает заново имеющиеся объектные модули.

Всё! Мы готовы к сборке. Все указанные фалы должны лежать в одном каталоге, например с именем ~/myOSkernel/src. Выполняем в терминале

$ cd ~/myOSkernel/src
$ make

ииии...

as --32  -o init.o init.s
init.s: Assembler messages:
init.s: Warning: конец файла не в конце строки; вставлен символ новой строки
cc -nostdlib -nostdinc -fno-builtin -fno-stack-protector -m32 -g   -c -o main.o main.c
ld -T link.ld -m elf_i386  -o kernel init.o main.o

Да! Наше ядро таки успешно собрано.

$ ls -l
итого 32
-rw-r--r-- 1 maisvendoo users  760 июл 10 17:25 init.o
-rw-r--r-- 1 maisvendoo users  920 июл 10 15:31 init.s
-rwxr-xr-x 1 maisvendoo users 5593 июл 10 17:25 kernel
-rw-r--r-- 1 maisvendoo users  267 июл 10 16:02 link.ld
-rw-r--r-- 1 maisvendoo users  449 июл 10 17:24 main.c
-rw-r--r-- 1 maisvendoo users 1740 июл 10 17:25 main.o
-rw-r--r-- 1 maisvendoo users  454 июл 10 17:24 Makefile

Теперь остается только загрузить его и проверить как оно работает.


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


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


3.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

и...

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

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

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

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

grub> ls


и видим список разделов HDD

10038



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

grub> ls (hd0,1)/boot/


Таки да, лежит, там куда мы его и положили

10039




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

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


и... ничего не произошло, курсор снова мигает

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

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

grub> boot


10041




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

  • Заготовка ядра, с поддержкой мультизагрузки
  • Мы уже находимся в защищенном режиме процессора (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
10042
  и наблюдаем меню GRUB2 с нашей ОС в качестве пункта загрузки :)

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

3.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()...?

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

Maisvendoo (обсуждение) 08:55, июля 23, 2013 (UTC)

Всего: 2

Материалы сообщества доступны в соответствии с условиями лицензии CC-BY-SA , если не указано иное.