initial commit

This commit is contained in:
msh356 2026-07-19 14:41:16 +03:00
commit 32ced713c3
42 changed files with 1986 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
**.o
**.bin
zalupa.iso

45
Makefile Normal file
View file

@ -0,0 +1,45 @@
# Настройки
CC = gcc
AS = nasm
LD = ld
# Добавь эти флаги в CFLAGS:
CFLAGS = -m32 -ffreestanding -O2 -Wall -Wextra \
-fno-stack-protector -fno-exceptions \
-fno-pie -fno-stack-protector \
-fno-asynchronous-unwind-tables \
-mno-sse -mno-sse2 -mno-sse3 -mno-3dnow -mno-mmx \
-I./src
ASFLAGS = -f elf32
LDFLAGS = -m elf_i386 -T linker.ld -nostdlib
# Файлы
SOURCES = $(wildcard src/*.c)
ASM_SOURCES = $(wildcard src/*.asm)
OBJECTS = src/boot.o $(SOURCES:.c=.o) $(ASM_SOURCES:.asm=.o)
TARGET = kernel.bin
all: $(TARGET) iso
# Сборка ядра
$(TARGET): $(OBJECTS)
$(LD) $(LDFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.asm
$(AS) $(ASFLAGS) $< -o $@
# Сборка ISO
iso: $(TARGET)
mkdir -p iso/boot/
cp $(TARGET) iso/boot/
grub-mkrescue -o zalupa.iso iso/
run: iso
qemu-system-i386 -vga std -cdrom zalupa.iso -serial stdio -monitor none #-d int,cpu_reset -no-reboot -no-shutdown
clean:
rm -f src/*.o $(TARGET) zalupa.iso
rm -rf iso/boot/$(TARGET)

75
README.md Normal file
View file

@ -0,0 +1,75 @@
<img src="assets/logo.png" style="width: 100%; height: auto;">
<h1 align="center">ZalupaCalc 2</h1>
---
ZalupaCalc 2 - это инновационная операционная система, превращающая старые ПК в... калькуляторы. Ваш ПК не будет напрягаться вычислениями, т.к. из ОС выкинуто все, что НЕ НУЖНО калькулятору.
## Роадмап
### 1. Ядро
- [x] Оно работает
- [x] IDT, GDT
- [x] Драйвер UART COM (чтение)
- [x] Драйвер клавиатуры PS/2 (базовый)
- [x] Драйвер клавиатуры PS/2 (нормальный, с Shift, Caps Lock и прочим)
- [x] Драйвер мыши
- [x] Драйвер таймера
- [x] Физ. менеджер памяти
- [x] Вирт. менеджер памяти
- [x] Аллокатор памяти
- [x] PC Speaker
- [x] Куча ядра
### 2. Графика
- [ ] Вывод на экран попиксельно
- [ ] Вывод на экран прямоугольников и прочих фигур
- [ ] Вывод на экран текста
- [ ] Двойная буферизация
- [ ] Вывод курсора мыши на экран, движение
- [ ] Кнопки (touch zones, массив в памяти где будут координаты областей и действия при нажатии)
### 3. Математика
- [ ] Простые операции (число операнд число, базовые операнды)
- [ ] Дроби, float
- [ ] Выражения (более сложные математические операции со скобками и расставлением порядка действий)
- [ ] Больше операндов
- [ ] Инженерные функции (sin, cos и че там еще у крутых математиков)
### 4. Калькулятор
- [ ] Окно
- [ ] Рабочий ввод с клавиатуры в калькулятор
- [ ] Рабочий ввод с мыши в калькулятор (кнопки калькулятора)
### 5. Мелочевка
- [ ] Часы в углу экрана
- [ ] Логотип загрузки
- [ ] Анимации
### 6. Сохранение данных
- [ ] Драйвер ATA PIO (чтение)
- [ ] Драйвер ATA PIO (запись)
- [ ] Парсер MBR
- [ ] Запись в MBR
- [ ] Собственная ФС (скорее хранилище данных) с настройками калькулятора, историей операций итд...
- [ ] Установщик
### 7. Рофляные фукции
- [ ] Напоминания о намазе
- [ ] Офлайн казино
- [ ] Офлайн покер
- [ ] Офлайн блекджек
- [ ] Сапер
- [ ] Пасьянс косынка
## Где что есть что?
- `assets/` - ассеты для README и системы
- `iso/` - корень ISO
- `src/` - там сурсы

BIN
assets/logo.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

7
iso/boot/grub/grub.cfg Normal file
View file

@ -0,0 +1,7 @@
set timeout=0
set default=0
menuentry "ZalupaCalc 2" {
multiboot /boot/kernel.bin
boot
}

32
linker.ld Normal file
View file

@ -0,0 +1,32 @@
ENTRY(_start)
SECTIONS
{
/* Начинаем с 1 МБ */
. = 1M;
.text : AT(ADDR(.text))
{
/* Принудительно размещаем заголовок Multiboot на первой позиции */
KEEP(*(.multiboot))
*(.text)
}
.rodata : ALIGN(4K)
{
*(.rodata)
}
.data : ALIGN(4K)
{
*(.data)
}
.bss : ALIGN(4K)
{
*(COMMON)
*(.bss)
}
_kernel_end = .;
}

45
src/boot.asm Normal file
View file

@ -0,0 +1,45 @@
[bits 32]
section .multiboot
MULTIBOOT_HEADER_MAGIC equ 0x1BADB002
; Бит 0: выравнивание, Бит 1: meminfo, Бит 2: графический запрос
MULTIBOOT_HEADER_FLAGS equ 0x00000007
MULTIBOOT_HEADER_CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
header:
dd MULTIBOOT_HEADER_MAGIC
dd MULTIBOOT_HEADER_FLAGS
dd MULTIBOOT_HEADER_CHECKSUM
; Эти поля обязательны для флага 0x00000004:
dd 0 ; header_addr (0 для ELF)
dd 0 ; load_addr
dd 0 ; load_end_addr
dd 0 ; bss_end_addr
dd 0 ; entry_addr
; Теперь поля графики:
dd 0 ; mode_type (0 = линейный графический режим)
dd 1024 ; width
dd 768 ; height
dd 32 ; depth
section .text
global _start
extern kernel_main
_start:
mov esp, stack_top
push eax ; magic
push ebx ; mbi (здесь будет указатель на структуру с инфой о видео!)
call kernel_main
.hang:
cli
hlt
jmp .hang
section .bss
align 16
stack_bottom:
resb 16384
stack_top:

46
src/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
src/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

24
src/gdt_asm.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

83
src/heap.c Normal file
View file

