Full Code of thedenisnikulin/os-project for AI

master 33f060c4b5b9 cached
66 files
133.5 KB
43.6k tokens
42 symbols
1 requests
Download .txt
Showing preview only (204K chars total). Download the full file or copy to clipboard to get everything.
Repository: thedenisnikulin/os-project
Branch: master
Commit: 33f060c4b5b9
Files: 66
Total size: 133.5 KB

Directory structure:
gitextract_w89a63ku/

├── .bashrc
├── .gitignore
├── README.md
├── guide/
│   ├── 00-BOOT-SECTOR/
│   │   ├── ex00/
│   │   │   ├── README.md
│   │   │   └── main.asm
│   │   ├── ex01/
│   │   │   └── main.asm
│   │   ├── ex02/
│   │   │   ├── main.asm
│   │   │   └── org_demo.asm
│   │   ├── ex03/
│   │   │   └── main.asm
│   │   ├── ex04/
│   │   │   ├── main.asm
│   │   │   └── print_string.asm
│   │   ├── ex05/
│   │   │   ├── main.asm
│   │   │   └── print_hex.asm
│   │   ├── ex06/
│   │   │   └── main.asm
│   │   ├── ex07/
│   │   │   ├── disk_load.asm
│   │   │   └── main.asm
│   │   └── ex08/
│   │       ├── gdt.asm
│   │       ├── main.asm
│   │       ├── print_string_pm.asm
│   │       └── switch.asm
│   └── 01-KERNEL/
│       ├── ex00/
│       │   ├── BUILD.md
│       │   ├── README.md
│       │   ├── boot/
│       │   │   ├── bootsect.asm
│       │   │   ├── disk_load.asm
│       │   │   ├── gdt.asm
│       │   │   ├── kernel_entry.asm
│       │   │   ├── print_hex.asm
│       │   │   ├── print_string.asm
│       │   │   ├── print_string_pm.asm
│       │   │   └── switch.asm
│       │   ├── build/
│       │   │   └── Makefile
│       │   └── kernel/
│       │       └── kernel.c
│       └── ex01/
│           ├── boot/
│           │   ├── bootsect.asm
│           │   ├── disk_load.asm
│           │   ├── gdt.asm
│           │   ├── kernel_entry.asm
│           │   ├── print_hex.asm
│           │   ├── print_string.asm
│           │   ├── print_string_pm.asm
│           │   └── switch.asm
│           ├── build/
│           │   └── Makefile
│           ├── common.c
│           ├── common.h
│           ├── drivers/
│           │   ├── lowlevel_io.c
│           │   ├── lowlevel_io.h
│           │   ├── screen.c
│           │   └── screen.h
│           └── kernel/
│               └── kernel.c
└── src/
    ├── boot/
    │   ├── bootsect.asm
    │   ├── disk_load.asm
    │   ├── gdt.asm
    │   ├── kernel_entry.asm
    │   ├── print_hex.asm
    │   ├── print_string.asm
    │   ├── print_string_pm.asm
    │   └── switch.asm
    ├── build/
    │   └── Makefile
    ├── common.c
    ├── common.h
    ├── drivers/
    │   ├── lowlevel_io.c
    │   ├── lowlevel_io.h
    │   ├── screen.c
    │   └── screen.h
    └── kernel/
        ├── kernel.c
        ├── utils.c
        └── utils.h

================================================
FILE CONTENTS
================================================

================================================
FILE: .bashrc
================================================
function emu() {    # compile and run emulator
    nasm $1.asm -f bin -o temp.bin
	qemu-system-x86_64 temp.bin
}


================================================
FILE: .gitignore
================================================
*.bin
*.raw

# -- automatically generated --

# Prerequisites
*.d

# Object files
*.o
*.ko
*.obj
*.elf

# Linker output
*.ilk
*.map
*.exp

# Precompiled Headers
*.gch
*.pch

# Libraries
*.lib
*.a
*.la
*.lo

# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib

# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

# Debug files
*.dSYM/
*.su
*.idb
*.pdb

# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf


================================================
FILE: README.md
================================================
# os-project
>Пишем свою собственную операционную систему с нуля!

Идея написать ОС возникла у меня в процессе поиска идеи для сайд-проекта. Это исключительно хобби-проект, не рассчитанный на серьезность и достоверность, и хотя я пытался объяснить многие новые и неочевидные концепты, с которыми я столкнулся в процессе разработки, я мог что-то упустить, так как я сам только учусь - именно поэтому я настоятельно рекомендую пользоваться гуглом и любыми другими источниками информации когда вы познакомитесь с чем-то новым в гайде. Гуглите абсолютно всё. Я серьезно.

**Prerequisites: **Для комфортного прохождения гайда нужно уметь программировать на языке Си на базовом уровне (одно из обязательных требований: понимать принципы работы с указателями), иметь опыт разработки на высокоуровневых ЯП. С синтаксисом ассемблера можно ознакомиться по ссылке ниже, но все же рекомендую побольше почитать или посмотреть по нему туториалов.

## Навигация по репозиторию
<ins>**`guide/`**</ins> --- гайд с последовательными уроками, теорией и задокументированным кодом
* Гайд разделен на главы, например `00-BOOT-SECTOR`
* Главы разделены на упражнения, например `ex00`
* Упражнения содержат в себе код и теорию. Выглядят как `main.asm`

<ins>**`src/`**</ins> --- исходный код ОС

## Установка и запуск

