OS Programming

Pisanie systemów operacyjnych - tryb rzeczywisty

Część I - tryb rzeczywisty
Część II - tryb chroniony
Część III - przerwania, wyjątki, GRUB.
Dodatek - mapa pamięci


Na początek chciałbym wyjaśnić co skłoniło mnie do napisania tego kursu, bo przecież w sieci jest tego pełno. Powodem jest to, że żaden kurs nie odpowie ci na możliwie dużą ilość pytań, które pojawiają się przy pierwszym kontakcie z pisaniem OSa. Omówię tu jedynie elementarne podstawy w sztuce pisania systemów operacyjnych, które mogą jedynie posłużyć tym, że łapiąc za inny kurs będziesz znał te podstawy. Jeśli jednak tekst będzie do bani - zawsze jest przycisk usuń :)

01h. Założenia.


Nasz mini OS nie będzie robił nic poza wyświetleniem komunikatu i restartem komputera po naciśnięciu klawisza Escape, więc nie, nie zostaniesz Billem tak szybko :), za to zrozumiesz każdą napisaną linijkę twojego OSa (mam nadzieję). Będzie posiadał bootloadera, który załaduje i uruchomi kernela, wszystko będzie pracowało w trybie Real Mode.

02h. Co nam będzie potrzebne?


Ponieważ nasz OS będzie w całości napisany w assemblerze potrzebujemy kompilatora. Ja wybrałem Netwide Assembler (http://nasm.sourceforge.net/), który jest prosty, dobry a co najważniejsze darmowy. Dodatkowo możesz zaopatrzyć w program Lizard, który zawiera NASMa i wygodny edytor (http://4programmers.net/file.php/id=1766). Będziemy potrzebować również aplikacji do zapisywania obrazów na dyskietkę, polecam Rawrite (http://uranus.it.swin.edu.au/~jn/linux/rawwrite.htm). Ponieważ nie będziemy implementować obsługi systemu plików (nawet minimalnej do znalezienia kernela na dyskietce) będziemy musieli potrafić łączyć dwa pliki w jeden (program, który to zrobi napiszemy w delphi). Oczywiście moglibyśmy połączyć bootloadera i kernela w jeden plik z poziomu kodu asm, ale chodzi tu o ukazanie istoty bootloadera i kernela jako oddzielnych programów.

program merge;
 
{$APPTYPE CONSOLE}
 
var
  final, f: file of byte;
  buf, q: byte;
begin
  if (paramcount < 3) or (paramstr(1) = 'help') then begin
    writeln('uzycie: merge.exe <1 plik> <2 plik> <3...> <plik wynikowy>');
    readln;
  end else begin
    assignfile(final, paramstr(paramcount));
    rewrite(final);
    for q := 1 to paramcount -1 do begin
      writeln(paramstr(q));
      assignfile(f, paramstr(q));
      reset(f);
      while not eof(f) do begin
        blockread(f, buf, sizeof(buf));
        blockwrite(final, buf, sizeof(buf));
      end;
      closefile(f);
    end;
    closefile(final);
  end;
  writeln('-> ' + paramstr(paramcount));
end.


Myśle, że kodu nie trzeba objaśniać, między innymi dlatego, że nie jest to cześć naszego OSa a jedynie narzędzie przy jego tworzeniu.

03h. Budowa dyskietki 1.44.


Przed jakimikolwiek operacjami na nośniku danych musimy poznać jego budowę. My nasz system umieścimy na dyskietce 1.44 i dlatego w skrócie opisze tu jej budowę. Wielkiej filozofii tutaj nie ma, bo taka dyskietka posiada dwie strony (stacja ma dwie głowice), po 80 cylindrów po każdej stronie i 18 sektorów w każdym cylindrze. Każdy sektor może pomieścić 512b danych (2*80*18*512 = 1474560b). Pierwszy sektor nazywamy bootsektorem, ponieważ to właśnie w nim znajduje się program inicjujący system operacyjny, jak również informacje o systemie plików itp. Kolejne sektory są tylko dla nas i możemy umieszczać tam co tylko chcemy, na przykład kernela.

04h. Basic Input/Output System.


BIOS (Basic Input Output System) jest najbardziej podstawowym systemem jaki mamy w naszych komputerach, to dzięki niemu będziemy mogli wyświetlić tekst na ekranie (oczywiście możemy odwołać się bezpośrednio do pamięci, ale mi zależy bardziej na prostocie), czy wczytać kernela z dyskietki - od razu zapomnij o wyświetlaniu np listy plików etc. to musielibyśmy zrobić sami (może innym razem), dzięki niemu będziemy mieli jedynie dostęp do poszczególnych części nośnika. I w końcu to właśnie BIOS rozpoznaje, ładuje do pamięci bootsektor i go uruchamia.

05h. W jaki sposób BIOS ładuje system, czyli wreście coś konkretnego.


Większość z nas wie, że w programie Setup (taka część BIOSa) możemy skonfigurować kolejność napędów, na których komputer będzie szukał systemu. BIOS ładuje pierwsze 512 bajtów z dysku (dyskietki, płyty CD/DVD czy czegokolwiek innego) do pamięci pod adresem 07C0:0000 i skacze w to miejsce (wykonuje znajdujący się tam kod). Aby to nastąpiło BIOS musi wiedzieć, czy pierwsze pół kilobajta jest na pewno bootloaderem (bo przecież komendy procesora /opcodes/ to przecież to samo co inne dane - bajty, a nie na każdym dysku musi być system), w tym celu sprawdzane są 511 i 512 bajt, które powinny wynosić odpowiednio 55h i AAh.

06h. Więc piszemy bootloadera!


Zacznijmy od najprostszego kodu, który po skompilowaniu da nam gotowy do zapisania bootsektor.

;tutaj zaczyna się nasz program
org 7C00h
 
;tworzymy nieskończoną pętlę
start:
  jmp start
 
;dopełniamy program do 510 bajtów
times 510 - ($ - start) db 0
 
;tworzymy znacznik bootsektora
db 55h
db AAh


Taki program kompilujemy jako flat-binary (nasm.exe bootsector.asm -f bin -o bootsector.bin /program Lizard utworzy plik *.com/) i za pomocą np Rawrite umieszczamy na dyskietce (traktując skompilowany plik jako jej obraz). Teraz mamy na dyskietce najprawdziwszy bootsektor, który sami napisaliśmy :).