@ -0,0 +1,83 @@
#include "heap.h"
#include "vmm.h"
#include "pmm.h"
#include "serial.h"
static heap_chunk_t* heap_start_chunk = NULL;
void kheap_init(void) {
kprint_serial("HEAP: Initializing Kernel Heap...\n");
// 1. Выделяем одну физ. страницу под старт кучи
void* phys_page = pmm_alloc_page();
// 2. Мапим её на виртуальный адрес HEAP_START
// Нам нужны флаги Present (1) + Write (2) = 3
vmm_map_page((uint32_t)phys_page, HEAP_START, 3);
// 3. Создаем базовый блок на всю эту страницу
heap_start_chunk = (heap_chunk_t*)HEAP_START;
heap_start_chunk->size = PAGE_SIZE - sizeof(heap_chunk_t);
heap_start_chunk->is_free = 1;
heap_start_chunk->next = NULL;
kprint_serial("HEAP: Kernel Heap initialized successfully at 0x00400000!\n");
}
void* kmalloc(size_t size) {
if (size == 0) return NULL;
// Выравниваем размер по 4 байта для стабильности на x86
size = (size + 3) & ~3;
heap_chunk_t* current = heap_start_chunk;
// Ищем подходящий свободный блок (First Fit)
while (current != NULL) {
if (current->is_free && current->size >= size) {
// Если блок сильно больше, чем надо, дробим его
// sizeof(heap_chunk_t) + 4 байта минимально на остаток
if (current->size >= size + sizeof(heap_chunk_t) + 4) {
heap_chunk_t* new_chunk = (heap_chunk_t*)((uint32_t)current + sizeof(heap_chunk_t) + size);
new_chunk->size = current->size - size - sizeof(heap_chunk_t);
new_chunk->is_free = 1;
new_chunk->next = current->next;
current->size = size;
current->next = new_chunk;
}
current->is_free = 0;
// Возвращаем указатель на память СРАЗУ ЗА заголовком
return (void*)((uint32_t)current + sizeof(heap_chunk_t));
}
current = current->next;
}
// По-хорошему, если памяти не хватило, нужно выделить еще страницу из PMM
// и расширить кучу. Но для первого теста выкинем панику.
kprint_serial("HEAP ERROR: OUT OF KERNEL HEAP MEMORY!\n");
return NULL;
}
void kfree(void* ptr) {
if (ptr == NULL) return;
// Смещаемся назад, чтобы получить доступ к заголовку блока
heap_chunk_t* chunk = (heap_chunk_t*)((uint32_t)ptr - sizeof(heap_chunk_t));
chunk->is_free = 1;
// Склеиваем последовательные свободные блоки (Coalescing), чтобы не было фрагментации
heap_chunk_t* current = heap_start_chunk;
while (current != NULL) {
if (current->is_free && current->next && current->next->is_free) {
current->size += sizeof(heap_chunk_t) + current->next->size;
current->next = current->next->next;
// Не шагаем вперед, проверяем текущий блок еще раз с новым next
continue;
}
current = current->next;
}
}

26
src/heap.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef HEAP_H
#define HEAP_H
#include <stdint.h>
#include <stddef.h>
// Начальный виртуальный адрес кучи ядра (3 МБ)
#define HEAP_START 0x00300000
// Структура заголовка блока памяти
typedef struct heap_chunk {
size_t size; // Размер полезных данных в байтах
uint8_t is_free; // Флаг: 1 - свободен, 0 - занят
struct heap_chunk* next; // Указатель на следующий блок
} heap_chunk_t;
// Инициализация кучи
void kheap_init(void);
// Выделение памяти
void* kmalloc(size_t size);
// Освобождение памяти
void kfree(void* ptr);
#endif

74
src/idt.c Normal file
View file

@ -0,0 +1,74 @@
#include "idt.h"
#include "io.h"
#include "serial.h"
#include "timer.h"
#include "keyboard.h"
#include "mouse.h"
struct idt_entry idt[256];
struct idt_ptr idtp;
extern void* isr_table[]; // Убедись, что это массив указателей на isr0-isr31
extern void irq0(), irq1(), irq12(), idt_load();
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(); extern void isr22(); extern void isr23();
extern void isr24(); extern void isr25(); extern void isr26(); extern void isr27();
extern void isr28(); extern void isr29(); extern void isr30(); extern void isr31();
void* isr_table[] = {
isr0, isr1, isr2, isr3, isr4, isr5, isr6, isr7,
isr8, isr9, isr10, isr11, isr12, isr13, isr14, isr15,
isr16, isr17, isr18, isr19, isr20, isr21, isr22, isr23,
isr24, isr25, isr26, isr27, isr28, isr29, isr30, isr31
};
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;
}
void idt_init(void) {
idtp.limit = (sizeof(struct idt_entry) * 256) - 1;
idtp.base = (uint32_t)&idt;
for (int i = 0; i < 32; i++)
idt_set_gate(i, (uint32_t)isr_table[i], 0x08, 0x8E);
idt_set_gate(32, (uint32_t)irq0, 0x08, 0x8E);
idt_set_gate(33, (uint32_t)irq1, 0x08, 0x8E);
idt_set_gate(44, (uint32_t)irq12, 0x08, 0x8E);
idt_load();
}
void isr_handler(struct registers regs) {
kprint_serial("Exception: ");
kprint_serial_dec(regs.int_no);
kprint_serial("\nSystem Halted.\n");
while(1) asm("hlt");
}
void irq_handler(struct registers regs) {
if (regs.int_no == 32) {
timer_handler();
} else if (regs.int_no == 33) {
keyboard_handler();
} else if (regs.int_no == 44) {
mouse_handler();
} else {
kprint_serial("Unknown IRQ: ");
kprint_serial_dec(regs.int_no);
kprint_serial("\n");
}
if (regs.int_no >= 40) outb(0xA0, 0x20); // Slave
outb(0x20, 0x20); // Master
}

36
src/idt.h Normal file
View file

@ -0,0 +1,36 @@
#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;
// Эти два поля ДОЛЖНЫ идти именно в таком порядке,
// чтобы 32 попало в int_no
uint32_t int_no;
uint32_t 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

5
src/idt_asm.asm Normal file
View file

@ -0,0 +1,5 @@
global idt_load
extern idtp
idt_load:
lidt [idtp]
ret

86
src/interrupt.asm Normal file
View file

