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):

  1. GNU C++ překladač gcc a GNU assembler as

  2. Binutils (linker) ld

  3. Vývojové knihovny pro C++

  4. 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.

ukázka úkolu a závislostí v Makefile
 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.

Makefile
 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.

loader.s
 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:
kernel.cpp
 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);
}
linker.ld
 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

/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
Bootování našeho systému

Zdroje