OK, ale obiecałem system wyświetlający napis więc jedziemy dalej. Jak już wiemy bootloader służy jedynie do załadowania kernela (w dzisiejszych systemach preloadera, a ten dopiero kernela, ale my zrobimy to łatwiej), tak więc co zrobimy? Otóż użyjemy funkcji BIOSa aby załadować kernela pod adres 0800:0000 (między bootloaderem a kernelem będziemy mieli 512 wolnych bajtów, które wykorzystamy jako stos, ale o tym później). Aby uprościć sobie sprawę nasz kernel nie będzie typowym plikiem, tylko kodem binarnym w 2 sektorze dyskietki (pamiętasz, w 1 jest nasz bootloader). Użyjemy do tego celu funkcji numer 2 przerwania 13h. Więc do dzieła, najpierw zobaczmy czego wymaga od nas ta funkcja (http://www.ctyme.com/intr/rb-0607.htm), tak więc

mov ah, 2      ;funkcja 2 przerwania 13h
mov al, 10     ;ilość sektorów do przeczytania (10*512 = 5kb)
mov ch, 0      ;cylinder
mov cl, 2      ;sektor (w 1 jest bootsector)
mov dh, 0      ;głowica
mov bx, 0800h  ;gdzie załadowac kernel (es:bx)
mov es, bx     ;dane do ES możemy umieścić tylko przez inny rejestr
xor bx, bx     ;bx równy 0
int 13h        ;wywołujemy przerwanie


Podsumowując załadowaliśmy 5kb z dyskietki pod adres 0800:0000, aby uruchomić znajdujący się tam kod, po prostu skaczemy do niego :)

jmp 0800h:0000h


Cały bootloader wygląda tak

org 7C00h
 
start:
  mov ah, 2
  mov al, 10
  mov ch, 0
  mov cl, 2
  mov dh, 0
  mov bx, 0800h
  mov es, bx
  xor bx, bx
  int 13h
 
  jmp 0800h:0000h
 
times 510 - ($ - start) db 0
 
db 55h
db 0AAh


07h. Szkielet jądra systemu.


