OS w pascalu cz. 1 - Prosty shell


Spis treści

     1 Wstęp
     2 Na początek assembler
     3 Kernel
     4 Shell
     5 Skrypt linkera
     6 Makefile
     7 QEMU
     8 Zakończenie


Wstęp


W tym kursię pokażę jak napisać prosty system we Free Pascalu. Nasz OS (skrót od Operating System) będzie posiadał prostego shella, obsługę przerwań, wyjątków i systemu plików FAT. Na razie może nic ci to nie mówić, ale myślę, że po przeczytaniu tego kursu wszystko powoli się wyjaśni.
Do napisania systemu będziemy potrzebowali kilku narzędzi. Oto one;


Na początku stwórz gdzieś na dysku folder o nazwie 4pOS w którym będą przechowywane źródła systemu. Tam stwórz katalogi img i output. Całość powinna wyglądać tak:


Na początek assembler


Jest to trochę dziwne, że w kursie o systemie w pascalu zacznę od assemblera, ale obiecuję, że później będziemy pisać głównie w pascalu.
Oto kod:
[BITS 32]
[SECTION .text]
EXTERN code,bss,end
mboot: ;informacje dla gruba
dd 0x1BADB002
dd 0x10001
dd -(0x1BADB002+0x10001)
dd bss
dd end
dd _start

GLOBAL _start

EXTERN StartKernel

L6:
 jmp L6

_start:
call StartKernel ;uruchamiamy kernela w pascalu

[SECTION .bss]
kstack: resd 1024

[SECTION .data]

Pierwsza linijka informuje kompilator, że piszemy kod 32bitowy. Dalej mamy informacje dla GRUBa. A na końcu przechodzimy do kodu w pascalu.

Kernel


A ten kod umieszczamy w pliku kernel.pas w katalogu 4pOS.
unit kernel;

interface

uses shell;

procedure StartKernel;
function inportb(port:word):byte;
procedure outportb(port, zn:word);
procedure reset;

implementation

procedure StartKernel;[public,alias:'StartKernel'];
var
        i:integer;
