Всем здрасте, и сегодня мы начнем наше прохождение через низкоуровневый кодинг - написание ОС. Сегодня мы напишем загрузчик (точнее конфиг к 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)


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

Комментарии (39)


  1. RodionGork
    18.08.2025 07:49

    Сегодня мы напишем загрузчик (точнее конфиг к GRUB) и простенькое ядро, которое будет выводить "Hello OSDev!"

    Занятно :) напомнило как дизассемблировал код вируса на дискетах - в общем-то "хелло Хабр" можно и в этом стартовом секторе просто уместить, безо всякого Grub и "простенького ядра".

    Я не к тому что "писать свою ОС" вредно - но чтобы она была как можно более "своей" там должно быть как можно менее готовых компонент :) Не серчайте за маленькую критику.


    1. SIISII
      18.08.2025 07:49

      А ещё неплохо, чтобы она была таки ОС, а не очередным ХеллоВорлдом прямо из загрузчика :)


      1. denis_iii
        18.08.2025 07:49

        Да, для этого нужен шедулер потоков с запуском, остановкой и переключением контекста.
        Либо из POSIX API либо из ReactOS. Что бы можно было что-то компилировать и минимально запускать.
        ps: ну и сокеты конечно, что бы минимальный interop был.


        1. RodionGork
          18.08.2025 07:49

          необязательно :) однозадачные ОС тоже в истории были...


      1. kaspary Автор
        18.08.2025 07:49

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


  1. SIISII
    18.08.2025 07:49

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

    unsigned char stack[4096] attribute((aligned(16)));

    Не знаю, как на чистых современных сях, а на це++ со стандарта 11 года выравнивание можно (и нужно) описывать стандартными средствами, а не с помощью нестандартных атрибутов -- гарантирует переносимость между различными компиляторами и всё такое.

    ADD. Посмотрел: в сях добавили тоже, но лишь в стандарте 23 года (https://en.cppreference.com/w/c/language/alignas.html):

    struct sse_t { alignas(16) float sse_data[4]; };


  1. Mutar
    18.08.2025 07:49

    И тут мы столкнемся с тем, что использовать stdlib ... нельзя! Потому что мы используем кросс-компилятор и библиотек он таких не знает.

    Какой еще кросс-компилятор?


    1. kaspary Автор
      18.08.2025 07:49

      кросс-компилятор - это такой компилятор, который компилирует не под ОС, на которрой он стоит, а под bare metal (без ОС) определенной архитектуры, например i686, x86_64


      1. DrArgentum
        18.08.2025 07:49

        используй вайб кодинг поменьше :)


        1. kaspary Автор
          18.08.2025 07:49

          ты о чем


      1. Mutar
        18.08.2025 07:49

        Я про то почему из-за компилятора у нас нету доступа к стандартному хедеру libc? Это не так работает! Тем более он не обязательно делает под bare metal.


        1. DrArgentum
          18.08.2025 07:49

          мне приходилось с автором общаться в одном чате. он активно использует LLM. вот поэтому, ему уже указывали на этот факт, но он не исправил


          1. kaspary Автор
            18.08.2025 07:49

            ага, кому вы врете, товарищ


            1. Mutar
              18.08.2025 07:49

              Врёт он или нет, разницы не имеет, ты не ответил на вопрос.


              1. kaspary Автор
                18.08.2025 07:49

                слушайте, какой вопрос?


                1. Mutar
                  18.08.2025 07:49

                1. zeroqxq
                  18.08.2025 07:49

                  Уважаемый пользователь! не путайте пожалуйста комментарии с вашим чатом! Создается впечатление что мы общаемся в чате


                1. Mutar
                  18.08.2025 07:49

                  прошло два часа, может соизволите ответить?!


            1. vi_is_raven
              18.08.2025 07:49

              Кому ТЫ врешь, нейроинженер?


          1. kaspary Автор
            18.08.2025 07:49

            вы кто вообще


            1. DrArgentum
              18.08.2025 07:49

              Я могу дать конкретные ссылки


              1. kaspary Автор
                18.08.2025 07:49

                ссылки не надо, не имеет смысла


                1. DrArgentum
                  18.08.2025 07:49

                  Имеет. Например ты сам признался что загрузчик написал ИИ.


                  1. kaspary Автор
                    18.08.2025 07:49

                    да-да-да, конечно


        1. kaspary Автор
          18.08.2025 07:49

          я честно говоря не знаю про libc.h, и да, необязательно под bare metal, но под архитектуру


  1. zeroqxq
    18.08.2025 07:49

    Честно говоря после прочтения статьи у меня остались очень смешанные чувство. Данный материал наноминает больше не какую либо статью , а скорее замтеки автора. Материал нормально не раскрыт, болтаются пустые куски кода, плоскость, однотипность статьи и негрмотность автора!


    1. kaspary Автор
      18.08.2025 07:49

      ну и начался рейд, о котором я и говорил


  1. daytona13
    18.08.2025 07:49

    Статья вызвала только смех и ничего более.


    1. kaspary Автор
      18.08.2025 07:49

      ну я так и говорил, что рейд будет


      1. DrArgentum
        18.08.2025 07:49

        Это не рейд, а нормальная реакция сообщества. Не надо выдавать себя за эксперта. Ваш код либо украден либо сгенерирован AI.


        1. kaspary Автор
          18.08.2025 07:49

          Ага, вы в чате в Telegram договаривались на меня натравить весь хабр


          1. zeroqxq
            18.08.2025 07:49

            Потому что мы мыслим одинкаово. И мы понимаем происхождение вашего кода. А вы явно что то путаете. Например группу Telegram и Habr


  1. VyacheslavHere
    18.08.2025 07:49

    Очередная статья псевдо-профи с ИИ знаниями. Есть статьи гораздо лучше. Lmao.


    1. kaspary Автор
      18.08.2025 07:49

      Наплыв только созданных аккаунтов продолжается.


  1. zeroqxq
    18.08.2025 07:49

    Я уже писал комментарий к этой статье. Но после прочтения защиты автора от "рейда" мне показалось что кроме скопированного из AI кода автор еще и имеет не имеет чувства ситуации. И продолжает общаться как в чате


  1. vi_is_raven
    18.08.2025 07:49

    Статья объективно не соответствует качеству Хабра и представляет собой мешанину из шизы и нейробреда. Жирнейший минус.


    1. kaspary Автор
      18.08.2025 07:49

      зато работает!


  1. NIK_VIK
    18.08.2025 07:49

    Ого и здесь статья такая появилась, буквально вчера смотрел ролик одного товарища на you tube прям один в один только текст при загрузке другой вписывал он https://youtu.be/-hz1gIIDJMA?si=FFoMuafZZM8G4jps


    1. kaspary Автор
      18.08.2025 07:49

      не знаю честно, у меня текст свой