Pisząc jądro systemu nie musimy się już martwić znacznikiem, ani tym, że nasz kod musi zmieścić się w 512b, po prostu kodujemy :). Jednak mamy pewien haczyk, otóż pisząc wlany system musimy pamiętać, że nie będziemy mieli dobrodziejstw wysokopoziomowych funkcji załadowanego systemu, bo - go nie ma. Czyli musimy zapomnieć np o przerwaniu 21h. Ale nie martw się, BIOS oferuje wystarczającym dla nas zestawem funkcji, a z resztą przecież obiecałem :). Pierw napiszemy najprostszy kod jądra, który jedyne co będzie robił to...działał

; skąd ja to znam
;pamiętasz, kernela załadowaliśmy pod adres 0800h:0000h, wiec
;zaczynamy od 0000h
org 0000h
 
start:
  jmp start


Teraz kompilujemy bootloadera i kernela, i łączymy je w jeden plik używając programu merge.exe, który napisaliśmy i skompilowaliśmy na początku tego tekstu (skompilowaliśmy?)

merge.exe bootsector.bin kernel.bin image.img


Po czym zapisujemy otrzymany plik na dyskietkę programem Rawrite traktując go jako obraz.

Na pozór działanie naszego systemu nie różni się od samego szkieletu bootloadera, lecz wykonuje on następujące czynności: ładuje kernela z 2 sektora dyskietki do pamięci pod adres 0800:0000 i przekazuje mu kontrole, po czym ten zapętla komputer :). Należałoby tez wytłumaczyć dlaczego zapętamy komputer, więc gdybyśmy tego nie zrobili procesor powędrował by dalej i wykonywał przypadkowe komendy znajdujące się na jego drodze (czyli tam gdzie wskazuje IP) co jest nieprzewidywalne w skutkach.

08h. Co ze stosem?


Stos jest takim kawałkiem pamięci do którego mamy szybki dostęp, możemy w nim przechowywać parametry procedury, jej adres powrotny i zmienne lokalne. Bez stosu za wiele byśmy nie zrobili, wiec przydzielimy mu teraz pamięć. Rzecz ta jest trywialnie prosta, bo ogranicza się jedynie do ustawienia jego segmentu, czyli rejestru ss i jego wierzchołka, czyli rejestru sp. Przy ładowaniu kernela do pamięci zostawiliśmy lukę w pamięci o rozmiarze 512b, tam właśnie będzie znajdował się nasz stos.

+-------------------+
| 512b | BOOTLOADER |
|------+------------|
| 512b | STOS       |
|------+------------|
| 5kb  | KERNEL     |
+-------------------+
Mapa pamięci naszego OSa


Powyższa mapa pozwoli ci lepiej zrozumieć podział pamięci w naszym systemie. Skoro juz wszystko wiadomo zajmijmy się kodowaniem.

mov ax, 07C0h
mov ss, ax    ;segment stosu
mov sp, 03FEh ;wierzcholek stosu


Jak łatwo się domyślić po tym kroku mamy dostępny 512 bajtowy (256 slow) stos.

09h. Wybieramy tryb graficzny.


Co prawda interesujący tryb graficzny (tekstowy 80x25) jest juz wybrany, ale wybierzemy go jeszcze raz, z dwóch powodów. Pierwszy to taki, ze po wybraniu trybu bufor ekranu się wyczyści, a drugi - będziemy wiedzieć jak to się robi :). Aby to zrobić wystarczy wywołać przerwanie 10h.

xor ah, ah  ;funkcja 0
mov al, 3   ;standardowy tryb tekstowy
int 10h     ;jedziemy


0Ah. Wyświetlamy tekst.


Wyświetlanie tekstu jest chyba najbardziej skomplikowana rzeczą w naszym systemie, a to dlatego, ze musimy w pętli przenieść wszystkie znaki naszego łańcucha na ekran i przestać w odpowiednim momencie. Do tego musimy przesunąć kursor na ekranie. Do zapisania łańcucha użyjemy "formatu" PChar, który jest ciągiem bajtów zakończonym bajtem #0 (NULL). Pozwoli to nam na wykrycie końca naszego tekstu.

