Низкоуровневый АД: пишем свою ОС — Часть 1. Загрузчик и стартовое ядро

от автора

Всем здрасте, и сегодня мы начнем наше прохождение через низкоуровневый кодинг — написание ОС. Сегодня мы напишем загрузчик (точнее конфиг к GRUB) и простенькое ядро, которое будет выводить «Hello OSDev!»

Что нам понадобится:

  • Linux (у меня Kali Linux 2025.1a)

  • i686-elf-gcc и i686-elf-ld (тык)

  • qemu-system-i386

  • nasm

  • grub-mkrescue

Шаг 1. Структура папок

Создадим несколько папок:

mkdir boot #тут будет лежать скрипт для линковки mkdir bin #тут - готовые бинарники mkdir kernel #само ядро mkdir iso #здесь будем собирать ISO mkdir iso/boot #файл ядра mkdir iso/boot/grub #тут конфиги GRUB

Шаг 2. Загрузчик

Тут все просто — мы будем использовать GRUB. Но нам будет нужно написать к нему конфиг, чтобы он смог загружать именно наш загрузчик. Он будет лежать в iso/boot/grub .Конфиг простой:

set timeout=0 set default=0  menuentry "Habr OS" {     multiboot /boot/kernel.bin }

в 4 строчке "Habr OS" — название нашей ОС, которая будет отображаться в меню выбора GRUB. Можете выбрать его сами (Но берите ТОЛЬКО английские буквы)

А теперь и сам наш загрузчик

; boot/loader.s [org 0x7C00] [bits 16]  KERNEL_OFFSET equ 0x1000 ; Адрес ядра в памяти (4 КБ после загрузчика)  start:     cli     mov ax, 0     mov ds, ax     mov es, ax     mov ss, ax     mov sp, 0x7C00     sti      call load_kernel     call enable_pm     jmp 0x08:KERNEL_OFFSET ; 0x08 = селектор кода из GDT  ; Загружаем ядро с диска (секторы 2-16) load_kernel:     mov bx, KERNEL_OFFSET     mov ah, 0x02      ; BIOS: read sectors     mov al, 15        ; Читаем 15 секторов (7.5 КБ)     mov ch, 0         ; Cylinder 0     mov cl, 2         ; Sector 2 (после загрузчика)     mov dh, 0         ; Head 0     int 0x13     jc .error     ret .error:     jmp $  ; Переход в защищённый режим enable_pm:     lgdt [gdt_descriptor]     mov eax, cr0     or eax, 1     mov cr0, eax     ret  ; GDT (минимальная, без ошибок) gdt:     dq 0x0                         ; Null descriptor code_segment:     dw 0xFFFF                      ; Limit (low 16 bits)     dw 0x0000                      ; Base (low 16 bits)     db 0x00                        ; Base (middle 8 bits)     db 10011010b                   ; Flags: code, 32-bit     db 11001111b                   ; Flags: limit (high 4 bits), granularity     db 0x00                        ; Base (high 8 bits) data_segment:     dw 0xFFFF     dw 0x0000     db 0x00     db 10010010b                   ; Flags: data     db 11001111b     db 0x00  gdt_descriptor:     dw gdt_descriptor - gdt - 1    ; Size of GDT     dd gdt                         ; Address of GDT  times 510 - ($ - $$) db 0 dw 0xAA55 

В нем мы в основном указываем GDT

Шаг 3. Ядро

Мы подошли к самому интересному — ядру. Создадим в папке kernel файл kernel.c. И тут мы столкнемся с тем, что использовать stdlib … нельзя! Потому что мы используем кросс-компилятор и библиотек он таких не знает. И тем более для каждой ОС она своя.

Давайте опишем сначала все для Multiboot (то есть для GRUB)

#define MULTIBOOT_MAGIC 0x1BADB002 #define MULTIBOOT_FLAGS 0  typedef unsigned int u32  typedef struct {     u32 magic;     u32 flags;     u32 checksum; } __attribute__((packed)) multiboot_header_t;  multiboot_header_t multiboot_header = {     MULTIBOOT_MAGIC,     MULTIBOOT_FLAGS,     -(MULTIBOOT_MAGIC + MULTIBOOT_FLAGS) };

Для GRUB — макросы с флагами и адресом загрузки после GRUB и структура с этими данными

Дальше — стек

unsigned char stack[4096] __attribute__((aligned(16))); unsigned char* stack_top = stack + sizeof(stack);

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

Далее у нас идет основная функция ядра — _start()

void _start() {     __asm__ volatile ("mov %0, %%esp" : : "r" (stack_top));      char* video_memory = (char*)0xB8000;     const char* message = "Hello OSDev!                                                                    And hello Habr.ru!";     for (int i = 0; message[i] != '\0'; i++) {         video_memory[i * 2] = message[i];         video_memory[i * 2 + 1] = 0x07;     }     while(1); }

Мы пишем через ассемблеровую вставку, что куча свободной памяти начинается с того места, где закончился стек. Далее — создаем переменную с адресом VGA-видеопамяти, создаем переменную с сообщением… Стоп. Зачем так много пробелов?? Это для переноса строки, ведь \n, \t и \v и т. п. НЕ работают в консоли (пока что). В цикле for мы выводим символ, причем всегда четный элемент видеопамяти (вкл. 0) — сам символ, а нечетным — его цвет (0x07 — светло-серый на черном). А цикл while позволяет не уйти процессору в дали дальние и выйти из памяти ядра.

Шаг 4. Сборка, запуск

Ах да, мы забыли про linker.ld и сборочный скрипт! Вот linker.ld (он в boot лежит):

ENTRY(_start)  OUTPUT_FORMAT(elf32-i386) OUTPUT_ARCH(i386)  SECTIONS {     . = 0x1000;      .text : {         *(.text._start)         *(.text*)     }      .rodata : {         *(.rodata*)     }      .data : {         *(.data)     }      .bss : {         *(COMMON)         *(.bss)     } } 

Тут отмечается точка старта программы.

Ну а теперь сборка! Для этого я использую спец. скрипт build.sh. Вот его внутренности:

nasm -f bin boot/loader.s -o bin/loader.bin && i686-elf-gcc -c kernel/kernel.c -o bin/kernel.o -ffreestanding -m32 && i686-elf-ld -T boot/linker.ld -o bin/kernel.bin bin/kernel.o && cat bin/loader.bin bin/kernel.bin > bin/os.bin && cp bin/kernel.bin iso/boot/kernel.bin && grub-mkrescue -o bin/os.iso iso && echo "build succsess" 

Первым делом мы собираем наш пользовательский загрузчик. Далее мы собираем ядро для bare metal и без подключения sdtlib. Дальше линкуем пользовательский загрузчик с ядром, копируем в папку где генрируется ISO и генерируем сам ISO. Дальше можно уже засунуть его в QEMU (команда qemu-system-i386 -cdrom bin/os.iso -machine pc -d int -D qemu.log) и посмотреть на те самые строчки, которые мы ждали…

Мы сделали это!

Мы сделали это!

Ну а далее на нашу ОС есть планы:
1. Реализуем ввод
2. Сделаем простые команды (типа shutdown, echo)


Спасибо что прочитали статью! Буду писать продолжение


ссылка на оригинал статьи https://habr.com/ru/articles/938104/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *