MULTIPLE: 2 изменения

CHORE: удалено лишнее
ROADMAP: закрыто 2 пункта
This commit is contained in:
msh356 2026-07-01 19:18:37 +03:00
parent d56df0b358
commit 884b58f304
16 changed files with 634 additions and 45 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
**.o
**.bin
**.iso

View file

@ -1,57 +1,73 @@
NASM = nasm
GCC = gcc
CC = gcc
CFLAGS = -m32 -ffreestanding -O2 -Wall -Wextra -Iinclude
AS = nasm
ASFLAGS = -f elf32
LD = ld
GRUB_RESCUE = grub-mkrescue
LDFLAGS = -m elf_i386 -T linker.ld
# Исходники
BOOT_SRC = boot/boot.asm
KERNEL_SRCS = kernel/kmain.c
DRIVER_SRCS = drivers/vga.c
TARGET = iso/boot/kernel.bin
ISO_IMAGE = peredOS.iso
# Автоматически собираем список всех объектных файлов
BOOT_OBJ = boot/boot.o
OBJS = kernel/kmain.o drivers/vga.o
# Жесткий список всех объектов, идущих в линкер
OBJ = boot/boot.o \
kernel/kmain.o \
kernel/gdt.o \
kernel/gdt_asm.o \
kernel/idt.o \
kernel/interrupt.o \
kernel/logger.o \
kernel/io.o \
drivers/serial.o \
drivers/vga.o
ISO_DIR = iso
KERNEL_BIN = $(ISO_DIR)/boot/kernel.bin
ISO_OUT = peredOS.iso
NASM_FLAGS = -f elf32
GCC_FLAGS = -m32 -ffreestanding -O2 -Wall -Wextra -c
LD_FLAGS = -m elf_i386 -T linker.ld
GRUB_FLAGS =
all: $(ISO_OUT)
# Сборка ISO
$(ISO_OUT): $(KERNEL_BIN)
@echo "[+] Крафтим ISO..."
$(GRUB_RESCUE) $(GRUB_FLAGS) -o $(ISO_OUT) $(ISO_DIR)
# Дефолтное правило (просто сборка бинарника)
all: $(TARGET)
@echo "[+] Сборка бинарника успешно завершена!"
# Линковка ядра
$(KERNEL_BIN): $(BOOT_OBJ) $(OBJS)
$(TARGET): $(OBJ)
@echo "[+] Линкуем ядро..."
@mkdir -p $(ISO_DIR)/boot
$(LD) $(LD_FLAGS) $(BOOT_OBJ) $(OBJS) -o $(KERNEL_BIN)
$(LD) $(LDFLAGS) $(OBJ) -o $(TARGET)
# Компиляция ассемблера
$(BOOT_OBJ): $(BOOT_SRC)
$(NASM) $(NASM_FLAGS) $(BOOT_SRC) -o $(BOOT_OBJ)
# Универсальное правило для компиляции Си-файлов в kernel/
# Компиляция Си-файлов ядра
kernel/%.o: kernel/%.c
@echo "[+] Компилируем ядро: $<"
$(GCC) $(GCC_FLAGS) $< -o $@
@echo "[+] Компилируем ядро (C): $<"
$(CC) $(CFLAGS) -c $< -o $@
# Универсальное правило для компиляции Си-файлов в drivers/
# Компиляция ассемблера GDT
kernel/gdt_asm.o: kernel/gdt.asm
@echo "[+] Компилируем gdt.asm..."
$(AS) $(ASFLAGS) kernel/gdt.asm -o kernel/gdt_asm.o
# Компиляция ассемблера IDT
kernel/interrupt.o: kernel/interrupt.asm
@echo "[+] Компилируем interrupt.asm..."
$(AS) $(ASFLAGS) kernel/interrupt.asm -o kernel/interrupt.o
# Компиляция драйверов
drivers/%.o: drivers/%.c
@echo "[+] Компилируем драйвер: $<"
$(GCC) $(GCC_FLAGS) $< -o $@
$(CC) $(CFLAGS) -c $< -o $@
run: all
qemu-system-i386 -cdrom $(ISO_OUT)
# Компиляция загрузочного сектора
boot/boot.o: boot/boot.asm
@echo "[+] Компилируем бутлоадер..."
$(AS) $(ASFLAGS) boot/boot.asm -o boot/boot.o
# Сборка полноценного ISO-образа через GRUB
iso: $(TARGET)
@echo "[+] Собираем загрузочный ISO-образ..."
grub-mkrescue -o $(ISO_IMAGE) iso/
# Честный запуск через ISO в QEMU
run: iso
@echo "[+] Запускаем peredOS в QEMU через CD-ROM..."
qemu-system-i386 -cdrom $(ISO_IMAGE) -serial stdio
# Очистка мусора
clean:
rm -f boot/*.o kernel/*.o drivers/*.o $(KERNEL_BIN) $(ISO_OUT)
@echo "[-] Очистка объектных файлов и образов..."
rm -f boot/*.o kernel/*.o drivers/*.o $(TARGET) $(ISO_IMAGE)
.PHONY: all run clean
.PHONY: all clean run iso

View file

@ -8,7 +8,8 @@
- [x] Оно работает
- [x] Драйвер VGA
- [ ] Прерывания (GDT/IDT)
- [x] Прерывания (GDT/IDT)
- [x] UART отладка
- [ ] Драйвер таймера
- [ ] Драйвер клавиатуры
- [ ] Нормальный драйвер VGA (скролл итд)

27
drivers/serial.c Normal file
View file

@ -0,0 +1,27 @@
#include "../kernel/io.h" // Подключаем наши порты
#define COM1 0x3F8
void serial_init(void) {
outb(COM1 + 1, 0x00);
outb(COM1 + 3, 0x80);
outb(COM1 + 0, 0x03);
outb(COM1 + 1, 0x00);
outb(COM1 + 3, 0x03);
outb(COM1 + 2, 0xC7);
}
int is_transmit_empty(void) {
return inb(COM1 + 5) & 0x20;
}
void serial_putc(char c) {
while (is_transmit_empty() == 0);
outb(COM1, c);
}
void kprint_serial(const char* str) {
for (int i = 0; str[i] != '\0'; i++) {
serial_putc(str[i]);
}
}

16
drivers/serial.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef SERIAL_H
#define SERIAL_H
// Инициализация COM1 порта на скорость 38400 бод, 8N1
void serial_init(void);
// Проверка, пуст ли передающий буфер порта
int is_transmit_empty(void);
// Отправка одного символа в COM1
void serial_putc(char c);
// Вывод строки (логов) в последовательный порт
void kprint_serial(const char* str);
#endif

24
kernel/gdt.asm Normal file
View file

@ -0,0 +1,24 @@
bits 32
global gdt_flush
gdt_flush:
; Берем адрес gdtp, переданный как аргумент функции (лежит в стеке)
mov eax, [esp + 4]
lgdt [eax] ; Загружаем новую GDT
; Обновляем сегменты данных. 0x10 — это смещение Kernel Data в нашей GDT (индекс 2 * 8 = 16 = 0x10)
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; Делаем дальний прыжок, чтобы обновить регистр CS.
; 0x08 — это смещение Kernel Code (индекс 1 * 8 = 8 = 0x08).
; Процессор прыгнет на метку .flush, но уже с новым селектором CS.
jmp 0x08:.flush
.flush:
ret

46
kernel/gdt.c Normal file
View file

@ -0,0 +1,46 @@
#include "gdt.h"
// Наша таблица GDT и указатель на неё
struct gdt_entry gdt[5];
struct gdt_ptr gdtp;
// Ассемблерная функция для загрузки регистра GDTR и обновления сегментов
extern void gdt_flush(uint32_t);
// Внутренняя функция для сборки дескриптора из человеческих параметров
static void gdt_set_gate(int32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran) {
gdt[num].base_low = (base & 0xFFFF);
gdt[num].base_middle = (base >> 16) & 0xFF;
gdt[num].base_high = (base >> 24) & 0xFF;
gdt[num].limit_low = (limit & 0xFFFF);
gdt[num].granularity = (limit >> 16) & 0x0F;
gdt[num].granularity |= gran & 0xF0;
gdt[num].access = access;
}
void gdt_init(void) {
// Настраиваем указатель GDT
gdtp.limit = (sizeof(struct gdt_entry) * 5) - 1;
gdtp.base = (uint32_t)&gdt;
// 1. Нулевой дескриптор (обязателен, всегда нули)
gdt_set_gate(0, 0, 0, 0, 0);
// 2. Сегмент кода ядра (Kernel Code)
// База = 0, Лимит = 0xFFFFF, Доступ = 0x9A, Гранулярность = 0xCF (страничная разметка, 4ГБ)
gdt_set_gate(1, 0, 0xFFFFF, 0x9A, 0xCF);
// 3. Сегмент данных ядра (Kernel Data)
// Всё то же самое, но Доступ = 0x92 (разрешена запись, это сегмент данных)
gdt_set_gate(2, 0, 0xFFFFF, 0x92, 0xCF);
// 4. Юзерский код (Ring 3) — пока заглушка, заюзаем в Части 2
gdt_set_gate(3, 0, 0, 0, 0);
// 5. Юзерские данные (Ring 3) — пока заглушка, заюзаем в Части 2
gdt_set_gate(4, 0, 0, 0, 0);
// Загружаем GDT в процессор
gdt_flush((uint32_t)&gdtp);
}

25
kernel/gdt.h Normal file
View file

@ -0,0 +1,25 @@
#ifndef GDT_H
#define GDT_H
#include <stdint.h>
// Структура одного дескриптора сегмента (8 байт)
struct gdt_entry {
uint16_t limit_low; // Младшие 16 бит лимита
uint16_t base_low; // Младшие 16 бит базы
uint8_t base_middle; // Следующие 8 бит базы
uint8_t access; // Байт доступа (права, кольцо защиты)
uint8_t granularity; // Гранулярность и старшие 4 бита лимита
uint8_t base_high; // Старшие 8 бит базы
} __attribute__((packed));
// Структура указателя для инструкции lgdt (6 байт)
struct gdt_ptr {
uint16_t limit; // Размер всей GDT в байтах минус 1
uint32_t base; // Физический адрес начала массива gdt_entry
} __attribute__((packed));
// Функции для вызова из ядра
void gdt_init(void);
#endif

82
kernel/idt.c Normal file
View file

@ -0,0 +1,82 @@
#include "idt.h"
// Сам массив IDT на 256 векторов и указатель на него
struct idt_entry idt[256];
struct idt_ptr idtp;
// Процессорные исключения (0-21)
extern void isr0(); extern void isr1(); extern void isr2(); extern void isr3();
extern void isr4(); extern void isr5(); extern void isr6(); extern void isr7();
extern void isr8(); extern void isr9(); extern void isr10(); extern void isr11();
extern void isr12(); extern void isr13(); extern void isr14(); extern void isr15();
extern void isr16(); extern void isr17(); extern void isr18(); extern void isr19();
extern void isr20(); extern void isr21();
// Аппаратные прерывания железа (IRQ)
extern void irq0(); extern void irq1();
// Внешняя функция на ассемблере для выполнения 'lidt'
extern void idt_load();
// Заполнение полей одного дескриптора
void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) {
idt[num].base_low = (base & 0xFFFF);
idt[num].base_high = ((base >> 16) & 0xFFFF);
idt[num].sel = sel;
idt[num].always0 = 0;
idt[num].flags = flags;
}
// Главная функция инициализации IDT
void idt_init(void) {
// Настраиваем указатель IDTR
idtp.limit = (sizeof(struct idt_entry) * 256) - 1;
idtp.base = (uint32_t)&idt;
// Зануляем всю таблицу перед использованием
for (int i = 0; i < 256; i++) {
idt_set_gate(i, 0, 0, 0);
}
// 0x08 — Селектор сегмента кода ядра
// 0x8E — Флаги: Present, Ring 0, 32-bit Interrupt Gate
// Пихаем исключения процессора в IDT
idt_set_gate(0, (uint32_t)isr0, 0x08, 0x8E); idt_set_gate(1, (uint32_t)isr1, 0x08, 0x8E);
idt_set_gate(2, (uint32_t)isr2, 0x08, 0x8E); idt_set_gate(3, (uint32_t)isr3, 0x08, 0x8E);
idt_set_gate(4, (uint32_t)isr4, 0x08, 0x8E); idt_set_gate(5, (uint32_t)isr5, 0x08, 0x8E);
idt_set_gate(6, (uint32_t)isr6, 0x08, 0x8E); idt_set_gate(7, (uint32_t)isr7, 0x08, 0x8E);
idt_set_gate(8, (uint32_t)isr8, 0x08, 0x8E); idt_set_gate(9, (uint32_t)isr9, 0x08, 0x8E);
idt_set_gate(10, (uint32_t)isr10, 0x08, 0x8E); idt_set_gate(11, (uint32_t)isr11, 0x08, 0x8E);
idt_set_gate(12, (uint32_t)isr12, 0x08, 0x8E); idt_set_gate(13, (uint32_t)isr13, 0x08, 0x8E);
idt_set_gate(14, (uint32_t)isr14, 0x08, 0x8E); idt_set_gate(15, (uint32_t)isr15, 0x08, 0x8E);
idt_set_gate(16, (uint32_t)isr16, 0x08, 0x8E); idt_set_gate(17, (uint32_t)isr17, 0x08, 0x8E);
idt_set_gate(18, (uint32_t)isr18, 0x08, 0x8E); idt_set_gate(19, (uint32_t)isr19, 0x08, 0x8E);
idt_set_gate(20, (uint32_t)isr20, 0x08, 0x8E); idt_set_gate(21, (uint32_t)isr21, 0x08, 0x8E);
// Вешаем аппаратные прерывания (после перемапливания PIC они будут тут)
idt_set_gate(32, (uint32_t)irq0, 0x08, 0x8E); // Таймер PIT
idt_set_gate(33, (uint32_t)irq1, 0x08, 0x8E); // Клавиатура
// Загружаем IDTR в процессор
idt_load();
}
// Сюда сходятся все исключения процессора (0-31)
void isr_handler(struct registers regs) {
// Пока у нас нет printf, тут будет пусто или заглушка для отладки.
// Когда процессор словит ошибку (например деление на 0), он вызовет эту функцию.
for(;;);
}
// Сюда сходятся все прерывания от железа (32-47)
void irq_handler(struct registers regs) {
// Если прерывание пришло от чипа PIC2 (IRQ 8-15, векторы 40-47), шлем ему EOI (End of Interrupt)
if (regs.int_no >= 40) {
__asm__ __volatile__("outb %%al, %%dx" :: "a"(0x20), "d"(0xA0));
}
// В любом случае шлем EOI главному чипу PIC1 (порты 0x20/0x20)
__asm__ __volatile__("outb %%al, %%dx" :: "a"(0x20), "d"(0x20));
// Сюда в будущем добавим вызовы конкретных драйверов таймера и клавы
}

33
kernel/idt.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef IDT_H
#define IDT_H
#include <stdint.h>
// Структура одного элемента таблицы IDT (8 байт)
struct idt_entry {
uint16_t base_low; // Младшие 16 бит адреса функции-обработчика
uint16_t sel; // Сегментный селектор кода (0x08 — Kernel Code в GDT)
uint8_t always0; // Всегда 0, зарезервировано процессором
uint8_t flags; // Флаги доступа (0x8E для Ring 0 прерываний)
uint16_t base_high; // Старшие 16 бит адреса функции-обработчика
} __attribute__((packed));
// Структура «указателя» для инструкции lidt (6 байт)
struct idt_ptr {
uint16_t limit; // Размер таблицы в байтах минус 1 (256 * 8 - 1)
uint32_t base; // Физический адрес начала массива структур idt_entry
} __attribute__((packed));
// Структура, передаваемая в Си-обработчик, содержащая состояние всех регистров
struct registers {
uint32_t ds; // Сегмент данных
uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // Напушено инструкцией pushad
uint32_t int_no, err_code; // Номер прерывания и код ошибки (если есть)
uint32_t eip, cs, eflags, useresp, ss; // Автоматически пушится процессором
};
// Объявляем функции для вызова извне
void idt_init(void);
void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags);
#endif

145
kernel/interrupt.asm Normal file
View file

@ -0,0 +1,145 @@
bits 32
; Экспортируем функции для Си
global idt_load
global isr0, isr1, isr2, isr3, isr4, isr5, isr6, isr7, isr8, isr9, isr10
global isr11, isr12, isr13, isr14, isr15, isr16, isr17, isr18, isr19, isr20, isr21
global irq0, irq1
; Импортируем Си-обработчики
extern idtp
extern isr_handler
extern irq_handler
; Инструкция загрузки регистра таблицы прерываний
idt_load:
lidt [idtp]
ret
; Макрос для исключений, КОТОРЫЕ НЕ ПУШАТ код ошибки (подставляем ложный 0)
%macro ISR_NOERRCODE 1
idt_stub_isr_%1:
push byte 0 ; Ложный код ошибки
push byte %1 ; Номер прерывания
jmp isr_common_stub
%endmacro
; Макрос для исключений, КОТОРЫЕ САМИ ПУШАТ код ошибки
%macro ISR_ERRCODE 1
idt_stub_isr_%1:
; Процессор уже напушил код ошибки сам
push byte %1 ; Номер прерывания
jmp isr_common_stub
%endmacro
; Макрос для прерываний от железа (IRQ)
%macro IRQ 2
idt_stub_irq_%2:
push byte 0 ; Ложный код ошибки
push byte %1 ; Номер прерывания (вектор в IDT, например 32)
jmp irq_common_stub
%endmacro
; Генерируем заглушки с помощью макросов
ISR_NOERRCODE 0
ISR_NOERRCODE 1
ISR_NOERRCODE 2
ISR_NOERRCODE 3
ISR_NOERRCODE 4
ISR_NOERRCODE 5
ISR_NOERRCODE 6
ISR_NOERRCODE 7
ISR_ERRCODE 8
ISR_NOERRCODE 9
ISR_ERRCODE 10
ISR_ERRCODE 11
ISR_ERRCODE 12
ISR_ERRCODE 13
ISR_ERRCODE 14
ISR_NOERRCODE 15
ISR_NOERRCODE 16
ISR_ERRCODE 17
ISR_NOERRCODE 18
ISR_NOERRCODE 19
ISR_NOERRCODE 20
ISR_ERRCODE 21
IRQ 32, 0 ; IRQ0 - Таймер
IRQ 33, 1 ; IRQ1 - Клавиатура
; Перенаправляем глобальные имена на сгенерированные макросами точки
isr0: jmp idt_stub_isr_0
isr1: jmp idt_stub_isr_1
isr2: jmp idt_stub_isr_2
isr3: jmp idt_stub_isr_3
isr4: jmp idt_stub_isr_4
isr5: jmp idt_stub_isr_5
isr6: jmp idt_stub_isr_6
isr7: jmp idt_stub_isr_7
isr8: jmp idt_stub_isr_8
isr9: jmp idt_stub_isr_9
isr10: jmp idt_stub_isr_10
isr11: jmp idt_stub_isr_11
isr12: jmp idt_stub_isr_12
isr13: jmp idt_stub_isr_13
isr14: jmp idt_stub_isr_14
isr15: jmp idt_stub_isr_15
isr16: jmp idt_stub_isr_16
isr17: jmp idt_stub_isr_17
isr18: jmp idt_stub_isr_18
isr19: jmp idt_stub_isr_19
isr20: jmp idt_stub_isr_20
isr21: jmp idt_stub_isr_21
irq0: jmp idt_stub_irq_0
irq1: jmp idt_stub_irq_1
; Общая точка сборки для Исключений Процессора
isr_common_stub:
pushad ; Пушит edi, esi, ebp, esp, ebx, edx, ecx, eax
mov ax, ds ; Сохраняем сегмент данных
push eax
mov ax, 0x10 ; Загружаем ядерный сегмент данных в регистры
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
call isr_handler
pop eax ; Восстанавливаем оригинальный сегмент данных
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popad ; Восстанавливаем регистры общего назначения
add esp, 8 ; Вычищаем из стека код ошибки и номер прерывания
iretd ; Возвращаемся из прерывания
; Общая точка сборки для Прерываний Железа (IRQ)
irq_common_stub:
pushad
mov ax, ds
push eax
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
call irq_handler
pop eax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popad
add esp, 8
iretd

48
kernel/io.c Normal file
View file

@ -0,0 +1,48 @@
#include <stdint.h>
static inline void outb(uint16_t port, uint8_t val) {
__asm__ __volatile__("outb %0, %1" : : "a"(val), "Nd"(port));
}
// Чтение байта из порта
static inline uint8_t inb(uint16_t port) {
uint8_t ret;
__asm__ __volatile__("inb %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
/* --- ВОРД (16 бит) --- */
// Запись ворда в порт (понадобится для звука AC97 и диска)
static inline void outw(uint16_t port, uint16_t val) {
__asm__ __volatile__("outw %0, %1" : : "a"(val), "Nd"(port));
}
// Чтение ворда из порта
static inline uint16_t inw(uint16_t port) {
uint16_t ret;
__asm__ __volatile__("inw %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
/* --- ДВОЙНОЙ ВОРД (32 бита) --- */
// Запись двойного ворда в порт (для PCI сканера в Части 4)
static inline void outl(uint16_t port, uint32_t val) {
__asm__ __volatile__("outl %0, %1" : : "a"(val), "Nd"(port));
}
// Чтение двойного ворда из порта
static inline uint32_t inl(uint16_t port) {
uint32_t ret;
__asm__ __volatile__("inl %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
/* --- ТОРМОЗ ДЛЯ СТАРЫХ МАТЕРИНОК --- */
// Короткая задержка (io_wait). Нужна при общении с контроллером прерываний PIC,
// чтобы старый чип успел переварить команду. Сбрасываем мусорный байт в неиспользуемый порт 0x80.
static inline void io_wait(void) {
__asm__ __volatile__("outb %%al, $0x80" : : "a"(0));
}

56
kernel/io.h Normal file
View file

@ -0,0 +1,56 @@
#ifndef IO_H
#define IO_H
#include <stdint.h>
/* --- БАЙТ (8 бит) --- */
// Запись байта в порт
static inline void outb(uint16_t port, uint8_t val) {
__asm__ __volatile__("outb %0, %1" : : "a"(val), "Nd"(port));
}
// Чтение байта из порта
static inline uint8_t inb(uint16_t port) {
uint8_t ret;
__asm__ __volatile__("inb %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
/* --- ВОРД (16 бит) --- */
// Запись ворда в порт (понадобится для звука AC97 и диска)
static inline void outw(uint16_t port, uint16_t val) {
__asm__ __volatile__("outw %0, %1" : : "a"(val), "Nd"(port));
}
// Чтение ворда из порта
static inline uint16_t inw(uint16_t port) {
uint16_t ret;
__asm__ __volatile__("inw %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
/* --- ДВОЙНОЙ ВОРД (32 бита) --- */
// Запись двойного ворда в порт (для PCI сканера в Части 4)
static inline void outl(uint16_t port, uint32_t val) {
__asm__ __volatile__("outl %0, %1" : : "a"(val), "Nd"(port));
}
// Чтение двойного ворда из порта
static inline uint32_t inl(uint16_t port) {
uint32_t ret;
__asm__ __volatile__("inl %1, %0" : "=a"(ret) : "Nd"(port));
return ret;
}
/* --- ТОРМОЗ ДЛЯ СТАРЫХ МАТЕРИНОК --- */
// Короткая задержка (io_wait). Нужна при общении с контроллером прерываний PIC,
// чтобы старый чип успел переварить команду. Сбрасываем мусорный байт в неиспользуемый порт 0x80.
static inline void io_wait(void) {
__asm__ __volatile__("outb %%al, $0x80" : : "a"(0));
}
#endif

View file

@ -1,9 +1,19 @@
#include "../drivers/vga.h"
#include "../drivers/serial.h"
#include "gdt.h"
#include "idt.h"
#include "logger.h"
void kernel_main(void) {
log_info(LOG_NORMAL, "Initializing serial...");
serial_init();
log_info(LOG_NORMAL, "Initializing VGA...");
vga_init();
vga_set_color(VGA_COLOR_WHITE, VGA_COLOR_LIGHT_GREEN);
vga_println("--- BOOT PEREDOS ---");
vga_println("---END BOOT PEREDOS---");
log_info(LOG_NORMAL, "Setting color white on black");
vga_set_color(VGA_COLOR_WHITE, VGA_COLOR_BLACK);
log_info(LOG_NORMAL, "Initializing GDT...");
gdt_init();
log_info(LOG_NORMAL, "Initializing IDT...");
idt_init();
while (1);
}

38
kernel/logger.c Normal file
View file

@ -0,0 +1,38 @@
#include "logger.h"
#include "../drivers/serial.h"
#include "../drivers/vga.h"
void logger_init(void) {
serial_init();
log_info(LOG_NORMAL, "Logger ready");
}
void klog(log_level_t level, const char* prefix, const char* message) {
// 1. Форматируем и отправляем в Serial в любом случае
kprint_serial("[");
kprint_serial(prefix);
kprint_serial("] ");
kprint_serial(message);
kprint_serial("\n");
// 2. Если уровень LOG_NORMAL, дублируем вывод на экран VGA
if (level == LOG_NORMAL) {
vga_print("[");
vga_print(prefix);
vga_print("] ");
vga_print(message);
vga_print("\n");
}
}
void log_info(log_level_t level, const char* message) {
klog(level, "INFO", message);
}
void log_warn(log_level_t level, const char* message) {
klog(level, "WARN", message);
}
void log_error(log_level_t level, const char* message) {
klog(level, "ERROR", message);
}

21
kernel/logger.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef LOGGER_H
#define LOGGER_H
// Уровни логирования
typedef enum {
LOG_NORMAL, // И в VGA, и в Serial (для важных событий системы)
LOG_DETAIL // Только в Serial (детальная отладка, дампы регистров и т.д.)
} log_level_t;
// Инициализация подсистемы
void logger_init(void);
// Универсальная функция логирования
void klog(log_level_t level, const char* prefix, const char* message);
// Удобные обёртки для повседневного использования
void log_info(log_level_t level, const char* message);
void log_warn(log_level_t level, const char* message);
void log_error(log_level_t level, const char* message);
#endif