1. Установить эмулятор QEMU (подробнее: https://www.qemu.org/download/)
```
sudo apt install qemu-kvm qemu
```
2. Собрать кросс-компилятор gcc для i386 архитектуры процессора. Удобнее использовать готовый отсюда: https://wiki.osdev.org/GCC_Cross-Compiler#Prebuilt_Toolchains. Для компьютеров на Linux с x86_64 архитектурой:
```
wget http://newos.org/toolchains/i386-elf-4.9.1-Linux-x86_64.tar.xz
mkdir /usr/local/i386elfgcc
tar -xf i386-elf-4.9.1-Linux-x86_64.tar.xz -C /usr/local/i386elfgcc --strip-components=1
export PATH=$PATH:/usr/local/i386elfgcc/bin
```
3. Клонировать и собрать проект
```
git clone https://github.com/thedenisnikulin/os-project
cd os-project/src/build
make
```
4. Запустить образ ОС с помощью эмулятора
```
qemu-system-i386 -fda os-image.bin
```

## Справочник по синтаксису ассемблера NASM
https://www.opennet.ru/docs/RUS/nasm/nasm_ru3.html


---
## Дополнительная информация
Ссылки на полезный материал которым я пользовался в качестве теории.
> На русском языке:
- Серия статей о ядре Linux и его внутреннем устройстве: https://github.com/proninyaroslav/linux-insides-ru
- Статья "Давай напишем ядро!": https://xakep.ru/2018/06/18/lets-write-a-kernel/
> На английском языке:
- Небольшая книга по разработке собственной ОС (70 страниц): https://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf
- Общее введене в разработку операционных систем: https://wiki.osdev.org/Getting_Started
- Туториал по разработке ядра операционной системы для 32-bit x86 архитектуры. Первые шаги в создании собсвтенной ОС: https://wiki.osdev.org/Bare_Bones
- Продолжение предыдущего туториала: https://wiki.osdev.org/Meaty_Skeleton
- Про загрузку ОС (booting): https://wiki.osdev.org/Boot_Sequence
- Список туториалов по написанию ядра и модулей к ОС: https://wiki.osdev.org/Tutorials
- Внушительных размеров гайд по разработке ОС с нуля: http://www.brokenthorn.com/Resources/OSDevIndex.html
- Книга, описывающая ОС xv6 (не особо вникал, но должно быть что-то годное): https://github.com/mit-pdos/xv6-riscv-book, сама ОС: https://github.com/mit-pdos/xv6-public
- "Небольшая книга о разработке операционных систем" https://littleosbook.github.io/
- Операционная система от 0 до 1 (книга): https://github.com/tuhdo/os01
- ОС, написанная как пример для предыдущей книги: https://github.com/tuhdo/sample-os
- Интересная статья про программирование модулей для Линукса и про системное программирование https://jvns.ca/blog/2014/09/18/you-can-be-a-kernel-hacker/
- Еще одна статья от автора предыдущей https://jvns.ca/blog/2014/01/04/4-paths-to-being-a-kernel-hacker/
- Пример простого модуля к ядру линукса: https://github.com/jvns/kernel-module-fun/blob/master/hello.c
- Еще один туториал о том, как написать ОС с нуля: https://github.com/cfenollosa/os-tutorial
- Статья "Давайте напишем ядро": https://arjunsreedharan.org/post/82710718100/kernels-101-lets-write-a-kernel
- Сабреддит по разработке ОС: https://www.reddit.com/r/osdev/
- Большой список идей для проектов для разных ЯП, включая C/C++: https://github.com/tuvtran/project-based-learning/blob/master/README.md
- Еще один список идей для проектов https://github.com/danistefanovic/build-your-own-x
- "Давайте напишем ядро с поддержкой ввода с клавиатуры и экрана": https://arjunsreedharan.org/post/99370248137/kernel-201-lets-write-a-kernel-with-keyboard-and 


================================================
FILE: guide/00-BOOT-SECTOR/ex00/README.md
================================================
# Интро

Когда мы включаем компьютер, он должен каким-то образом загрузить операционную систему. Он может загрузить ее с дискеты, с флэшки, с жесткого диска или с каих-либо других носителей.
Среда, которую дает нам компьютер вне ОС, может предоставить нам не так уж и много. Например, мы имеем BIOS (англ. Basic Input/Output Software), набор програм которые изначально загружены в памят и инициализированы как только компьютер включается. БИОС предоставляет базовый контроль над важными девайсами компьютера: экран, клавиатура, жесткий диск.
Чтобы БИОСу загрузить ОС, ему нужно узнать, есть ли ОС на определенном носителе. Для этого он читает первые 512 байтов носителя которые называются загрузочным сектором (англ. boot sector) и если они кончаются магическим числов 0xaa55,то он загружает код бут сектора в память, а процессор его исполняет.


================================================
FILE: guide/00-BOOT-SECTOR/ex00/main.asm
================================================
; ------------------------------------------------------------------------------
; Guide: 	00-BOOT-SECTOR
; File:		ex00 / main.asm
; Title:	Простая программа загрузочного сектора (boot sector), которая
;			запускает бесконечный цикл.
; ------------------------------------------------------------------------------
; Description:
;	Чтобы дать понять BIOS, что на текущей флэшке, компакт диске или жестком
;	диске расположена ОС, на этом носителе должен быть загрузочный сектор. BIOS
;	узнает загрузочный сектор по "магическому числу" из 2-х байт, равное 0xaa55
;	(в шестнадцатеричной системе исчисления). Простейший загрузочный сектор в
;	машинном коде выглядит так:
;
; 		e9 fd ff 00 00 00 00 00 00 00 00 00 00 00 00 00
; 		00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
;	   *
; 		00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa
;
;	Как видим, последние 2 байта действительно являются "магическим числом".
;	Первые 3 байта являются машинными инструкциями, которые запускают
;	бесконечный цикл, а остальное просто заполнено нулями т.к. наша программа
;	должна быть размером ровно 512 байт.
;
;	Чтобы перевести наш файл boot_sect.asm в машинный код, нужно написать:
;	nasm boot_sect.asm -f bin -o boot_sect.bin
;	Попробуйте запустить boot_sect.bin с помощью эмулятора (например, qemu), и
;	вы увидите "Booting from Hard Disk...", хотя если вы измените "магическое
;	число", соберете файл boot_sect.bin заново и запустите с помощью эмулятора,
;	то BIOS попытается загрузить ОС, но в итоге напишет "No bootable device".
;	А значит, у нас получилось создать загрузочный сектор!
; ------------------------------------------------------------------------------

						; Бесконечный цикл:
loop:					; Определяем метку "loop"

	jmp loop			; Инструкция "jmp" позволяет нам перейти к метке
						; "loop" (англ. jump - прыжок), т.е. создается
						; бесконечный цикл.

times 510-($-$$) db 0	; Инструкция "times" заставляет идущую после нее
						; команду выполняться опредленное количество раз, т.е.
						; times <кол-во раз> <команда>.
						; Токен $ высчитывает позицию начала текущей строки,
						; токен $$ - позицию начала текущей секции.
						; 510-($-$$) = 510-(2-0) = 508 байт. Чтобы лучше понять
						; как это работает, рекомендую посмотреть как программа
						; выглядит в машинном коде с помощью утилиты ghex.
						; Таким образом мы заполняем пространство нулями (db 0),
						; приводя нас к 510-му байту.
						; db расшифровывается как "declare bytes", т.е.
						; "объявить байты"

dw 0xaa55				; В последние два байта кладем "магическое число",
						; чтобы BIOS знал, что это загрузочный сектор


================================================
FILE: guide/00-BOOT-SECTOR/ex01/main.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex01 / main.asm
; Title:	Простая программа загрузочного сектора (boot sector), которая 
;			выводит "Hello" на экран, используя рутину BIOS'а 
; ------------------------------------------------------------------------------
; Description:
;	Чтобы вывести символ на экран, мы воспользуемся "scrolling tele-type BIOS
;	routine", то есть специальной рутиной BIOS, которая выводит символ на экран
;	и перемещает курсор, чтобы быть готовым напечатать следующий символ. Чтобы 
;	воспользоваться этой рутиной, нужно переместить в регистр ah число 0x0e, 
;	а также использовать прерывания (синтаксис прерываний в ассемблере - 
;	"int <номер прерывания>"). Прерывания - это механизм, с помощью которого 
;	процессор может быть прерван от выполнения того, чем он сейчас занят, чтобы 
;	выполнить какую-то другую команду. Мы будем использовать прерывание 0x10 
;	(это число - индекс обработчика прерывания в ISR (interrupt service 
;	routines). ISR это последовательность команд, ответсвенных за прерывание. 
;	Нам нужна команда под индексом 0x10), функцией которого является 
;	предоставление графических сервисов, вывод строк на экран и т.д.
; ------------------------------------------------------------------------------


mov ah, 0x0e			; Перемещаем число 0x0e в регистр ah, указывая BIOS'у
						; что нам нужна рутина tele-type, то есть режим вывода 
						; информации на экран

mov al, 'H'				; Перемещаем ASCII код символа 'H' в регистр al (команда
int 0x10				; mov), вызываем прерывание 0x10, которое выводит
mov al, 'e'				; на экран значение регистра al
int 0x10
mov al, 'l'
int 0x10
mov al, 'l'
int 0x10
mov al, 'o'
int 0x10

						; ------------------------------------------------------

jmp $					; Знак $ вычисляет позицию начала строки, содержащей
						; выражение, то есть мы прыгаем к началу этой же строки,
						; создавая бесконечный цикл

times 510-($-$$) db 0	; Заполняем ненужные байты нулями

dw 0xaa55				; Вставляем в конец "магическое число"

================================================
FILE: guide/00-BOOT-SECTOR/ex02/main.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex02 / main.asm
; Title:	Простая программа загрузочного сектора (boot sector), которая 
;			демонстрирует работу адресов
; ------------------------------------------------------------------------------
; Description: 
;	Наша цель - разобраться как работать с памятью на языке Ассемблер и узнать,
;	по какому адресу хранится загрузочный сектор (спойлер: по адресу 0x7c00).
;	Для этого мы попробуем вывести символ 'Х' на экран 4-мя разными способами.
;	Подготовка: если addr - адрес, то [addr] - значение на которое указывает 
;	адрес
; ------------------------------------------------------------------------------


mov ah, 0x0e			; Перемещаем число 0x0e в регистр ah, указывая BIOS'у
						; что нам нужна рутина tele-type, то есть режим вывода 
						; информации на экран

mov al, '1'				; Выводим номер способа (нужно лишь нам для понятности)
int 0x10
mov al, the_secret		; #1: перемещаем адрес the_secret.
int 0x10				; На экран выведется мусор, т.к. мы переместили в al 
						; сам адрес, а не хранящееся в нем значение.

mov al, '2'				; Выводим номер способа
int 0x10
mov al, [the_secret]	; #2: перемещаем значение по адресу the_secret.
int 0x10				; Может показаться вполне корректным, но на экран снова
						; выведется мусор, т.к the_secret объявлен не в том же
						; месте, что и наш загрузочный сектор, и поэтому мы не
						; сможем таким образом забрать из него 'X'.

mov al, '3'				; Выводим номер способа
int 0x10
mov bx, the_secret		; #3: перемещаем адрес the_secret в регистр bx,
add bx, 0x7c00			; добавляем к этому адресу адрес загрузочного сектора,
mov al, [bx]			; выводим значение по адресу bx.
int 0x10				; Вот теперь то мы и сможем вывести 'X'. Суть в том, что
						; чтобы мы смогли добраться до корректного значения
						; которое мы объявили, адрес этого значения должен быть
						; в пределах адресного пространства загрузочного 
						; сектора. Т.е. мы должны прибавить к адресу the_secret
						; адрес, с которого начинается загрузочный сектор.
						; то есть если the_secret = 0x2d, то прибавляя к нему
						; адрес загрузочного сектора 0x7c00 мы получим 0x7c2d.
						; Такая техника называется смещением (англ. offset)
						; Именно по этому адресу мы сможем достать 'X'.

mov al, '4'				; Выводим номер способа
int 0x10
mov al, [0x7c2d]		; #4: перемещаем значение адреса, который мы
int 0x10				; захардкодили (т.е. специально сами посчитали, заранее, 
						; не программно) Таким способом у нас тоже получится 
						; вывести 'X', но это плохой способ, т.к. адрес может 
						; в итоге отличаться.

						; ------------------------------------------------------

jmp $					; бесконечный цикл

the_secret:				; Объявляем метку the_secret,
	db 'X'				; объявляем байт, и инициализируем его с ASCII кодом,
						; соответствующим символу 'X'


times 510-($-$$) db 0	; Заполняем ненужные байты нулями

dw 0xaa55				; Вставляем в конец "магическое число"

================================================
FILE: guide/00-BOOT-SECTOR/ex02/org_demo.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex02 / org_demo.asm
; Title:	Простая программа загрузочного сектора (boot sector), которая 
;			демонстрирует работу адресов, используя директиву [org <адрес>]
; ------------------------------------------------------------------------------
; Description:
;	Мы выяснили в предыдущем упражнении, что чтобы процессор корректно нашел 
;	объявленное нами значение, нужно добавлять 0x7c00 к адресу этого значения 
;	(т.е. "сместить" адрес на 0x7c00, англ. offset - смещение). Это довольно 
;	неудобно, и поэтому ассемблер позволяет нам указать "глобальное смещение" с 
;	помощью директивы [org <адрес>]. Посмотрим как это работает.
; ------------------------------------------------------------------------------


[org 0x7c00]			; Указываем "глобальное смещение"

mov ah, 0x0e			; Перемещаем число 0x0e в регистр ah, указывая BIOS'у
						; что нам нужна рутина tele-type, то есть режим вывода 
						; информации на экран

mov al, '1'				; Выводим номер способа для удобства
int 0x10
mov al, the_secret		; #1: перемещаем адрес the_secret.
int 0x10				; На экран выведется мусор, т.к. мы переместили в al 
						; сам адрес, а не хранящееся в нем значение.

mov al, '2'				; Выводим номер способа
int 0x10
mov al, [the_secret]	; #2: перемещаем значение по адресу the_secret.
int 0x10				; В этот раз мы сможем вывести 'X', т.к. [the_secret]
						; будет разыменован по адресу the_secret + 0x7c00
						; благодаря "глобальному смещению"

mov al, '3'				; Выводим номер способа
int 0x10
mov bx, the_secret		; #3: перемещаем адрес the_secret в регистр bx,
add bx, 0x7c00			; добавляем к этому адресу адрес загрузочного сектора,
mov al, [bx]			; выводим значение по адресу bx.
int 0x10				; Мы не сможем вывести 'X', т.к. лишний раз делаем
						; смещение: 0x7c00 + the_secret + 0x7c00

mov al, '4'				; Выводим номер способа
int 0x10
mov al, [0x7c2d]		; #4: перемещаем значение адреса, который мы
int 0x10				; захардкодили (т.е. специально сами посчитали, заранее, 
						; не программно) Таким способом у нас тоже получится 
						; вывести 'X', но это плохой способ, т.к. адрес может 
						; в итоге отличаться.

						; ------------------------------------------------------

jmp $					; бесконечный цикл

the_secret:				; Объявляем метку the_secret,
	db 'X'				; объявляем байт, и инициализируем его с ASCII кодом,
						; соответствующим символу 'X'


times 510-($-$$) db 0	; Заполняем ненужные байты нулями

dw 0xaa55				; Вставляем в конец "магическое число"

================================================
FILE: guide/00-BOOT-SECTOR/ex03/main.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex03 / main.asm
; Title:	Простая программа загрузочного сектора, которая демонстрирует 
;			работу стека
; ------------------------------------------------------------------------------
; Description:
;	Цель: разобраться как работать со стеком на языке Ассемблер.
;	Теория:
;	1. Стек - структура данных, которая действует по принциу LIFO (last in first
;	out). Со стеком можно провести две операции: добавить в стек элемент
;	(добавлять можно только в конец) и взять элемент из стека (взять можно 
;	тоже только с конца). О стеке удобно думать как о стопке блинов.
;	2. Регистры для работы со стеком: BP и SP. BP содержит адрес начала стека,
;	а SP - адрес конца стека (т.е. начала стопки блинов и ее макушка соответсв.)
;	3. Стек, с которым мы будем работать, "растет" вниз, то есть мы добавляем 
;	значения в стек по адресу меньше конца стека. Допустим, если адрес конца 
;	стека находится в регистре SP и равен 0x8000, то добавляя что-нибудь в стек, 
;	значение регистра SP станет 0x8000 - 0x2 (если размер этого "чего-нибудь"
;	равен 2 байтам).
;	4. Операции со стеком: "push <значение>" чтобы добавить какое-либо значение 
;	в конец стека, "pop <регистр>" чтобы удалить последнее значение из стека и 
;	добавить его в указанный регистр.
; ------------------------------------------------------------------------------


mov ah, 0x0e			; Перемещаем число 0x0e в регистр AH, указывая BIOS'у
						; что нам нужна рутина tele-type, то есть режим вывода 
						; информации на экран

mov bp, 0x8000			; BP - регистр адреса начала стека, а SP - конца стека
mov sp, bp				; мы размещаем стек чуть выше адреса, из которого БИОС
						; загружает наш загрузочный сектор (в адресе 0x8000),
						; чтобы случайно не задеть загрузочный сектор.
						; Переносим адрес BP в SP, т.к. изначально стек пустой,
						; и поэтому адрес конца стека равен адресу начала

push 'A'				; Добавляем в стек символы
push 'B'
push 'C'


pop bx					; Переносим последнее значение из стека в регистр BX
						; (помним, что значение sp увеличится, а не уменьшится, 
						; т.к. стек "растет" вниз)
mov al, bl				; в bl находится значение*, взятое из стека с помощью 
						; команды pop. Перемещаем его в регистр AL
int 0x10				; Выводим 'C'


pop bx					; Переносим в регистр BX следующее значение из стека
mov al, bl 
int 0x10				; Выводим 'B'


mov al, [0x8000 - 0x2]	; Переносим в регистр AL значение по адресу 0x7ffe
						; (0x8000 - 0x2), т.е. адрес начала стека минус 2 байта,
						; для того чтобы подтвердить, что стек "растет" вниз
int 0x10				; Выводим 'A'

						; ------------------------------------------------------

jmp $					; бесконечный цикл

the_secret:				; Объявляем метку the_secret,
	db 'X'				; объявляем байт, и инициализируем его с числом,
						; соответствующим символу 'X' в таблице ASCII


times 510-($-$$) db 0	; Заполняем ненужные байты нулями

dw 0xaa55				; Вставляем в конец "магическое число"

; ------------------------------------------------------------------------------
; * почему BL, а не BX? Регистр bx устроен так, что состоит из двух других
;	регистров: BH и BL (оба размером 8 байт, а размер bx соответственно 16 байт)
;	Подобным образом же устроен и регистр AX, т.е. AH и AL. Т.к. мы используем
;	регистр AL, равный 8 байтам, чтобы выводить символы на экран, то нам нужно 
;	перемещать в этот регистр значение из регистра такого же размера. Поэтому
;	мы перемещаем туда значение из BL. (гугли: регистры в ассемблере)


================================================
FILE: guide/00-BOOT-SECTOR/ex04/main.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex04 / main.asm
; Title:	Простая программа загрузочного сектора, которая выводит на экран 
;			строку с помощью функции
; ------------------------------------------------------------------------------
; Description:
;	Кратко о том, как работают функции:
;	Чтобы вызвать функцию, используется команда call <метка>. Чтобы вернуть
;	(выйти) из функции, используется команда ret.
;	Команды call и ret используются в паре. Команда call помещает значение 
;	регистра EIP (в нем установлен адрес, следующий после call <метка> 
;	инструкции) в стек, а команда ret извлекает его и передаёт управление 
;	инструкции по этому адресу. Вы также можете определить аргументы, для 
;	вызываемой функции. Это можно сделать с помощью стека или регистров, т.е. 
;	перед вызовом функции занести параметры в стек или в определенный регистр, 
;	а в теле функции извлечь их.
; ------------------------------------------------------------------------------


[org 0x7c00]

mov bx, HELLO_MSG				; Перемещаем объявленную нами ниже строку в
								; регистр BX. В ассемблере строки представляют
								; из себя то же, что и строки в Си - идущие друг
								; за другом ячейки памяти, в которых занесено 
								; значение символов этой строки, поэтому в BX
								; в данный момент находится первый символ строки
								; (а точнее его ASCII код).

call print_string				; Вызываем функцию print_string с помощью
								; команды call

mov bx, GOODBYE_MSG
call print_string

								; ------------------------------------------------------

jmp $							; бесконечный цикл

%include "print_string.asm"		; директива %include вставляет весь код из
								; файла print_string.asm в место, откуда она
								; была вызвана.

HELLO_MSG:
	db "Hello, world!", 0		; Объявляем байты, содержащие строку с 
								; приветствием, заканчивающуюся нулем.
								; На самом деле эти байты размещаются прямо в
								; исполняемом файле, откомпилированном с помощью
								; nasm. Вот так вот.

GOODBYE_MSG:					; Объявляем метку GOODBYE_MSG
	db "Goodbye!", 0			; Объявляем массив из символом, заканчивающийся
								; нулем (гугли: нуль терминированная строка)

times 510-($-$$) db 0			; Заполняем оставшиеся байты нулями
dw 0xaa55						; Вставляем в конец "магическое число"


================================================
FILE: guide/00-BOOT-SECTOR/ex04/print_string.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex04 / print_string.asm
; Title:	Функция вывода строки на экран
; ------------------------------------------------------------------------------
; Description: null
; ------------------------------------------------------------------------------


print_string:			; Функция вывода строки на экран.
	pusha				; Когда мы используем функции, мы можем модифицировать
						; регистры прямо в них, что нарушает чистоту функции,
						; т.е. мы можем перезаписывать какие-то внешние
						; данные. Для этого мы добавляем значение всех регистров
						; в стек с помощью команды pusha, а в конце функции
						; мы возвращаем регистрам их изначальные значения,
						; которые возьмем из стека (команда popa).
						
	mov ah, 0x0e		; tele-type mode

loop:					; Метка loop (= цикл)
	mov al, [bx]		; Перемещаем значение BX в AL, т.к. мы помним что в BX
						; лежит первый символ строки (см. ./main.asm)
	cmp al, 0			; Команда cmp для сравнения AL и 0.
	je return			; (if) je = "jump if equal",
						; т.е. перемещаемся к коду с меткой return если AL == 0
	jmp put_char		; (else) в противном случае перемещаемся к put_char.

put_char:				; Метка put_char - вывод символа на экран.
	int 0x10			; Вызываем прерывание, которое позволяет вывести 
						; на экран значение регистра AL, в котором лежит [BX].
	inc bx				; inc <регистр> - увеличить на 1.
	jmp loop			; Возвращаемся обратно к циклу.

return:					; Метка return - завершаем функцию
	popa				; Возвращаем регистрам их изначальные значения.
	ret					; Заканчиваем выполнение функции.


================================================
FILE: guide/00-BOOT-SECTOR/ex05/main.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex05 / main.asm
; Title:	Простая программа загрузочного сектора, которая выводит на экран 
;			шестнадцатеричное число (число 0x1fb6)
; ------------------------------------------------------------------------------
; Description: null
; ------------------------------------------------------------------------------


[org 0x7c00]

mov dx, 0x1fb6					; Перемещаем 0x1fb6 в регистр DX чтобы потом
								; задействовать его в функции print_hex
mov bx, HEX_OUT					; перемещаем HEX_OUT в BX

call print_hex					; Вызываем функцию print_hex

								; ----------------------------------------------

jmp $							; бесконечный цикл

%include "../ex04/print_string.asm"	; директива %include вставляет весь код из
%include "print_hex.asm"			; файла print_string.asm в место, откуда она
									; была вызвана.

HEX_OUT:						; Объявляем метку HEX_OUT. Она нужна как шаблон,
								; на котором мы будем отображать наше число.
	db "0x0000", 0				; Объявляем массив из символом, заканчивающийся
								; нулем (гугли: нуль терминированная строка)

times 510-($-$$) db 0			; Заполняем ненужные байты нулями
dw 0xaa55						; Вставляем в конец "магическое число"


================================================
FILE: guide/00-BOOT-SECTOR/ex05/print_hex.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex05 / print_hex.asm
; Title:	Функция вывода шестнадцатеричного числа на экран
; ------------------------------------------------------------------------------
; Description: 
;	Как выглядит регистр EAX (32 b = 32 бита):
;
;						EAX (32 b)
;		---------------------------------------------
;		|					|			|			|
;		|					|  AH (8 b)	|  AL (8 b) |
;		|					|			|			|
;		---------------------------------------------
;									AX (16 b)
;
;	Как видим, eax содержит в себе регистр AX, который в свою очередь разделен 
;	на AH (A HIGH, верхний) и AL (a low, нижний).
; ------------------------------------------------------------------------------


; Помним, что в main.asm:
; DX  =  0x1fb6
; BX  =  "0x0000" 

print_hex:
	pusha						; Сохраняем значения регистров в стеке
	mov cx, 0					; Регистр CX будет служить счетчиком

loop1:
	cmp cx, 4					; if (CX < 4)
	jl print					; Переходим к print
	jmp end						; else переходим к end

print:
	mov ax, dx					; В AX теперь 0x1fb6
	and ax, 0x000f				; В AX теперь 6 (последняя цифра от 0x1fb6).
	cmp ax, 9					; if (AX > 9) (проверяем обозначается ли число
								; буквой т.к. мы помним что цифра больше 9 в 
								; 16-ричной системе исчисления обозначается
								; буквой латинского алфавита)
	jg num_to_abc				; Переходим к num_to_abc
	jmp next

num_to_abc:						; Перевод числа в букву (так, как она бы
								; выглядела в 16-ричной СИ), например число 15
								; в десятичной будет равно f в 16-ричной
	add ax, 39					; Добавляем к этому числу 39, чтобы затем еще
								; добавить 48 ('0'), получая код 
								; соответсвующего символа в ASCII (например,
								; f (как число, то есть 15) + 39 + 48 (код '0')
								; = 102 (то есть 'f' в ASCII, как нам и нужно)
	jmp next

next:
	add ax, '0'					; Добавляем 48 в AX
	mov bx, HEX_OUT + 5			; Теперь bx указывает на последний символ строки
								; HEX_OUT
	sub bx, cx					; BX = BX - counter (для итерации)
	mov [bx], al				; Так как мы разыменовываем BX (вот так: [BX]),
								; то [BX] это не регистр, а ссылка на память,
								; и так как AX = 16 бит (2 байта), чтобы нам не
								; перезаписать лишнюю память, в [BX] мы помещаем
								; не AX, а AL, размер которого равен 1-му байту.
	ror dx, 4					; было: 0x1fb6, стало: 0x61fb (переносим
								; последнюю цифру в начало)
	inc cx						; counter++
	jmp loop1					; переходим обратно к loop1

end:
	mov bx, HEX_OUT				; Делаем так, чтобы bx снова указывал на первый
								; символ строки HEX_OUT
	call print_string			; выводи на экран строку из регистра BX
	popa						; Возвращаем регистрам их изначальное значение
	ret							; Заканчиваем выполнение функции


================================================
FILE: guide/00-BOOT-SECTOR/ex06/main.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex06 / main.asm
; Title:	Простая программа загрузочного сектора (boot sector), которая 
;			демонстрирует работу сегментации
; ------------------------------------------------------------------------------
; Description:
;	Когда процессор запущен в его начальном режиме (16-bit real mode), 
;	максимальный размер регистров = 16 бит, поэтому самый большой размер, 
;	который мы можем использовать это 0xffff, который равен примерно 64 KB.
;	Чтобы преодолеть этот лимит, существуют специальные регистры, которые 
;	называются регистры сегментов - CS, DS, SS, ES, означающие Code, Data, Stack
;	и Extra соответственно.
;	Память разделена на сегменты, которые индексированы регистрами сегментов
;	(т.е. к примеру в регистре DS лежит адрес начала Data-сегмента). Поэтому, 
;	когда мы указываем какой-либо 16-битный адрес, процессор автоматически
;	высчитывает абсолютный адрес, сдвигая указанный нами адрес от начала нужного
;	сегмента.
;	Высчитывая абсолютный адрес, процессор умножает на 16 значение в регистре 
;	сегмента и добавляет указанный нами адрес. Например, если мы установим 
;	значение сегмента DS как 0x4d и попробуем сделать что-то вроде 
;	"mov ax, [0x20]", то значение, добавляемое в AX, будет загружено 
;	из адреса 0x4f0 (16 * 0x4d + 0x20).
;	Как можно догадаться, с помощью сегментации мы можем добиться того же, что и
;	с помощью директивы [org <адрес>], как мы делали в ex02/org_demo.asm.
; ------------------------------------------------------------------------------


mov ah, 0x0e			; Перемещаем число 0x0e в регистр AH, указывая BIOS'у
						; что нам нужна рутина tele-type, то есть режим вывода 
						; информации на экран

mov al, '1'				; Выводим номер способа для удобства
int 0x10
mov al, [the_secret]	; #1: так как мы не указывали никакого смещения ни с
int 0x10				; помощью org директивы ни с помощью сегментации,
						; 'X' не выведется

mov al, '2'				; Выводим номер способа
int 0x10
mov bx, 0x7c0			; т.к. мы не можем напрямую написать "mov ds, 0x7c0",
mov ds, bx				; используем BX как промежуточное хранилище
mov al, [the_secret]	; 'X' выведется, так как мы сделали смещение.
int 0x10				; Стоит заметить, что мы переводим в DS 0x7c0, а не 
						; 0x7c00, так как помним, что значение, перемещаемое в
						; AL будет загружено из адреса, который будет
						; высчитываться как 16 * ds + the_secret,
						; а 16 * 0x7c0 == 0x7c00, то есть так как нам и нужно.

mov al, '3'				; Выводим номер способа
int 0x10
mov al, [es:the_secret]	; Явно указываем процессору, использовать сегмент ES
int 0x10				; (просто для демонстрации возможностей и чтобы понять
						; как это работает)
						; 'X' не выведется, т.к. мы не переместили в регистр ES
						; число 0x7c0, и поэтому смещение на адрес 16 * 0x7c0 
						; выполнено не будет.

mov al, '4'				; Выводим номер способа
int 0x10
mov bx, 0x7c0			; Используя BX как промежуточное хранилище, перемещаем
mov es, bx				; в регистр ES 0x7c0.
mov al, [es:the_secret]	; Явно указываем процессору сегмент ES
int 0x10				; 'X' выведется.

						; ------------------------------------------------------

jmp $					; бесконечный цикл

the_secret:
	db 'X'

times 510-($-$$) db 0	; Заполняем ненужные байты нулями
dw 0xaa55				; Вставляем в конец "магическое число"


================================================
FILE: guide/00-BOOT-SECTOR/ex07/disk_load.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / disk_load.asm
; Title:	Функция чтения диска
; ------------------------------------------------------------------------------
; Description:
;	Чтобы лучше понять, что здесь происходит, разберитесь с тем, что такое CHS
;	по ссылке https://ru.wikipedia.org/wiki/CHS
; ------------------------------------------------------------------------------


disk_load:
	push dx
	mov ah, 0x02			; Указвыаем БИОСу что нам нужна рутина чтения диска
							; Указываем что нам нужно:
	mov al, dh				; 1. Прочитать кол-во секторов, равное значению в DH
	mov ch, 0x00			; 2. Выбрать нулевой цилиндр
	mov dh, 0x00			; 3. Выбрать нулевую головку
	mov cl, 0x02			; 4. Начинать считывать со второго сектора (т.е.
							; первый свободный сектор сразу после загруочного 
							; сектора, т.к. загрузочный сектор находится по 
							; адресу 0x01)
	int 0x13				; Вызываем прерывание для чтения

							; У БИОСа может не получиться прочитать диск, и
							; чтобы дать нам знать что произошла ошибка, он,
							; во-первых, обновляет специальный флаг CF (carry 
							; flag) специальным значением, которое означает 
							; ошибку, а во-вторых, кладет в регистр AL кол-во 
							; секторов, которые у него получилось прочитать.
	
	jc disk_error			; jc - инструкция для прыжка на указанную метку,
							; которая выполняется только если CF (carry flag)
							; сигнализирует об ошибке

	pop dx					; Восстанавливаем регистр DX из стека
	cmp dh, al				; если AL (кол-во прочитанных секторов) != DH
							; (предполагаемое кол-во секторов),
	jne disk_sectors_error	; то выводим на экран сообщение об ошибке и зависаем
							; (то есть запускаем бесконечный цикл)
	jmp disk_success
	jmp disk_exit			; Заканчиваем выполнение функции

disk_success:
	mov bx, SUCCESS_MSG
	call print_string
	jmp disk_exit

disk_error:
	mov bx, DISK_ERR_MSG	; Перемещаем в BX сообщение об ошибке
	call print_string		; Выводим его на экран
	mov dh, al
	call print_hex
	jmp disk_loop			; бесконечный цикл

disk_sectors_error:
	mov bx, SECTORS_ERR_MSG
	call print_string

SUCCESS_MSG:
	db "Disk was successfully read ", 0

DISK_ERR_MSG:
	db "Disk read error! ", 0

SECTORS_ERR_MSG:
	db "Incorrect number of sectors read ", 0

disk_loop:
	jmp $

disk_exit:
	ret


================================================
FILE: guide/00-BOOT-SECTOR/ex07/main.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex07 / main.asm
; Title:	Простая программа загрузочного сектора, которая демонстрирует 
;			чтение с диска
; ------------------------------------------------------------------------------
; Description: Операционная система не уберется в 512 байтов, поэтому нам нужно
;				уметь прочитать что-то с диска.
; ------------------------------------------------------------------------------


[org 0x7c00]

mov bp, 0x8000						; Распологаем наш стек подальше в безопасное
mov sp, bp							; место

mov bx, 0x9000						; Данные из секторов будут загружаться в
									; адрес 0x0000(ES):0x9000(BX), т.е.
									; (ES * 16 + BS), равный 0x90000

mov dh, 2							; Загрузим 2 сектора

call disk_load						; Загружаем диск

mov dx, [0x9000]					; Выводим на экран первое загрузившееся
call print_hex						; "слово" (т.е. машинное слово = 2 байта)
									; предполагая, что оно будет равно 0xdada
									; (распологается по адресу 0x9000)

mov dx, [0x9000 + 512]				; Выводим на экран первое "слово" из 2-го
									; загруженного нами сектора. Должно быть
									; равно 0xface
call print_hex

jmp $

%include "../ex04/print_string.asm"	; Функция печати строки
%include "../ex05/print_hex.asm"	; Функция печати 16-ричного числа
%include "disk_load.asm"			; Функция чтения диска

HEX_OUT:
	db "0x0000", 0

times 510-($-$$) db 0
dw 0xaa55

; БИОС загрузит только первые 512 байтов с диска, поэтому если мы специально
; добавим пару секторов (тоже по 512 байт), мы сможем убедиться в том что у 
; нас получилось загрузить эти самые сектора. 
; TODO: explain better.

times 256 dw 0xdada					; второй сектор
times 256 dw 0xface					; третий сектор


================================================
FILE: guide/00-BOOT-SECTOR/ex08/gdt.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / gdt.asm
; Title:	Определяем GDT (глобальная таблица дескрипторов)
; ------------------------------------------------------------------------------
; Description:
; 	Способ, которым процессор переводит логический адрес в физический, в
; 	32-битном защищенном режиме отличается от 16-битного реального режима.
; 	Вместо того, чтобы умножить значение регистра сегмента на 16 и прибавить к
; 	этому "смещение" (offset), регистр сегмента становится индексом 
; 	определенного дескриптора сегмента в GDT.
; 	Дескриптор сегмента - это 8-битная структура, которая определяет свойства
; 	этого сегмента:
; 		- Base address (32 bits), определяющий откуда сегмент начинается в
; 		физической памяти.
; 		- Segment Limit (20 bits), определяющий размер сегмента
;		- Различные флаги, которые устанавливают каким образом процессор будет
; 		"относиться" к сегментам, например уровень привилегий и т.д.
;
; 	Флаги:
;	* 1-ые флаги:
; 		- present flag (флаг присутствия). Если его значение "1", то это 
;		указывает, что сегмент присутствует в памяти (это нужно для виртуальной 
;		памяти)
;		- privilege flag (флаг привилегии). Значение "0" - самый высокий уровень
;		привилегии
;		- descriptor type (тип дескриптора). "1" - для сегмента кода или 
;		сегмента данных
; 	* Флаги типа:
;		- code (флаг кода). "1" - для кода, "0" - для даннных
;		- conformig (флаг подчинения). "0" - чтобы код в другом сегменте с
;		более низким уровнем привилегий не смог вызвать код из этого сегмента - 
;		это ключ к защите памяти (memory protection).
;		- readable (читаемость). "1" - если читаемый, "0" - только исполняемый.
;		- writable. Разрешает сегменту данных быть записываемым, в противном
;		случае, он будет доступен только для чтения.
;		- accessed (флаг доступа). Этот флаг устанавливается, когда происходит
; 		обращение к сегменту.
;		- expand down. Флаг (бит), позволяющий сегменту расширяться вниз.
;	* 2-ые флаги:
;		- granulariy (гранулярность). "0" - байтовая гранулярность, лимит
; 		задается в байтах, если "1" - страничная гранулярность, в 4кб блоках.
;		Если выбрать страничную гранулярность и установить значение лимита как
;		0xfffff, то лимит умножится на 16*16*16 (4кб), и лимит станет 0xfffff000
; 		позволяя нашему сегменту занять 4гб места в памяти.
;		- 32-bit default. "1" - т.к. наш сегмент будет содержать 32-битный код.
;		- 64-bit code segment. "0" - т.к. не используется на 32-битных 
;		процессорах.
;		- AVL (available). Определяет доступность сегмента для использования 
;		системным программным обеспечением (используются только ОС).
; ------------------------------------------------------------------------------

gdt_start:					; Эта пустая метка нужно для того чтобы удобнее
							; посчитать размер GDT для ее дескриптора 
							; (end - start)

gdt_null:					; Необходимый нулевой дескриптор для GDT
	dd 0x0					; dd - define double (двойное "слово"", т.е.
	dd 0x0					; 4 байта).

gdt_code:					; Определяем дескриптор сегмента кода
	dw 0xffff				; Limit (bits 0-15)
	dw 0x0					; Base (bits 0-15)
	db 0x0					; Base (bits 16-23)
	db 10011010b			; Первые флаги + флаги типа (смотрим по битам)
							; present: 1, privilege: 00, descriptor type: 1
							; code: 1, conforming: 0, readable: 1, accessed: 0
	db 11001111b			; Вторые флаги + длина сегмента (bits 16-19):
							; granularity: 1, 32-bit default: 1,
							; 64-bit default: 0, AVL: 0
	db 0x0					; Base (bits 24-31)

gdt_data:					; Определяем дескриптор сегмента кода
	dw 0xffff				; Limit (bits 0-15)
	dw 0x0					; Base (bits 0-15)
	db 0x0					; Base (bits 16-23)
	db 10010010b			; Первые флаги + флаги типа (смотрим по битам)
	db 11001111b			; Вторые флаги + длина сегмента (bits 16-19)
	db 0x0					; Base (bits 24-31)

gdt_end:					; Пустая метка


gdt_descriptor:						; дескриптор GDT
	dw gdt_end - gdt_start - 1		; Размер GDT
	dd gdt_start					; Адрес начала GDT


CODE_SEG equ gdt_code - gdt_start	; Определяем некоторые константы. 
DATA_SEG equ gdt_data - gdt_start	; Они понадобятся для регистров сегментов в
									; 32-битном защищенном режиме. Например,
									; когда мы установим регистр DS = 0x10 (т.е
									; 16 байтов) в этом режиме, процессор
									; поймет что мы хотим использовать сегмент,
									; находящийся в смещении 0x10 в GDT,
									; т.е. в нашем случае это сегмент данных
									; (0x0 -> NULL, 0x08 -> сегмент кода,
									; 0x10 -> сегмент данных)


================================================
FILE: guide/00-BOOT-SECTOR/ex08/main.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / main.asm
; Title:	Программа загрузочного сектора, которая входит в 32-битный 
; 			Защищенный Режим
; ------------------------------------------------------------------------------
; Description: 
;	Было бы неплохо остаться в 16-битном реальном режиме, но чтобы использовать
;	возможности процессора полностью, мы должны переключиться в 32-битный
;	защищенный режим.
;	Основные отличия 32-битного защищенного режима:
;		1. Регистры увеличены до 32 бит. Чтобы использовать увеличенные регистры
;		нужно добавить букву "е" перед ними. Пример: AX -> EAX, BX -> EBX
;		2. Добавлены 2 новых регистров сегмента: FS и GS.
;		3. Доступны 32-битные смещения (размер сегмента сможет достигать 4гб)
;		4. Процессор поддерживает более сложную сегментацию памяти, у которой
;		есть 2 достоинства:
;			a. Коду в одном сегменте запрещено исполнять код в другом, более
;			привилигированном сегменте, поэтому вы можете защитить код ядра
;			от кода пользовательских приложений.
;			b. Процессор может предоставлять виртуальную память для процессов
;			пользователя.
;		5. Обработка прерываний также более сложна.
;	Самая сложная часть перехода в 32PM - мы должны подготовить сложную
;	структуру данных, которая называется глобальная таблица дескрипторов (GDT).
; ------------------------------------------------------------------------------


[org 0x7c00]

	mov bp, 0x9000					; Устанавливаем стек
	mov sp, bp

	mov bx, MSG_REAL_MODE
	call print_string				; Печатаем сообщение на экран

	call switch_to_pm				; Переключаемся на загрузочный режим

	jmp $

%include "../ex06/print_string.asm"	; Вывод строки
%include "gdt.asm"					; GDT
%include "print_string_pm.asm"		; Вывод строки в 32 PM
%include "switch.asm"				; Переключиться на 32 PM

[bits 32]

BEGIN_PM:							; Сюда мы попадем после переключения в PM
	mov ebx, MSG_PROT_MODE
	call print_string_pm			; Печатаем сообщение на экран

	jmp $

MSG_REAL_MODE:
	db "Started in 16-bit Real Mode", 0
MSG_PROT_MODE:
	db "Successfully landed in 32-bit Protected mode", 0

times 510-($-$$) db 0
dw 0xaa55


================================================
FILE: guide/00-BOOT-SECTOR/ex08/print_string_pm.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / print_string_pm.asm
; Title:	Функция вывода строки на экран в 32-битном защищенном режиме
; ------------------------------------------------------------------------------
; Description:
;	Плюсы 32-битного режима: возможность использовать 32-битные регистры и
;	адрессацию памяти, защищенную память виртуальную память
;	Минусы: отсутствие БИОС прерываний, требование наличия GDT (об этом позже)
;	В этой программе мы напишем новую функцию печати строки, но без прерываний
;	БИОСа, а напрямую манипулируя VGA видеопамятью, вместо вызова int 0x10.
;	VGA размещена начиная с адреса 0xb8000, и у VGA имеется специальный
;	текстовый режим, поэтому нам не придется напрямую рисовать пиксели.
;	Особенности:
;	1. Символ представляется в виде 2-х байтов. Первый байт = сам символ,
;	второй байт = 4 бита на цвет текста и еще 4 на цвет фона. 
;	Например, чтобы распечатать символ 'A' белым текстом на черном фоне, мы
;	испольузуем 0x410f: 0x41 == 'A', 0 == белый, f == черный.
; ------------------------------------------------------------------------------


[bits 32]					; Используем 32-битный режим

							; Определяем некоторые константы
VIDEO_MEMORY equ 0xb8000	; = адрес начала памяти VGA
WHITE_ON_BLACK equ 0x0f		; = цвет символов (0x0f - белый на черном)

print_string_pm:
	pusha
	mov edx, VIDEO_MEMORY	; Перемещаем в EDX адрес начала массива видеопамяти

print_string_pm_loop:
							; Помним, что AX (2б) = AH(1б) и AL(1б)
	mov al, [ebx]			; Сохраняем символ из EBX в AL
	mov ah, WHITE_ON_BLACK	; Устанавливаем цвет символов в AH
							; таким образом AX получается равен символу + цвету

	cmp al, 0				; if (AL == 0), т.е. если конец массива, то
	je print_string_pm_done	; заканчиваем выполнение функции
							; else:
	mov [edx], ax			; video_memory[EDX] = AX
	add ebx, 1				; переходим к следующему символу (+1, просто массив)
	add edx, 2				; переходим к следующему адресу в VGA (+2 т.к.
							; два байта на символ)

	jmp print_string_pm_loop

print_string_pm_done:
	popa
	ret


================================================
FILE: guide/00-BOOT-SECTOR/ex08/switch.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / switch.asm
; Title:	Переключаемся в PM (Protected mode, защищенный режим)
; ------------------------------------------------------------------------------
; Description:
;	Чтобы сделать свитч, нам нужно:
;		1. Отключить прерывания (процессор просто будет их игнорировать), т.к.
; 		в PM, прерывания обрабатываются совершенно по-другому в отличие от
;		Real Mode. Даже если процессор и смог бы распределить сигналы прерываний
;		по конкретным BIOS обработчикам прерываний, БИОС обработчики будут
;		обрабатывать 16-битный код, что повлекло бы за собой ошибки.
;		2. Загрузить GDT дескриптор
;		3. Изменяем первый бит регистра управления cr0 на "1"
;		https://en.wikipedia.org/wiki/Control_register#CR0
;		4. Т.к. процессор использует специальную технику, которая называется
;		называется pipelining (гугли: вычислительный конвейер, полезная статья
;		на хабре: https://habr.com/ru/post/182002/), и поэтому сразу после того,
;		как перевести процессор в PM (что мы и сделали в предыдущем пункте), 
;		нам нужно заставить процессор завершить всю работу в конвейере, чтобы
;		быть уверенным, что все будущие инструкции будут выполнены корректно.
;		Конвейер загружает в себя некоторые количество последующих после текущей
;		инструкций, но конвейеру не очень нравятся инструкции типа call и jmp,
;		т.к. процессор не знает полностью какие инструкции будут следовать за
;		ними, в особенности если мы вызовем jmp или call "прыгая" в другой
;		сегмент. Поэтому нам нужно сделать "дальний прыжок", чтобы стереть 
;		обрабатываемые в конвейере инструкции.
;		Сам прыжок: 	jmp <сегмент>:<адрес смещения>
; ------------------------------------------------------------------------------


[bits 16]

switch_to_pm:
	cli						; Отключаем прерывания (cli = clear interrupts)
	
	lgdt [gdt_descriptor]	; Загружаем GDT дескриптор (lgdt = load GDT)

	mov eax, cr0			; Чтобы перейти в PM, нужно чтобы первый бит
	or eax, 0x1				; регистра управления cr0 был 1
	mov cr0, eax

	jmp CODE_SEG:init_pm	; Делаем "дальний прыжок" в наш новый 32-битный
							; сегмент кода. Это так же заставляет процессор
							; завершить обрабатываемые в конвейере инструкции. 

[bits 32]

init_pm:					; в PM, наши старые сегменты бесполезны, поэтому
	mov ax, DATA_SEG		; мы делаем так, чтобы регистры всех сегментов
	mov ds, ax				; указывали на сегмент данных, который мы определили
	mov ss, ax				; в GDT (см. ./gdt.asm)
	mov es, ax
	mov fs, ax
	mov gs, ax

	mov ebp, 0x90000		; Обновляем позицию стека, чтобы он был на самом
	mov esp, ebp			; верху свободного места

	call BEGIN_PM			; Вызываем функцию из ./main.asm


================================================
FILE: guide/01-KERNEL/ex00/BUILD.md
================================================
# Процесс сборки

Для сборки нам понадобится собрать кросс-компилятор gcc для i386 архитектуры процессора. Удобнее использовать готовый отсюда: https://wiki.osdev.org/GCC_Cross-Compiler#Prebuilt_Toolchains. Для компьютеров на Linux с x86_64 архитектурой:
```
wget http://newos.org/toolchains/i386-elf-4.9.1-Linux-x86_64.tar.xz
mkdir /usr/local/i386elfgcc
tar -xf i386-elf-4.9.1-Linux-x86_64.tar.xz -C /usr/local/i386elfgcc --strip-components=1
export PATH=$PATH:/usr/local/i386elfgcc/bin
```
Далее можно использовать установленный тулчейн с помощью команд `i386-elf-gcc` для gcc, `i386-elf-ld` для линкера, и т.д.

1. Итак, сначала компилируем загрузочный сектор. Получится бинарный файл.
```
nasm bootsect.asm -f bin -o bootsect.bin
```
Флаги:
- `-f bin` - формат бинарный
- `-o bootsect.bin` - output file = bootsect.bin
2. Далее компилируем ядро в объектный файл, чтобы потом собрать его вместе с kernel_entry.
```
i386-elf-gcc -ffreestanding -c kernel.c -o kernel.o
```
Флаги:
- `-ffreestanding` - в режиме `freestanding`, единственные доступные заголовочные файлы стандартной библиотеки это <float.h>, <iso646.h>, <limits.h>, <stdarg.h>, <stdbool.h>, <stddef.h> и <stdint.h>. Также эта опция указывает компилятору не полагаться на то, что стандартные функции имеют их обычное определение. Это предотвратит компилятор от оптимизации, которую он делает на основе предположений о поведении функций из стандартных библиотек. Например в `hosted` режиме (противополжен `freestanding`), gcc знает о том, что имеющаяся библиотека соответствует спецификации стандарта языка Си. Он может преобразовать `printf("hi\n")` в `puts("hi")`, т.к. имеет представление из определения стандартной IO библиотеки что эти две функции ведут себя одинаково в данном случае. А с флагом `-ffreestanding` gcc не проводит подобных оптимизаций. Подобнее: http://cs107e.github.io/guides/gcc/, https://stackoverflow.com/questions/18711719/freestanding-gcc-and-builtin-functions.
- `-c kernel.c` - флаг указывает на то, что файл после компиляции не нужно линковать
- `-o kernel.o` - output object file
3. Компилируем `kernel_entry.asm`
```
nasm kernel_entry.asm -f elf -o kernel_entry.o
```
Флаги:
- `-f elf` - формат: ELF (Executable and Linkable Format)

4. Линкуем `kernel_entry.o` и `kernel.o` вместе в один бинарный файл `kernel.bin`
```
i386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o --oformat binary
```
Флаги:
- `-o kernel.bin` - output file = kernel.bin
- `-Ttext 0x1000` - этот флаг распологает секцию `.text` по адресу `0x1000`.
- `--oformat binary` - output format = binary

5. Соединяем два файла `bootsect.bin` и `kernel.bin` в один `os-image.bin` с помощью утилиты cat
```
cat bootsect.bin kernel.bin > os-image.bin
```
6. Запускаем образ ОС с помощью эмулятора qemu
```
qemu-system-i386 -fda os-image.bin
```
Флаги:
- `-fda` - использовать файл как образ флоппи диска (дискеты).

Вуаля!

> Подробнее: [Makefile](build/Makefile)


================================================
FILE: guide/01-KERNEL/ex00/README.md
================================================
# `01-KERNEL / ex00`: Процесс сборки, структура проекта, дебаг
> Новые штучки: `Makefile`, `gcc`, `gdb`

Новые файлы упражнения, которые нужно посмотреть:
- `boot/newline.asm`
- `boot/bootsect.asm`
- `boot/kernel_entry.asm`
- `kernel/kernel.c`
- `build/Makefile`

## Процесс сборки
Описан тут [BUILD.md](BUILD.md)

## Структура проекта
- `boot` - файлы загрузочного сектора `.asm`
- `kernel` - файлы ядра `.c`
- `build` - мейкфайл и скомпилированные файлы, включая образ ОС `.o` `.bin` `.elf`

## Дебаг

1. Запускаем программу make с таргетом debug
```
$ make debug
```
2. Запускаем GDB в отдельном терминале
```
$ gdb
```
3. Подключаемся к порту 1234, который по дефолту прослушивает QEMU
```
(gdb) target remote localhost:1234
```
4. Загружаем символьный файл `kernel.elf` который предоставляет GDB полезную информацию для дебага. Это нужно для перевода символов (названия функций и переменных) в адреса, номеров строк в адреса кода и так далее.
```
(gdb) symbol-file kernel.elf
```
4. Ставим брейкпоинт на функции `kmain`
```
(gdb) b kmain
```
5. Запускаем программу
```
(gdb) continue
```
6. Идем читать гайд по GDB
```
Например тут https://habr.com/ru/post/491534/
```


================================================
FILE: guide/01-KERNEL/ex00/boot/bootsect.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	01-KERNEL
; File:		ex00 / bootsect.asm
; Title:	Программа загрузочного сектора, которая загружает ядро, написанное
;			на C в 32-битный защищенный режим.
; ------------------------------------------------------------------------------
; Description:
; ------------------------------------------------------------------------------


[org 0x7c00]

KERNEL_OFFSET equ 0x1000	; Смещение в памяти, из которого мы загрузим ядро

	mov [BOOT_DRIVE], dl	; BIOS stores our boot drive in DL , so it ’s
							; best to remember this for later. (Remember that
							; the BIOS sets us the boot drive in 'dl' on boot)
	mov bp, 0x9000			; Устанавливаем стек
	mov sp, bp

	mov bx, MSG_REAL_MODE	; Печатаем сообщение
	call print_string

	call load_kernel		; Загружаем ядро
	call switch_to_pm		; Переключаемся в Защищенный Режим
	jmp $

%include "print_string.asm"		; ф. печати строки
%include "print_hex.asm"		; ф. печати 16-ричного числа
%include "disk_load.asm"		; ф. чтения диска
%include "print_string_pm.asm"	; ф. печати строки (32PM)
%include "switch.asm"			; ф. переключения в 32PM
%include "gdt.asm"				; таблица GDT

[bits 16]

load_kernel:
	mov bx, MSG_LOAD_KERNEL
	call print_string		; Печатаем сообщение о том, то мы загружаем ядро
							; Устанавливаем параметры для функции disk_load:
	mov bx, KERNEL_OFFSET	; Загрузим данные в место памяти по		TODO: disk_load main lookup
							; смещению KERNEL_OFFSET
	mov dh, 16				; Загрузим много секторов. *
	mov dl, [BOOT_DRIVE]	; Загрузим данные из BOOT_DRIVE (Возвращаем BOOT_DRIVE)
	call disk_load			; Вызываем функцию disk_load
	ret


[bits 32]					; Сюда мы попадем после переключения в 32PM

BEGIN_PM:
	mov ebx, MSG_PROT_MODE
	call print_string_pm	; Печатаем сообщение об успешной загрузке в 32PM
	call KERNEL_OFFSET		; Переходим в адрес, по которому загрузился код ядра
	jmp $


BOOT_DRIVE:			db 0
MSG_REAL_MODE:		db "Started in 16-bit Real Mode", 0
MSG_PROT_MODE:		db "Successfully landed in 32-bit Protected mode", 0
MSG_LOAD_KERNEL:	db "Loading kernel into VIDEO_MEMORY", 0

times 510-($-$$) db 0
dw 0xaa55

; ------
; * - Забавный факт: если загрузить меньше секторов, то мы столкнемся со
; странными ошибками когда будем писать ядро на Си. Например, аргументы
; функции могут быть повреждены, а строки "обрезаны".


================================================
FILE: guide/01-KERNEL/ex00/boot/disk_load.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / disk_load.asm
; Title:	Функция чтения диска
; ------------------------------------------------------------------------------
; Description:
;	Чтобы лучше понять, что здесь происходит, разберитесь с тем, что такое CHS
;	по ссылке https://ru.wikipedia.org/wiki/CHS
; ------------------------------------------------------------------------------


disk_load:
	push dx
	mov ah, 0x02			; Указвыаем БИОСу что нам нужна рутина чтения диска
							; Указываем что нам нужно:
	mov al, dh				; 1. Прочитать кол-во секторов, равное значению в dh
	mov ch, 0x00			; 2. Выбрать нулевой цилиндр
	mov dh, 0x00			; 3. Выбрать нулевую головку
	mov cl, 0x02			; 4. Начинать считывать со второго сектора (т.е.
							; первый свободный сектор сразу после загруочного 
							; сектора, т.к. загрузочный сектор находится по 
							; адресу 0x01)
	int 0x13				; Вызываем прерывание для чтения

							; У БИОСа может не получиться прочитать диск, и
							; чтобы дать нам знать что произошла ошибка, он,
							; во-первых, обновляет специальный флаг CF (carry 
							; flag) специальным значением, которое означает 
							; ошибку, а во-вторых, кладет в регистр AL кол-во 
							; секторов, которые у него получилось прочитать.
	
	jc disk_error			; jc - инструкция для прыжка на указанную метку,
							; которая выполняется только если CF (carry flag)
							; сигнализирует об ошибке

	pop dx					; Восстанавливаем регистр DX из стека
	cmp dh, al				; если AL (кол-во прочитанных секторов) != DH
							; (предполагаемое кол-во секторов),
	jne disk_sectors_error	; то выводим на экран сообщение об ошибке и зависаем
							; (то есть запускаем бесконечный цикл)
	jmp disk_success
	jmp disk_exit			; Заканчиваем выполнение функции

disk_success:
	mov bx, SUCCESS_MSG
	call print_string
	jmp disk_exit

disk_error:
	mov bx, DISK_ERR_MSG	; Перемещаем в BX сообщение об ошибке
	call print_string		; Выводим его на экран
	mov dh, al
	call print_hex
	jmp disk_loop			; бесконечный цикл

disk_sectors_error:
	mov bx, SECTORS_ERR_MSG
	call print_string

SUCCESS_MSG:
	db "Disk was successfully read ", 0

DISK_ERR_MSG:
	db "Disk read error! ", 0

SECTORS_ERR_MSG:
	db "Incorrect number of sectors read ", 0

disk_loop:
	jmp $

disk_exit:
	ret

================================================
FILE: guide/01-KERNEL/ex00/boot/gdt.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / gdt.asm
; Title:	Определяем GDT (глобальная таблица дескрипторов)
; ------------------------------------------------------------------------------
; Description:
; 	Способ, которым процессор переводит логический адрес в физический, в
; 	32-битном защищенном режиме отличается от 16-битного реального режима.
; 	Вместо того, чтобы умножить значение регистра сегмента на 16 и прибавить к
; 	этому "смещение" (offset), регистр сегмента становится индексом 
; 	определенного дескриптора сегмента в GDT.
; 	Дескриптор сегмента - это 8-битная структура, которая определяет свойства
; 	этого сегмента:
; 		- Base address (32 bits), определяющий откуда сегмент начинается в
; 		физической памяти.
; 		- Segment Limit (20 bits), определяющий размер сегмента
;		- Различные флаги, которые устанавливают каким образом процессор будет
; 		"относиться" к сегментам, например уровень привилегий и т.д.
;
; 	Флаги:
;	* 1-ые флаги:
; 		- present flag (флаг присутствия). Если его значение "1", то это 
;		указывает, что сегмент присутствует в памяти (это нужно для виртуальной 
;		памяти)
;		- privilege flag (флаг привилегии). Значение "0" - самый высокий уровень
;		привилегии
;		- descriptor type (тип дескриптора). "1" - для сегмента кода или 
;		сегмента данных
; 	* Флаги типа:
;		- code (флаг кода). "1" - для кода, "0" - для даннных
;		- conformig (флаг подчинения). "0" - чтобы код в другом сегменте с
;		более низким уровнем привилегий не смог вызвать код из этого сегмента - 
;		это ключ к защите памяти (memory protection).
;		- readable (читаемость). "1" - если читаемый, "0" - только исполняемый.
;		- writable. Разрешает сегменту данных быть записываемым, в противном
;		случае, он будет доступен только для чтения.
;		- accessed (флаг доступа). Этот флаг устанавливается, когда происходит
; 		обращение к сегменту.
;		- expand down. Флаг (бит), позволяющий сегменту расширяться вниз.
;	* 2-ые флаги:
;		- granulariy (гранулярность). "0" - байтовая гранулярность, лимит
; 		задается в байтах, если "1" - страничная гранулярность, в 4кб блоках.
;		Если выбрать страничную гранулярность и установить значение лимита как
;		0xfffff, то лимит умножится на 16*16*16 (4кб), и лимит станет 0xfffff000
; 		позволяя нашему сегменту занять 4гб места в памяти.
;		- 32-bit default. "1" - т.к. наш сегмент будет содержать 32-битный код.
;		- 64-bit code segment. "0" - т.к. не используется на 32-битных 
;		процессорах.
;		- AVL (available). Определяет доступность сегмента для использования 
;		системным программным обеспечением (используются только ОС).
; ------------------------------------------------------------------------------

gdt_start:					; Эта пустая метка нужно для того чтобы удобнее
							; посчитать размер GDT для ее дескриптора 
							; (end - start)

gdt_null:					; Необходимый нулевой дескриптор для GDT
	dd 0x0					; dd - define double (двойное слово, т.е. 4 байта)
	dd 0x0

gdt_code:					; Определяем дескриптор сегмента кода
	dw 0xffff				; Limit (bits 0-15)
	dw 0x0					; Base (bits 0-15)
	db 0x0					; Base (bits 16-23)
	db 10011010b			; Первые флаги + флаги типа (смотрим по битам)
							; present: 1, privilege: 00, descriptor type: 1
							; code: 1, conforming: 0, readable: 1, accessed: 0
	db 11001111b			; Вторые флаги + длина сегмента (bits 16-19):
							; granularity: 1, 32-bit default: 1,
							; 64-bit default: 0, AVL: 0
	db 0x0					; Base (bits 24-31)

gdt_data:					; Определяем дескриптор сегмента кода
	dw 0xffff				; Limit (bits 0-15)
	dw 0x0					; Base (bits 0-15)
	db 0x0					; Base (bits 16-23)
	db 10010010b			; Первые флаги + флаги типа (смотрим по битам)
	db 11001111b			; Вторые флаги + длина сегмента (bits 16-19)
	db 0x0					; Base (bits 24-31)

gdt_end:					; Пустая метка


gdt_descriptor:						; дескриптор GDT
	dw gdt_end - gdt_start - 1		; Размер GDT
	dd gdt_start					; Адрес начала GDT


CODE_SEG equ gdt_code - gdt_start	; Определяем некоторые константы. 
DATA_SEG equ gdt_data - gdt_start	; Они понадобятся для регистров сегментов в
									; 32-битном защищенном режиме. Например,
									; когда мы установим регистр DS = 0x10 (т.е
									; 16 байтов) в этом режиме, процессор
									; поймет что мы хотим использовать сегмент,
									; находящийся в смещении 0x10 в нашем GDT,
									; т.е. в нашем случае это сегмент данных
									; (0x0 -> NULL, 0x08 -> сегмент кода,
									; 0x10 -> сегмент данных)

================================================
FILE: guide/01-KERNEL/ex00/boot/kernel_entry.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	01-KERNEL
; File:		ex00 / kernel_entry.asm
; Title:	Код, служащий входной точкой для функции kmain из kernel.c
; ------------------------------------------------------------------------------
; Description:
; ------------------------------------------------------------------------------


[bits 32]
[extern kmain]	; Определяем 'внешнюю' штуку с названием kmain - она понадобится
				; линкеру чтобы собрать все вместе
call kmain		; Вызываем определенную выше функцию, которая будет доступна
				; после линковки. Это функция kmain из kernel.c
jmp $


================================================
FILE: guide/01-KERNEL/ex00/boot/print_hex.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex05 / print_hex.asm
; Title:	Функция вывода шестнадцатеричного числа на экран
; ------------------------------------------------------------------------------
; Description: 
;	Как выглядит регистр EAX (32 b = 32 бита):
;
;						EAX (32 b)
;		---------------------------------------------
;		|					|			|			|
;		|					|  AH (8 b)	|  AL (8 b) |
;		|					|			|			|
;		---------------------------------------------
;									AX (16 b)
;
;	Как видим, EAX содержит в себе регистр AX, который в свою очередь разделен 
;	на AH (a high, верхний) и AL (a low, нижний).
; ------------------------------------------------------------------------------


; Помним, что в main.asm:
; DX  =  0x1fb6
; BX  =  "0x0000" (а точнее, BX="0", т.к. указывает на первый элемент)

print_hex:
	pusha						; Сохраняем значения регистров в стеке
	mov cx, 0					; Регистр CX будет служить счетчиком

loop1:
	cmp cx, 4					; if (CX < 4)
	jl print					; Переходим к print
	jmp end						; else переходим к end

print:
	mov ax, dx					; В AX теперь 0x1fb6
	and ax, 0x000f				; В AX теперь 6 (последняя цифра от 0x1fb6).
	cmp ax, 9					; if (AX > 9) (проверяем обозначается ли число
								; буквой т.к. мы помним что цифра больше 9 в 
								; 16-ричной системе исчисления обозначается
								; буквой латинского алфавита)
	jg num_to_abc				; Переходим к num_to_abc
	jmp next

num_to_abc:						; Перевод числа в букву (так, как она бы
								; выглядела в 16-ричной СИ), например число 15
								; в десятичной будет равно f в 16-ричной
	add ax, 39					; Добавляем к этому числу 39, чтобы затем еще
								; добавить 48 ('0'), получая код 
								; соответсвующего символа в ASCII (например,
								; f (как число, то есть 15) + 39 + 48 (код '0')
								; = 102 (то есть 'f' в ASCII, как нам и нужно)
	jmp next

next:
	add ax, '0'					; Добавляем 48 в ax
	mov bx, HEX_OUT + 5			; Теперь bx указывает на последний символ строки
								; HEX_OUT
	sub bx, cx					; BX = BX - counter (для итерации)
	mov [bx], al				; Так как мы разыменовываем bx (вот так: [bx]),
								; то [bx] это не регистр, а ссылка на память,
								; и так как ax = 16 бит (2 байта), чтобы нам не
								; перезаписать лишнюю память, в [bx] мы помещаем
								; не ax, а al, размер которого равен 1-му байту.
	ror dx, 4					; было: 0x1fb6, стало: 0x61fb (переносим
								; последнюю цифру в начало)
	inc cx						; counter++
	jmp loop1					; переходим обратно к loop1

end:
	mov bx, HEX_OUT				; Делаем так, чтобы bx снова указывал на первый
								; символ строки HEX_OUT
	call print_string			; выводи на экран строку из регистра bx
	popa						; Возвращаем регистрам их изначальное значение
	ret							; Заканчиваем выполнение функции


HEX_OUT:	db "0x0000", 0

================================================
FILE: guide/01-KERNEL/ex00/boot/print_string.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex04 / print_string.asm
; Title:	Функция вывода строки на экран
; ------------------------------------------------------------------------------
; Description: null
; ------------------------------------------------------------------------------


print_string:			; Функция вывода строки на экран.
	pusha				; Когда мы используем функции, мы можем модифицировать
						; регистры прямо в них, что нарушает чистоту функции,
						; т.е. мы можем перезаписывать какие-то внешние
						; данные. Для этого мы добавляем значение всех регистров
						; в стек с помощью команды pusha, а в конце функции
						; мы возвращаем регистрам их изначальные значения,
						; которые возьмем из стека (команда popa).
						
	mov ah, 0x0e		; tele-type mode
	loop:				; Метка loop (= цикл)
		mov al, [bx]	; Перемещаем значение BX в AL, т.к. мы помним что в BX
						; лежит первый символ строки (см. ./main.asm)
		cmp al, 0		; Команда cmp для сравнения AL и 0.
		je newline		; (if) je = "jump if equal",
						; т.е. перемещаемся к коду с меткой return если AL == 0
		jmp put_char	; (else) в противном случае перемещаемся к put_char.

put_char:				; Метка put_char - вывод символа на экран.
	int 0x10			; Вызываем прерывание, которое позволяет вывести 
						; на экран значение регистра AL, в котором лежит [bx].
	inc bx				; inc <регистр> - увеличить на 1.
	jmp loop			; Возвращаемся обратно к циклу.

newline:
	mov ah, 0x0e
	mov al, 0x0a	; (0x0a) \n - new line
	int 0x10
	mov al, 0x0d	; (0x0d) \r - carriage return
	int 0x10
	jmp return

return:					; Метка return - завершаем функцию
	popa				; Возвращаем регистрам их изначальные значения.
	ret					; Заканчиваем выполнение функции.


================================================
FILE: guide/01-KERNEL/ex00/boot/print_string_pm.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / print_string_pm.asm
; Title:	Функция вывода строки на экран в 32-битном защищенном режиме
; ------------------------------------------------------------------------------
; Description:
;	Плюсы 32-битного режима: возможность использовать 32-битные регистры и
;	адрессацию памяти, защищенную память виртуальную память
;	Минусы: отсутствие БИОС прерываний, требование наличия GDT (об этом позже)
;	В этой программе мы напишем новую функцию печати строки, но без прерываний
;	БИОСа, а напрямую манипулируя VGA видеопамятью, вместо вызова int 0x10.
;	VGA память размещена начиная с адреса 0xb8000, и у VGA имеется специальный
;	текстовый режим, поэтому нам не придется напрямую рисовать пиксели.
;	Особенности:
;	1. Символ представляется в виде 2-х байтов. Первый байт - сам символ,
;	второй байт - 4 бита на цвет текста и еще 4 на цвет фона. 
;	Например, чтобы распечатать символ 'A' белым текстом на черном фоне, мы
;	испольузуем 0x410f: 0x41 == 'A', 0 == белый, f == черный.
; ------------------------------------------------------------------------------


[bits 32]					; Используем 32-битный режим

							; Определяем некоторые константы
VIDEO_MEMORY equ 0xb8000	; = адрес начала памяти VGA
WHITE_ON_BLACK equ 0x0f		; = цвет символов (0x0f - белый на черном)

print_string_pm:
	pusha
	mov edx, VIDEO_MEMORY	; Перемещаем в EDX адрес начала массива видеопамяти

print_string_pm_loop:
							; Помним, что AX (2б) = AH(1б) и AL(1б)
	mov al, [ebx]			; Сохраняем символ из EBX в AL
	mov ah, WHITE_ON_BLACK	; Устанавливаем цвет символов в AH
							; таким образом AX получается равен символу + цвету

	cmp al, 0				; if (AL == 0), т.е. если конец массива, то
	je print_string_pm_done	; заканчиваем выполнение функции
							; else:
	mov [edx], ax			; video_memory[EDX] = AX
	add ebx, 1				; переходим к следующему символу (+1, просто массив)
	add edx, 2				; переходим к следующему адресу в VGA (+2 т.к.
							; два байта на символ)

	jmp print_string_pm_loop

print_string_pm_done:
	popa
	ret


================================================
FILE: guide/01-KERNEL/ex00/boot/switch.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / switch.asm
; Title:	Переключаемся в PM (Protected mode, защищенный режим)
; ------------------------------------------------------------------------------
; Description:
;	Чтобы сделать свитч, нам нужно:
;		1. Отключить прерывания (процессор просто будет их игнорировать), т.к.
; 		в PM, прерывания обрабатываются совершенно по-другому в отличие от
;		Real Mode. Даже если процессор и смог бы распределить сигналы прерываний
;		по конкретным BIOS обработчикам прерываний, БИОС обработчики будут
;		обрабатывать 16-битный код, что повлекло бы за собой ошибки.
;		2. Загрузить GDT дескриптор
;		3. Изменяем первый бит регистра управления cr0 на "1"
;		https://en.wikipedia.org/wiki/Control_register#CR0
;		4. Т.к. процессор использует специальную технику, которая называется
;		называется pipelining (гугли: вычислительный конвейер, полезная статья
;		на хабре: https://habr.com/ru/post/182002/), и поэтому сразу после того,
;		как перевести процессор в PM (что мы и сделали в предыдущем пункте), 
;		нам нужно заставить процессор завершить всю работу в конвейере, чтобы
;		быть уверенным, что все будущие инструкции будут выполнены корректно.
;		Конвейер загружает в себя некоторые количество последующих после текущей
;		инструкций, но конвейеру не очень нравятся инструкции типа call и jmp,
;		т.к. процессор не знает полностью какие инструкции будут следовать за
;		ними, в особенности если мы вызовем jmp или call "прыгая" в другой
;		сегмент. Поэтому нам нужно сделать "дальний прыжок", чтобы завершить
;		обрабатываемые в конвейере инструкции.
;		Сам прыжок: 	jmp <сегмент>:<адрес смещения>
; ------------------------------------------------------------------------------


[bits 16]

switch_to_pm:
	cli						; Отключаем прерывания (cli = clear interrupts)
	
	lgdt [gdt_descriptor]	; Загружаем GDT дескриптор (lgdt = load GDT)

	mov eax, cr0			; Чтобы перейти в PM, нужно чтобы первый бит
	or eax, 0x1				; регистра управления cr0 был 1
	mov cr0, eax

	jmp CODE_SEG:init_pm	; Делаем "дальний прыжок" в наш новый 32-битный
							; сегмент кода. Это так же заставляет процессор
							; завершить обрабатываемые в конвейере инструкции. 

[bits 32]

init_pm:					; в PM, наши старые сегменты бесполезны, поэтому
	mov ax, DATA_SEG		; мы делаем так, чтобы регистры всех сегментов
	mov ds, ax				; указывали на сегмент данных, который мы определили
	mov ss, ax				; в GDT (см. ./gdt.asm)
	mov es, ax
	mov fs, ax
	mov gs, ax

	mov ebp, 0x90000		; Обновляем позицию стека, чтобы он был на самом
	mov esp, ebp			; верху свободного места

	call BEGIN_PM			; Вызываем функцию из ./main.asm

================================================
FILE: guide/01-KERNEL/ex00/build/Makefile
================================================
# флаг для дебага для gcc
CFLAGS = -g

run: os-image.bin
	qemu-system-i386 -fda os-image.bin
	make clean

debug: os-image.bin kernel.elf
	# kernel.elf нужен для gdb как symbol-file
	# флаг -s указывает qemu открыть и прослушивать 1234 порт
	# чтобы gdb смог соединиться с ним для дебага
	# флаг -S указыает QEMU не запускать образ и подождать подключений
	qemu-system-i386 -s -S -fda os-image.bin
	make clean

os-image.bin: bootsect.bin kernel.bin
	cat bootsect.bin kernel.bin > os-image.bin

bootsect.bin:
	cd ../boot/ && nasm bootsect.asm -f bin -o ../build/bootsect.bin && cd -

kernel.bin: kernel_entry.o kernel.o
	i386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o --oformat binary

kernel_entry.o:
	nasm ../boot/kernel_entry.asm -f elf -o kernel_entry.o

kernel.o:
	i386-elf-gcc ${CFLAGS} -ffreestanding -c ../kernel/kernel.c

kernel.elf: kernel_entry.o kernel.o 
	# как kernel.bin только без --oformat binary
	i386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o -o kernel.elf

clean:
	rm *.bin *.o *.elf


================================================
FILE: guide/01-KERNEL/ex00/kernel/kernel.c
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex00 / kernel / kernel.c
*	Title:	Программа на Си, в которую мы загрузимся после boot'а.
* ------------------------------------------------------------------------------
*	Description:
* ----------------------------------------------------------------------------*/


void 	func() {}		/* Эта функция нужна чтобы показать, что ядро будет
						загружаться не с начала этого файла (0x00), а с
						функции kmain. Если бы это было не так, то вместо kmain
						код бы начал выполняться с этой функции, а т.к. она
						ничего не делает, то мы бы не получили результата */

void	kmain()
{
	char *video_memory = (char *) 0xb8000;	/* Распологаем символ 'x' по */
	*video_memory = 'x';					/* адресу 0xb8000 */
}


================================================
FILE: guide/01-KERNEL/ex01/boot/bootsect.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	01-KERNEL
; File:		ex00 / bootsect.asm
; Title:	Программа загрузочного сектора, которая загружает ядро, написанное
;			на C в 32-битный защищенный режим.
; ------------------------------------------------------------------------------
; Description:
; ------------------------------------------------------------------------------


[org 0x7c00]

KERNEL_OFFSET equ 0x1000	; Смещение в памяти, из которого мы загрузим ядро

	mov [BOOT_DRIVE], dl	; BIOS stores our boot drive in DL , so it ’s
							; best to remember this for later. (Remember that
							; the BIOS sets us the boot drive in 'dl' on boot)
	mov bp, 0x9000			; Устанавливаем стек
	mov sp, bp

	mov bx, MSG_REAL_MODE	; Печатаем сообщение
	call print_string

	call load_kernel		; Загружаем ядро
	call switch_to_pm		; Переключаемся в Защищенный Режим
	jmp $

%include "print_string.asm"		; ф. печати строки
%include "print_hex.asm"		; ф. печати 16-ричного числа
%include "disk_load.asm"		; ф. чтения диска
%include "print_string_pm.asm"	; ф. печати строки (32PM)
%include "switch.asm"			; ф. переключения в 32PM
%include "gdt.asm"				; таблица GDT

[bits 16]

load_kernel:
	mov bx, MSG_LOAD_KERNEL
	call print_string		; Печатаем сообщение о том, то мы загружаем ядро
							; Устанавливаем параметры для функции disk_load:
	mov bx, KERNEL_OFFSET	; Загрузим данные в место памяти по		TODO: disk_load main lookup
							; смещению KERNEL_OFFSET
	mov dh, 16				; Загрузим много секторов. *
	mov dl, [BOOT_DRIVE]	; Загрузим данные из BOOT_DRIVE (Возвращаем BOOT_DRIVE)
	call disk_load			; Вызываем функцию disk_load
	ret


[bits 32]					; Сюда мы попадем после переключения в 32PM

BEGIN_PM:
	mov ebx, MSG_PROT_MODE
	call print_string_pm	; Печатаем сообщение об успешной загрузке в 32PM
	call KERNEL_OFFSET		; Переходим в адрес, по которому загрузился код ядра
	jmp $


BOOT_DRIVE:			db 0
MSG_REAL_MODE:		db "Started in 16-bit Real Mode", 0
MSG_PROT_MODE:		db "Successfully landed in 32-bit Protected mode", 0
MSG_LOAD_KERNEL:	db "Loading kernel into VIDEO_MEMORY", 0

times 510-($-$$) db 0
dw 0xaa55

; ------
; * - Забавный факт: если загрузить меньше секторов, то мы столкнемся со
; странными ошибками когда будем писать ядро на Си. Например, аргументы
; функции могут быть повреждены, а строки "обрезаны".


================================================
FILE: guide/01-KERNEL/ex01/boot/disk_load.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / disk_load.asm
; Title:	Функция чтения диска
; ------------------------------------------------------------------------------
; Description:
;	Чтобы лучше понять, что здесь происходит, разберитесь с тем, что такое CHS
;	по ссылке https://ru.wikipedia.org/wiki/CHS
; ------------------------------------------------------------------------------


disk_load:
	push dx
	mov ah, 0x02			; Указвыаем БИОСу что нам нужна рутина чтения диска
							; Указываем что нам нужно:
	mov al, dh				; 1. Прочитать кол-во секторов, равное значению в dh
	mov ch, 0x00			; 2. Выбрать нулевой цилиндр
	mov dh, 0x00			; 3. Выбрать нулевую головку
	mov cl, 0x02			; 4. Начинать считывать со второго сектора (т.е.
							; первый свободный сектор сразу после загруочного 
							; сектора, т.к. загрузочный сектор находится по 
							; адресу 0x01)
	int 0x13				; Вызываем прерывание для чтения

							; У БИОСа может не получиться прочитать диск, и
							; чтобы дать нам знать что произошла ошибка, он,
							; во-первых, обновляет специальный флаг CF (carry 
							; flag) специальным значением, которое означает 
							; ошибку, а во-вторых, кладет в регистр AL кол-во 
							; секторов, которые у него получилось прочитать.
	
	jc disk_error			; jc - инструкция для прыжка на указанную метку,
							; которая выполняется только если CF (carry flag)
							; сигнализирует об ошибке

	pop dx					; Восстанавливаем регистр DX из стека
	cmp dh, al				; если AL (кол-во прочитанных секторов) != DH
							; (предполагаемое кол-во секторов),
	jne disk_sectors_error	; то выводим на экран сообщение об ошибке и зависаем
							; (то есть запускаем бесконечный цикл)
	jmp disk_success
	jmp disk_exit			; Заканчиваем выполнение функции

disk_success:
	mov bx, SUCCESS_MSG
	call print_string
	jmp disk_exit

disk_error:
	mov bx, DISK_ERR_MSG	; Перемещаем в BX сообщение об ошибке
	call print_string		; Выводим его на экран
	mov dh, al
	call print_hex
	jmp disk_loop			; бесконечный цикл

disk_sectors_error:
	mov bx, SECTORS_ERR_MSG
	call print_string

SUCCESS_MSG:
	db "Disk was successfully read ", 0

DISK_ERR_MSG:
	db "Disk read error! ", 0

SECTORS_ERR_MSG:
	db "Incorrect number of sectors read ", 0

disk_loop:
	jmp $

disk_exit:
	ret

================================================
FILE: guide/01-KERNEL/ex01/boot/gdt.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / gdt.asm
; Title:	Определяем GDT (глобальная таблица дескрипторов)
; ------------------------------------------------------------------------------
; Description:
; 	Способ, которым процессор переводит логический адрес в физический, в
; 	32-битном защищенном режиме отличается от 16-битного реального режима.
; 	Вместо того, чтобы умножить значение регистра сегмента на 16 и прибавить к
; 	этому "смещение" (offset), регистр сегмента становится индексом 
; 	определенного дескриптора сегмента в GDT.
; 	Дескриптор сегмента - это 8-битная структура, которая определяет свойства
; 	этого сегмента:
; 		- Base address (32 bits), определяющий откуда сегмент начинается в
; 		физической памяти.
; 		- Segment Limit (20 bits), определяющий размер сегмента
;		- Различные флаги, которые устанавливают каким образом процессор будет
; 		"относиться" к сегментам, например уровень привилегий и т.д.
;
; 	Флаги:
;	* 1-ые флаги:
; 		- present flag (флаг присутствия). Если его значение "1", то это 
;		указывает, что сегмент присутствует в памяти (это нужно для виртуальной 
;		памяти)
;		- privilege flag (флаг привилегии). Значение "0" - самый высокий уровень
;		привилегии
;		- descriptor type (тип дескриптора). "1" - для сегмента кода или 
;		сегмента данных
; 	* Флаги типа:
;		- code (флаг кода). "1" - для кода, "0" - для даннных
;		- conformig (флаг подчинения). "0" - чтобы код в другом сегменте с
;		более низким уровнем привилегий не смог вызвать код из этого сегмента - 
;		это ключ к защите памяти (memory protection).
;		- readable (читаемость). "1" - если читаемый, "0" - только исполняемый.
;		- writable. Разрешает сегменту данных быть записываемым, в противном
;		случае, он будет доступен только для чтения.
;		- accessed (флаг доступа). Этот флаг устанавливается, когда происходит
; 		обращение к сегменту.
;		- expand down. Флаг (бит), позволяющий сегменту расширяться вниз.
;	* 2-ые флаги:
;		- granulariy (гранулярность). "0" - байтовая гранулярность, лимит
; 		задается в байтах, если "1" - страничная гранулярность, в 4кб блоках.
;		Если выбрать страничную гранулярность и установить значение лимита как
;		0xfffff, то лимит умножится на 16*16*16 (4кб), и лимит станет 0xfffff000
; 		позволяя нашему сегменту занять 4гб места в памяти.
;		- 32-bit default. "1" - т.к. наш сегмент будет содержать 32-битный код.
;		- 64-bit code segment. "0" - т.к. не используется на 32-битных 
;		процессорах.
;		- AVL (available). Определяет доступность сегмента для использования 
;		системным программным обеспечением (используются только ОС).
; ------------------------------------------------------------------------------

gdt_start:					; Эта пустая метка нужно для того чтобы удобнее
							; посчитать размер GDT для ее дескриптора 
							; (end - start)

gdt_null:					; Необходимый нулевой дескриптор для GDT
	dd 0x0					; dd - define double (двойное слово, т.е. 4 байта)
	dd 0x0

gdt_code:					; Определяем дескриптор сегмента кода
	dw 0xffff				; Limit (bits 0-15)
	dw 0x0					; Base (bits 0-15)
	db 0x0					; Base (bits 16-23)
	db 10011010b			; Первые флаги + флаги типа (смотрим по битам)
							; present: 1, privilege: 00, descriptor type: 1
							; code: 1, conforming: 0, readable: 1, accessed: 0
	db 11001111b			; Вторые флаги + длина сегмента (bits 16-19):
							; granularity: 1, 32-bit default: 1,
							; 64-bit default: 0, AVL: 0
	db 0x0					; Base (bits 24-31)

gdt_data:					; Определяем дескриптор сегмента кода
	dw 0xffff				; Limit (bits 0-15)
	dw 0x0					; Base (bits 0-15)
	db 0x0					; Base (bits 16-23)
	db 10010010b			; Первые флаги + флаги типа (смотрим по битам)
	db 11001111b			; Вторые флаги + длина сегмента (bits 16-19)
	db 0x0					; Base (bits 24-31)

gdt_end:					; Пустая метка


gdt_descriptor:						; дескриптор GDT
	dw gdt_end - gdt_start - 1		; Размер GDT
	dd gdt_start					; Адрес начала GDT


CODE_SEG equ gdt_code - gdt_start	; Определяем некоторые константы. 
DATA_SEG equ gdt_data - gdt_start	; Они понадобятся для регистров сегментов в
									; 32-битном защищенном режиме. Например,
									; когда мы установим регистр DS = 0x10 (т.е
									; 16 байтов) в этом режиме, процессор
									; поймет что мы хотим использовать сегмент,
									; находящийся в смещении 0x10 в нашем GDT,
									; т.е. в нашем случае это сегмент данных
									; (0x0 -> NULL, 0x08 -> сегмент кода,
									; 0x10 -> сегмент данных)

================================================
FILE: guide/01-KERNEL/ex01/boot/kernel_entry.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	01-KERNEL
; File:		ex00 / kernel_entry.asm
; Title:	Код, служащий входной точкой для функции kmain из kernel.c
; ------------------------------------------------------------------------------
; Description:
; ------------------------------------------------------------------------------


[bits 32]
[extern kmain]	; Определяем 'внешнюю' штуку с названием kmain - она понадобится
				; линкеру чтобы собрать все вместе
call kmain		; Вызываем определенную выше функцию, которая будет доступна
				; после линковки. Это функция kmain из kernel.c
jmp $


================================================
FILE: guide/01-KERNEL/ex01/boot/print_hex.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex05 / print_hex.asm
; Title:	Функция вывода шестнадцатеричного числа на экран
; ------------------------------------------------------------------------------
; Description: 
;	Как выглядит регистр EAX (32 b = 32 бита):
;
;						EAX (32 b)
;		---------------------------------------------
;		|					|			|			|
;		|					|  AH (8 b)	|  AL (8 b) |
;		|					|			|			|
;		---------------------------------------------
;									AX (16 b)
;
;	Как видим, EAX содержит в себе регистр AX, который в свою очередь разделен 
;	на AH (a high, верхний) и AL (a low, нижний).
; ------------------------------------------------------------------------------


; Помним, что в main.asm:
; DX  =  0x1fb6
; BX  =  "0x0000" (а точнее, BX="0", т.к. указывает на первый элемент)

print_hex:
	pusha						; Сохраняем значения регистров в стеке
	mov cx, 0					; Регистр CX будет служить счетчиком

loop1:
	cmp cx, 4					; if (CX < 4)
	jl print					; Переходим к print
	jmp end						; else переходим к end

print:
	mov ax, dx					; В AX теперь 0x1fb6
	and ax, 0x000f				; В AX теперь 6 (последняя цифра от 0x1fb6).
	cmp ax, 9					; if (AX > 9) (проверяем обозначается ли число
								; буквой т.к. мы помним что цифра больше 9 в 
								; 16-ричной системе исчисления обозначается
								; буквой латинского алфавита)
	jg num_to_abc				; Переходим к num_to_abc
	jmp next

num_to_abc:						; Перевод числа в букву (так, как она бы
								; выглядела в 16-ричной СИ), например число 15
								; в десятичной будет равно f в 16-ричной
	add ax, 39					; Добавляем к этому числу 39, чтобы затем еще
								; добавить 48 ('0'), получая код 
								; соответсвующего символа в ASCII (например,
								; f (как число, то есть 15) + 39 + 48 (код '0')
								; = 102 (то есть 'f' в ASCII, как нам и нужно)
	jmp next

next:
	add ax, '0'					; Добавляем 48 в ax
	mov bx, HEX_OUT + 5			; Теперь bx указывает на последний символ строки
								; HEX_OUT
	sub bx, cx					; BX = BX - counter (для итерации)
	mov [bx], al				; Так как мы разыменовываем bx (вот так: [bx]),
								; то [bx] это не регистр, а ссылка на память,
								; и так как ax = 16 бит (2 байта), чтобы нам не
								; перезаписать лишнюю память, в [bx] мы помещаем
								; не ax, а al, размер которого равен 1-му байту.
	ror dx, 4					; было: 0x1fb6, стало: 0x61fb (переносим
								; последнюю цифру в начало)
	inc cx						; counter++
	jmp loop1					; переходим обратно к loop1

end:
	mov bx, HEX_OUT				; Делаем так, чтобы bx снова указывал на первый
								; символ строки HEX_OUT
	call print_string			; выводи на экран строку из регистра bx
	popa						; Возвращаем регистрам их изначальное значение
	ret							; Заканчиваем выполнение функции


HEX_OUT:	db "0x0000", 0

================================================
FILE: guide/01-KERNEL/ex01/boot/print_string.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex04 / print_string.asm
; Title:	Функция вывода строки на экран
; ------------------------------------------------------------------------------
; Description: null
; ------------------------------------------------------------------------------


print_string:			; Функция вывода строки на экран.
	pusha				; Когда мы используем функции, мы можем модифицировать
						; регистры прямо в них, что нарушает чистоту функции,
						; т.е. мы можем перезаписывать какие-то внешние
						; данные. Для этого мы добавляем значение всех регистров
						; в стек с помощью команды pusha, а в конце функции
						; мы возвращаем регистрам их изначальные значения,
						; которые возьмем из стека (команда popa).
						
	mov ah, 0x0e		; tele-type mode
	loop:				; Метка loop (= цикл)
		mov al, [bx]	; Перемещаем значение BX в AL, т.к. мы помним что в BX
						; лежит первый символ строки (см. ./main.asm)
		cmp al, 0		; Команда cmp для сравнения AL и 0.
		je newline		; (if) je = "jump if equal",
						; т.е. перемещаемся к коду с меткой return если AL == 0
		jmp put_char	; (else) в противном случае перемещаемся к put_char.

put_char:				; Метка put_char - вывод символа на экран.
	int 0x10			; Вызываем прерывание, которое позволяет вывести 
						; на экран значение регистра AL, в котором лежит [bx].
	inc bx				; inc <регистр> - увеличить на 1.
	jmp loop			; Возвращаемся обратно к циклу.

newline:
	mov ah, 0x0e
	mov al, 0x0a	; (0x0a) \n - new line
	int 0x10
	mov al, 0x0d	; (0x0d) \r - carriage return
	int 0x10
	jmp return

return:					; Метка return - завершаем функцию
	popa				; Возвращаем регистрам их изначальные значения.
	ret					; Заканчиваем выполнение функции.


================================================
FILE: guide/01-KERNEL/ex01/boot/print_string_pm.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / print_string_pm.asm
; Title:	Функция вывода строки на экран в 32-битном защищенном режиме
; ------------------------------------------------------------------------------
; Description:
;	Плюсы 32-битного режима: возможность использовать 32-битные регистры и
;	адрессацию памяти, защищенную память виртуальную память
;	Минусы: отсутствие БИОС прерываний, требование наличия GDT (об этом позже)
;	В этой программе мы напишем новую функцию печати строки, но без прерываний
;	БИОСа, а напрямую манипулируя VGA видеопамятью, вместо вызова int 0x10.
;	VGA память размещена начиная с адреса 0xb8000, и у VGA имеется специальный
;	текстовый режим, поэтому нам не придется напрямую рисовать пиксели.
;	Особенности:
;	1. Символ представляется в виде 2-х байтов. Первый байт - сам символ,
;	второй байт - 4 бита на цвет текста и еще 4 на цвет фона. 
;	Например, чтобы распечатать символ 'A' белым текстом на черном фоне, мы
;	испольузуем 0x410f: 0x41 == 'A', 0 == белый, f == черный.
;
;		Bit:     | 15 14 13 12 11 10 9 8 | 7 6 5 4 | 3 2 1 0 |
;		Content: | ASCII                 | FG      | BG      |
;
; ------------------------------------------------------------------------------


[bits 32]					; Используем 32-битный режим

							; Определяем некоторые константы
VIDEO_MEMORY equ 0xb8000	; = адрес начала памяти VGA
WHITE_ON_BLACK equ 0x0f		; = цвет символов (0x0f - белый на черном)

print_string_pm:
	pusha
	mov edx, VIDEO_MEMORY	; Перемещаем в EDX адрес начала массива видеопамяти

print_string_pm_loop:
							; Помним, что AX (2б) = AH(1б) и AL(1б)
	mov al, [ebx]			; Сохраняем символ из EBX в AL
	mov ah, WHITE_ON_BLACK	; Устанавливаем цвет символов в AH
							; таким образом AX получается равен символу + цвету

	cmp al, 0				; if (AL == 0), т.е. если конец массива, то
	je print_string_pm_done	; заканчиваем выполнение функции
							; else:
	mov [edx], ax			; video_memory[EDX] = AX
	add ebx, 1				; переходим к следующему символу (+1, просто массив)
	add edx, 2				; переходим к следующему адресу в VGA (+2 т.к.
							; два байта на символ)

	jmp print_string_pm_loop

print_string_pm_done:
	popa
	ret


================================================
FILE: guide/01-KERNEL/ex01/boot/switch.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / switch.asm
; Title:	Переключаемся в PM (Protected mode, защищенный режим)
; ------------------------------------------------------------------------------
; Description:
;	Чтобы сделать свитч, нам нужно:
;		1. Отключить прерывания (процессор просто будет их игнорировать), т.к.
; 		в PM, прерывания обрабатываются совершенно по-другому в отличие от
;		Real Mode. Даже если процессор и смог бы распределить сигналы прерываний
;		по конкретным BIOS обработчикам прерываний, БИОС обработчики будут
;		обрабатывать 16-битный код, что повлекло бы за собой ошибки.
;		2. Загрузить GDT дескриптор
;		3. Изменяем первый бит регистра управления cr0 на "1"
;		https://en.wikipedia.org/wiki/Control_register#CR0
;		4. Т.к. процессор использует специальную технику, которая называется
;		называется pipelining (гугли: вычислительный конвейер, полезная статья
;		на хабре: https://habr.com/ru/post/182002/), и поэтому сразу после того,
;		как перевести процессор в PM (что мы и сделали в предыдущем пункте), 
;		нам нужно заставить процессор завершить всю работу в конвейере, чтобы
;		быть уверенным, что все будущие инструкции будут выполнены корректно.
;		Конвейер загружает в себя некоторые количество последующих после текущей
;		инструкций, но конвейеру не очень нравятся инструкции типа call и jmp,
;		т.к. процессор не знает полностью какие инструкции будут следовать за
;		ними, в особенности если мы вызовем jmp или call "прыгая" в другой
;		сегмент. Поэтому нам нужно сделать "дальний прыжок", чтобы завершить
;		обрабатываемые в конвейере инструкции.
;		Сам прыжок: 	jmp <сегмент>:<адрес смещения>
; ------------------------------------------------------------------------------


[bits 16]

switch_to_pm:
	cli						; Отключаем прерывания (cli = clear interrupts)
	
	lgdt [gdt_descriptor]	; Загружаем GDT дескриптор (lgdt = load GDT)

	mov eax, cr0			; Чтобы перейти в PM, нужно чтобы первый бит
	or eax, 0x1				; регистра управления cr0 был 1
	mov cr0, eax

	jmp CODE_SEG:init_pm	; Делаем "дальний прыжок" в наш новый 32-битный
							; сегмент кода. Это так же заставляет процессор
							; завершить обрабатываемые в конвейере инструкции. 

[bits 32]

init_pm:					; в PM, наши старые сегменты бесполезны, поэтому
	mov ax, DATA_SEG		; мы делаем так, чтобы регистры всех сегментов
	mov ds, ax				; указывали на сегмент данных, который мы определили
	mov ss, ax				; в GDT (см. ./gdt.asm)
	mov es, ax
	mov fs, ax
	mov gs, ax

	mov ebp, 0x90000		; Обновляем позицию стека, чтобы он был на самом
	mov esp, ebp			; верху свободного места

	call BEGIN_PM			; Вызываем функцию из ./main.asm

================================================
FILE: guide/01-KERNEL/ex01/build/Makefile
================================================
C_FILES = $(shell find ../drivers/*.c ../*.c)
temp = $(notdir $(C_FILES))
O_FILES = ${temp:.c=.o}

# флаг для дебага для gcc
CFLAGS = -g

run: os-image.bin
	qemu-system-i386 -fda os-image.bin
	make clean

debug: os-image.bin kernel.elf
	# kernel.elf нужен для gdb как symbol-file
	# флаг -s указывает qemu открыть и прослушивать 1234 порт
	# чтобы gdb смог соединиться с ним для дебага
	# флаг -S указыает QEMU не запускать образ и подождать подключений
	qemu-system-i386 -s -S -fda os-image.bin
	make clean

os-image.bin: bootsect.bin kernel.bin
	cat bootsect.bin kernel.bin > os-image.bin

bootsect.bin:
	cd ../boot/ && nasm bootsect.asm -f bin -o ../build/bootsect.bin && cd -

kernel.bin: kernel_entry.o kernel.o
	i386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o $(O_FILES) --oformat binary

kernel_entry.o:
	nasm ../boot/kernel_entry.asm -f elf -o kernel_entry.o

kernel.o:
	i386-elf-gcc ${CFLAGS} -ffreestanding -c ../kernel/kernel.c $(C_FILES)

kernel.elf: kernel_entry.o kernel.o 
	# как kernel.bin только без --oformat binary
	i386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o $(O_FILES) -o kernel.elf

clean:
	rm *.bin *.o *.elf

================================================
FILE: guide/01-KERNEL/ex01/common.c
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / common.c
*	Title:	Всякие удобные константы, типы и функции
* ------------------------------------------------------------------------------
*	Description:
* ----------------------------------------------------------------------------*/


#include "common.h"

void	memcpy(u8 *src, u8 *dest, u32 bytes)
{
	u32 i;

	i = 0;
	while (i < bytes)
	{
		dest[i] = src[i];
		i++;
	}
}

================================================
FILE: guide/01-KERNEL/ex01/common.h
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / common.h
*	Title:	Всякие удобные константы, типы и функции
* ------------------------------------------------------------------------------
*	Description:
* ----------------------------------------------------------------------------*/


#ifndef COMMON_H
#define COMMON_H

// Указанная размерность характерна только для архитектуры x86
// Подробнее про типы данных: https://metanit.com/cpp/c/2.3.php

typedef unsigned int	u32;	// беззнаковое целое число размером 32 бита
typedef 		 int	s32; 	// целое число 32 бита со знаком
typedef unsigned short	u16;	// и т.д.
typedef 		 short	s16;
typedef unsigned char	u8;
typedef 		 char	s8;

void	memcpy(u8 *src, u8 *dest, u32 bytes);

#endif


================================================
FILE: guide/01-KERNEL/ex01/drivers/lowlevel_io.c
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / drivers / lowlevel_io.c
*	Title:	Низкоуровневые I/O функции для работы с девайсами
* ------------------------------------------------------------------------------
*	Description:
*		Обычно есть два способа взаимодействовать с hardware - memory-mapped I/O
*		и I/O ports. 
*		Если девайс использует memory-mapped I/O, то чтобы передать ему 
*		какую-либо информацию, нужно записать ее в специальный адрес. Например, 
*		чтобы вывести символ на экран, мы записывали его в адрес 0xb8000.
*		Если девайс использует I/O ports, то для взаимодействия с ним
*		используются инструкции ассемблера in и out.
*
*       Чтобы работать с дейвасом, нам достаточно знать что в нем есть некий
*       контролирующий чип, который делает всю грязную работу за нас и позволяет
*       процессору взаимодействовать с напрямую с железом дейваса. У такого чипа
*       есть регистры в которые можно что-то записывать или что-то из них
*       читать, и значение этих регистров указывет чипу что делать.
*
*       Как мы будем читать/писать в регистры контролирующих чипов девайса? Вот
*       что нужно знать:
*       1. В архитектуре процессоров Интел, регистры контроля девайсами
*       расположены в специальном I/O адресном пространстве.
*       2. Инструкции ввода-ввывода используют такой синтаксис (intel syntax):
*       in <записать результат сюда (регистр процессора)>, <прочитать
*                                       содержимое отсюда (регистр девайса)>
*       out <записать сюда (регистр девайса)>, <вот это>
*       3. К сожалению, в языке Си нет подобных конструкций, так что нам
*       придется использовать inline assembly. Ща посмотрим как.
* ----------------------------------------------------------------------------*/


unsigned char   port_byte_in(unsigned short port)
{
    /* Функция-обертка над assembly, читающая 1 байт из параметра port */
    /* unsigned short port: адрес регистра какого-либо девайса, из которого */
    /* мы что-то прочтем. */

    /* Используется другой синтаксис ассембли (GAS). Обратите внимание, что */
    /* выражение "mov dest, src" в GAS мы запишем как "mov src, dest", т.е. */
    /* "in dx, al" означает прочитать содержимое порта (адрес которого */
    /* находится в DX) и положить в AL. */
    /* Символ % означает регистр, а т.к. % - escape symbol, то мы */
    /* пишем еще один %. */
    /* Перемещаем результат в регистр AL т.к. размер AL == 1 байт */
    unsigned char result;
	__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
	/* разберем только что вызванную функцию: */
	/* "in %%dx, %%al"		- Прочитать содержимое порта и положить это в AL */
	/* : "=a" (result)		- Положить значение AL в переменную result */
	/* : "d" (port)			- Загрузить port в регистр EDX (extended DX: 32b) */
    return (result);		/* Возвращаем прочитанное содержимое из port */
}


void    port_byte_out(unsigned short port, unsigned char data)
{
    /* Функция-обертка над assembly, пишущая data (1 байт) в port */
    /* unsigned short port: адрес регистра девайса, в который что-то запишем */
    /* unsigned char data: 1 байт какой-то информации (например, символ) */
	__asm__("out %%al, %%dx" : : "a" (data), "d" (port));
	/* разберем только что вызванную функцию: */
	/* "out %%al, %%dx"		- Записать data в port */
	/* : : "a" (data)		- Загрузить data в регистр EAX */
	/* : "d" (port)			- Загрузить port в регистр EDX */
}


unsigned char   port_word_in(unsigned short port)
{
    /* Функция-обертка над assembly, читающая 2 байта из параметра port */
    /* Перемещаем результат в регистр AX т.к. размер AX == 2 байта */
    unsigned short result;
    __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
    return (result);
}


void port_word_out(unsigned short port, unsigned short data)
{
    /* Функция-обертка над assembly, пишущая data (2 байта, т.е. word) в port */
    __asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
}


================================================
FILE: guide/01-KERNEL/ex01/drivers/lowlevel_io.h
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / drivers / lowlevel_io.h
*	Title:	Заголовочный файл для lowlevel_io.c
* ------------------------------------------------------------------------------
*	Description:
* ----------------------------------------------------------------------------*/


unsigned char   port_byte_in(unsigned short port);
void    port_byte_out(unsigned short port, unsigned char data);
unsigned char   port_word_in(unsigned short port);
void port_word_out(unsigned short port, unsigned short data);


================================================
FILE: guide/01-KERNEL/ex01/drivers/screen.c
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / drivers / screen.c
*	Title:	Функции работы с экраном
* ------------------------------------------------------------------------------
*	Description:
* ----------------------------------------------------------------------------*/


#include "screen.h"
#include "lowlevel_io.h"
#include "../common.h"


void	kprint(u8 *str)
{
	/* Функция печати строки */
	
	// u8 *str: указатель на строку (на первый символ строки). Строка должна
	// быть null-terminated.

	while (*str)
	{
		putchar(*str, WHITE_ON_BLACK);
		str++;
	}
}

void	putchar(u8 character, u8 attribute_byte)
{
	/* Более высокоуровневая функция печати символа */

	// u8 character: байт, соответствующий символу
	// u8 attribute_byte: байт, соответствующий цвету текста/фона символа

	u16 offset;

	offset = get_cursor();
	if (character == '\n')
	{
		// Переводим строку.
		if ((offset / 2 / MAX_COLS) == (MAX_ROWS - 1)) 
			scroll_line();
		else
			set_cursor((offset - offset % MAX_COLS) + MAX_COLS*2);
	}
	else 
	{
		if (offset == (MAX_COLS * MAX_ROWS * 2)) scroll_line();
		write(character, attribute_byte, offset);
		set_cursor(offset+2);
	}
}

void	scroll_line()
{
	/* Функция скроллинга */

	u8 i = 1;		// Начинаем со второй строки.
	u16 last_line;	// Начало последней строки.

	while (i < MAX_ROWS)
	{
		memcpy(
			(u8 *)(VIDEO_ADDRESS + (MAX_COLS * i * 2)),
			(u8 *)(VIDEO_ADDRESS + (MAX_COLS * (i-1) * 2)),
			(MAX_COLS*2)
		);
		i++;
	}

	last_line = (MAX_COLS*MAX_ROWS*2) - MAX_COLS*2;
	i = 0;
	while (i < MAX_COLS)
	{
		write('\0', WHITE_ON_BLACK, (last_line + i * 2));
		i++;
	}
	set_cursor(last_line);
}

void	clear_screen()
{
	/* Функция очистки экрана */

	u16	offset = 0;
	while (offset < (MAX_ROWS * MAX_COLS * 2))
	{
		write('\0', WHITE_ON_BLACK, offset);
		offset += 2;
	}
	set_cursor(0);
}

void	write(u8 character, u8 attribute_byte, u16 offset)
{
	/* Функция печати символа на экран с помощью VGA по адресу 0xb8000 */

	// u8 character: байт, соответствующий символу
	// u8 attribute_byte: байт, соответствующий цвету текста/фона символа
	// u16 offset: смещение (позиция), по которому нужно распечатать символ
	
	u8 *vga = (u8 *) VIDEO_ADDRESS;
	vga[offset] = character;
	vga[offset + 1] = attribute_byte;
}

u16		get_cursor()
{
	/* Функция, возвращающая позицию курсора (char offset). */

	port_byte_out(REG_SCREEN_CTRL, 14);				// Запрашиваем верхний байт
	u8 high_byte = port_byte_in(REG_SCREEN_DATA);	// Принимаем его
	port_byte_out(REG_SCREEN_CTRL, 15);				// Запрашиваем нижний байт
	u8 low_byte = port_byte_in(REG_SCREEN_DATA);	// Принимаем и его
	// Возвращаем смещение умножая его на 2, т.к. порты возвращают смещение в
	// клетках экрана (cell offset), а нам нужно в символах (char offset), т.к.
	// на каждый символ у нас 2 байта
	return (((high_byte << 8) + low_byte) * 2);
}

void	set_cursor(u16 pos)
{
	/* Функция, устаналивающая курсор по смещнию (позиции) pos */
	/* Поиграться с битами можно тут http://bitwisecmd.com/ */

	pos /= 2;	// конвертируем в cell offset (в позицию по клеткам, а не
				// символам)
	// Устанавливаем позицию курсора
	port_byte_out(REG_SCREEN_CTRL, 14);			// Указываем, что будем
												// передавать верхний байт
	port_byte_out(REG_SCREEN_DATA, (pos >> 8));	// Передаем верхний байт
	port_byte_out(REG_SCREEN_CTRL, 15);			// Указываем, что будем
												// передавать нижний байт
	port_byte_out(REG_SCREEN_DATA, (pos & 0xff));	// передаем нижний байт
}


================================================
FILE: guide/01-KERNEL/ex01/drivers/screen.h
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / drivers / screen.h
*	Title:	Заголовочный файл для screen.c
* ------------------------------------------------------------------------------
*	Description:
* ----------------------------------------------------------------------------*/


#include "../common.h"

#define VIDEO_ADDRESS 0xb8000	// Адрес начала VGA для печати символов
#define MAX_ROWS 25				// макс. строк
#define MAX_COLS 80				// макс. столбцов

#define WHITE_ON_BLACK 0x0f		// 0x0 == white fg, 0xf == black bg

// Адреса I/O портов для взаимодействия с экраном.
#define REG_SCREEN_CTRL 0x3d4	// этот порт для описания данных
#define REG_SCREEN_DATA 0x3d5	// а этот порт для самих данных

void	kprint(u8 *str);
void	putchar(u8 character, u8 attribute_byte);
void	clear_screen();
void	write(u8 character, u8 attribute_byte, u16 offset);
void	scroll_line();
u16		get_cursor();
void	set_cursor(u16 pos);


================================================
FILE: guide/01-KERNEL/ex01/kernel/kernel.c
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / kernel / kernel.c
*	Title:	Ядро
* ------------------------------------------------------------------------------
*	Description:
* ----------------------------------------------------------------------------*/


#include "../common.h"
#include "../drivers/screen.h"


s32		kmain()
{	
	/* Простая программа чтобы продемонстрировать чего мы добились. */
	/* Поиграйтесь с кодом и добавьте больше логики, попробуйте заполнить все */
	/* 25 строк чтобы посмотреть работу функции scroll_line. */

	u8 i;
	
	clear_screen();

	i = 0;
	while (i < 3)
	{
		kprint("Hello, world!\n");
		i++;
	}

	return 0;
}


================================================
FILE: src/boot/bootsect.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	01-KERNEL
; File:		ex00 / bootsect.asm
; Title:	Программа загрузочного сектора, которая загружает ядро, написанное
;			на C в 32-битный защищенный режим.
; ------------------------------------------------------------------------------
; Description:
; ------------------------------------------------------------------------------


[org 0x7c00]

KERNEL_OFFSET equ 0x1000	; Смещение в памяти, из которого мы загрузим ядро

	mov [BOOT_DRIVE], dl	; BIOS stores our boot drive in DL , so it ’s
							; best to remember this for later. (Remember that
							; the BIOS sets us the boot drive in 'dl' on boot)
	mov bp, 0x9000			; Устанавливаем стек
	mov sp, bp

	mov bx, MSG_REAL_MODE	; Печатаем сообщение
	call print_string

	call load_kernel		; Загружаем ядро
	call switch_to_pm		; Переключаемся в Защищенный Режим
	jmp $

%include "print_string.asm"		; ф. печати строки
%include "print_hex.asm"		; ф. печати 16-ричного числа
%include "disk_load.asm"		; ф. чтения диска
%include "print_string_pm.asm"	; ф. печати строки (32PM)
%include "switch.asm"			; ф. переключения в 32PM
%include "gdt.asm"				; таблица GDT

[bits 16]

load_kernel:
	mov bx, MSG_LOAD_KERNEL
	call print_string		; Печатаем сообщение о том, то мы загружаем ядро
							; Устанавливаем параметры для функции disk_load:
	mov bx, KERNEL_OFFSET	; Загрузим данные в место памяти по		TODO: disk_load main lookup
							; смещению KERNEL_OFFSET
	mov dh, 16				; Загрузим много секторов. *
	mov dl, [BOOT_DRIVE]	; Загрузим данные из BOOT_DRIVE (Возвращаем BOOT_DRIVE)
	call disk_load			; Вызываем функцию disk_load
	ret


[bits 32]					; Сюда мы попадем после переключения в 32PM

BEGIN_PM:
	mov ebx, MSG_PROT_MODE
	call print_string_pm	; Печатаем сообщение об успешной загрузке в 32PM
	call KERNEL_OFFSET		; Переходим в адрес, по которому загрузился код ядра
	jmp $


BOOT_DRIVE:			db 0
MSG_REAL_MODE:		db "Started in 16-bit Real Mode", 0
MSG_PROT_MODE:		db "Successfully landed in 32-bit Protected mode", 0
MSG_LOAD_KERNEL:	db "Loading kernel into VIDEO_MEMORY", 0

times 510-($-$$) db 0
dw 0xaa55

; ------
; * - Забавный факт: если загрузить меньше секторов, то мы столкнемся со
; странными ошибками когда будем писать ядро на Си. Например, аргументы
; функции могут быть повреждены, а строки "обрезаны".


================================================
FILE: src/boot/disk_load.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / disk_load.asm
; Title:	Функция чтения диска
; ------------------------------------------------------------------------------
; Description:
;	Чтобы лучше понять, что здесь происходит, разберитесь с тем, что такое CHS
;	по ссылке https://ru.wikipedia.org/wiki/CHS
; ------------------------------------------------------------------------------


disk_load:
	push dx
	mov ah, 0x02			; Указвыаем БИОСу что нам нужна рутина чтения диска
							; Указываем что нам нужно:
	mov al, dh				; 1. Прочитать кол-во секторов, равное значению в dh
	mov ch, 0x00			; 2. Выбрать нулевой цилиндр
	mov dh, 0x00			; 3. Выбрать нулевую головку
	mov cl, 0x02			; 4. Начинать считывать со второго сектора (т.е.
							; первый свободный сектор сразу после загруочного 
							; сектора, т.к. загрузочный сектор находится по 
							; адресу 0x01)
	int 0x13				; Вызываем прерывание для чтения

							; У БИОСа может не получиться прочитать диск, и
							; чтобы дать нам знать что произошла ошибка, он,
							; во-первых, обновляет специальный флаг CF (carry 
							; flag) специальным значением, которое означает 
							; ошибку, а во-вторых, кладет в регистр AL кол-во 
							; секторов, которые у него получилось прочитать.
	
	jc disk_error			; jc - инструкция для прыжка на указанную метку,
							; которая выполняется только если CF (carry flag)
							; сигнализирует об ошибке

	pop dx					; Восстанавливаем регистр DX из стека
	cmp dh, al				; если AL (кол-во прочитанных секторов) != DH
							; (предполагаемое кол-во секторов),
	jne disk_sectors_error	; то выводим на экран сообщение об ошибке и зависаем
							; (то есть запускаем бесконечный цикл)
	jmp disk_success
	jmp disk_exit			; Заканчиваем выполнение функции

disk_success:
	mov bx, SUCCESS_MSG
	call print_string
	jmp disk_exit

disk_error:
	mov bx, DISK_ERR_MSG	; Перемещаем в BX сообщение об ошибке
	call print_string		; Выводим его на экран
	mov dh, al
	call print_hex
	jmp disk_loop			; бесконечный цикл

disk_sectors_error:
	mov bx, SECTORS_ERR_MSG
	call print_string

SUCCESS_MSG:
	db "Disk was successfully read ", 0

DISK_ERR_MSG:
	db "Disk read error! ", 0

SECTORS_ERR_MSG:
	db "Incorrect number of sectors read ", 0

disk_loop:
	jmp $

disk_exit:
	ret

================================================
FILE: src/boot/gdt.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / gdt.asm
; Title:	Определяем GDT (глобальная таблица дескрипторов)
; ------------------------------------------------------------------------------
; Description:
; 	Способ, которым процессор переводит логический адрес в физический, в
; 	32-битном защищенном режиме отличается от 16-битного реального режима.
; 	Вместо того, чтобы умножить значение регистра сегмента на 16 и прибавить к
; 	этому "смещение" (offset), регистр сегмента становится индексом 
; 	определенного дескриптора сегмента в GDT.
; 	Дескриптор сегмента - это 8-битная структура, которая определяет свойства
; 	этого сегмента:
; 		- Base address (32 bits), определяющий откуда сегмент начинается в
; 		физической памяти.
; 		- Segment Limit (20 bits), определяющий размер сегмента
;		- Различные флаги, которые устанавливают каким образом процессор будет
; 		"относиться" к сегментам, например уровень привилегий и т.д.
;
; 	Флаги:
;	* 1-ые флаги:
; 		- present flag (флаг присутствия). Если его значение "1", то это 
;		указывает, что сегмент присутствует в памяти (это нужно для виртуальной 
;		памяти)
;		- privilege flag (флаг привилегии). Значение "0" - самый высокий уровень
;		привилегии
;		- descriptor type (тип дескриптора). "1" - для сегмента кода или 
;		сегмента данных
; 	* Флаги типа:
;		- code (флаг кода). "1" - для кода, "0" - для даннных
;		- conformig (флаг подчинения). "0" - чтобы код в другом сегменте с
;		более низким уровнем привилегий не смог вызвать код из этого сегмента - 
;		это ключ к защите памяти (memory protection).
;		- readable (читаемость). "1" - если читаемый, "0" - только исполняемый.
;		- writable. Разрешает сегменту данных быть записываемым, в противном
;		случае, он будет доступен только для чтения.
;		- accessed (флаг доступа). Этот флаг устанавливается, когда происходит
; 		обращение к сегменту.
;		- expand down. Флаг (бит), позволяющий сегменту расширяться вниз.
;	* 2-ые флаги:
;		- granulariy (гранулярность). "0" - байтовая гранулярность, лимит
; 		задается в байтах, если "1" - страничная гранулярность, в 4кб блоках.
;		Если выбрать страничную гранулярность и установить значение лимита как
;		0xfffff, то лимит умножится на 16*16*16 (4кб), и лимит станет 0xfffff000
; 		позволяя нашему сегменту занять 4гб места в памяти.
;		- 32-bit default. "1" - т.к. наш сегмент будет содержать 32-битный код.
;		- 64-bit code segment. "0" - т.к. не используется на 32-битных 
;		процессорах.
;		- AVL (available). Определяет доступность сегмента для использования 
;		системным программным обеспечением (используются только ОС).
; ------------------------------------------------------------------------------

gdt_start:					; Эта пустая метка нужно для того чтобы удобнее
							; посчитать размер GDT для ее дескриптора 
							; (end - start)

gdt_null:					; Необходимый нулевой дескриптор для GDT
	dd 0x0					; dd - define double (двойное слово, т.е. 4 байта)
	dd 0x0

gdt_code:					; Определяем дескриптор сегмента кода
	dw 0xffff				; Limit (bits 0-15)
	dw 0x0					; Base (bits 0-15)
	db 0x0					; Base (bits 16-23)
	db 10011010b			; Первые флаги + флаги типа (смотрим по битам)
							; present: 1, privilege: 00, descriptor type: 1
							; code: 1, conforming: 0, readable: 1, accessed: 0
	db 11001111b			; Вторые флаги + длина сегмента (bits 16-19):
							; granularity: 1, 32-bit default: 1,
							; 64-bit default: 0, AVL: 0
	db 0x0					; Base (bits 24-31)

gdt_data:					; Определяем дескриптор сегмента кода
	dw 0xffff				; Limit (bits 0-15)
	dw 0x0					; Base (bits 0-15)
	db 0x0					; Base (bits 16-23)
	db 10010010b			; Первые флаги + флаги типа (смотрим по битам)
	db 11001111b			; Вторые флаги + длина сегмента (bits 16-19)
	db 0x0					; Base (bits 24-31)

gdt_end:					; Пустая метка


gdt_descriptor:						; дескриптор GDT
	dw gdt_end - gdt_start - 1		; Размер GDT
	dd gdt_start					; Адрес начала GDT


CODE_SEG equ gdt_code - gdt_start	; Определяем некоторые константы. 
DATA_SEG equ gdt_data - gdt_start	; Они понадобятся для регистров сегментов в
									; 32-битном защищенном режиме. Например,
									; когда мы установим регистр DS = 0x10 (т.е
									; 16 байтов) в этом режиме, процессор
									; поймет что мы хотим использовать сегмент,
									; находящийся в смещении 0x10 в нашем GDT,
									; т.е. в нашем случае это сегмент данных
									; (0x0 -> NULL, 0x08 -> сегмент кода,
									; 0x10 -> сегмент данных)

================================================
FILE: src/boot/kernel_entry.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	01-KERNEL
; File:		ex00 / kernel_entry.asm
; Title:	Код, служащий входной точкой для функции kmain из kernel.c
; ------------------------------------------------------------------------------
; Description:
; ------------------------------------------------------------------------------


[bits 32]
[extern kmain]	; Определяем 'внешнюю' штуку с названием kmain - она понадобится
				; линкеру чтобы собрать все вместе
call kmain		; Вызываем определенную выше функцию, которая будет доступна
				; после линковки. Это функция kmain из kernel.c
jmp $


================================================
FILE: src/boot/print_hex.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex05 / print_hex.asm
; Title:	Функция вывода шестнадцатеричного числа на экран
; ------------------------------------------------------------------------------
; Description: 
;	Как выглядит регистр EAX (32 b = 32 бита):
;
;						EAX (32 b)
;		---------------------------------------------
;		|					|			|			|
;		|					|  AH (8 b)	|  AL (8 b) |
;		|					|			|			|
;		---------------------------------------------
;									AX (16 b)
;
;	Как видим, EAX содержит в себе регистр AX, который в свою очередь разделен 
;	на AH (a high, верхний) и AL (a low, нижний).
; ------------------------------------------------------------------------------


; Помним, что в main.asm:
; DX  =  0x1fb6
; BX  =  "0x0000" (а точнее, BX="0", т.к. указывает на первый элемент)

print_hex:
	pusha						; Сохраняем значения регистров в стеке
	mov cx, 0					; Регистр CX будет служить счетчиком

loop1:
	cmp cx, 4					; if (CX < 4)
	jl print					; Переходим к print
	jmp end						; else переходим к end

print:
	mov ax, dx					; В AX теперь 0x1fb6
	and ax, 0x000f				; В AX теперь 6 (последняя цифра от 0x1fb6).
	cmp ax, 9					; if (AX > 9) (проверяем обозначается ли число
								; буквой т.к. мы помним что цифра больше 9 в 
								; 16-ричной системе исчисления обозначается
								; буквой латинского алфавита)
	jg num_to_abc				; Переходим к num_to_abc
	jmp next

num_to_abc:						; Перевод числа в букву (так, как она бы
								; выглядела в 16-ричной СИ), например число 15
								; в десятичной будет равно f в 16-ричной
	add ax, 39					; Добавляем к этому числу 39, чтобы затем еще
								; добавить 48 ('0'), получая код 
								; соответсвующего символа в ASCII (например,
								; f (как число, то есть 15) + 39 + 48 (код '0')
								; = 102 (то есть 'f' в ASCII, как нам и нужно)
	jmp next

next:
	add ax, '0'					; Добавляем 48 в ax
	mov bx, HEX_OUT + 5			; Теперь bx указывает на последний символ строки
								; HEX_OUT
	sub bx, cx					; BX = BX - counter (для итерации)
	mov [bx], al				; Так как мы разыменовываем bx (вот так: [bx]),
								; то [bx] это не регистр, а ссылка на память,
								; и так как ax = 16 бит (2 байта), чтобы нам не
								; перезаписать лишнюю память, в [bx] мы помещаем
								; не ax, а al, размер которого равен 1-му байту.
	ror dx, 4					; было: 0x1fb6, стало: 0x61fb (переносим
								; последнюю цифру в начало)
	inc cx						; counter++
	jmp loop1					; переходим обратно к loop1

end:
	mov bx, HEX_OUT				; Делаем так, чтобы bx снова указывал на первый
								; символ строки HEX_OUT
	call print_string			; выводи на экран строку из регистра bx
	popa						; Возвращаем регистрам их изначальное значение
	ret							; Заканчиваем выполнение функции


HEX_OUT:	db "0x0000", 0

================================================
FILE: src/boot/print_string.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex04 / print_string.asm
; Title:	Функция вывода строки на экран
; ------------------------------------------------------------------------------
; Description: null
; ------------------------------------------------------------------------------


print_string:			; Функция вывода строки на экран.
	pusha				; Когда мы используем функции, мы можем модифицировать
						; регистры прямо в них, что нарушает чистоту функции,
						; т.е. мы можем перезаписывать какие-то внешние
						; данные. Для этого мы добавляем значение всех регистров
						; в стек с помощью команды pusha, а в конце функции
						; мы возвращаем регистрам их изначальные значения,
						; которые возьмем из стека (команда popa).
						
	mov ah, 0x0e		; tele-type mode
	loop:				; Метка loop (= цикл)
		mov al, [bx]	; Перемещаем значение BX в AL, т.к. мы помним что в BX
						; лежит первый символ строки (см. ./main.asm)
		cmp al, 0		; Команда cmp для сравнения AL и 0.
		je newline		; (if) je = "jump if equal",
						; т.е. перемещаемся к коду с меткой return если AL == 0
		jmp put_char	; (else) в противном случае перемещаемся к put_char.

put_char:				; Метка put_char - вывод символа на экран.
	int 0x10			; Вызываем прерывание, которое позволяет вывести 
						; на экран значение регистра AL, в котором лежит [bx].
	inc bx				; inc <регистр> - увеличить на 1.
	jmp loop			; Возвращаемся обратно к циклу.

newline:
	mov ah, 0x0e
	mov al, 0x0a	; (0x0a) \n - new line
	int 0x10
	mov al, 0x0d	; (0x0d) \r - carriage return
	int 0x10
	jmp return

return:					; Метка return - завершаем функцию
	popa				; Возвращаем регистрам их изначальные значения.
	ret					; Заканчиваем выполнение функции.


================================================
FILE: src/boot/print_string_pm.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / print_string_pm.asm
; Title:	Функция вывода строки на экран в 32-битном защищенном режиме
; ------------------------------------------------------------------------------
; Description:
;	Плюсы 32-битного режима: возможность использовать 32-битные регистры и
;	адрессацию памяти, защищенную память виртуальную память
;	Минусы: отсутствие БИОС прерываний, требование наличия GDT (об этом позже)
;	В этой программе мы напишем новую функцию печати строки, но без прерываний
;	БИОСа, а напрямую манипулируя VGA видеопамятью, вместо вызова int 0x10.
;	VGA память размещена начиная с адреса 0xb8000, и у VGA имеется специальный
;	текстовый режим, поэтому нам не придется напрямую рисовать пиксели.
;	Особенности:
;	1. Символ представляется в виде 2-х байтов. Первый байт - сам символ,
;	второй байт - 4 бита на цвет текста и еще 4 на цвет фона. 
;	Например, чтобы распечатать символ 'A' белым текстом на черном фоне, мы
;	испольузуем 0x410f: 0x41 == 'A', 0 == белый, f == черный.
;
;		Bit:     | 15 14 13 12 11 10 9 8 | 7 6 5 4 | 3 2 1 0 |
;		Content: | ASCII                 | FG      | BG      |
;
; ------------------------------------------------------------------------------


[bits 32]					; Используем 32-битный режим

							; Определяем некоторые константы
VIDEO_MEMORY equ 0xb8000	; = адрес начала памяти VGA
WHITE_ON_BLACK equ 0x0f		; = цвет символов (0x0f - белый на черном)

print_string_pm:
	pusha
	mov edx, VIDEO_MEMORY	; Перемещаем в EDX адрес начала массива видеопамяти

print_string_pm_loop:
							; Помним, что AX (2б) = AH(1б) и AL(1б)
	mov al, [ebx]			; Сохраняем символ из EBX в AL
	mov ah, WHITE_ON_BLACK	; Устанавливаем цвет символов в AH
							; таким образом AX получается равен символу + цвету

	cmp al, 0				; if (AL == 0), т.е. если конец массива, то
	je print_string_pm_done	; заканчиваем выполнение функции
							; else:
	mov [edx], ax			; video_memory[EDX] = AX
	add ebx, 1				; переходим к следующему символу (+1, просто массив)
	add edx, 2				; переходим к следующему адресу в VGA (+2 т.к.
							; два байта на символ)

	jmp print_string_pm_loop

print_string_pm_done:
	popa
	ret


================================================
FILE: src/boot/switch.asm
================================================
; ------------------------------------------------------------------------------
; Guide:	00-BOOT-SECTOR
; File:		ex08 / switch.asm
; Title:	Переключаемся в PM (Protected mode, защищенный режим)
; ------------------------------------------------------------------------------
; Description:
;	Чтобы сделать свитч, нам нужно:
;		1. Отключить прерывания (процессор просто будет их игнорировать), т.к.
; 		в PM, прерывания обрабатываются совершенно по-другому в отличие от
;		Real Mode. Даже если процессор и смог бы распределить сигналы прерываний
;		по конкретным BIOS обработчикам прерываний, БИОС обработчики будут
;		обрабатывать 16-битный код, что повлекло бы за собой ошибки.
;		2. Загрузить GDT дескриптор
;		3. Изменяем первый бит регистра управления cr0 на "1"
;		https://en.wikipedia.org/wiki/Control_register#CR0
;		4. Т.к. процессор использует специальную технику, которая называется
;		называется pipelining (гугли: вычислительный конвейер, полезная статья
;		на хабре: https://habr.com/ru/post/182002/), и поэтому сразу после того,
;		как перевести процессор в PM (что мы и сделали в предыдущем пункте), 
;		нам нужно заставить процессор завершить всю работу в конвейере, чтобы
;		быть уверенным, что все будущие инструкции будут выполнены корректно.
;		Конвейер загружает в себя некоторые количество последующих после текущей
;		инструкций, но конвейеру не очень нравятся инструкции типа call и jmp,
;		т.к. процессор не знает полностью какие инструкции будут следовать за
;		ними, в особенности если мы вызовем jmp или call "прыгая" в другой
;		сегмент. Поэтому нам нужно сделать "дальний прыжок", чтобы завершить
;		обрабатываемые в конвейере инструкции.
;		Сам прыжок: 	jmp <сегмент>:<адрес смещения>
; ------------------------------------------------------------------------------


[bits 16]

switch_to_pm:
	cli						; Отключаем прерывания (cli = clear interrupts)
	
	lgdt [gdt_descriptor]	; Загружаем GDT дескриптор (lgdt = load GDT)

	mov eax, cr0			; Чтобы перейти в PM, нужно чтобы первый бит
	or eax, 0x1				; регистра управления cr0 был 1
	mov cr0, eax

	jmp CODE_SEG:init_pm	; Делаем "дальний прыжок" в наш новый 32-битный
							; сегмент кода. Это так же заставляет процессор
							; завершить обрабатываемые в конвейере инструкции. 

[bits 32]

init_pm:					; в PM, наши старые сегменты бесполезны, поэтому
	mov ax, DATA_SEG		; мы делаем так, чтобы регистры всех сегментов
	mov ds, ax				; указывали на сегмент данных, который мы определили
	mov ss, ax				; в GDT (см. ./gdt.asm)
	mov es, ax
	mov fs, ax
	mov gs, ax

	mov ebp, 0x90000		; Обновляем позицию стека, чтобы он был на самом
	mov esp, ebp			; верху свободного места

	call BEGIN_PM			; Вызываем функцию из ./main.asm

================================================
FILE: src/build/Makefile
================================================
C_FILES = $(shell find ../kernel/*.c ../drivers/*.c ../*.c)
temp = $(notdir $(C_FILES))
O_FILES = ${temp:.c=.o}

# флаг для дебага для gcc
CFLAGS = -g

run: os-image.bin
	qemu-system-i386 -fda os-image.bin
	make clean

debug: os-image.bin kernel.elf
	# kernel.elf нужен для gdb как symbol-file
	# флаг -s указывает qemu открыть и прослушивать 1234 порт
	# чтобы gdb смог соединиться с ним для дебага
	# флаг -S указыает QEMU не запускать образ и подождать подключений
	qemu-system-i386 -s -S -fda os-image.bin
	make clean

os-image.bin: bootsect.bin kernel.bin
	cat bootsect.bin kernel.bin > os-image.bin

bootsect.bin:
	cd ../boot/ && nasm bootsect.asm -f bin -o ../build/bootsect.bin && cd -

kernel.bin: kernel_entry.o kernel.o
	i386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o $(O_FILES) --oformat binary

kernel_entry.o:
	nasm ../boot/kernel_entry.asm -f elf -o kernel_entry.o

kernel.o:
	i386-elf-gcc ${CFLAGS} -ffreestanding -c $(C_FILES)

kernel.elf: kernel_entry.o kernel.o 
	# как kernel.bin только без --oformat binary
	i386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o $(O_FILES) -o kernel.elf

clean:
	rm *.bin *.o *.elf

================================================
FILE: src/common.c
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / common.c
*	Title:	Всякие удобные константы, типы и функции
* ------------------------------------------------------------------------------
*	Description:
* ----------------------------------------------------------------------------*/


#include "common.h"

void	memcpy(u8 *src, u8 *dest, u32 bytes)
{
	u32 i;

	i = 0;
	while (i < bytes)
	{
		dest[i] = src[i];
		i++;
	}
}

================================================
FILE: src/common.h
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / common.h
*	Title:	Всякие удобные константы, типы и функции
* ------------------------------------------------------------------------------
*	Description:
* ----------------------------------------------------------------------------*/


#ifndef COMMON_H
#define COMMON_H

// Указанная размерность характерна только для архитектуры x86
// Подробнее про типы данных: https://metanit.com/cpp/c/2.3.php

typedef unsigned int	u32;	// беззнаковое целое число размером 32 бита
typedef 		 int	s32; 	// целое число 32 бита со знаком
typedef unsigned short	u16;	// и т.д.
typedef 		 short	s16;
typedef unsigned char	u8;
typedef 		 char	s8;

void	memcpy(u8 *src, u8 *dest, u32 bytes);

#endif


================================================
FILE: src/drivers/lowlevel_io.c
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / drivers / lowlevel_io.c
*	Title:	Низкоуровневые I/O функции для работы с девайсами
* ------------------------------------------------------------------------------
*	Description:
*		Обычно есть два способа взаимодействовать с hardware - memory-mapped I/O
*		и I/O ports. 
*		Если девайс использует memory-mapped I/O, то чтобы передать ему 
*		какую-либо информацию, нужно записать ее в специальный адрес. Например, 
*		чтобы вывести символ на экран, мы записывали его в адрес 0xb8000.
*		Если девайс использует I/O ports, то для взаимодействия с ним
*		используются инструкции ассемблера in и out.
*
*       Чтобы работать с дейвасом, нам достаточно знать что в нем есть некий
*       контролирующий чип, который делает всю грязную работу за нас и позволяет
*       процессору взаимодействовать с напрямую с железом дейваса. У такого чипа
*       есть регистры в которые можно что-то записывать или что-то из них
*       читать, и значение этих регистров указывет чипу что делать.
*
*       Как мы будем читать/писать в регистры контролирующих чипов девайса? Вот
*       что нужно знать:
*       1. В архитектуре процессоров Интел, регистры контроля девайсами
*       расположены в специальном I/O адресном пространстве.
*       2. Инструкции ввода-ввывода используют такой синтаксис (intel syntax):
*       in <записать результат сюда (регистр процессора)>, <прочитать
*                                       содержимое отсюда (регистр девайса)>
*       out <записать сюда (регистр девайса)>, <вот это>
*       3. К сожалению, в языке Си нет подобных конструкций, так что нам
*       придется использовать inline assembly. Ща посмотрим как.
* ----------------------------------------------------------------------------*/


unsigned char   port_byte_in(unsigned short port)
{
    /* Функция-обертка над assembly, читающая 1 байт из параметра port */
    /* unsigned short port: адрес регистра какого-либо девайса, из которого */
    /* мы что-то прочтем. */

    /* Используется другой синтаксис ассембли (GAS). Обратите внимание, что */
    /* выражение "mov dest, src" в GAS мы запишем как "mov src, dest", т.е. */
    /* "in dx, al" означает прочитать содержимое порта (адрес которого */
    /* находится в DX) и положить в AL. */
    /* Символ % означает регистр, а т.к. % - escape symbol, то мы */
    /* пишем еще один %. */
    /* Перемещаем результат в регистр AL т.к. размер AL == 1 байт */
    unsigned char result;
	__asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
	/* разберем только что вызванную функцию: */
	/* "in %%dx, %%al"		- Прочитать содержимое порта и положить это в AL */
	/* : "=a" (result)		- Положить значение AL в переменную result */
	/* : "d" (port)			- Загрузить port в регистр EDX (extended DX: 32b) */
    return (result);		/* Возвращаем прочитанное содержимое из port */
}


void    port_byte_out(unsigned short port, unsigned char data)
{
    /* Функция-обертка над assembly, пишущая data (1 байт) в port */
    /* unsigned short port: адрес регистра девайса, в который что-то запишем */
    /* unsigned char data: 1 байт какой-то информации (например, символ) */
	__asm__("out %%al, %%dx" : : "a" (data), "d" (port));
	/* разберем только что вызванную функцию: */
	/* "out %%al, %%dx"		- Записать data в port */
	/* : : "a" (data)		- Загрузить data в регистр EAX */
	/* : "d" (port)			- Загрузить port в регистр EDX */
}


unsigned char   port_word_in(unsigned short port)
{
    /* Функция-обертка над assembly, читающая 2 байта из параметра port */
    /* Перемещаем результат в регистр AX т.к. размер AX == 2 байта */
    unsigned short result;
    __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
    return (result);
}


void port_word_out(unsigned short port, unsigned short data)
{
    /* Функция-обертка над assembly, пишущая data (2 байта, т.е. word) в port */
    __asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
}


================================================
FILE: src/drivers/lowlevel_io.h
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / drivers / lowlevel_io.h
*	Title:	Заголовочный файл для lowlevel_io.c
* ------------------------------------------------------------------------------
*	Description:
* ----------------------------------------------------------------------------*/


unsigned char   port_byte_in(unsigned short port);
void    port_byte_out(unsigned short port, unsigned char data);
unsigned char   port_word_in(unsigned short port);
void port_word_out(unsigned short port, unsigned short data);


================================================
FILE: src/drivers/screen.c
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / drivers / screen.c
*	Title:	Функции работы с экраном
* ------------------------------------------------------------------------------
*	Description:
* ----------------------------------------------------------------------------*/


#include "screen.h"
#include "lowlevel_io.h"
#include "../common.h"


void	kprint(u8 *str)
{
	/* Функция печати строки */
	
	// u8 *str: указатель на строку (на первый символ строки). Строка должна
	// быть null-terminated.

	while (*str)
	{
		putchar(*str, WHITE_ON_BLACK);
		str++;
	}
}

void	putchar(u8 character, u8 attribute_byte)
{
	/* Более высокоуровневая функция печати символа */

	// u8 character: байт, соответствующий символу
	// u8 attribute_byte: байт, соответствующий цвету текста/фона символа

	u16 offset;

	offset = get_cursor();
	if (character == '\n')
	{
		// Переводим строку.
		if ((offset / 2 / MAX_COLS) == (MAX_ROWS - 1)) 
			scroll_line();
		else
			set_cursor((offset - offset % (MAX_COLS*2)) + MAX_COLS*2);
	}
	else 
	{
		if (offset == (MAX_COLS * MAX_ROWS * 2)) scroll_line();
		write(character, attribute_byte, offset);
		set_cursor(offset+2);
	}
}

void	scroll_line()
{
	/* Функция скроллинга */

	u8 i = 1;		// Начинаем со второй строки.
	u16 last_line;	// Начало последней строки.

	while (i < MAX_ROWS)
	{
		memcpy(
			(u8 *)(VIDEO_ADDRESS + (MAX_COLS * i * 2)),
			(u8 *)(VIDEO_ADDRESS + (MAX_COLS * (i-1) * 2)),
			(MAX_COLS*2)
		);
		i++;
	}

	last_line = (MAX_COLS*MAX_ROWS*2) - MAX_COLS*2;
	i = 0;
	while (i < MAX_COLS)
	{
		write('\0', WHITE_ON_BLACK, (last_line + i * 2));
		i++;
	}
	set_cursor(last_line);
}

void	clear_screen()
{
	/* Функция очистки экрана */

	u16	offset = 0;
	while (offset < (MAX_ROWS * MAX_COLS * 2))
	{
		write('\0', WHITE_ON_BLACK, offset);
		offset += 2;
	}
	set_cursor(0);
}

void	write(u8 character, u8 attribute_byte, u16 offset)
{
	/* Функция печати символа на экран с помощью VGA по адресу 0xb8000 */

	// u8 character: байт, соответствующий символу
	// u8 attribute_byte: байт, соответствующий цвету текста/фона символа
	// u16 offset: смещение (позиция), по которому нужно распечатать символ
	
	u8 *vga = (u8 *) VIDEO_ADDRESS;
	vga[offset] = character;
	vga[offset + 1] = attribute_byte;
}

u16		get_cursor()
{
	/* Функция, возвращающая позицию курсора (char offset). */

	port_byte_out(REG_SCREEN_CTRL, 14);				// Запрашиваем верхний байт
	u8 high_byte = port_byte_in(REG_SCREEN_DATA);	// Принимаем его
	port_byte_out(REG_SCREEN_CTRL, 15);				// Запрашиваем нижний байт
	u8 low_byte = port_byte_in(REG_SCREEN_DATA);	// Принимаем и его
	// Возвращаем смещение умножая его на 2, т.к. порты возвращают смещение в
	// клетках экрана (cell offset), а нам нужно в символах (char offset), т.к.
	// на каждый символ у нас 2 байта
	return (((high_byte << 8) + low_byte) * 2);
}

void	set_cursor(u16 pos)
{
	/* Функция, устаналивающая курсор по смещнию (позиции) pos */
	/* Поиграться с битами можно тут http://bitwisecmd.com/ */

	// конвертируем в cell offset (в позицию по клеткам, а не символам)
	pos /= 2;

	// Указываем, что будем передавать верхний байт
	port_byte_out(REG_SCREEN_CTRL, 14);
	// Передаем верхний байт
	port_byte_out(REG_SCREEN_DATA, (u8)(pos >> 8));
	// Указываем, что будем передавать нижний байт
	port_byte_out(REG_SCREEN_CTRL, 15);
	// Передаем нижний байт
	port_byte_out(REG_SCREEN_DATA, (u8)(pos & 0xff));
}


================================================
FILE: src/drivers/screen.h
================================================
/*------------------------------------------------------------------------------
*	Guide:	01-KERNEL
*	File:	ex01 / drivers / screen.h
*	Title:	Заголовочный файл для screen.c
* ------------------------------------------------------------------------------
*	Description:
* ----------------------------------------------------------------------------*/


#include "../common.h"

#define VIDEO_ADDRESS 0xb8000	// Адрес начала VGA для печати символов
#define MAX_ROWS 25				// макс. строк
#define MAX_COLS 80				// макс. столбцов

#define WHITE_ON_BLACK 0x0f		// 0x0 == white fg, 0xf == black bg

// Адреса I/O портов для взаимодействия с экраном.
#define REG_SCREEN_CTRL 0x3d4	// этот порт для описания данных
#define REG_SCREEN_DATA 0x3d5	// а этот порт для самих данных

void	kprint(u8 *str);
void	putchar(u8 character, u8 attribute_byte);
void	clear_screen();
void	write(u8 character, u8 attribute_byte, u16 offset);
void	scroll_line();
u16		get_cursor();
void	set_cursor(u16 pos);


================================================
FILE: src/kernel/kernel.c
================================================
#include "../common.h"
#include "../drivers/screen.h"
#include "./utils.h"


s32		kmain()
{	
	clear_screen();
	
	kprint_rick_and_morty();
	
	kprint(
		"- Look Rick, we are in an OS!\n"
		"- Damn Morty, that's fantastic!\n\n"
	);
	kprint("So... Welcome to my OS!\n\n");
	kprint(
		"Actually, the functionality of this OS is limited only to displaying that creepy "
		"ASCII art. No commands, no more features. The only purpose of this OS is "
		"educational - you can follow easy steps in guide/ folder and create your own OS, "
		"just like this. The guide is well-documented and all written in Russian.\n\n"
	);
	kprint("Thank you for your attention!\n");
	kprint("Find me on GitHub: ");
	kprint_colored("https://github.com/thedenisnikulin", 0xf0);
	return 0;
}


================================================
FILE: src/kernel/utils.c
================================================
#include "./utils.h"
#include "../drivers/screen.h"
#include "../common.h"

void	kprint_rick_and_morty()
{
	u8 *char_map[] = {
		"__#_#_#\n",
		"__#####____", "#####\n",
		"__#", "###", "#____", "#", "###", "#\n",
		"__#", "#", "#", "#", "#____", "#", "#", "#", "#", "#\n",
		"__#", "#", "#", "#", "#____", "#", "#", "#", "#", "#\n",
		"__#####____", "#####\n",
		"___###_____", "_###_\n",
		"__##", "#", "##____", "#####\n",
		"__##", "#", "##____", "#", "###", "#\n",
		"__##", "#", "##____", "#", "###", "#\n",
		"__#", "#", "#", "#", "#____", "#", "###", "#\n",
		"___##", "#______", "###\n",
		"___#_#______#_#\n"
	};
	u8 color_map[] = { 
		0xbb,
		0xbb, 0x66,
		0xbb, 0xff, 0xbb, 0x66, 0xff, 0x66,
		0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff,
		0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff,
		0xff, 0xff,
		0xff, 0xff,
		0x77, 0xbb, 0x77, 0xee,
		0x77, 0xbb, 0x77, 0xff, 0xee, 0xff,	
		0x77, 0xbb, 0x77, 0xff, 0xee, 0xff,
		0xff, 0x77, 0xbb, 0x77, 0xff, 0xff, 0xee, 0xff,
		0x99, 0x77, 0x99,
		0x99
	};
	u8 i = 0;
	while (i < 61)
	{
		kprint_colored(char_map[i], color_map[i]);
		i++;
	}
}

void	kprint_colored(u8 *str, u8 attr)
{
	while (*str)
	{
		if (*str == '_')
			putchar(*str, 0x00);	
		else
			putchar(*str, attr);
		str++;
	}
}


================================================
FILE: src/kernel/utils.h
================================================
#include "../common.h"


void	kprint_rick_and_morty();
void	kprint_colored(u8 *str, u8 attr);
Download .txt
gitextract_w89a63ku/

├── .bashrc
├── .gitignore
├── README.md
├── guide/
│   ├── 00-BOOT-SECTOR/
│   │   ├── ex00/
│   │   │   ├── README.md
│   │   │   └── main.asm
│   │   ├── ex01/
│   │   │   └── main.asm
│   │   ├── ex02/
│   │   │   ├── main.asm
│   │   │   └── org_demo.asm
│   │   ├── ex03/
│   │   │   └── main.asm
│   │   ├── ex04/
│   │   │   ├── main.asm
│   │   │   └── print_string.asm
│   │   ├── ex05/
│   │   │   ├── main.asm
│   │   │   └── print_hex.asm
│   │   ├── ex06/
│   │   │   └── main.asm
│   │   ├── ex07/
│   │   │   ├── disk_load.asm
│   │   │   └── main.asm
│   │   └── ex08/
│   │       ├── gdt.asm
│   │       ├── main.asm
│   │       ├── print_string_pm.asm
│   │       └── switch.asm
│   └── 01-KERNEL/
│       ├── ex00/
│       │   ├── BUILD.md
│       │   ├── README.md
│       │   ├── boot/
│       │   │   ├── bootsect.asm
│       │   │   ├── disk_load.asm
│       │   │   ├── gdt.asm
│       │   │   ├── kernel_entry.asm
│       │   │   ├── print_hex.asm
│       │   │   ├── print_string.asm
│       │   │   ├── print_string_pm.asm
│       │   │   └── switch.asm
│       │   ├── build/
│       │   │   └── Makefile
│       │   └── kernel/
│       │       └── kernel.c
│       └── ex01/
│           ├── boot/
│           │   ├── bootsect.asm
│           │   ├── disk_load.asm
│           │   ├── gdt.asm
│           │   ├── kernel_entry.asm
│           │   ├── print_hex.asm
│           │   ├── print_string.asm
│           │   ├── print_string_pm.asm
│           │   └── switch.asm
│           ├── build/
│           │   └── Makefile
│           ├── common.c
│           ├── common.h
│           ├── drivers/
│           │   ├── lowlevel_io.c
│           │   ├── lowlevel_io.h
│           │   ├── screen.c
│           │   └── screen.h
│           └── kernel/
│               └── kernel.c
└── src/
    ├── boot/
    │   ├── bootsect.asm
    │   ├── disk_load.asm
    │   ├── gdt.asm
    │   ├── kernel_entry.asm
    │   ├── print_hex.asm
    │   ├── print_string.asm
    │   ├── print_string_pm.asm
    │   └── switch.asm
    ├── build/
    │   └── Makefile
    ├── common.c
    ├── common.h
    ├── drivers/
    │   ├── lowlevel_io.c
    │   ├── lowlevel_io.h
    │   ├── screen.c
    │   └── screen.h
    └── kernel/
        ├── kernel.c
        ├── utils.c
        └── utils.h
Download .txt
SYMBOL INDEX (42 symbols across 12 files)

FILE: guide/01-KERNEL/ex00/kernel/kernel.c
  function func (line 10) | void 	func() {}
  function kmain (line 16) | void	kmain()

FILE: guide/01-KERNEL/ex01/common.c
  function memcpy (line 12) | void	memcpy(u8 *src, u8 *dest, u32 bytes)

FILE: guide/01-KERNEL/ex01/common.h
  type u32 (line 16) | typedef unsigned int	u32;
  type s32 (line 17) | typedef 		 int	s32;
  type u16 (line 18) | typedef unsigned short	u16;
  type s16 (line 19) | typedef 		 short	s16;
  type u8 (line 20) | typedef unsigned char	u8;
  type s8 (line 21) | typedef 		 char	s8;

FILE: guide/01-KERNEL/ex01/drivers/lowlevel_io.c
  function port_byte_in (line 34) | unsigned char   port_byte_in(unsigned short port)
  function port_byte_out (line 57) | void    port_byte_out(unsigned short port, unsigned char data)
  function port_word_in (line 70) | unsigned char   port_word_in(unsigned short port)
  function port_word_out (line 80) | void port_word_out(unsigned short port, unsigned short data)

FILE: guide/01-KERNEL/ex01/drivers/screen.c
  function kprint (line 15) | void	kprint(u8 *str)
  function putchar (line 29) | void	putchar(u8 character, u8 attribute_byte)
  function scroll_line (line 55) | void	scroll_line()
  function clear_screen (line 82) | void	clear_screen()
  function write (line 95) | void	write(u8 character, u8 attribute_byte, u16 offset)
  function u16 (line 108) | u16		get_cursor()
  function set_cursor (line 122) | void	set_cursor(u16 pos)

FILE: guide/01-KERNEL/ex01/kernel/kernel.c
  function s32 (line 14) | s32		kmain()

FILE: src/common.c
  function memcpy (line 12) | void	memcpy(u8 *src, u8 *dest, u32 bytes)

FILE: src/common.h
  type u32 (line 16) | typedef unsigned int	u32;
  type s32 (line 17) | typedef 		 int	s32;
  type u16 (line 18) | typedef unsigned short	u16;
  type s16 (line 19) | typedef 		 short	s16;
  type u8 (line 20) | typedef unsigned char	u8;
  type s8 (line 21) | typedef 		 char	s8;

FILE: src/drivers/lowlevel_io.c
  function port_byte_in (line 34) | unsigned char   port_byte_in(unsigned short port)
  function port_byte_out (line 57) | void    port_byte_out(unsigned short port, unsigned char data)
  function port_word_in (line 70) | unsigned char   port_word_in(unsigned short port)
  function port_word_out (line 80) | void port_word_out(unsigned short port, unsigned short data)

FILE: src/drivers/screen.c
  function kprint (line 15) | void	kprint(u8 *str)
  function putchar (line 29) | void	putchar(u8 character, u8 attribute_byte)
  function scroll_line (line 55) | void	scroll_line()
  function clear_screen (line 82) | void	clear_screen()
  function write (line 95) | void	write(u8 character, u8 attribute_byte, u16 offset)
  function u16 (line 108) | u16		get_cursor()
  function set_cursor (line 122) | void	set_cursor(u16 pos)

FILE: src/kernel/kernel.c
  function s32 (line 6) | s32		kmain()

FILE: src/kernel/utils.c
  function kprint_rick_and_morty (line 5) | void	kprint_rick_and_morty()
  function kprint_colored (line 45) | void	kprint_colored(u8 *str, u8 attr)
Condensed preview — 66 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (207K chars).
[
  {
    "path": ".bashrc",
    "chars": 113,
    "preview": "function emu() {    # compile and run emulator\n    nasm $1.asm -f bin -o temp.bin\n\tqemu-system-x86_64 temp.bin\n}\n"
  },
  {
    "path": ".gitignore",
    "chars": 476,
    "preview": "*.bin\n*.raw\n\n# -- automatically generated --\n\n# Prerequisites\n*.d\n\n# Object files\n*.o\n*.ko\n*.obj\n*.elf\n\n# Linker output\n"
  },
  {
    "path": "README.md",
    "chars": 4631,
    "preview": "# os-project\n>Пишем свою собственную операционную систему с нуля!\n\nИдея написать ОС возникла у меня в процессе поиска ид"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex00/README.md",
    "chars": 845,
    "preview": "# Интро\n\nКогда мы включаем компьютер, он должен каким-то образом загрузить операционную систему. Он может загрузить ее с"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex00/main.asm",
    "chars": 2617,
    "preview": "; ------------------------------------------------------------------------------\n; Guide: \t00-BOOT-SECTOR\n; File:\t\tex00 "
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex01/main.asm",
    "chars": 2077,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex01 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex02/main.asm",
    "chars": 3016,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex02 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex02/org_demo.asm",
    "chars": 2577,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex02 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex03/main.asm",
    "chars": 3561,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex03 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex04/main.asm",
    "chars": 2363,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex04 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex04/print_string.asm",
    "chars": 1650,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex04 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex05/main.asm",
    "chars": 1270,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex05 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex05/print_hex.asm",
    "chars": 2753,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex05 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex06/main.asm",
    "chars": 3358,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex06 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex07/disk_load.asm",
    "chars": 2343,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex07/main.asm",
    "chars": 1759,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex07 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex08/gdt.asm",
    "chars": 4503,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex08/main.asm",
    "chars": 2157,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex08/print_string_pm.asm",
    "chars": 2107,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/00-BOOT-SECTOR/ex08/switch.asm",
    "chars": 2705,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/01-KERNEL/ex00/BUILD.md",
    "chars": 2933,
    "preview": "# Процесс сборки\n\nДля сборки нам понадобится собрать кросс-компилятор gcc для i386 архитектуры процессора. Удобнее испол"
  },
  {
    "path": "guide/01-KERNEL/ex00/README.md",
    "chars": 1174,
    "preview": "# `01-KERNEL / ex00`: Процесс сборки, структура проекта, дебаг\n> Новые штучки: `Makefile`, `gcc`, `gdb`\n\nНовые файлы упр"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/bootsect.asm",
    "chars": 2358,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t01-KERNEL\n; File:\t\tex00 / boot"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/disk_load.asm",
    "chars": 2342,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/gdt.asm",
    "chars": 4498,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/kernel_entry.asm",
    "chars": 640,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t01-KERNEL\n; File:\t\tex00 / kern"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/print_hex.asm",
    "chars": 2830,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex05 /"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/print_string.asm",
    "chars": 1787,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex04 /"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/print_string_pm.asm",
    "chars": 2114,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/01-KERNEL/ex00/boot/switch.asm",
    "chars": 2705,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/01-KERNEL/ex00/build/Makefile",
    "chars": 1036,
    "preview": "# флаг для дебага для gcc\nCFLAGS = -g\n\nrun: os-image.bin\n\tqemu-system-i386 -fda os-image.bin\n\tmake clean\n\ndebug: os-imag"
  },
  {
    "path": "guide/01-KERNEL/ex00/kernel/kernel.c",
    "chars": 815,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex00 / kerne"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/bootsect.asm",
    "chars": 2358,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t01-KERNEL\n; File:\t\tex00 / boot"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/disk_load.asm",
    "chars": 2342,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/gdt.asm",
    "chars": 4498,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/kernel_entry.asm",
    "chars": 640,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t01-KERNEL\n; File:\t\tex00 / kern"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/print_hex.asm",
    "chars": 2830,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex05 /"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/print_string.asm",
    "chars": 1787,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex04 /"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/print_string_pm.asm",
    "chars": 2234,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/01-KERNEL/ex01/boot/switch.asm",
    "chars": 2705,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "guide/01-KERNEL/ex01/build/Makefile",
    "chars": 1167,
    "preview": "C_FILES = $(shell find ../drivers/*.c ../*.c)\ntemp = $(notdir $(C_FILES))\nO_FILES = ${temp:.c=.o}\n\n# флаг для дебага для"
  },
  {
    "path": "guide/01-KERNEL/ex01/common.c",
    "chars": 488,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / commo"
  },
  {
    "path": "guide/01-KERNEL/ex01/common.h",
    "chars": 798,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / commo"
  },
  {
    "path": "guide/01-KERNEL/ex01/drivers/lowlevel_io.c",
    "chars": 3992,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drive"
  },
  {
    "path": "guide/01-KERNEL/ex01/drivers/lowlevel_io.h",
    "chars": 591,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drive"
  },
  {
    "path": "guide/01-KERNEL/ex01/drivers/screen.c",
    "chars": 3506,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drive"
  },
  {
    "path": "guide/01-KERNEL/ex01/drivers/screen.h",
    "chars": 982,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drive"
  },
  {
    "path": "guide/01-KERNEL/ex01/kernel/kernel.c",
    "chars": 712,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / kerne"
  },
  {
    "path": "src/boot/bootsect.asm",
    "chars": 2358,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t01-KERNEL\n; File:\t\tex00 / boot"
  },
  {
    "path": "src/boot/disk_load.asm",
    "chars": 2342,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "src/boot/gdt.asm",
    "chars": 4498,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "src/boot/kernel_entry.asm",
    "chars": 640,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t01-KERNEL\n; File:\t\tex00 / kern"
  },
  {
    "path": "src/boot/print_hex.asm",
    "chars": 2830,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex05 /"
  },
  {
    "path": "src/boot/print_string.asm",
    "chars": 1787,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex04 /"
  },
  {
    "path": "src/boot/print_string_pm.asm",
    "chars": 2234,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "src/boot/switch.asm",
    "chars": 2705,
    "preview": "; ------------------------------------------------------------------------------\n; Guide:\t00-BOOT-SECTOR\n; File:\t\tex08 /"
  },
  {
    "path": "src/build/Makefile",
    "chars": 1144,
    "preview": "C_FILES = $(shell find ../kernel/*.c ../drivers/*.c ../*.c)\ntemp = $(notdir $(C_FILES))\nO_FILES = ${temp:.c=.o}\n\n# флаг "
  },
  {
    "path": "src/common.c",
    "chars": 488,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / commo"
  },
  {
    "path": "src/common.h",
    "chars": 798,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / commo"
  },
  {
    "path": "src/drivers/lowlevel_io.c",
    "chars": 3992,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drive"
  },
  {
    "path": "src/drivers/lowlevel_io.h",
    "chars": 591,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drive"
  },
  {
    "path": "src/drivers/screen.c",
    "chars": 3449,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drive"
  },
  {
    "path": "src/drivers/screen.h",
    "chars": 982,
    "preview": "/*------------------------------------------------------------------------------\n*\tGuide:\t01-KERNEL\n*\tFile:\tex01 / drive"
  },
  {
    "path": "src/kernel/kernel.c",
    "chars": 763,
    "preview": "#include \"../common.h\"\n#include \"../drivers/screen.h\"\n#include \"./utils.h\"\n\n\ns32\t\tkmain()\n{\t\n\tclear_screen();\n\t\n\tkprint_"
  },
  {
    "path": "src/kernel/utils.c",
    "chars": 1274,
    "preview": "#include \"./utils.h\"\n#include \"../drivers/screen.h\"\n#include \"../common.h\"\n\nvoid\tkprint_rick_and_morty()\n{\n\tu8 *char_map"
  },
  {
    "path": "src/kernel/utils.h",
    "chars": 94,
    "preview": "#include \"../common.h\"\n\n\nvoid\tkprint_rick_and_morty();\nvoid\tkprint_colored(u8 *str, u8 attr);\n"
  }
]

About this extraction

This page contains the full source code of the thedenisnikulin/os-project GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 66 files (133.5 KB), approximately 43.6k tokens, and a symbol index with 42 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!