Napíšeme si vlastní operační systém od základů. K tomu budeme potřebovat znalosti assembleru a C++ a následující programovací nástroje (pracovat budeme na Linuxu):
-
GNU C++ překladač gcc a GNU assembler as
-
Binutils (linker) ld
-
Vývojové knihovny pro C++
-
nástroj make pro sestavování projektů
Na Debianu a klonech stačí nainstalovat
sudo apt install g++ binutils libc6-dev-i386
Psaní operačního systému je velmi těžká věc, ale díky tomu pochopíme do detailu, jak to všechno funguje a naučíme se to, co se nikdo jiný nemůže naučit.
make
Abychom nemuseli sestavovat (kompilovat, linkovat a instalovat) všechno ručně, použijeme nástroj make, který to bude dělat za nás.
Proto nejdříve napíšeme předpis pro make, který se standardně jmenuje Makefile.
make funguje tak, že mu řekneme co má vytvořit (zadáme mu úkol) a podle definice v Makefile to make udělá.
Úkol vždy začíná na prvním sloupci řádku a končí dvojtečkou, za dvojtečkou jsou tzv. závislosti. Pod úkolem je zapsáno, jak to má make udělat.
Jednotlivé úkoly jsou od sebe odděleny prázdným řádkem.
1
2
3
4
5
6
7
8
9
10
ukol1: (1)
jak udelat ukol1 (1. krok) (2)
jak udelat ukol1 (2. krok) (3)
jak udelat ukol1 (3. krok) (4)
ukol2: zavislost1 (5)
jak mam udelat ukol2 (6)
ukol3: zavislost1 zavislost2 (7)
jak mam udelat ukol3 (8)
| 1 | úkol1 nezávisí na ničem |
| 2 | 1., co make udělá při řešení úkolu 1 (prázdné místo před "jak udelat ukol1 (1. krok)" je znak <tabulátor>) |
| 3 | 2., co make udělá při řešení úkolu 1 (prázdné místo před "jak udelat ukol1 (2. krok)" je znak <tabulátor>) |
| 4 | 3., co make udělá při řešení úkolu 1 (prázdné místo před "jak udelat ukol1 (3. krok)" je znak <tabulátor>) |
| 5 | úkol2 závisí na splněném úkolu1 (aby se mohl úkol2 začít dělat, musí být hotov úkol1) |
| 6 | předpis, jak se má udělat úkol2 |
| 7 | úkol3 závisí na úkolu1 a úkolu2 |
| 8 | předpis, jak se má udělat úkol3 |
make postupuje tak, že nejprve vyřeší závislosti, a pak úkol udělá. Při dělání úkolu postupuje odshora dolů. Pokud je úkol časově novější než závislosti, je považován za hotový.
Naše Makefile
Do proměnné GCCPARAMS si uložíme parametry kompilátoru. Do proměnné ASPARAMS si uložíme parametry assembleru. Do proměnné LDPARAMS si uložímě parametry linkeru. Do proměnné objects si budeme ukládat objektové soubory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GCCPARAMS = -m32 -fno-use-cxa-atexit -nostdlib -fno-builtin -fno-rtti -fno-exceptions -fno-leading-underscore (1)
ASPARAMS = --32 (2)
LDPARAMS = -melf_i386 (3)
objects = loader.o kernel.o
%.o: %.cpp (4)
gcc $(GCCPARAMS) -c -o $@ $< (5)
%.o: %.s (6)
as $(ASPARAMS) -o $@ $< (7)
mykernel.bin: linker.ld $(objects) (8)
ld $(LDPARAMS) -T $< -o $@ $(objects) (9)
install: mykernel.bin (10)
sudo cp $< /boot/mykernel.bin (11)
| 1 | Parametry kompilátoru gcc: kompilujeme pro 32bit, nebudeme používat ukazatel na vyjimky (exceptions), nemáme stdlib, nebudeme generovat informace o každé třídě s virtuálními funkcemi, nemáme vyjimky, nedělej úvodní podrtžítko v názvu funkce |
| 2 | Parametry assembleru: kompilujeme pro 32bit |
| 3 | Parametry linkeru: linkuj pro architekturu i386 a udělej formát spustitelného souboru elf |
| 4 | Pravidlo pro překlad z C++ do objektového kódu |
| 5 | Jak se překládají zdrojové soubory C++ do objektového kódu. Zavolá se gcc s parametry GCCPARAMS, $@ značí cílový soubor a $< zdrojový soubor |
| 6 | Pravidlo pro překlad z assembleru do objektového kódu |
| 7 | Zavolá se assembler as s parametry ASPARAMS |
| 8 | Pravislo pro sestavení výsledného jádra OS |
| 9 | Zavolá se linker ld s parametry LDPARAMS |
| 10 | Pravidlo pro instalaci jádra OS na lokální počítač |
| 11 | Potřebujeme být root, abychom mohli instalovat jádro OS |
Zavaděč operačního systému
Zavaděč operačního systému bohužel nemůžeme napsat v nějakém vyšším programovacím jazyce, ale musíme se spustit do hlubin a použít Assembler. Budeme programovat na úrovni strojových instrukcí procesoru.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
.set MAGIC, 0x1badb002
.set FLAGS, (1<<0 | 1<<1)
.set CHECKSUM, -(MAGIC + FLAGS)
.section .multiboot
.long MAGIC
.long FLAGS
.long CHECKSUM
.section .text
.extern kernelMain
.extern callConstructors
.global loader
loader:
mov $kernel_stack, %esp
call callConstructors
push %eax
push %ebx
call kernelMain
_stop:
cli
hlt
jmp _stop
.section .bss
.space 2*1024*1024; # 2 MiB
kernel_stack:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void printf(char* str)
{
static unsigned short* VideoMemory = (unsigned short*)0xb8000;
for(int i = 0; str[i] != '\0'; ++i)
VideoMemory[i] = (VideoMemory[i] & 0xFF00) | str[i];
}
typedef void (*constructor)();
extern "C" constructor start_ctors;
extern "C" constructor end_ctors;
extern "C" void callConstructors()
{
for(constructor* i = &start_ctors; i != &end_ctors; i++)
(*i)();
}
extern "C" void kernelMain(const void* multiboot_structure, unsigned int /*multiboot_magic*/)
{
printf("Ahoj Jirko! --- https://sspvc.lixis.cz");
while(1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ENTRY(loader)
OUTPUT_FORMAT(elf32-i386)
OUTPUT_ARCH(i386:i386)
SECTIONS
{
. = 0x0100000;
.text :
{
*(.multiboot)
*(.text*)
*(.rodata)
}
.data :
{
start_ctors = .;
KEEP(*( .init_array ));
KEEP(*(SORT_BY_INIT_PRIORITY( .init_array.* )));
end_ctors = .;
*(.data)
}
.bss :
{
*(.bss)
}
/DISCARD/ : { *(.fini_array*) *(.comment) }
}
Aby se náš OS mohl spustit musíme malinko upravit /boot/grub-grub.cfg
1
2
3
4
5
6
7
8
9
### BEGIN /etc/grub.d/40_custom ### (1)
# This file provides an easy way to add custom menu entries. Simply type the
# menu entries you want to add after this comment. Be careful not to change
# the 'exec tail' line above.
menuentry 'Muj operacni system 1' { (2)
multiboot /boot/mykernel.bin (3)
boot (4)
}
### END /etc/grub.d/40_custom ### (5)
| 1 | Najdeme sekci 40_custom (uvnitř si můžeme psát, co chceme) |
| 2 | menuentry označuje řádek v menu grubu, pojmenujeme si ho 'Muj operacni system 1' |
| 3 | Řekneme grubu, kde má hledat kernel |
| 4 | Nabootuj to |
| 5 | Tento řádek nesmíme smazat |
Zdroje
Viktor Engelman
poštovní adresa: Zimmerstrasse 16 03044 Chotěbuz Kontakt: Telefon: +49 170 500 5810 E-mail: viktor@wyoos.org