Na początek napiszemy procedurę wyświetlającą jeden znak na ekranie i przesuwająca kursor. Jak zwykle posłużymy sie przerwaniami, które są po prostu łatwe w obsłudze. Do wyświetlenia znaku skorzystamy z funkcji 9 przerwania 10h, za parametry przyjmuje ona wartość ASCII znaku (rejestr AL), numer strony (BH i u nas będzie to 0), atrybut znaku (jego kolor, kolor tła, czy znak ma migać - rejestr BL) i ilość powtórzeń (CX). Po wyświetleniu znaku przesuniemy kursor w prawo funkcjami 2 (zapisz pozycje) i 3 (odczytaj pozycje). Obie swoje parametry maja w DH (wiersz) i DL (kolumna), z jakim wyjątkiem, ze 2 je zapisuje a 3 pobiera.

char:
  ;procedura wyświetla znak i przesuwa kursor
  ;wejście: al: znak, bl: atrybut
 
  push bx     ;kładziemy BX na stos, aby na końcu procedury go przywrócić
 
  mov ah, 9   ;numer funkcji wyświetlającej znak w miejscu kursora
  xor bh, bh  ;numer strony ustawiamy na 0
  mov cx, 1   ;znak wyświetlimy 1 raz
  int 10h     ;do dzieła!
 
  ;pobierz pozycje
  mov ah, 3   ;funkcja pobierania pozycji kursora
  xor bh, bh  ;numer strony (0)
  int 10h     ;odczytaj pozycje
 
  ;dodaj i zapisz pozycje
  add dl, 1   ;dodajemy 1 kolumnie
  mov ah, 2   ;funkcja zapisywania
  int 10h     ;zapisz pozycje
 
  pop bx      ;przywróć poprzedni stan BX
ret           ;wyjdź z podprogramu


Teraz, gdy mamy juz procedurę do wyświetlania znaku napiszemy procedurę, która wyświetli nam cały łańcuch. Będzie ona polegała na tym, ze w pętli będziemy wyświetlać kolejne bajty danych zaczynając od adresu pierwszego znaku podanego w parametrze (u nas będzie to AX) aż do wystąpienia znaku pustego - NULL (to nie jest spacja!). Znamy juz teorie, teraz przeniesiemy ja do assemblera:

write:
  ;procedura wyświetla tekst na ekranie
  ;wejście: ax: wskaźnik do tekstu, bl: atrybut
 
  mov si, ax        ;musimy użyć rejestru segmentowego aby zaadresować wskaźnik
  .next:            ;poczatek petli
    mov al, [cs:si] ;zapisz do al wartość aktualnego znaku (patrz parametry dla procedury char)
    cmp al, 0       ;porównaj aktualny znak z NULL
    je end          ;jeśli są równe, skocz do wyjścia
    call char       ;jeśli nie, wyświetl znak
    add si, 1       ;przesuń się w prawo do następnego znaku
  jmp .next         ;skocz do początku pętli
  end:              ;tutaj skoczymy, jeśli wystąpi NULL
ret                 ;wyjdź z podprogramu


I juz możemy zadeklarować łańcuch i go wyświetlić.

0Bh. "Obsługujemy klawiaturę"