begin
        for i:=0 to 127 do //dzieki temu nie beda wyswietlaly sie niepozadane znaki na starcie
                keyp[i]:=True;

        for i:=0 to 100 do //czyszczenie zmiennej "Command"
                Command[i]:=#0;

        ClearScreen;
        SetColor(White, Black);
        PrintStr('Witaj w systemie ');
        SetColor(Green, Black);
        PrintStr('4pOS!');

        PrintStr(#13);
        SetColor(LightGray,Black);
        PrintStr('>');
        SetColor(White,Black);
        while true do
                keys; //procedura odczytujaca nacisniete klawisze
end;

function inportb(port:word):byte;[public, alias: 'inportb'];
var
        temp : byte ;
begin
        asm
                mov dx,port
                in al,dx
                mov temp , al
        end;
end;


procedure outportb(port, zn:word);[public, alias: 'outportb'];
var
        zz:char;
begin
        zz:=char(zn);
        asm
                mov dx, port
                mov al, zz
                out dx, al
        end;
end;

procedure reset;[public, alias: 'reset'];
begin
        asm
                mov        al, 0feh
                out        64h, al
        end;
end;

end.

Mamy tu procedurę startową, i kilka innych procedur, które przydadzą się póżniej.

Shell


Ponieważ shell pisze się najłatwiej i najprzyjemniej zaczniemy od niego.
Ale najpierw wyjaśnię co to jest shell.
Shell to inaczej powłoka. Jest pośrednikiem pomiędzy użytkownikiem a systemem operacyjnym. Od użytkownika przyjmuje polecenia, a system dzięki niemu wyświetla informacje dla użytkownika.

Plik shell.pas zaczniemy od ustalenia kilku stałych
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;

        F1=59;
        F2=60;
        F3=61;
        F4=62;
        F5=63;
        F6=64;
        F7=65;
        F8=66;
        F9=67;
        F10=68;
        F11=87;
        F12=88;
        Enter=28;
        Crtl=29;
        Alt=56;
        BkSpc=14;
        Space=57;
        Shift=42;
        CapsLock=58;
        up=72 ;
        left=75;
        right=77;
        down=80 ;

        tblchar : array [1..57] of char=
           ('0','1','2','3','4','5','6','7','8','9','0','?','=','0',' ',
           'Q','W','E','R','T','Y','U','I','O','P','[',']','0','0',
           'A','S','D','F','G','H','J','K','L',' ','{','}','0','0',
           'Z','X','C','V','B','N','M',',','.','/','0','*','0',' ');
... i zmiennych
var
        Screen: pcharPChar ($B8000);
        CursorX, CursorY: integer;
        Color: char;
        Background: integer = 0;
        backspace: array [0..4000] of integer;
        Command: PChar;
        e: integer = 0;
        keyp: array[0..127] of Boolean;
        address: Word;
        cmd: integer =0;


Odczytywanie naciśniętego klawisza to tylko odczytanie bajtu z portu 0x60, a potem zamienienie go na znak. Na koniec wysłanie bajtu 0x20 na port 0x20. W zależności od naciśniętego klawisza procedura wykonuje różne czynności. Dla klawisza backspace będzie to skasowanie ostatniego znaku, dla klawisza enter wywołania polecenia, a dla reszty wyświetlanie znaku na ekranie.
procedure keys;
var
        b: Byte;
        key: array[0..127] of Boolean;
        z: integer=2;
begin
        b:= inportb($60); //czytamy z portu
        if b>$7F then
                key[b xor $80]:= False
        else
                key[b]:= True
//BACKSPACE
        if key[BkSpc] then //jezeli nacisniety backspace
        begin
                if keyp[BkSpc]<>True then
                begin
                        address:=CursorX*2+CursorY*160;
                        if backspace[address-2] = 1 then //jezeli zostalo wpisane z klawiatury
                        begin
                                CursorX := CursorX - 1; //cofamy kursor
                                Screen[CursorX*2+CursorY*160]:=#0; //usuwamy znak
                                e:=e-1; //cofamy pozycje komendy
                                Command[e]:=#0; //usuwamy znak z komendy
                                SetXY(CursorX,CursorY); //ustawiamy kursor
                        end;
                        keyp[BkSpc]:=True;
                end;
        end
        else
        begin
                keyp[BkSpc]:=False;
        end;
//ENTER
        if key[enter] then //jezeli enter
        begin
                if keyp[enter]<>True then
                begin
                        Command2; //wykonujemy polecenie
                        keyp[enter]:=True;
                end;
        end
        else
        begin
                keyp[enter]:=False;
        end;

        repeat
                if key[z] then //jezeli cos innego
                begin
                        if keyp[z]<>True then
                        begin
                                PrintKey(tblchar[z]); //piszemy na ekranie
                                Command[e]:=tblchar[z]; //dodajemy do komendy
                                e+=1;
                                keyp[z]:=True;
                        end;
                end
                else
                        keyp[z]:=False;
                z+=1;
        until z=127;
        outportb($20, $20);
end;
Chwilę zatrzymajmy się na backspace. Wiadomo, że backspace kasuje tylko te znaki które uzytkownik wpisał z klawiatury, a nie ruszał tych które napisał program. Informacje o tym przechowuje zmienna backspace. Kiedy ma wartość 1 to znak możemy spokojnie skasować.

Mówiłem o pisaniu na ekran. Jest to nic innego niż kopiowanie znaku do pamięci ekranu. W nieparzystych komórkach tej tablicy przechowywany jest kolor znaku, a sam znak jest przechowywany w komórkach parzystych.
Procedury, które wypiszą na ekran znak (PrintKey) i łacuch (PrintStr):
procedure PrintKey(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;       
        backspace[address]:=1;
        backspace[address+1]:=1;
        SetXY(CursorX+1, CursorY);
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;       
                backspace[address]:=0;
                backspace[address+1]:=0;
                SetXY(CursorX+1, CursorY);
                i:=i + 1;
        until text[i] = #0

                end;
        end;
end;
Procedura do czyszczenia ekranu jest bardzo prosta
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;
A procedurą SetColor ustawimy kolor aktualnego znaku
procedure SetColor(txt,back: integer);
begin
        color:=char(txt+back*16);
end;
Trzeba jeszcze ustawić pozycję kursora
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;
I w przypadku gdy znaków będzie za dużo pomoże nam procedura Scroll
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;
Teraz pora na interpreter poleceń
procedure Command2;
var
        pol : integer= 0;
        a:integer;
        i : integer = 0;
        q : pchar;
        b:integer=0;
        wynik: pchar;
        ii:integer =0;
begin
        cmd := 0;
        if (Command[0]='E') and (Command[1]='X') and (Command[2]='I') and (Command[3]='T') and (Command[4]=#0) then
        begin // exit
                reset;
        end
        else
        if (Command[0]='H') and (Command[1]='E') and (Command[2]='L') and (Command[3]='P') and (Command[4]=#0) then
        begin // help
                PrintStr(#13);
                PrintStr('EXIT - resetuje komputer');
                PrintStr(#13);
                PrintStr('HELP - wyswietla pomoc');
                        end
        else
        begin
                PrintStr(#13);
                PrintStr('Polecenie "');
                PrintStr(Command);
                PrintStr('" nie zostalo odnalezione.');
        end;

        e:=0;
        PrintStr(#13);
        SetColor(LightGray,Black);
        PrintStr('>');
        SetColor(White,Black);
        repeat //czyszczenie zmiennej "Command"
                Command[pol]:=#0;
                pol+=1;
        until pol=100;
end;
I shell gotowy :)

Skrypt linkera


OUTPUT_FORMAT("elf32-i386")
ENTRY(_start)
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 = .;
}
Nię będę go opisywał, bo szczerze mówiąc sam dokładnie nie wiem o co w nim chodzi ;)

Makefile


Makefile wygeneruje nam ładny obraz dyskietki
4pOS:
        nasm -f elf start.asm -o output/start.o
        fpc -a -Anasmelf kernel.pas -FE"output" -Fu"shell.pas" -RIntel
        ld --emit-relocs output/start.o output/kernel.o output/shell.o -T"kernel.ld" -o "4pOS.bin"
        sudo mount 4pOS.ima img -o loop
        sudo cp 4pOS.bin img
        sudo umount img


QEMU


Obraz wczytujemy do emulatora QEMU poleceniem
qemu -fda 4pOS.ima


Zakończenie


A oto efekt naszej pracy:


Załączam też źródła:
OS_w_pascalu-src1.zip (90.88 kB)
Informacje
Ostatnia modyfikacja 15-01-2010 22:40 Ostatni autor lukasz1235
Ilość wyświetleń 1350 Wersja 1
Komentarz
lukasz1235 dnia 03-04-2010 11:50
W arcie wszystko jest napisane:
Sueroski dnia 03-04-2010 10:41
do kompilatora NASM bądź TASM (w  tym przypadku chyba TASM, patrząc na składnię)
M-M dnia 26-01-2010 19:37
"Na początek assembler"
Do jakiego kompilatora to wpisać ?

Copyright © 2000-2006 by Coyote Group 0.9.3-pre3
Czas generowania strony: 0.3002 sek. (zapytań SQL: 10)