UPD. В данный урок были внесены изменения связанные с созданием образа диска для ВМ с ядром и загрузчиком. Теперь содержание статьи соответствует действительности.
Например, если ядро требует загрузки в режиме VESA (что, кстати, является плохим признаком), Вы можете проинформировать об этом загрузчик и он позаботиться о настройке соответствующего окружения.
Чтобы сделать ядро совместимым со стандартом, Вам необходимо добавить куда-нибудь в ядро специальный заголовок (вообще-то он должен располагаться в первых 4 кБ файла ядра).
MBOOT_HEADER_MAGIC — Специальный идентификатор. Показывает, что ядро соответствует требованиям Multiboot Specification.
MBOOT_HEADER_FLAGS — Мы просим GRUB, чтобы он выровнял по границе страницы все секции ядра (MBOOT_PAGE_ALIGN), а также сообщил нам некоторую информацию о памяти (MBOOT_MEM_INFO). В некоторых случаях также использую константу MBOOT_AOUT_KLUDGE, но т.к. мы используем для нашего ядра формат файла ELF, а не a.out, эту опцию указывать не следует.
MBOOT_CHECKSUM — Поле добавлено для контроля ошибок.
mboot — адрес заголовка
code,bss,end,start — все эти символы определены линковщиком. Мы используем их, чтобы сообщить загрузчику куда могут быть размещены различные секции нашего ядра.
При загрузке GRUB помещает указатель на структуру с необходимой нам информацией в регистр EBX. Он может быть использован для доступа к окружению, которое для нас подготовил GRUB.
Все хорошо, но код до сих пор не линкуется.
К примеру вызов функции:
Все просто.
Как Вы можете убедиться, строка
Это наш первый вариант функции kmain(). Как Вы можете видеть она принимает один аргумент - указатель на структуру, подготовленную для нас загрузчиком GRUB. Мы определим ее позже.
Все что делает сейчас наша функция kmain() - это возвращает константу 0xDEADBABA.
Так как мы добавили новый файл в наш проект, мы должны внести изменения в наш Makefile. Отредактируйте следующие строки:
Теперь Вы можете собрать наше ядро и попробовать запустить его. В папке src/ выполним команду:
Если мы загрузимся с этого образа (например в VirtualBox), то увидим стандартное меню загрузчика GRUB.
Отсутствие этого меню означает только одно: где-то была допущена ошибка.
При выборе пункта меню или по истечении времени таймера GRUB загрузит наше ядро в память и передаст ему управление.
Код уже на github.
В следующий раз мы выведем на экран текст лицензии GPL.
2.1. Код для загрузки
Итак, настало время писать код. Хотя основной язык разработки ядра - С, есть некоторые модули, которые просто необходимо написать на языке ассемблера. Один из таких модулей - код инициализации и загрузки ядра.; ; boot.s -- Kernel start location. Also defines multiboot header ; Based on Bran's kernel development tutorial file start.asm ; MBOOT_PAGE_ALIGN equ 1<<0 ; Загрузить ядро и модули по границе страницы MBOOT_MEM_INFO equ 1<<1 ; Запросить от загрузчика информацию о памяти MBOOT_HEADER_MAGIC equ 0x1BADB002 ; Специальный флаг для загрузчика ; NOTE: мы не используем MBOOT_AOUT_KLUDGE. Это означает что GRUB не сообщит ; адрес таблицы символов MBOOT_HEADER_FLAGS equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO MBOOT_CHECKSUM equ -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS) [BITS 32] ; загрузчик сам переводит процессор в защищенный режим [GLOBAL mboot] ; чтобы 'mboot' был доступен из кода на C [EXTERN code] ; начало секции .text [EXTERN bss] ; начало секции .bss [EXTERN end] ; конец последней загружаемой секции mboot: dd MBOOT_HEADER_MAGIC ; GRUB будет искать это значение на каждой ; 4-кБ границе в файле ядра dd MBOOT_HEADER_FLAGS ; Сообщает загрузчику опции загрузки ядра dd MBOOT_CHECKSUM ; Контрольная сумма первых двух полей dd mboot ; адрес текущего дескриптора dd code ; адрес начала секции .text dd bss ; адрес конца секции .data dd end ; адрес конца всех секций dd start ; адрес точки входа [GLOBAL start] ; объявляем метку точки вход глобальной [EXTERN kmain] ; адрес функции main start: push ebx ; загрузить в стек адрес структуры, полученной от загрузчика ; запускаем ядро cli ; запрещаем прерывания call kmain ; вызываем функцию kmain jmp $ ; Бесконечный цикл, чтобы процессор не начал выполнять ; код (мусор), находящийся после кода ядра.
2.2. Разбираемся в коде.
В приведенном выше коде только четыре строки являются, собственно, исполняемым кодом.push ebx cli call kmain jmp $Все остальное - это описание заголовка для загрузчика.
2.2.1. Загрузчик
Загрузчик GRUB соответствует стандарту, описываемому в Multiboot Specification. Наше ядро тоже должно соответствовать этому стандарту, чтобы мы могли переложить часть работы по настройке окружения на загрузчик.Например, если ядро требует загрузки в режиме VESA (что, кстати, является плохим признаком), Вы можете проинформировать об этом загрузчик и он позаботиться о настройке соответствующего окружения.
Чтобы сделать ядро совместимым со стандартом, Вам необходимо добавить куда-нибудь в ядро специальный заголовок (вообще-то он должен располагаться в первых 4 кБ файла ядра).
dd MBOOT_HEADER_MAGIC dd MBOOT_HEADER_FLAGS dd MBOOT_CHECKSUM dd mboot dd code dd bss dd end dd startИспользуемые константы определены выше.
MBOOT_HEADER_MAGIC — Специальный идентификатор. Показывает, что ядро соответствует требованиям Multiboot Specification.
MBOOT_HEADER_FLAGS — Мы просим GRUB, чтобы он выровнял по границе страницы все секции ядра (MBOOT_PAGE_ALIGN), а также сообщил нам некоторую информацию о памяти (MBOOT_MEM_INFO). В некоторых случаях также использую константу MBOOT_AOUT_KLUDGE, но т.к. мы используем для нашего ядра формат файла ELF, а не a.out, эту опцию указывать не следует.
MBOOT_CHECKSUM — Поле добавлено для контроля ошибок.
mboot — адрес заголовка
code,bss,end,start — все эти символы определены линковщиком. Мы используем их, чтобы сообщить загрузчику куда могут быть размещены различные секции нашего ядра.
При загрузке GRUB помещает указатель на структуру с необходимой нам информацией в регистр EBX. Он может быть использован для доступа к окружению, которое для нас подготовил GRUB.
2.2.2. Вернемся к коду
Итак, сразу после загрузки, наш код помещает указатель на структуру, подготовленную загрузчиком, в стек, запрещает прерывания, вызывает функцию 'kmain' (которую мы еще не определили) и уходит в бесконечный цикл.Все хорошо, но код до сих пор не линкуется.
2.3. Добавляем немного кода на C.
Чтобы легко перейти от кода на языке ассемблера к коду на С необходимо знать некоторые правила.- Аргументы в функцию передаются через стек.
- Параметры проталкиваются в стек справа-налево.
- Значение, возвращаемое функцией, помещается в регистр EAX.
К примеру вызов функции:
d = func(a,b,c);на языке ассемблера будет выглядеть так:
push [c] push [b] push [a] call func mov [d],eax
Все просто.
Как Вы можете убедиться, строка
push ebxв коде выше есть ни что иное, как передача значения из EBX как параметра функции kmain().
2.3.1. Код на С.
// main.c -- Defines the C-code kernel entry point, calls initialisation routines. int kmain(struct multiboot *mboot_ptr) { // All our initialisation calls will go in here return 0xDEADBABA; }
Это наш первый вариант функции kmain(). Как Вы можете видеть она принимает один аргумент - указатель на структуру, подготовленную для нас загрузчиком GRUB. Мы определим ее позже.
Все что делает сейчас наша функция kmain() - это возвращает константу 0xDEADBABA.
2.4. Компиляция и сборка проекта.
UPD. В данный параграф были внесены изменения связанные с созданием образа диска для ВМ с ядром и загрузчиком.Так как мы добавили новый файл в наш проект, мы должны внести изменения в наш Makefile. Отредактируйте следующие строки:
SOURCES = boot.o main.o CFLAGS = -nostdlib -nostdinc -fno-builtin -fno-stack-protectorЭти строки нужны, чтобы GCC не пытался собрать наше ядро вместе со стандартной библиотекой C из Вашей *nix-системы.
Теперь Вы можете собрать наше ядро и попробовать запустить его. В папке src/ выполним команду:
$ makeПосле этого у вас в этой папке должен появиться бинарный файл kernel. Этот файл необходимо записать на образ, созданный нами в предыдущем уроке. Способ описан здесь и здесь.
Если мы загрузимся с этого образа (например в VirtualBox), то увидим стандартное меню загрузчика GRUB.
. | |
Меню загрузчика GRUB |
При выборе пункта меню или по истечении времени таймера GRUB загрузит наше ядро в память и передаст ему управление.
Код уже на github.
В следующий раз мы выведем на экран текст лицензии GPL.
Комментариев нет:
Отправить комментарий