Tytuł w cudzysłowie ponieważ nasza klawiatura nie będzie miała nawet własnego bufora, za to będziemy wiedzieli, kiedy użytkownik naciśnie klawisz, będziemy nawet wiedzieli jaki! A na to "wszystko" pozwoli nam funkcja 0 przerwania 16h (http://www.ctyme.com/intr/rb-1754.htm). Zalożenie będzie takie, że w pętli będziemy odczytywać klawisz i sprawdzać który to. Więc do dzieła

start:
  xor ah, ah   ;takie xorowanie jest szybsze od mov ah, 0
  int 16h      ;i w AH mamy scancode, w AL kod ASCII klawisza
  cmp al, 1Bh  ;porównaj al z 1Bh (kod ASCII klawisza ESC)
  je reset     ;jeśli równe, skocz do procedury resetowania (napiszemy później)
  jmp start    ;powracamy na początek


0Ch. Resetujemy komputer.


Resetowanie komputera będzie polegać na skoku w cześć BIOSu, którą wykonuje sie po starcie komputera. Jest to adres FFFF:0000. Po drodze do komórki 40:72 zapisujemy wartość, która "powie" BIOSowi, czy ma wykonywać ponownie testy pamięci etc. Aby to zrobił przypisujemy do niej wartość 0, jeśli nie (tzw. gracy reset) wartość 1234h. Po tym po prostu skaczemy do FFFF:0000. Jak zwykle kod

reset:
  mov bx, 40h               ;używamy BX do zapisania wartości w rejestrze segmentowym
  mov ds, bx                ;BX ładuje w DS
  mov word [ds:72h], 1234h  ;ustawiamy gorący reset
  jmp 0FFFFh:0000h          ;skaczemy do FFFF:0000


0Dh. Sklejamy wszystko w kupe.


Gdy juz mamy wszystko czego potrzebowaliśmy, możemy złożyć nasz pierwszy (pierwszy?) mini system operacyjny. Kompilowanie bootloadera i kernela opisałem wcześniej, wiec nie pozostaje mi nic innego jak podać kompletny kod jadra

org 0000h
 
;ustawiamy stos
mov ax, 07C0h
mov ss, ax    ;segment stosu
mov sp, 03FEh ;wierzchołek stosu
 
;wybieramy tryb ekranu
xor ah, ah  ;funkcja 0
mov al, 3   ;standardowy tryb tekstowy
int 10h     ;jedziemy
 
;wyświetlamy komunikat
mov ax, welcome  ;wskaźnik do tekstu
mov bl, 2        ;na zielono
call write       ;wykonujemy procedurze
 
mov ax, name     ;wskaźnik do tekstu
mov bl, 5        ;na fioletowo
call write       ;wykonujemy procedurę
 
mov ax, last     ;wskaźnik do tekstu
mov bl, 2        ;na zielono
call write       ;wykonujemy procedurę
 
;główna petla
start:
  xor ah, ah   ;takie xorowanie jest szybsze od mov ah, 0
  int 16h      ;i w AH mamy scancode, w AL kod ASCII klawisza
  cmp al, 1Bh  ;porownaj al z 1Bh (kod ASCII klawisza ESC)
  je reset     ;jeśli równe, skocz do procedury resetowania (napiszemy później)
  jmp start    ;powracamy na początek
 
char:
  ;procedura wyświetla znak i przesuwa kursor
  ;wejście: al: znak, bl: atrybut
 
  push bx     ;kładziemy BX na stos, aby na końcu procedury go przywrócić
 
  mov ah, 9   ;numer funkcji wyświetlającej znak w miejscu kursora
  xor bh, bh  ;numer strony ustawiamy na 0
  mov cx, 1   ;znak wyświetlimy 1 raz
  int 10h     ;do dzieła!
 
  ;pobierz pozycje
  mov ah, 3   ;funkcja pobierania pozycji kursora
  xor bh, bh  ;numer strony (0)
  int 10h     ;odczytaj pozycje
 
  ;dodaj i zapisz pozycje
  add dl, 1   ;dodajemy 1 kolumnie
  mov ah, 2   ;funkcja zapisywania
  int 10h     ;zapisz pozycje
 
  pop bx      ;przywróć poprzedni stan BX
ret           ;wyjdź z podprogramu
 
write:
  ;procedura wyświetla tekst na ekranie
  ;wejście: ax: wskaźnik do tekstu, bl: atrybut
 
  mov si, ax        ;musimy użyć rejestru segmentowego aby zaadresować wskaźnik
  .next:            ;początek pętli
    mov al, [cs:si] ;zapisz do al wartość aktualnego znaku (patrz parametry dla procedury char)
    cmp al, 0       ;porównaj aktualny znak z NULL
    je end          ;jeśli są rożne, skocz do wyjścia
    call char       ;jeśli nie, wyświetl znak
    add si, 1       ;przesuń się w prawo do następnego znaku
  jmp .next         ;skocz do początku pętli
  end:              ;tutaj skoczymy, jeśli wystąpi NULL
ret                 ;wyjdź z podprogramu
 
reset:
  mov bx, 40h               ;używamy BX do zapisania wartości w rejestrze segmentowym
  mov ds, bx                ;BX ładuje w DS
  mov word [ds:72h], 1234h  ;ustawiamy gorący reset
  jmp 0FFFFh:0000h          ;skaczemy do FFFF:0000
 
;zmienne
welcome: db 'Witaj w ',0
name:    db 'Krzeslo Operating System',0
last:    db ', wciśnij ESC aby zrestartowac komputer :)',0


Taki kod kompilujemy, wraz z bootloaderem umieszczamy na dyskietkę za pomocą Rawrite, restartujemy komputer i cieszymy się naszym dziełem.

0Eh. Co dalej?


Jeśli naprawdę myślisz o pisaniu systemów operacyjnych (właściwie jednego na całe życie) to ten artykuł jest dopiero jednym centymetrem w kilometrach, które cię czekają na drodze do napisania funkcjonalnego OSa. Czynność ta jest nie tylko pracochłonna, ale stwarza wiele problemów, których czasem nie można rozwiązać (jest to bardzo trudne).

Jeśli to cię nie zniechęciło pamiętaj o podstawowych zasadach pisania systemów. Po pierwsze nie myśl o stworzeniu super systemu, tylko o tym, żeby on działał. Wielu ludzi przedstawia bitmapy prezentujące bajeranckie GUI i myśli, ze polowa pracy za nimi, pierwsze co należy zrobić to działające jadro i powłokę tekstowa (konsole), która potrafi przyjmować komendy od użytkownika. Musisz tez zaplanować w jaki sposób twój system ma uruchamiać programy, musisz przydzielić im pamięć, stos, napisać mechanizm kolejkowania zadań. Do tego należy obsłużyć system plików (istniejący, lub wymyslec nowy) i mase innych rzeczy.

0Fh. Zakończenie


Mam nadzieję, że osiągnąłem cel, jakim było pokazanie jak napisać swój własny mini (mikro) system operacyjny podzielony na bootloadera i jądro. Postaram się w miarę nowych pomysłów i wiedzy dopisywać kolejne części :). Będę wdzięczny za wszelkie uwagi, komentarze odnośnie tego artykułu, które możesz kierować pod maila, ew. na ircu (#netsoft @ irc.ircnet.pl).

Pozdrowienia dla wszystkich użytkowników 4p i deweloperów grupy Netsoft.
Wolverine (wolverine at daminet dot pl).

44 komentarze

Kriper 2013-08-04 16:16

pomozcie! czytanie sektora (bynajmniej u mnie) nie dziala

klex234 2011-05-27 14:02

dyskietka! a nie lepiej płyta cd czy coś

klexprogramista234 2011-05-25 16:38

rozwinołem to i umię jeszcze się wyłanczać :)

