OS w Pascalu cz. 1

lukasz1235

1 Wstęp
     1.1 fpc i systemy 64-bitowe
     1.2 Drzewo katalogów
2 BIOS
3 Co robi GRUB?
4 start.asm
5 kernel.pas
6 kernel.ld
7 Makefile

Wstęp

Celem tego kursu będzie pokazanie jak napisać prosty system operacyjny w Pascalu. Oto lista narzędzi które będziemy potrzebowali: * system operacyjny (najlepiej Linux) * Free Pascal (http://www.freepascal.org) * NASM (http://sourceforge.net/projects/nasm/) * GNU Make (http://www.gnu.org/software/make/) * GNU LD (http://www.gnu.org/software/binutils/) * QEMU (http://www.qemu.org/) lub Bochs (http://bochs.sourceforge.net/) * mount (w Windowsie można użyć VFD (http://chitchat.at.infoseek.co.jp/vmware/vfd.html#download))

fpc i systemy 64-bitowe

Ponieważ nasz system będzie 32-bitowy, pisząc pod Linuksem 64-bitowym w repozytorium mamy standardowo 64-bitowy kompilator. W takim przypadku musimy zastosować się do wskazówek ze strony: http://wiki.lazarus.freepascal.org/Cross_compiling#To_Linux

Drzewo katalogów

Powinno wyglądać mniej więcej tak: ``` 4pOS + img + output + src + kernel console.pas kernel.pas start.asm system.pas kernel.ld 4pos.ima Makefile ```

BIOS

Po uruchomieniu komputera pierwszym programem który jest uruchamiany jest BIOS. Na początku sprawdza sprzęt, a następnie skanuje dyski i napędy w poszukiwaniu systemu. O tym czy na nośniku jest zapisany system operacyjny świadczą ostatnie dwa bajty pierwszego sektora. Są wtedy równe 0x55 i 0xAA. Po znalezieniu systemu BIOS ładuje pierwszy sektor pod adres 0x7C00 i skacze tam. W przypadku naszego systemu BIOS załaduje GRUBa.

Co robi GRUB?

GRUB po uruchomieniu odblokowuje linię A20, ustawia GDT, włącza tryb chroniony procesora i ładuje nasz kernel do pamięci. Więc nie musimy się teraz martwić o te rzeczy, ale pisanie sterowników do dysków i obsługi systemów plików i tak nas nie ominie.

start.asm

Na początku króciutki kod w asmie: ```asm [BITS 32] [SECTION .text] mboot: dd 0x1BADB002 ; Header magic dd 0x00001 ; Flags (PAGE_ALIGN) dd -(0x1BADB002+0x00001) ; Checksum ``` Mamy tutaj nagłówek multiboot: Nazwa | Wartość | Opis Magic | 0x1BADB002 | Magiczna stała wartość Flags | 0x00000001 | Flagi. Zapalamy tylko pierwszy bit flagi PAGE_ALIGN. Oznacza ona, że adres kernela jest wyrównany do rozmiaru strony (4KiB) Checksum | -(0x1BADB002+0x00001) | Suma kontrolna. Musi być równa sumie pól `magic` i `flags`. Oczywiście pól jest o wiele więcej, ale tylko te trzy są wymagane.

Więcej informacji: http://www.gnu.org/software/grub/manual/multiboot/multiboot.html

kernel.pas

```pascal {$ASMMODE INTEL}

unit kernel;

interface

uses console;

procedure StartKernel;

implementation

procedure StartKernel;[public,alias:'StartKernel'];
begin
ClearScreen;
SetColor(Green,Black);
PrintStr('4pOS 0.1');

while true do begin end; //Nieskonczona petla
end;

end.

Ten plik też długi nie jest. Mamy tu jedynie czyszczenie ekranu i wypisanie zielonego napisu `4pOS 0.1`. Następnie procesor wskakuje do nieskończonej pętli. Proste prawda? ;)

<h1>console.pas</h1>
```pascal
unit Console;

interface

const
   Black = 0;
   Blue = 1;
   Green = 2;
   Cyan = 3;
   Red = 4;
   Magenta = 5;
   Brown = 6;
   LightGray = 7;
   DarkGray = 8;
   LightBlue = 9;
   LightGreen = 10;
   LightCyan = 11;
   LightRed = 12;
   LightMagenta = 13;
   Yellow = 14;
   White = 15;

var
   Screen: PChar =  PChar ($B8000);
   CursorX, CursorY: integer;
   Color: char;
   Background: integer = 0;

procedure ClearScreen;
procedure PrintStr(text: PChar);
procedure PrintInt(number:integer);
procedure PrintChar(ch: Char);
procedure SetColor(txt,back:integer);
procedure SetXY(x,y: integer);
procedure Scroll;

implementation

procedure ClearScreen;
var
   i: integer;
begin
   for i:=0 to 2000 do
   begin
      Screen[i*2]:=#0;
      Screen[i*2-1]:=char(white);
   end;
end;

procedure PrintStr(text: PChar);
var
   address: Word;
   i: integer;
begin
   i:=0;
   if (CursorX > 79) or (text[i] = #13) then 
   begin
      SetXY(0, CursorY+1);
   end 
   else
   begin
      if (text[i] = #10) then
      begin
         SetXY(CursorX, CursorY+1);
      end
      else
      begin
         if (CursorY > 24) then
         begin
            SetXY(CursorX, 24);
            Scroll;
         end;
   
         repeat
            address:= CursorX*2 + CursorY * 160;
            Screen[address]:= text[i];
            Screen[address+1]:= color;
            SetXY(CursorX+1, CursorY);
            i:=i + 1;
         until text[i] = #0
   
      end;
   end;
end;
   
procedure PrintInt(number: integer);
var
   chars: array [0..11] of Char;
   negative: Boolean;
   i: integer;
   txt: PChar;
begin
   txt:= @chars[11];
   i:=11;
   negative:= False;
   if number<0 then 
   begin 
      number:=-number;
      negative:= True;
   end;
   
   repeat
      chars[i]:= char((number mod 10) + byte('0'));
      number:= number div 10;
      number-=1;
      Dec(txt);
   until number=0;

   if negative=True then
   begin
      PrintStr('-');
   end;

   PrintStr(txt);
end;

procedure PrintChar(ch: Char);
var
   address: Word;
begin
   if (CursorX > 79) or (ch = #13) then
      SetXY(0, CursorY+1)
   else
      if ch = #10 then
         SetXY(CursorX, CursorY+1)
      else
         if CursorY > 24 then
         begin
            SetXY(CursorX, 24);
            Scroll;
         end;
      
   address:= CursorX*2 + CursorY * 160;
   Screen[address]:= ch;
   Screen[address+1]:= color;
   SetXY(CursorX+1, CursorY);
end;
   
procedure SetColor(txt,back: integer);
begin
   color:=char(txt+back*16);
end;
   
procedure Scroll;
var
   address : word;
begin
   for address:=0 to 1920 do
   begin
      Screen[address*2]   := Screen[address*2+160];
      Screen[address*2+1] := Screen[address*2+1+160];
   end;
   
   for address:=1921 to 2000 do
   begin
      Screen[address*2]  := #0;
      Screen[address*2+1]:= char(15); 
   end;
end;
   
procedure SetXY(x,y: integer);
var
   temp: integer;
begin
   CursorX := x;
   CursorY := y;
   temp:=y*80+x;
   outportb($3D4, 14);
   outportb($3D5, temp>>8);
   outportb($3D4, 15);
   outportb($3D5, temp);
end;
   
   
end.

Tu mamy już dłuższy kod. Screen jest to adres pamięci ekranu. Wszystko co tam zapiszemy zostanie wyświetlone przez kartę graficzną na ekranie. GRUB ustawił kolorowy tryb tekstowy 80x25, gdzie na każdy znak przypadają dwa bajty. Pierwszy to poprostu znak, a drugi to kolor. Bity 7..4 to kolor tła, a bity 3..0 to kolor znaku. Myślę, że kod nie wymaga większego tłumaczenia.

kernel.ld

``` OUTPUT_FORMAT("elf32-i386") ENTRY(StartKernel) SECTIONS { .text 0x100000 : { text = .; _text = .; __text = .; *(.text) . = ALIGN(4096); } .data : { data = .; _data = .; __data = .; *(.data) kimage_text = .; LONG(text); kimage_data = .; LONG(data); kimage_bss = .; LONG(bss); kimage_end = .; LONG(end); . = ALIGN(4096); } .bss : { bss = .; _bss = .; __bss = .; *(.bss) . = ALIGN(4096); } end = .; _end = .; __end = .; } ``` Powyższy kod to skrypt linkera. Tłumaczenie tego odbiega od tematu, więc dam tylko linka: http://sourceware.org/binutils/docs-2.21/ld/Scripts.html#Scripts

Makefile

``` 4pOS.bin: output/start.o kernel ld -m elf_i386 output/start.o output/kernel.o output/system.o output/console.o -T"src/kernel.ld" -o "4pOS.bin"

output/start.o: src/kernel/start.asm
nasm -f elf32 src/kernel/start.asm -o output/start.o

kernel:
/sciezka/do/ppc386 -a -Aelf src/kernel/kernel.pas -Fu"src/kernel" -FE"output" -O3 -Op3 -Si -Sc -Sg -Xd -Tlinux -Rintel

install: 4pOS.bin
sudo mount 4pos.ima img -o loop
sudo cp 4pOS.bin img
sudo umount img

clean:
rm output/*

run: install
qemu -fda 4pos.ima

 Aby skompilować OSa należy wydać polecenie `make`, a obraz dyskietki stworzymy poleceniem `make run`.
        
<h1>Zakończenie</h1>
Efekt nie jest może widowiskowy, ale już wkrótce druga część kursu.
![4pos01.png](//static.4programmers.net/uploads/attachment/11237371864e58b6aa3b1c9.png)

I źródła: [4pOS01.zip](//4programmers.net/Download/111569/211)

9 komentarzy

jak ktoś nie ma linuxa to może użyć programu cygwin terminal

Podejrzewam, że WinDos korzysta z przerwań DOSa (int 21h), więc żeby zadziałało musiałbyś zaimplementować te przerwania we własnym kernelu. O przerwaniach było w kolejnej części, która zaginęła w akcji. Jeśli jesteś zainteresowany daj znać - podeślę na PW.

Ewentualnie można przepisać te unity tak by nie korzystały z mechanizmów DOSa/Windowsa.

Jednak obie opcje według mnie mijają się trochę z celem, chyba, że chcesz utrzymywać kompatybilność z tymi systemami.

Przede wszystkim jednak nie mieszaj w to TP - jest mało warty podczas pisania kernela.

A tak teoretycznie (i praktycznie), jak skopiuje pliki PAS z folderu systemowego TurboPascala (np.: WinDos), to będę miał więcej komend/możliwości?

  1. Gdzieś był ale zaginął w akcji. Jeśli Ci zależy to daj do siebie jakiś kontakt to podeślę.

A to się będę mógł cieszyć podwójnie - Minecraft 1.8 oraz druga część kursu :)

We wrześniu :)

@lukasz1235: Kiedy kolejna część? ;)

Jakiś bug w kojocie. Już zgłoszone na Redmine.

Teraz jest znacznie lepiej :)
Nie mogę się doczekać kolejnych części ;)
BTW, nie można pobrać załącznika.