@ -0,0 +1,86 @@
; interrupt.asm
[extern isr_handler]
[extern irq_handler]
%macro ISR_NOERR 1
global isr%1
isr%1:
cli
push 0
push %1
jmp common_isr_stub
%endmacro
%macro ISR_ERR 1
global isr%1
isr%1:
cli
push %1
jmp common_isr_stub
%endmacro
; --- Исключения (0-31) ---
%assign i 0
%rep 32
%if i == 8 || (i >= 10 && i <= 14) || i == 17
ISR_ERR i
%else
ISR_NOERR i
%endif
%assign i i+1
%endrep
; --- Аппаратные прерывания (IRQ -> Векторы 32, 33, 44) ---
%macro IRQ 2
global irq%1
irq%1:
cli
push 0
push %2
jmp common_irq_stub
%endmacro
IRQ 0, 32
IRQ 1, 33
IRQ 12, 44
common_isr_stub:
pusha
mov ax, ds
push eax
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp
call isr_handler
pop eax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popa
add esp, 8
iret
common_irq_stub:
pusha
mov ax, ds
push eax
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp
call irq_handler
pop eax
pop eax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
popa
add esp, 8
iret

48
src/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
src/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

121
src/keyboard.c Normal file
View file

@ -0,0 +1,121 @@
#include "keyboard.h"
#include "serial.h"
#include "io.h"
// Состояние модификаторов клавиатуры
static int is_e0 = 0;
static int shift_pressed = 0;
static int caps_lock_active = 0;
// Скан-коды управления (Set 1)
#define SCAN_LSHIFT 0x2A
#define SCAN_RSHIFT 0x36
#define SCAN_CAPS_LOCK 0x3A
// Скан-коды стрелок (после 0xE0)
#define SCAN_UP 0x48
#define SCAN_DOWN 0x50
#define SCAN_LEFT 0x4B
#define SCAN_RIGHT 0x4D
// Таблица 1: Обычные символы (без Shift)
static const char kbdus_lower[128] = {
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
'\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0,
'\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*', 0,
' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, '-', 0, 0, 0, '+', 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
// Таблица 2: Символы при зажатом Shift
static const char kbdus_upper[128] = {
0, 27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
'\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
0, 'A', 's', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', 0,
'|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, '*', 0,
' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, '_', 0, 0, 0, '+', 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
void keyboard_init(void) {
shift_pressed = 0;
caps_lock_active = 0;
is_e0 = 0;
kprint_serial("[INFO] Full keyboard driver with Modifiers initialized.\n");
}
void keyboard_handler(void) {
uint8_t status = inb(0x64);
// Бит 5 равен 1, если данные пришли от мыши (Auxiliary Device)
// Мышь не должна обрабатываться здесь!
if (status & 0x20) {
// Очистим порт, чтобы не висел, но не обрабатываем как клаву
inb(0x60);
return;
}
// Если мы здесь, значит это точно клавиатура
if (!(status & 0x01)) return; // Нет данных
uint8_t scancode = inb(0x60);
// Обработка префикса 0xE0
if (scancode == 0xE0) {
is_e0 = 1;
return;
}
// Обработка Break Code (отпускание)
if (scancode & 0x80) {
uint8_t released_code = scancode & 0x7F;
if (released_code == SCAN_LSHIFT || released_code == SCAN_RSHIFT) {
shift_pressed = 0;
}
is_e0 = 0; // Сброс префикса
return;
}
// Обработка стрелок (расширенные)
if (is_e0) {
is_e0 = 0;
switch (scancode) {
case SCAN_UP: /* ... */ break;
case SCAN_DOWN: /* ... */ break;
case SCAN_LEFT: /* ... */ break;
case SCAN_RIGHT: /* ... */ break;
}
return;
}
// Обычные модификаторы
if (scancode == SCAN_LSHIFT || scancode == SCAN_RSHIFT) {
shift_pressed = 1;
return;
}
if (scancode == SCAN_CAPS_LOCK) {
caps_lock_active = !caps_lock_active;
return;
}
// Печать символов
if (scancode < 128) {
char c = 0;
int use_upper = shift_pressed;
char base_char = kbdus_lower[scancode];
int is_alpha = (base_char >= 'a' && base_char <= 'z');
if (is_alpha && caps_lock_active) use_upper = !use_upper;
c = (use_upper) ? kbdus_upper[scancode] : kbdus_lower[scancode];
if (c != 0) {
char str[2] = {c, 0};
kprint_serial(str);
}
}
}

10
src/keyboard.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef KEYBOARD_H
#define KEYBOARD_H
// Инициализация (пока просто выведем лог, что всё ок)
void keyboard_init(void);
// Основной обработчик прерывания, который мы вызовем из idt.c
void keyboard_handler(void);
#endif

171
src/kmain.c Normal file
View file

@ -0,0 +1,171 @@
#include "serial.h"
#include "mouse.h"
#include "timer.h"
#include "speaker.h"
#include "gdt.h"
#include "idt.h"
#include "heap.h"
#include "pmm.h"
#include "vmm.h"
#include "pic.h"
#include "logger.h"
#include "string.h"
#include "multiboot.h"
extern uint32_t _kernel_end; // Символ 'end' прописан в конце любого стандартного linker.ld
uint32_t kernel_end = (uint32_t)&_kernel_end;
void kernel_main(uint32_t mboot_addr, uint32_t magic) {
log_info(LOG_NORMAL, "Initializing serial...");
serial_init();
if (magic != MULTIBOOT_BOOTLOADER_MAGIC) {
log_error(LOG_NORMAL, "fuck, i got invalid multiboot magic. IS THAT REALLY THE MULTIBOOT YOU TALKED ABOUT?");
while(1);
}
struct multiboot_info* mbi = (struct multiboot_info*)mboot_addr;
if (!(mbi->flags & (1 << 6))) {
log_error(LOG_NORMAL, "fuck, i haven't got a memory map. IS THAT REALLY THE MULTIBOOT YOU TALKED ABOUT?");
while(1);
}
log_info(LOG_NORMAL, "Initializing GDT...");
gdt_init();
log_info(LOG_NORMAL, "Initializing IDT...");
idt_init();
log_info(LOG_NORMAL, "Remapping PIC...");
pic_remap(32, 40);
log_info(LOG_NORMAL, "Unmasking IRQ 0... (timer)");
pic_clear_mask(0);
log_info(LOG_NORMAL, "Unmasking IRQ 1... (keyboard)");
pic_clear_mask(1);
log_info(LOG_NORMAL, "Unmasking IRQ 2... (slave pic)");
pic_clear_mask(2);
log_info(LOG_NORMAL, "Unmasking IRQ 12... (mouse)");
pic_clear_mask(12);
log_info(LOG_NORMAL, "Registering timer at 100Hz");
timer_init(100);
log_info(LOG_NORMAL, "Registering mouse");
mouse_init();
kprint_serial("--------------------\n");
log_info(LOG_NORMAL, "Enabling PMM...");
pmm_init(mbi, (uint32_t)&_kernel_end);
kprint_serial("--- Memory Map From GRUB ---");
struct multiboot_mmap_entry* mmap = (struct multiboot_mmap_entry*)mbi->mmap_addr;
while((uint32_t)mmap < mbi->mmap_addr + mbi->mmap_length) {
char addr_buf[32];
char len_buf[32];
itoa((uint32_t)mmap->addr, addr_buf, 16);
itoa((uint32_t)mmap->len, len_buf, 10);
kprint_serial("Region: Base=0x");
kprint_serial(addr_buf);
kprint_serial(" Size=");
kprint_serial(len_buf);
if (mmap->type == MULTIBOOT_MEMORY_AVAILABLE) {
kprint_serial(" [FREE RAM]\n");
} else {
kprint_serial(" [RESERVED]\n");
}
// Переходим к следующей записи (учитываем поле size)
mmap = (struct multiboot_mmap_entry*)((uint32_t)mmap + mmap->size + sizeof(mmap->size));
}
kprint_serial("--- trying to allocate some fucking memory... ---\n");
kprint_serial("page 1... ");
void* p1 = pmm_alloc_page();
kprint_serial("page 2...");
void* p2 = pmm_alloc_page();
kprint_serial("ok!");
char buf[32];
kprint_serial("PMM TEST: Page 1 allocated at 0x");
itoa((uint32_t)p1, buf, 16);
kprint_serial(buf);
kprint_serial("\n");
kprint_serial("PMM TEST: Page 2 allocated at 0x");
itoa((uint32_t)p2, buf, 16);
kprint_serial(buf);
kprint_serial("\n");
kprint_serial("freeing the pages back... page 1... ");
pmm_free_page(p1);
kprint_serial("page 2... ");
pmm_free_page(p2);
kprint_serial("it don't died!\n");
kprint_serial("\n--- PMM MEMORY STATS ---\n");
pmm_get_stats(); // Просто запускаем сбор, она запишет всё в глобальную переменную
// Берем указатель на глобальную переменную
pmm_stats_t* my_stats = pmm_get_stats_ptr();
kprint_serial("Total Memory: ");
itoa(my_stats->total_memory >> 20, buf, 10);
kprint_serial(buf);
kprint_serial(" MB\n");
kprint_serial("Used Memory: ");
itoa(my_stats->used_memory >> 10, buf, 10);
kprint_serial(buf);
kprint_serial(" KB\n");
kprint_serial("Free Memory: ");
itoa(my_stats->free_memory >> 20, buf, 10);
kprint_serial(buf);
kprint_serial(" MB\n");
kprint_serial("------------------------\n\n");
log_info(LOG_NORMAL, "Enabling VMM...");
vmm_init();
log_info(LOG_NORMAL, "Enabling KHeap...");
kheap_init();
kprint_serial("--- HEAP TESTING ---\n");
// Выделяем три разных куска памяти
int* arr1 = (int*)kmalloc(10 * sizeof(int));
char* str = (char*)kmalloc(64);
int* arr2 = (int*)kmalloc(5 * sizeof(int));
char hbuf[32];
kprint_serial("Allocated arr1 at virtual address: 0x");
itoa((uint32_t)arr1, hbuf, 16);
kprint_serial(hbuf);
kprint_serial("\n");
kprint_serial("Allocated str at virtual address: 0x");
itoa((uint32_t)str, hbuf, 16);
kprint_serial(hbuf);
kprint_serial("\n");
// Запишем туда данные, чтобы проверить, что маппинг страниц не падает при записи
arr1[0] = 1337;
str[0] = 'H';
str[1] = 'A';
str[2] = 'I';
str[3] = '\0';
kprint_serial("Data integrity check: str is ");
kprint_serial(str);
kprint_serial("\n");
// Освобождаем память обратно
kfree(arr1);
kfree(str);
kfree(arr2);
kprint_serial("Kfree done. It don't died twice!\n");
log_info(LOG_NORMAL, "Enabling IRQ...");
__asm__ __volatile__("sti");
speaker_beep(100, 10);
speaker_beep(125, 10);
speaker_beep(150, 10);
speaker_beep(175, 10);
speaker_beep(200, 10);
speaker_beep(225, 10);
speaker_beep(250, 10);
speaker_beep(275, 10);
speaker_beep(300, 10);
speaker_beep(325, 10);
speaker_beep(350, 400);
log_info(LOG_NORMAL, "OK!");
while (1);
}

27
src/logger.c Normal file
View file

@ -0,0 +1,27 @@
#include "logger.h"
#include "serial.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(prefix);
kprint_serial(": ");
kprint_serial(message);
kprint_serial("\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
src/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

128
src/mouse.c Normal file
View file

@ -0,0 +1,128 @@
#include "mouse.h"
#include "io.h"
#include "string.h"
#include "serial.h" // Для kprint_serial
#define PS2_DATA 0x60
#define PS2_STATUS 0x64
#define PS2_COMMAND 0x64
static void ps2_wait_write(void) {
while (inb(PS2_STATUS) & 0x02);
}
static void ps2_wait_read(void) {
while (!(inb(PS2_STATUS) & 0x01));
}
static void mouse_write(uint8_t data) {
ps2_wait_write();
outb(PS2_COMMAND, 0xD4); // Сигнал: шлем команду именно мыши
ps2_wait_write();
outb(PS2_DATA, data);
}
static uint8_t mouse_read(void) {
ps2_wait_read();
return inb(PS2_DATA);
}
void mouse_init(void) {
uint8_t status;
kprint_serial("Initializing PS/2 Mouse...\n");
// Включаем второй порт PS/2 (мышь)
ps2_wait_write();
outb(PS2_COMMAND, 0xA8);
io_wait();
// Читаем Command Byte, чтобы включить прерывания
ps2_wait_write();
outb(PS2_COMMAND, 0x20);
ps2_wait_read();
status = inb(PS2_DATA) | 2; // Бит 1 — прерывания IRQ12 от мыши
status &= ~0x20; // Сбрасываем Бит 5 — активируем Mouse Clock
// Записываем Command Byte обратно
ps2_wait_write();
outb(PS2_COMMAND, 0x60);
ps2_wait_write();
outb(PS2_DATA, status);
// Настраиваем саму мышь
mouse_write(0xF6); // Загрузить дефолтные параметры
mouse_read(); // Читаем ACK (0xFA)
mouse_write(0xF4); // Включаем передачу пакетов (Data Reporting)
mouse_read(); // Читаем ACK (0xFA)
kprint_serial("Mouse initialized successfully.\n");
}
static uint8_t mouse_cycle = 0;
static uint8_t mouse_packet[3];
static int mouse_x = 40; // Стартуем по центру текстового экрана 80x25
static int mouse_y = 12;
void mouse_handler(void) {
uint8_t status = inb(PS2_STATUS);
// Проверяем, что в буфере реально есть данные и они от мыши (бит 5 выставлен)
if (!(status & 0x01) || !(status & 0x20)) {
return;
}
// ДЕБАГ: выводим каждый байт
kprint_serial("M_BYTE: ");
kprint_serial_hex(status);
kprint_serial("\n");
mouse_packet[mouse_cycle++] = inb(PS2_DATA);
if (mouse_cycle == 3) {
mouse_cycle = 0;
// Бит 3 первого байта всегда должен быть равен 1 (проверка синхронизации)
if (!(mouse_packet[0] & 0x08)) {
return;
}
uint8_t flags = mouse_packet[0];
int offset_x = (int)mouse_packet[1];
int offset_y = (int)mouse_packet[2];
// Обработка знаков смещения (8 бит -> 32 бита с сохранением знака)
if (flags & 0x10) offset_x |= 0xFFFFFF00;
if (flags & 0x20) offset_y |= 0xFFFFFF00;
mouse_x += offset_x;
mouse_y -= offset_y; // Инвертируем Y, так как в VGA координатах 0 верх
// Ограничиваем рамками консоли 80x25
if (mouse_x < 0) mouse_x = 0;
if (mouse_x >= 80) mouse_x = 79;
if (mouse_y < 0) mouse_y = 0;
if (mouse_y >= 25) mouse_y = 24;
// Чек кнопок
int left_click = flags & 0x01;
int right_click = flags & 0x02;
// Выводим инфу в UART через itoa
char buf[16];
kprint_serial("Mouse: X=");
itoa(mouse_x, buf, 10);
kprint_serial(buf);
kprint_serial(" Y=");
itoa(mouse_y, buf, 10);
kprint_serial(buf);
if (left_click) kprint_serial(" [LEFT]");
if (right_click) kprint_serial(" [RIGHT]");
kprint_serial("\n");
}
}

9
src/mouse.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef MOUSE_H
#define MOUSE_H
#include <stdint.h>
void mouse_init(void);
void mouse_handler(void);
#endif

47
src/multiboot.h Normal file
View file

@ -0,0 +1,47 @@
#ifndef MULTIBOOT_H
#define MULTIBOOT_H
#include <stdint.h>
#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002
struct multiboot_mmap_entry {
uint32_t size;
uint64_t addr;
uint64_t len;
#define MULTIBOOT_MEMORY_AVAILABLE 1
#define MULTIBOOT_MEMORY_RESERVED 2
#define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE 3
#define MULTIBOOT_MEMORY_NVS 4
#define MULTIBOOT_MEMORY_BADRAM 5
uint32_t type;
} __attribute__((packed));
struct multiboot_info {
uint32_t flags;
uint32_t mem_lower;
uint32_t mem_upper;
uint32_t boot_device;
uint32_t cmdline;
uint32_t mods_count;
uint32_t mods_addr;
uint32_t num;
uint32_t size;
uint32_t addr;
uint32_t shndx;
uint32_t mmap_length;
uint32_t mmap_addr; // <-- Вот это нам нужно! Адрес карты памяти
uint32_t drives_length;
uint32_t drives_addr;
uint32_t config_table;
uint32_t boot_loader_name;
uint32_t apm_table;
uint32_t vbe_control_info;
uint32_t vbe_mode_info;
uint16_t vbe_mode;
uint16_t vbe_interface_seg;
uint16_t vbe_interface_off;
uint16_t vbe_interface_len;
} __attribute__((packed));
#endif

76
src/pic.c Normal file
View file

@ -0,0 +1,76 @@
#include "pic.h"
#include "io.h"
#include "logger.h"
#define PIC1_COMMAND 0x20
#define PIC1_DATA 0x21
#define PIC2_COMMAND 0xA0
#define PIC2_DATA 0xA1
#define ICW1_INIT 0x10 // Инициализация
#define ICW1_IC4 0x01 // Будет ICW4
#define ICW4_8086 0x01 // Режим 8086/8186
void pic_remap(uint8_t master_offset, uint8_t slave_offset) {
log_info(LOG_DETAIL, "Remapping PIC...");
// Сохраняем текущие маски прерываний (какое железо сейчас отключено)
uint8_t a1 = inb(PIC1_DATA);
uint8_t a2 = inb(PIC2_DATA);
// Свисток к началу инициализации (ICW1)
outb(PIC1_COMMAND, ICW1_INIT | ICW1_IC4);
io_wait();
outb(PIC2_COMMAND, ICW1_INIT | ICW1_IC4);
io_wait();
// Задаем новые смещения векторов (ICW2)
outb(PIC1_DATA, master_offset); // Master IRQ 0-7 -> вешаем на master_offset (32)
io_wait();
outb(PIC2_DATA, slave_offset); // Slave IRQ 8-15 -> вешаем на slave_offset (40)
io_wait();
// Связываем Master и Slave между собой (ICW3)
outb(PIC1_DATA, 4); // Сообщаем Master, что Slave сидит на IRQ2 (00000100б = 4)
io_wait();
outb(PIC2_DATA, 2); // Сообщаем Slave его каскадный ID (2)
io_wait();
// Включаем режим 8086 (ICW4)
outb(PIC1_DATA, ICW4_8086);
io_wait();
outb(PIC2_DATA, ICW4_8086);
io_wait();
// Восстанавливаем сохраненные маски
outb(PIC1_DATA, a1);
outb(PIC2_DATA, a2);
log_info(LOG_DETAIL, "PIC remapped");
}
// Запретить прерывание от конкретного IRQ
void pic_set_mask(uint8_t irq_line) {
uint16_t port;
if (irq_line < 8) {
port = PIC1_DATA;
} else {
port = PIC2_DATA;
irq_line -= 8;
}
uint8_t value = inb(port) | (1 << irq_line);
outb(port, value);
}
// Разрешить прерывание от конкретного IRQ
void pic_clear_mask(uint8_t irq_line) {
uint16_t port;
if (irq_line < 8) {
port = PIC1_DATA;
} else {
port = PIC2_DATA;
irq_line -= 8;
}
uint8_t value = inb(port) & ~(1 << irq_line);
outb(port, value);
}

13
src/pic.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef PIC_H
#define PIC_H
#include <stdint.h>
// Перемапливание базовых векторов прерываний для Master и Slave PIC
void pic_remap(uint8_t master_offset, uint8_t slave_offset);
// Маскирование/размаскирование конкретной линии IRQ
void pic_set_mask(uint8_t irq_line);
void pic_clear_mask(uint8_t irq_line);
#endif

119
src/pmm.c Normal file
View file

@ -0,0 +1,119 @@
#include "pmm.h"
#include "string.h"
#include "logger.h"
#include "serial.h"
// Указатель на начало нашей битовой карты в памяти
static uint32_t* pmm_bitmap = NULL;
static uint32_t pmm_bitmap_size = 0;
static uint32_t total_pages = 0;
static pmm_stats_t global_stats;
// Вспомогательные инлайны для быстрой работы с битами ядра
static inline void bitmap_set(uint32_t page_index) {
pmm_bitmap[page_index / 32] |= (1 << (page_index % 32));
}
static inline void bitmap_clear(uint32_t page_index) {
pmm_bitmap[page_index / 32] &= ~(1 << (page_index % 32));
}
static inline int bitmap_test(uint32_t page_index) {
return (pmm_bitmap[page_index / 32] & (1 << (page_index % 32))) != 0;
}
void pmm_init(struct multiboot_info* mbi, uint32_t kernel_end) {
// Рассчитываем менеджер на 128 МБ памяти
uint32_t mem_size = 128 * 1024 * 1024;
total_pages = mem_size / PAGE_SIZE;
// Саму карту пихаем прямо за концом ядра (куда указывает линкер)
pmm_bitmap = (uint32_t*)kernel_end;
pmm_bitmap_size = total_pages / 8; // Размер карты в байтах (4096 байт)
uint32_t bitmap_end = kernel_end + pmm_bitmap_size;
// Шаг 1: Помечаем вообще всё как занятое (1). Безопасность превыше всего.
memset(pmm_bitmap, 0xFF, pmm_bitmap_size);
// Шаг 2: Парсим карту памяти от GRUB и открываем доступные регионы
struct multiboot_mmap_entry* mmap = (struct multiboot_mmap_entry*)mbi->mmap_addr;
while((uint32_t)mmap < mbi->mmap_addr + mbi->mmap_length) {
// Если GRUB говорит, что этот кусок RAM свободен для юзания
if (mmap->type == MULTIBOOT_MEMORY_AVAILABLE) {
uint32_t start_addr = (uint32_t)mmap->addr;
uint32_t end_addr = start_addr + (uint32_t)mmap->len;
for (uint32_t addr = start_addr; addr < end_addr; addr += PAGE_SIZE) {
uint32_t page_idx = addr / PAGE_SIZE;
if (page_idx < total_pages) {
bitmap_clear(page_idx); // Ставим 0 (страница свободна)
}
}
}
// Шагаем к следующей записи в таблице Multiboot
mmap = (struct multiboot_mmap_entry*)((uint32_t)mmap + mmap->size + sizeof(mmap->size));
}
// Шаг 3: Жестко защищаем саму ОС!
// Всё, что лежит от физического адреса 0x0 до bitmap_end (код ядра, стек, сама битовая карта),
// мы принудительно забиваем единицами, чтобы аллокатор туда никогда не залез.
for (uint32_t addr = 0; addr < bitmap_end; addr += PAGE_SIZE) {
bitmap_set(addr / PAGE_SIZE);
}
log_info(LOG_NORMAL, "pmm works.");
}
void* pmm_alloc_page(void) {
// Тупо линейно ищем первый попавшийся бит, равный 0
for (uint32_t i = 0; i < total_pages; i++) {
if (!bitmap_test(i)) {
bitmap_set(i); // Застолбили страницу (поставили 1)
return (void*)(i * PAGE_SIZE); // Считаем её реальный физический адрес
}
}
log_error(LOG_NORMAL, "PMM: FUCK, WHERE IS ALL OUR FUCKING MEMORY?!");
return NULL;
}
void pmm_free_page(void* addr) {
uint32_t page_idx = ((uint32_t)addr) / PAGE_SIZE;
if (page_idx < total_pages) {
bitmap_clear(page_idx); // Сбросили в 0, страница снова чиста
}
}
void pmm_get_stats(void) {
kprint_serial("Someone wants memory stats! Running the test!\n");
uint32_t used = 0;
uint32_t free = 0;
for (uint32_t i = 0; i < total_pages; i++) {
if (bitmap_test(i)) {
used++;
} else {
free++;
}
}
kprint_serial("Okay, i am collected the stats. ");
// Пишем в ГЛОБАЛЬНУЮ переменную
global_stats.total_pages = total_pages;
global_stats.used_pages = used;
global_stats.free_pages = free;
global_stats.total_memory = total_pages << 12;
global_stats.used_memory = used << 12;
global_stats.free_memory = free << 12;
kprint_serial("Goodbye!\n");
}
// Добавим функцию-геттер, которая просто вернет указатель на эту глобальную память
pmm_stats_t* pmm_get_stats_ptr(void) {
return &global_stats;
}

30
src/pmm.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef PMM_H
#define PMM_H
#include <stdint.h>
#include "multiboot.h"
#define PAGE_SIZE 4096
// Инициализация: передаем инфу от GRUB и адрес конца ядра из линкера
void pmm_init(struct multiboot_info* mbi, uint32_t kernel_end);
// Выделить одну страницу памяти (возвращает физический адрес)
void* pmm_alloc_page(void);
// Освободить страницу памяти по адресу
void pmm_free_page(void* addr);
typedef struct {
uint32_t total_memory; // Всего памяти в байтах
uint32_t used_memory; // Занято в байтах
uint32_t free_memory; // Свободно в байтах
uint32_t total_pages; // Всего страниц
uint32_t used_pages; // Занято страниц
uint32_t free_pages; // Свободно страниц
} pmm_stats_t;
void pmm_get_stats(void);
pmm_stats_t* pmm_get_stats_ptr(void);
#endif

60
src/serial.c Normal file
View file

@ -0,0 +1,60 @@
#include "io.h" // Подключаем наши порты
#include <stdint.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]);
}
}
void kprint_serial_hex(uint32_t num) {
const char hex_chars[] = "0123456789ABCDEF";
char buffer[11]; // "0x" + 8 символов + '\0'
buffer[0] = '0';
buffer[1] = 'x';
buffer[10] = '\0';
// Заполняем с конца (8 символов)
for (int i = 9; i >= 2; i--) {
buffer[i] = hex_chars[num & 0xF];
num >>= 4;
}
kprint_serial(buffer);
}
void kprint_serial_dec(uint32_t num) {
if (num == 0) {
kprint_serial("0");
return;
}
char buf[12];
int i = 10;
buf[11] = '\0';
while (num > 0) {
buf[i--] = (num % 10) + '0';
num /= 10;
}
kprint_serial(&buf[i + 1]);
}

20
src/serial.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef SERIAL_H
#define SERIAL_H
#include <stdint.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);
void kprint_serial_hex(uint32_t num);
void kprint_serial_dec(uint32_t num);
#endif

48
src/speaker.c Normal file
View file

@ -0,0 +1,48 @@
#include "speaker.h"
#include "timer.h" // Теперь здесь виден extern uint32_t system_ticks
#include "io.h"
void sleep_ms(uint32_t ms) {
// 1 тик = 10 мс на частоте 100 Гц
uint32_t ticks_to_wait = ms / 10;
if (ticks_to_wait == 0) ticks_to_wait = 1; // Защита от слишком маленького интервала
uint32_t start_ticks = system_ticks;
// Ждем, пока разница тиков не достигнет нужного значения
while ((system_ticks - start_ticks) < ticks_to_wait) {
// Аппаратно засыпаем до следующего прерывания PIT
__asm__ __volatile__("hlt");
}
}
void speaker_play(uint32_t frequency) {
if (frequency == 0) return;
uint32_t divisor = 1193182 / frequency;
// Настраиваем Канал 2 PIT (порт 0x43, режим 0xB6)
outb(0x43, 0xB6);
// Передаем делитель в порт данных Канала 2 (0x42)
outb(0x42, (uint8_t)(divisor & 0xFF));
outb(0x42, (uint8_t)((divisor >> 8) & 0xFF));
// Подключаем PIT к динамику через порт управления системными функциями
uint8_t speaker_state = inb(0x61);
if ((speaker_state & 3) != 3) {
outb(0x61, speaker_state | 3);
}
}
void speaker_stop(void) {
// Отключаем динамик, сбрасывая 0 и 1 биты
uint8_t speaker_state = inb(0x61) & 0xFC;
outb(0x61, speaker_state);
}
void speaker_beep(uint32_t frequency, uint32_t duration_ms) {
speaker_play(frequency);
sleep_ms(duration_ms); // Наш халяльный sleep по прерываниям
speaker_stop();
}

15
src/speaker.h Normal file
View file

@ -0,0 +1,15 @@
#ifndef SPEAKER_H
#define SPEAKER_H
#include <stdint.h>
// Включить звук заданной частоты (в Герцах)
void speaker_play(uint32_t frequency);
// Выключить звук
void speaker_stop(void);
// Издать короткий писк (бип) заданной частоты и длительности
void speaker_beep(uint32_t frequency, uint32_t duration);
#endif

183
src/string.c Normal file
View file

@ -0,0 +1,183 @@
#include "string.h"
// Заполнить кусок памяти определенным байтом (очень нужно для очистки буферов)
void* memset(void* dest, int val, size_t count) {
uint8_t* temp = (uint8_t*)dest;
for (size_t i = 0; i < count; i++) {
temp[i] = (uint8_t)val;
}
return dest;
}
// Скопировать кусок памяти (будем использовать для скроллинга VGA экрана)
void* memcpy(void* dest, const void* src, size_t count) {
uint8_t* d = (uint8_t*)dest;
const uint8_t* s = (const uint8_t*)src;
for (size_t i = 0; i < count; i++) {
d[i] = s[i];
}
return dest;
}
// Скопировать кусок памяти с учетом возможного пересечения областей
void* memmove(void* dest, const void* src, size_t count) {
uint8_t* d = (uint8_t*)dest;
const uint8_t* s = (const uint8_t*)src;
if (d < s) {
for (size_t i = 0; i < count; i++) {
d[i] = s[i];
}
} else {
for (size_t i = count; i > 0; i--) {
d[i - 1] = s[i - 1];
}
}
return dest;
}
// Сравнение блоков памяти
int memcmp(const void* s1, const void* s2, size_t count) {
const uint8_t* p1 = (const uint8_t*)s1;
const uint8_t* p2 = (const uint8_t*)s2;
for (size_t i = 0; i < count; i++) {
if (p1[i] != p2[i]) {
return p1[i] - p2[i];
}
}
return 0;
}
// Длина строки
size_t strlen(const char* str) {
size_t len = 0;
while (str[len] != '\0') {
len++;
}
return len;
}
// Копирование строки
char* strcpy(char* dest, const char* src) {
size_t i = 0;
while ((dest[i] = src[i]) != '\0') {
i++;
}
return dest;
}
// Конкатенация строк
char* strcat(char* dest, const char* src) {
size_t dest_len = strlen(dest);
size_t i = 0;
while ((dest[dest_len + i] = src[i]) != '\0') {
i++;
}
return dest;
}
// Сравнение строк (понадобится, когда будем парсить команды в терминале)
int strcmp(const char* s1, const char* s2) {
while (*s1 && (*s1 == *s2)) {
s1++;
s2++;
}
return *(const unsigned char*)s1 - *(const unsigned char*)s2;
}
// Строка в число
int atoi(const char* str) {
int res = 0;
int sign = 1;
int i = 0;
// Пропускаем пробелы, если они есть
while (str[i] == ' ' || str[i] == '\t') {
i++;
}
// Учитываем знак
if (str[i] == '-') {
sign = -1;
i++;
} else if (str[i] == '+') {
i++;
}
// Считаем само число
while (str[i] >= '0' && str[i] <= '9') {
res = res * 10 + (str[i] - '0');
i++;
}
return sign * res;
}
// Число в строку (поддерживает base = 10 и base = 2)
char* itoa(int value, char* str, int base) {
int i = 0;
int is_negative = 0;
// Защита от дурака
if (base < 2 || base > 36) {
str[0] = '\0';
return str;
}
// Обрабатываем 0
if (value == 0) {
str[i++] = '0';
str[i] = '\0';
return str;
}
// Отрицательные числа обрабатываем только для десятичной системы
if (value < 0 && base == 10) {
is_negative = 1;
value = -value;
}
// Переводим число в строку (но она получится задом наперед)
while (value != 0) {
int rem = value % base;
str[i++] = (rem > 9) ? (rem - 10) + 'a' : rem + '0';
value = value / base;
}
// Добавляем минус
if (is_negative) {
str[i++] = '-';
}
str[i] = '\0';
// Переворачиваем строку в нормальный вид
int start = 0;
int end = i - 1;
while (start < end) {
char temp = str[start];
str[start] = str[end];
str[end] = temp;
start++;
end--;
}
return str;
}
// Число в красивый Hex вид (например, 255 -> "0x000000ff")
char* itox(uint32_t value, char* str) {
char* hex_digits = "0123456789abcdef";
str[0] = '0';
str[1] = 'x';
str[10] = '\0'; // 2 символа на "0x" + 8 символов на 32-битное число
// Заполняем с конца
for (int i = 9; i >= 2; i--) {
str[i] = hex_digits[value & 0xF];
value >>= 4;
}
return str;
}

28
src/string.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef STRING_H
#define STRING_H
#include <stddef.h>
#include <stdint.h>
// Работа с памятью
void* memset(void* dest, int val, size_t count);
void* memcpy(void* dest, const void* src, size_t count);
void* memmove(void* dest, const void* src, size_t count);
int memcmp(const void* s1, const void* s2, size_t count);
// Работа со строками
size_t strlen(const char* str);
char* strcpy(char* dest, const char* src);
char* strcat(char* dest, const char* src);
int strcmp(const char* s1, const char* s2);
// Перевод строки в число
int atoi(const char* str);
// Перевод числа в строку (десятичную)
char* itoa(int value, char* str, int base);
// Специальный быстрый аналог для Hex (16-ричной системы), незаменим для дампов памяти
char* itox(uint32_t value, char* str);
#endif

27
src/timer.c Normal file
View file

@ -0,0 +1,27 @@
#include "timer.h"
#include "io.h"
#include "serial.h"
#include "logger.h"
// Глобальный счетчик тиков системы
uint32_t system_ticks = 0;
void timer_init(uint32_t frequency) {
log_info(LOG_NORMAL, "Initializing PIT Timer...");
// Вычисляем делитель частоты
uint32_t divisor = 1193182 / frequency;
// Шлем команду: Канал 0, Режим 3 (генератор меандра), доступ по очереди Младший/Старший байт
outb(0x43, 0x36);
// Раскалываем 16-битный делитель на два байта и шлем в порт данных Канала 0 (0x40)
outb(0x40, (uint8_t)(divisor & 0xFF));
outb(0x40, (uint8_t)((divisor >> 8) & 0xFF));
log_info(LOG_NORMAL, "Timer registered");
}
void timer_handler(void) {
system_ticks++;
}

12
src/timer.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef TIMER_H
#define TIMER_H
#include <stdint.h>
// Делаем счетчик видимым для всего ядра
extern uint32_t system_ticks;
void timer_init(uint32_t frequency);
void timer_handler(void);
#endif

75
src/vmm.c Normal file
View file

@ -0,0 +1,75 @@
#include "vmm.h"
#include "pmm.h"
#include "serial.h"
// 1. Обязательное выравнивание для CR3 и таблиц страниц
uint32_t page_directory[1024] __attribute__((aligned(4096)));
uint32_t first_page_table[1024] __attribute__((aligned(4096)));
extern void vmm_enable_paging(uint32_t* page_directory);
void vmm_init(void) {
kprint_serial("VMM: Initializing paging...\n");
// 2. Очищаем каталог (ставим Not Present)
for (int i = 0; i < 1024; i++) {
page_directory[i] = 0x00000002; // Read/Write, Not Present
}
// 3. Identity Map для первых 4 МБ
// i * 4096 дает физический адрес страницы
for (uint32_t i = 0; i < 1024; i++) {
first_page_table[i] = (i * 0x1000) | 0x3; // Present + RW
}
// 4. Указываем каталогу на таблицу
// (uint32_t)first_page_table — это уже физический адрес,
// если ядро по 1МБ и мы не используем сложные смещения.
page_directory[0] = ((uint32_t)first_page_table) | 0x3;
kprint_serial("DEBUG: page_directory is at: 0x");
kprint_serial_hex((uint32_t)page_directory);
kprint_serial("\n");
kprint_serial("DEBUG: page_directory[0] contains: 0x");
kprint_serial_hex(page_directory[0]);
kprint_serial("\n");
kprint_serial("VMM: Paging configured. Enabling CR0...\n");
// 5. Включаем
vmm_enable_paging(page_directory);
kprint_serial("VMM: FUCK, IT WORKS!\n");
}
void vmm_map_page(uint32_t phys, uint32_t virt, uint32_t flags) {
// Выковыриваем индексы из виртуального адреса
uint32_t pd_idx = virt >> 22; // Старшие 10 бит — индекс каталога
uint32_t pt_idx = (virt >> 12) & 0x3FF; // Средние 10 бит — индекс таблицы
if (!(page_directory[pd_idx] & 1)) {
// 1. Выделяем физическую страницу под НОВУЮ таблицу
uint32_t phys_table = (uint32_t)pmm_alloc_page();
// 2. Ставим её в каталог (Present + Write)
page_directory[pd_idx] = (phys_table & 0xFFFFF000) | 3;
// 3. Очищаем новую таблицу (важно!)
uint32_t* new_table = (uint32_t*)(page_directory[pd_idx] & 0xFFFFF000);
for(int i=0; i<1024; i++) new_table[i] = 0;
}
// Достаем адрес таблицы страниц из каталога (обнуляем нижние 12 бит флагов)
uint32_t* table = (uint32_t*)(page_directory[pd_idx] & 0xFFFFF000);
// Запихиваем физический адрес и флаги в таблицу страниц
// PAGE_SIZE у нас 4096, поэтому phys должен быть выровнен (нижние 12 бит затрутся флагами)
table[pt_idx] = (phys & 0xFFFFF000) | flags;
// ВАЖНО: Процессор кэширует таблицы в TLB (Translation Lookaside Buffer).
// Если мы изменили маппинг существующей страницы, процессор об этом не узнает.
// Нужно сбросить кэш для этого адреса инструкцией invlpg:
__asm__ __volatile__("invlpg (%0)" : : "r"(virt) : "memory");
}

17
src/vmm.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef VMM_H
#define VMM_H
#include <stdint.h>
// Элемент каталога/таблицы страниц
#define PAGE_PRESENT 0x1 // Страница есть в памяти
#define PAGE_WRITE 0x2 // Можно писать (Read/Write)
#define PAGE_USER 0x4 // Доступно из Ring 3 (User mode)
// Инициализация страничного обмена
void vmm_init(void);
// Отобразить виртуальный адрес на физический
void vmm_map_page(uint32_t phys, uint32_t virt, uint32_t flags);
#endif

13
src/vmm_asm.asm Normal file
View file

@ -0,0 +1,13 @@
global vmm_enable_paging
vmm_enable_paging:
mov eax, [esp+4] ; Получили аргумент (phys_directory)
mov cr3, eax ; Загрузили CR3
mov eax, cr0
or eax, 0x80000000 ; Включили пейджинг
mov cr0, eax
; ВОТ ЭТО ОБЯЗАТЕЛЬНО:
jmp 0x08:.flush ; Far jump сбрасывает пайплайн CPU
.flush:
ret