klex234 2011-05-24 14:51

super brawo!!! tylko jakie są kody do innych klawiszy?????

klex234 2011-05-12 14:38

czy to w assamblerze ???

kempa65 2009-06-05 12:07

Po wciśnięciu ESC wirtualny komp się cały czas resetuje, aż nacisnę drugi raz ESC :P Trzeba by to poprawić... Ja na assemblerze się za bardzo nie znam, bo dopiero się uczę go, ale mi się wydaje, że wystarczy wyczyścić gdzieś jeszcze rejestr AH z kodem klawisza, albo może w inny sposób pobrać klawisz. Odpalałem go na emulatorze Virtual PC z dyskietki. Macie jakieś pomysły? A może to ta maszyna wirtualna tak ma? Jak jest z tym u was?

Dynax 2008-05-19 14:16

Mój program merge w C#: http://nopaste.gamedev.pl/?id=2025

//Tylko ja go Binder nazwałem :)

Spine 2008-02-29 08:53

Tego IDE, Lizard, nie ma go już ??

Wolverine 2007-12-15 13:39

darktemplar ~ yyy, ocb?

Dannte 2007-09-22 13:19

Artykuł bardzo fajny - imo dobrze, że taki szczegółowy, bo przedstawiając znany nam problem powinno się do niego podchodzić w sposób "hmm... o co ja bym zapytał, gdybym się na tym nie znał". Ja osobiście polecam stronkę http://www.nondot.org/sabre/os/articles chociaż tak naprawdę nie zagłębiałem się w nią (zamierzam :P), tylko pobieżnie obejrzałem.
Mam tylko pytanie co do ustalenia stosu: co ze spodem stosu? Rejestr bp nie powinien być ustawiony na początkową wartość sp?

darktemplar 2007-08-11 16:22

Kochaniutki atrybut w trybie tekstowym jest przez Int10h/0xE zupełnie olewany! Polecam http://www.ctyme.com/intr/rb-0106.htm

MKF 2006-09-24 18:57

Tu podaję źródło mojej implementacji programu merge dla C:

Użycie:

merge [plik1]  [plik2] ... [plik 254]  >  [plik wynikowy]


merge.c
#include <stdio.h>
 
int main(int argc, char *argv[])
{
    if (argc < 2) return 255;
 
    FILE *SrcFile;
    char *Buffer;
    for (int i = 1; i < argc; i++)
    {
        if ((SrcFile = fopen(argv[i], "r")) == NULL) return i;
 
        while (!feof(SrcFile))
        {
            fread(&Buffer, sizeof(char), 1, SrcFile);
            printf("%c", Buffer);     
        }
 
        fclose(SrcFile);    
 
    }
    return 0;    
}


errorlevel 0: wszystko OK
errorlevel 255: nie podano plików wejściowych
errorlevel 1...254: błąd otwierania pliku ... (np. plik nie istnieje)

Mulligun 2006-09-21 18:16

Czym skompilować ten kod w Delphi???
kompiluje Borlandowskim Delphi Developer Studio 2006 - i wywala jakies syfisate błedy!!!
kompiluje Free Pascal'em - krzyczy że nie zna procedury assignfile!!!
NIE znam Delphi i NIE chcę się (przynajmniej narazie) w niego zagłębiać
POMOCY !!!

Wolverine 2006-06-18 09:11

W sumie mozna, ale potem by bylo, ze jeden plik a cos tam jeszcze dogrywamy, tak jak jest teraz latwiej wytlumaczyc.

NeooeN 2006-06-10 13:50

Ja sie tam nie znam więc moge sie mylić ale mam dwa spostrzeżenia.
Po pierwsze czy nie można pisać całości razem tj. bootloader i cała reszta, przecież tak czy siak sie skompiluje.
Dalej to czy nie można by połączyć procedur write i char? Co miało na celu rozbicie ich na dwie różne?

MrKaktus 2006-05-01 00:36

Jezeli naprawde chcecie poczytac o pisaniu OSow to zapraszam na POLSKI portal dla OS Developerow:

www.areoos.com/osdevpl/

MrKaktus

Wolverine 2005-08-15 14:41

<quote>U mnie bez tego nie chiało dzialać.</quote>
hmm to bardzo dziwne bo bios sam w dl zostawia numer napedu z ktorego odczytal bootloadera.

Rurik 2005-08-12 23:02

Gratuluje świetnego artukułu!

W kodzie BOOTLOADER\'a można by dopisać nastepującą linijke:

MOV DL, 0 ;Numer dysku (0=A:, 1=B:, 80h=C:, 81h=D:, itd.)

U mnie bez tego nie chiało dzialać. Poza tym wszystko jest pięknie. Pierwszy tekst który rozumiem od początku do końca. Co prawda nie mam Delphi, ale MERGE po drobnych zmianach skompilowalo sie w Pascalu. Lece czytać drugą część!

Wolverine 2005-07-13 10:25

Obrazek? Poczytaj o grafice w 13h, tam sie zazwyczaj korzysta tylko z biosa, wiec pojdzie, tylko musisz byc w RM.

Bagard 2005-06-26 22:17

Znalazłem błąd w artykule i prosze o jego poprawienie :)

"Obie swoje parametry maja w DH (kolumna) i DL (wiersz)"

Obydwa parametry są ustawione odwrotnie...

DH - wiersz oraz DL - kolumna

Natknołem się na niego gdy potrzebowałem, aby tekst mi się wyświetlał w następnej linijce:)

Wolverine 2005-06-02 20:29

A no bo tak mialo byc, zauwaz, ze na poczatku ten art nazywal sie \"pisanie osa dla topornych\", ale zostal przemianowany, z reszta na wiele wiecej mnie nie stac :P

Stoneman 2005-04-13 21:21

Jeżeli patrzeć czy ten art realizuje swoje założenia to oceniam go na 5. Natomiast jeżeli mam oceniaś forme to juz nie jest tak super. Tekst jest kierowany jakby dla kogoś kto nigdy sie nawet assemblerem nie bawił. Jestem poglądu że trzeba postawić pewną poprzeczke bo nie każdy moze sie bawić w pisanie OS, trzeba mieć troche własnej wiedzy zdobytej wcześniej. Jeżli ktoś jest zainteresowany poważnie pisaniem OSa w assemblerze i tylko assemblerze to prosze o kontakt na [email protected] - poszukuje kogoś do pomocy z jakimiś dobrymi pomysłami, ale przedewszystkim z podstawową wiedzą odnośnie tematu :)

Bagard 2005-02-19 15:51

Jedyny kurs jaki rozumiem w całości:) Jest super!
Może by tak więcej... gdyż brak wiedzy mnie gryzie;)
Ocena końcowa - 6!

brodny 2005-02-15 18:20

Ta strona (http://www.osdever.net/) też fajna, tylko ma 1 szkopuł - NIENAWIDZĘ czytać po angielsku :) Nie znaczy, że nie rozumiem, no ale... Jakoś dłużej to trwa :) No i tak nie odpręża już lektura :)

iYYa24 2005-02-02 21:31

Super art !!! 6 !!!

Spine 2004-12-31 00:14

dzięki tobie Wolverine mam o tym jakiekolwiek pojęcie ;)
czekam na arta o systemie plików :]
powodzenia.
Jak się trochę pouczę assemblera to spróbuję jakiś system na trybie 13h napisać ;P
hmmm, fajnie byłoby napisać jakąś grę na 13h z własnym systemem operacyjnym :D
może się nie doczytałem w arcie, ale taki plik com może mieć tylko 64 KB ;)
ale to i tak bardzo dużo skoro skompilowany kernel zajmuje tylko ok 240 bajtów ;)

ic3 2004-12-30 14:44

Jak bys potrzebowal pomocy to mozesz na mnie liczyc : D. (GG/Email: 2366471 / ice_man11 (at) wp.pl). Aha, dla  zainteresowanych http://www.osdever.net/. : )

Wolverine 2004-12-30 12:58

taa, sam mysle o napisaniu czegos co bedzie mialo prosta konsole i potrafilo ladowac i uruchamiac programy :)

ic3 2004-12-30 02:04

BTW warto zaopatrzyc sie w Bochs'a - emulator PC (http://bochs.sourceforge.net).  No i co tu dodac, pisanie OS'a to cool sprawa i kupa radochy ze komp rusza z NASZEJ dyskietki : )....

Wolverine 2004-12-29 16:24

i A20 :) imo wszystko ma sens, np zeby sie czegos nauczyc :)

ic3 2004-12-29 16:06

No np GRUB :). W sumie przelaczyc sie do PM to raptem kilka linijek kodu asemblera (tylko jeszcze to GDT) Kurcze, jakos mnie ciagnie zeby pisac system :/. Tylko czy to ma jakikolwiek sens?...

Wolverine 2004-12-28 09:33

wlasciwie to pmode zajmie sie juz dobry bootloader, jesli uzyjesz gotowca ...

ic3 2004-12-27 23:26

szkoda ze nie ma nic o protected mode. Kiedys nawet mialem pomysl zeby napisac system, ale zatrzymalem sie gdzies przy IDT i przerwaniach :P... mialem format dysku i juz nie ma projektu :(. A nawet byla obsluga klawiatury :P . oczywiscie w protected mode ;).

MrPascal 2004-12-27 16:41

a można to samo zrobić tyle że w C albo Pascalu? Fajny artykuł tylko jak dla mnie troszku za mały - mogłeś jeszcze jakąś funkcję dorzucić do tego Os'a

Amras 2004-12-26 20:48

Wg mnie, niesamowity art. Bardzo dobrze napisany. Dosc szczegółowo. Mozna by bylo jeszcze zamieścić linka do gotowego obrazu z nowym systemem. Podobnie jak _ece czekam na kontynuacje.

_exe 2004-12-26 15:12

Bardzo ciekawy art. Czekam na kolejne części

Snowak 2004-12-25 16:08

Jest jeszcze 1 literówka...

Wolverine 2004-12-25 15:58

poprawione, mam nadzieje, ze wszystkie..

Qyon 2004-12-25 14:17

Popraw błędy!!! [sciana]

dzikimisiu 2005-07-06 14:03

cze dziex za arta jest super ale mam pytanie jak w assamberze dodać obrazek ??