Kurs OS - część 1

Data dodania: 2011-09-18, Autor: Fr3m3n, Dodał: Karol, Wyświetleń: 601
Zapewne nieraz zastanawiałeś się, w jaki sposób jest zbudowany system operacyjny - być może uważasz to za niezwykle trudną, bliską magii rzecz, albo też mniej więcej wiesz jak to działa, ale nie znasz szczegółów. W obu przypadkach ten kurs powinien być pomocny :)
1. Segmenty w trybie chronionym - Global Descriptor Table
Segmenty w trybie chronionym są podobne do swoich kuzynów z trybu rzeczywistego. W trybie rzeczywistym wyglądało to tak:
segment:offset
Prawidłowy, 20 bitowy adres procesor otrzymywał poprzez pomnozenie segmentu przez 16
(czyli przesuniecie bitowe w lewo o 4) i dodanie do powstałej wartości offsetu.
Czyli np 0x0100:0x543 to:
0x31543=0x3100*16+0x543
Stąd też limit pamięci do 1MB - 0xFFFFF = 1048575 = 1 MB.
W trybie chronionym sprawa przedstawia się nieco inaczej. Otóż mamy tablicę z informacji
o danym segmencie (o tym za chwilę) - taka informacja nazywa się deskryptor segmentu.
Pusty deskryptor
Deskryptor 1
Deskryptor 2
Deskryptor 3
...
Maksymalna liczba deskryptorów w jednej tablicy to 8192 - 13 bitów.
Numer segmentu znajduje się w jednym z rejestrów segmentowych:
| cs | - segment kodu |
| ds | - segment danych |
| ss | - segment stosu |
| es,fs,gs | - segmenty dodatkowe |
Index (13 bitów) to numer deskryptora segmentu w jednej z tablic, T - określa czy tą tablicą jest LDT czy GDT RPL - Requested Privilege Level, 2 bity Poziomów uprzywilejowania jest 4, nazywają sie one 'ring' (pierścień). Ring0 to prawa największe (kernel), ring3 to prawa najmniejsze (zwykły program). O poziomach uprzywilejowania więcej powiem później. -GDT- GDT (Global Descriptor Table) jest to tablica deskryptorów segmentu, wspólna dla wszystkich procesów i całego systemu. (LDT - Local Descriptor Table - może zostać dany każdemu procesowi z osobna). LDT narazie nie jest nam potrzebne. Format deskryptora segmentu (i GDT, i LDT) wygląda tak:
Base address - 32 bitowy adres pierwszego bajtu segmentu w 4 gb adresowalnej przestrzeni. Dla największej wydajności powinienien być zalignowany na 16. Segment limit - 20 bitowy limit segmentu, czyli adres gdzie kończy się segment. Jego interpretacja zależy od stanu bitu Granularity (B 31:24, S 19:16 oznacza bity zmiennej, czyli np Base 23:16 oznacza że w tym miejscu mają się znaleźć bity od 16 do 23 Base address.) Typ - rodzaj segmentu. Dane (read/read-write) albo kod (execute/execute-read/conforming). O typach za chwilę. S - System - 1 oznacza segment systemowy (LDT,TSS,Call-gate,przerwanie,Trap-gate, Task-gate), 0 segment danych albo kodu (GDT). DPL - Descriptor Privilege Level - Poziom uprzywilejowania deskryptora. (czyli ring). P - Present - obecny. Informuje, czy dany segment jest obecny w systemie. Każda próba pobrania danych z takiego segmentu zaowocuje błedem 'General Protection Fault'. Może być użyty do zaimplementowania swapowania (czyli zapisu rzadko używanej pamięci na dysk). Po prostu po wystąpieniu takiego błędu system załadowałby pamięc z dysku, a następnie powrócił do funkcji. AVL - Available - dostepny do czegokolwiek, może być użyty przez system operacyjny, dla procesora nie ma znaczenia. L - Long - ustawiony na 1 oznacza, ze deskryptor segmentu jest 64 bitowy. Na procesorach 32 bitowych zawsze musi byc ustawiony na 0. D/B - W GDT oznacza rozmiar operacji - 16 i 32. (domyslny rozmiar adresu, rejestrów, ...). 0 oznacza 16 bit, 1 - 32. G - Granularity. Bardzo ważny bit - ma wpływ na interpretację Segment Limit. Ustawiony na 0 nie robi nic - segment limit jest interpretowany 'dosłownie' - może mieć od 1 bajta do 1 megabajta. Jesli jest ustawiony na 1, limit segmentu jest przesuwany o 12 bitow w lewo (shl), następnie powstałe zerowe bity sa ustawiane na 1, i dopiero wtedy jest interpretowany. Przykład:Granularity = 0 Segment limit = 2 Limit segmentu będzie wynosić dwa bajty. Wywołanie adresu 2 spowoduje błąd General Protection Exception (#GP). Dostępne adresy to 0 i 1. Granularity = 1 Segment limit = 2 Limit segmentu = (2 shl 12) or 0xfff = 12287 bajtów. Limit jest od base address do 12287. Wyzszy adres spowoduje błąd General Protection Exception. Granularity = 1 Segment limit = 0 Limit segmentu = (0 shl 12) or 0xfff = 0xfff = 4096 Itd.Typy deskryptorów segmentu: Danych:
- 0000b - Data Read Only
0010b - Data Read/Write
0100b - Data Read Only, Expand Down
0110b - Data Read/Write, Expand Down
- 1000b - Execute Only
1010b - Execute/Read
1100b - Execute Only, conforming
1110b - Execute/Read, conforming
Expand-down data segment:
Adres
linearny:
#GP Adresy niższe niż Segment Limit dają General Protection Exception
0x000AAAAA SEGMENT LIMIT
...
0x00FFFFFF BASE ADDRESS
-- Adresy wyższe niż Base Address są niemożliwe
Expand-up data/code segment:
Adres
linearny:
-- Adresy niższe niż Base Address są niemożliwe
0x000AAAAA BASE ADDRESS
...
0x00FFFFFF SEGMENT LIMIT
#GP adresy wyższe niż Segment Limit dają #GP
Adresowanie w danym segmencie jest relatywne do base address.
W segmentach typu expand-up adres 0 daje adres linearny o wartości Base Address,
adres 1 daje Base Address+1, itd.
Expand down data segment (segment stosu):
W segmentach expand-down jest trochę inaczej:
- Adres 0xFFFFFFFF w segmencie da w rezultacie base address
Adres w segmencie: Adres linearny
(Base-segment limit) Segment Limit
0xFFFFFFFF Base Address
Czyli - najwyższy adres to 0xFFFFFFF - jest on ładowany do esp.
Każdy push odejmuje od esp 4.
Gdy stos dojdzie do segment limit, zostanie wywołane #GP.
Funkcja do obsługi błędów zaalokuje wtedy pamięć (gdziekolwiek),
skopiuje cały stos na koniec zaalokowanego obszaru, odpowiednio zmieni base address i
segment limit - base address na adres linearny nowego obszaru, a segment limit na nowy
dolny limit segmentu (większy niż poprzednio).
Dzięki temu pamięć na stos można alokowac 'w miarę potrzeb',
gdyż cały ten mechanizm jest całkowicie niewidoczny dla programu -
- esp i segment ss są ciągle takie same.
Conforming code
'Conforming code' (ulegający kod) jest to segment dla kodu, pozwalający na wykonanie go z
mniej uprzywilejowanych segmentów. Call do funkcji w ring0, wykonany w ring3,
nie spowoduje błędu.
Kod będzie jednak wykonywany w ring3, tak samo segment stosu i danych się nie zmieni.
Conforming code działa tylko 'w górę' tzn. call albo jmp z ring0 do ring3 (nawet conforming)
skończy się błędem (zamiast tego trzeba uzyć tzw. call gate/task gate).
Załadowanie deskryptora GDT odbywa się przy pomocy polecenia lgdt.
lgdt [gdt_descriptor]
gdt_descriptor:
dw ilosc_deskryptorów_segmentu*8-1
dd linearny_adres_gdt
2. Bootsector - odczytywanie dyskietki i odblokowanie a20
Bootsector jest to pierwszy sektor danego dysku. Jego 'znakiem rozpoznawczym' jest to, że jego dwa ostatnie bajty to 0x55,0xAA. Po starcie systemu i wykonaniu POST-u (Power on self test), bios rozpoczyna wyszukiwanie napędów. Gdy napotka taki, którego dwa ostatnie bajty pierwszego sektora to 0x55 0xAA, ładuje całość do pamięci, po czym uruchamia go. Tutaj mała ciekawostka - skąd się biorą napisy typu 'Nieprawidłowy dysk; wyjmij dyskietkę po czym naciśnij klawisz' i inne podobne wiadomości? Otóż - takie dyskietki również posiadają bootsektor. Jego jedyną funkcja jest wypisanie tego tekstu i oczekiwanie na klawisz, po czym zainicjowanie restartu. Wiemy teraz jaki warunek musi spełniać bootsektor, napiszmy więc jeden: boot.asm
use16
org 0
start:
push 0xb800 ;segment ekranu
pop es
xor di,di
mov al,'a'
mov ah,9
stosw
hlt
times 510 - ($ - start) db 0
db 0x55
db 0xAA
Skompilujmy to, i uruchommy. (zob. konfiguracja Bochs)
Wynik wygląda tak:
Nie jest to specjalnie użyteczna funkcja. Zadaniem naszego bootsektora będzie załadowanie systemu i skok do niego: boot.asmuse16 org 0 start: push 0x0010 ;segment 0x0010 pop es mov ax,0x0201 ;funkcja czytania sektorow, 1 sektor do odczytania mov cx,2 ;numer sektora xor dx,dx ;glowica 0,dysk 0 = dyskietka a xor bx,bx ;adres 0x0010:0000 int 0x13 jc blad jmp 0x0010:0000 blad: hlt times 510 - ($ - start) db 0 dw 0aa55h include 'os.asm'os.asmuse16 push 0xb800 ;segment ekranu pop es xor di,di mov al,'a' mov ah,9 stosw hltPo skompilowaniu pliku boot.asm (ctrl+f9 w gui) powinniśmy otrzymać 524 bajtowy plik boot.bin. Po uruchomieniu tak powstałego 'systemu' powinniśmy ujrzeć błękitną literkę 'a' w lewym górnym rogu ekranu, tak samo jak w poprzednim przykładzie - jednak tym razem bootsector jedynie ładuje nasz 'system', a ten zajmuje się wyświetleniem napisu. Do powstałego pliku bootsectora nalezy jeszcze dodać tylko odblokowanie linii a20.Linia A20
Co to jest A20 - jest to 20 bit (21, liczac od 1) linii adresowej (Address Line) x86. W procesorze 80x88 i 80x86 było tylko 20 linii adresowych (od 0 do 19) - wystarczająco dużo dla jednego megabajta. Sposób adresowania zezwałał jednak na adresy większe niż 1 MB:1 MB = 1048575 = 0xFFFF:0x000F = 0xFFFFF (dokładnie 20 bitów zapalonych) 0xFFFF:0xFFFF = 0x10FFEF 0x10FFEF - 0xFFFFF = 0xFFFF = 65520 bajtów.Można więc było zaadresować o 65520 bajtów 'za dużo' niż pozwalała na to architektura procesora. Jako że bit 20 nie istniał, adres linearny 0x10FFEF ( czyli 100001111111111101111b) stawał się 0xFFEF (1111111111101111b) - bit 20 był zawsze zerem. Wszystko było dobrze aż do czasu pojawienia się procesora 286 - - ten posiadał już tryb chroniony i potrafił zaadresować 16 megabajtów pamięci (24 linie adresowe). Dla zachowania kompatybilności z starszymi procesorami posiadał dwa tryby: tryb rzeczywisty i tryb chroniony, które dla tego samego powodu posiadają dzisiejsze procesory. Posiadał jednak 24 linie adresowe, więc 'zakręcanie' adresów nie występowało - szybko się okazało, że są programy korzystające z tej właściwości i że nie działają one na 286 (dzięki zaokragląniu adresu można było szybko dostać się do pierwszych 64K pamięci, bez zmiany rejestrów segmentowych). IBM postanowił rozwiązać ten problem, podłączając pod kontroler klawiatury (8042, bedzie o nim mowa później) bramkę AND połączoną z bitem 20 linii adresowej. Od tej pory całkowita kompatybilność została uzyskana. Ma to jednak wpływ na adresowanie nawet teraz - dla zachowania kompatybilności bramka A20 jest domyślnie wyłączona. Można tą bramkę włączyć zapisując bezpośrednio do kontrolera klawiatury, można tez skorzystać z System Port A (dostępny właściwie na każdej dzisiejszej płycie) i odblokować A20 przez niego (tzw. Fast A20 Gate). Jest to lepsza posunięcie, gdyż wśród tzw. embedded devices kontroler klawiatury nie musi występować. Co prawda powinien on być wtedy emulowany, ale po co ryzykować :) boot.asmuse16 org 0x7C00 start: push 0x0010 ;segment 0x0010 pop es mov ax,0x0201 ;funkcja czytania sektorow, 1 sektor do odczytania mov cx,2 ;numer sektora xor dx,dx ;glowica 0,dysk 0 = dyskietka a xor bx,bx ;adres 0x0010:0000 int 0x13 jc blad unlock_a20: ;fast a20 unlock in al,0x92 test al,2 jnz @f ;juz ustawiona or al,2 out 0x92,al @@: jmp 0x0010:0000 blad: hlt times 510 - ($ - start) db 0 dw 0aa55h include 'os.asm'Jest to już właściwie końcowa wersja bootsektora - jedyne co od tej pory będziemy zmieniać to liczba sektorów jaka nasz bootsektor ma nam załadować do pamięci.3. Przejscie do trybu chronionego i wypisanie napisu
Wiemy już właściwie wszystko, co jest nam potrzebne aby napisać prosty system w trybie chronionym. Poszczególne kroki wyglądają tak: Bootsektor: 1. Załaduj sektory z systemem pod ustalony przez nas adres pamięci 2. Odblokuj A20 3. Skocz do załadowanego kodu systemu w pamięciPrzejscie w tryb chroniony zakończone Kod boot.asm nie zmienił się. os.asm
System: Wyłącz przerwania instrukcją CLI- Ustal początkowe dwa segmenty GDT:
Ustal adres linearny gdt Załaduj deskryptor gdt instrukcją LGDT Przejdź do trybu chronionego poprzez ustawienie bitu 1 rejestru CR0 Ustaw rejestr segmentowy cs poprzez far jmp do dalszej czesci kodu systemu
- Segment danych (read/write), base address 0, segment limit 4GB, DPL 0
- Segment kodu (execute/read), base address 0, segment limit 4GB, DPL 0
Kod 32 bitowy
Ustaw poprawny rejestr segmentowy:stosu (ss)danych (ds)dodatkowy (es)Gs i fs najlepiej ustawic na 0
Ustaw w esp adres stosuinclude '%fasminc%/os.inc' include 'macro.asm' org 0 use16 cli ;wylacz przerwania cld mov ax, 0x0010 mov ds, ax ;ustaw prawidlowy, linearny adres gdt add dword [gdt_descriptor+2], 0x100 lgdt [gdt_descriptor] mov eax, cr0 or eax, 1 ;przejscie do pmode mov cr0, eax jmp (1 shl 3):pm ;ustawienie cs na 32 bitowy selektor segmentu ;descriptor seg_limit*,base_address*,type*,system*,dpl*,present*,avl*,_64*,d_b*,granularity* gdt: dq 0 ;null segment gdt_ldt 0xffff,0,execute_ro,1,0,1,1,0,32,1 gdt_ldt 0xffff,0,rw,1,0,1,1,0,32,1 gdt_end: gdt_descriptor: dw gdt_end - gdt - 1 dd gdt org 0x0100+$ use32 START: pm: ;ustaw poprawne segmenty mov ax, (2 shl 3) mov ss,ax mov ds,ax xor edx,edx mov fs,dx mov es,ax mov gs,dx mov esp,0x000A0000 ;wyczysc ekran mov edi,[ekran] xor eax,eax mov ecx,(80*25)/2 rep stosd ;ustaw jasnobłękitny kolor stdcall setcolor,9 ;wypisz tekst stdcall puts,witaj hlt ;funkcja ustawia zmienna oznaczajaca kolor na podany argument setcolor: ;(kolor) mov al,byte[esp+4] mov byte[kolor],al ret 4 ;funkcja puts wyswietla lancuch znakow (zakonczony znakiem zerowym), ;na ekran puts: ;(asciiz *napis) push esi mov esi,[esp+8] push edi mov edi,0xb8000 ;0xb8000 = adres pierwszego znaku ekranu mov ah,[kolor] @@: lodsb test al,al jz @f stosw jmp @b @@: mov [ekran],edi pop edi pop esi ret 4 ;dane ekran dd 0xb8000 witaj db "Witaj w moim systemie operacyjnym!",0 kolor db ?macro.asm - przydatne makra, w obecnej chwili posiada tylko jedno - do tworzenia deskryptorów GDT i LDT. Posiada prostą kontrolę błędów na wypadek pomyłki.macro gdt_ldt seg_limit*,base_address*,type*,system*,dpl*,present*,avl*,_64*,d_b*,granularity* { local _db if d_b eq 16 _db=0 else if d_b eq 32 _db=1 else display "Bad default operation size",0x0d,0x0a err end if if granularity > 1 | granularity < 0 display "Bad granularity",0x0d,0x0a err end if if _64 > 1 | _64 < 0 display "Bad 64 bit code segment",0x0d,0x0a err end if if avl > 1 | avl < 0 display "Bad available bit",0x0d,0x0a err end if if present > 1 | present < 0 display "Bad present bit",0x0d,0x0a err end if if dpl > 3 | dpl < 0 display "Bad descriptor privilege level",0x0d,0x0a err end if if system > 1 | system < 0 display "Bad system bit",0x0d,0x0a err end if if type > 15 | type < 0 display "Bad type",0x0d,0x0a err end if if seg_limit > 0xfffff display "Bad system bit",0x0d,0x0a err end if dw (seg_limit and 0xffff) dw (base_address and 0xffff) db ((base_address shr 16) and 0x00ff) db (present shl 7) or (dpl shl 5) or (system shl 4) or type db (granularity shl 7) or (_db shl 6) or (_64 shl 5) or (avl shl 4) or ((seg_limit shl 16) and 0x0F) db (base_address shr 24) } ;data ro equ 0 accessed equ 1 rw equ 2 ro_expand_down equ 4 rw_expand_down equ 6 ;code execute equ 8 execute_ro equ 10 conforming_execute equ 12 conforming_execute_ro equ 14%fasminc%/os.inc - różne standardowe makra istniejące w fasmie dla windows, wywalone trochę niepotrzebnych. Jest on dość duży, na dodatek nie ma większego związku z samym pisaniem osa, więc umieściłem go na samym koncu tego kursu. %fasminc% jest to zmienna środowiskowa, oznaczająca katalog Include Fasma, np C:\Fasm\Include. Kompilujemy (ctrl+f9) plik boot.asm, uruchamiamy Bochs... i cieszymy się napisem wyświetlonym w trybie chronionym :)
4. Przerwania programowe - ustawianie Interrupt Descriptor Table
Przerwania sa to po prostu zdarzenia przerywające normalną pracę procesora i wywołujące inną, 'swoją' procedurę. Przerwania mogą być programowe albo sprzętowe: Przerwania programowe to nic innego jak instrukcja int n, gdzie n jest numerem przerwania, np. znany chyba wszystkim int3 (wywołujący przerwanie numer 3). Istnieją jeszcze przerwania sprzętowe - IRQ (Interrupt Request) - takim przerwaniem np. klawiatura (dokładniej układ 8042) informuje nas, że został naciśniety klawisz. Są one zarządzane przez PIC - Programmable Interrupt Controller (teraz się właściwie już tego nie używa - obecnym standardem jest APIC - Advanced Interrupt ... - ogromną różnicą w stosunku do starego układu jest możliwość nadawania priorytetów przerwaniom; PIC jest jednak dużo prostszy do obsługi, więc w tym kursie nie opiszę APIC, ew. opiszę dużo później). Tymi zagadnieniami zajmiemy się jednak później - teraz zajmiemy się poprawną obsługa przerwań programowych.
Task Gate - dokonuje sprzętowej zmiany kontekstu (context switch). O tym później. Trap Gate i Interrupt Gate - jest w nich zapisany adres funkcji obsługującej przerwanie. Różnią się jednym istotnym szczegółem: Wywołanie poprzez interrupt gate zeruje flage IF (tzn. wyłącza przerwania), natomiast poprzez Trap Gate - nie. D - Tryb pracy procesora: 1 - 32 bity; 0 - 16. Myślę że znaczenie pozostałych pól jest zrozumiałe - jest ono takie samo jak w przypadku GDT. Ustawmy sobie dla przykładu jedno przerwanie: os.asminclude '%fasminc%/os.inc' include 'macro.asm' org 0 use16 cli ;wylacz przerwania cld mov ax, 0x0010 mov ds, ax ;ustaw prawidlowy, linearny adres gdt add dword [gdt_descriptor+2], 0x100 lgdt [gdt_descriptor] mov eax, cr0 or eax, 1 ;przejscie do pmode mov cr0, eax jmp (1 shl 3):pm ;ustawienie cs na 32 bitowy selektor segmentu ;descriptor seg_limit*,base_address*,type*,system*,dpl*,present*,avl*,_64*,d_b*,granularity* gdt: dq 0 ;null segment gdt_ldt 0xffff,0,execute_ro,1,0,1,1,0,32,1 gdt_ldt 0xffff,0,rw,1,0,1,1,0,32,1 gdt_end: gdt_descriptor: dw gdt_end - gdt - 1 dd gdt org 0x0100+$ use32 START: pm: ;ustawianie poprawnych segmentow mov ax, (2 shl 3) mov ss,ax mov ds,ax xor edx,edx mov fs,dx mov es,ax mov gs,dx mov esp,0x000A0000 ;wyczysc ekran mov edi,[ekran] xor eax,eax mov ecx,(80*25)/2 rep stosd stdcall setcolor,9 ;stdcall puts,witaj ;ustaw idt lidt [idt_descriptor] ;sprawdz, czy int0 dziala int 0 hlt setcolor: ;(kolor) mov al,byte[esp+4] mov byte[kolor],al ret 4 puts: ;(asciiz *napis) push esi mov esi,[esp+8] push edi mov edi,0xb8000 mov ah,[kolor] @@: lodsb test al,al jz @f stosw jmp @b @@: mov [ekran],edi pop edi pop esi ret 4 ekran dd 0xb8000 kolor db ? _int0: stdcall puts,"Przerwanie 0" iretd ;idt idt: ;int0 idt_int (1 shl 3),_int0,32,0,1 ;makro do ustawiania idt ;seg_selector*,offset*,size*,dpl*,p* idt_end: idt_descriptor: dw idt_end - idt - 1 dd idtmacro.asmmacro idt_int seg_selector*,offset*,size*,dpl*,p* { local _db if size eq 16 _db=0 else if size eq 32 _db=1 else display "Bad default operation size",0x0d,0x0a err end if if seg_selector > 0xFFFF | seg_selector < 0 display "Bad Segment Selector",0x0d,0x0a end if if dpl > 3 | dpl < 0 display "Bad descriptor privilege level",0x0d,0x0a err end if if p > 1 | p < 0 display "Bad present bit",0x0d,0x0a err end if dw (offset and 0xFFFF) dw seg_selector db 0 db (00000110b or (p shl 7) or (dpl shl 5) or (_db shl 3)) dw (offset shr 16) } macro gdt_ldt seg_limit*,base_address*,type*,system*,dpl*,present*,avl*,_64*,d_b*,granularity* { local _db if d_b eq 16 _db=0 else if d_b eq 32 _db=1 else display "Bad default operation size",0x0d,0x0a err end if if granularity > 1 | granularity < 0 display "Bad granularity",0x0d,0x0a err end if if _64 > 1 | _64 < 0 display "Bad 64 bit code segment",0x0d,0x0a err end if if avl > 1 | avl < 0 display "Bad available bit",0x0d,0x0a err end if if present > 1 | present < 0 display "Bad present bit",0x0d,0x0a err end if if dpl > 3 | dpl < 0 display "Bad descriptor privilege level",0x0d,0x0a err end if if system > 1 | system < 0 display "Bad system bit",0x0d,0x0a err end if if type > 15 | type < 0 display "Bad type",0x0d,0x0a err end if if seg_limit > 0xfffff display "Bad system bit",0x0d,0x0a err end if dw (seg_limit and 0xffff) dw (base_address and 0xffff) db ((base_address shr 16) and 0x00ff) db (present shl 7) or (dpl shl 5) or (system shl 4) or type db (granularity shl 7) or (_db shl 6) or (_64 shl 5) or (avl shl 4) or ((seg_limit shl 16) and 0x0F) db (base_address shr 24) } ;data ro equ 0 accessed equ 1 rw equ 2 ro_expand_down equ 4 rw_expand_down equ 6 ;code execute equ 8 execute_ro equ 10 conforming_execute equ 12 conforming_execute_ro equ 14Skompilujmy to (przypominam - kompilujemy plik boot.asm, zeby os dołączył się nam na koniec), uruchamiamy Bochs... i cieszymy się z ustawionego przerwania :)
Resztę przerwań ustaw sam korzystając z podanego schematu. Tabela przerwań:
| 00 | Błąd dzielenia przez zero |
| 01 | Zarezerwowane |
| 02 | NMI |
| 03 | Breakpoint |
| 04 | Overflow (przepełnienie) |
| 05 | Przerwanie instrukcji BOUND |
| 06 | Błędna instrukcja (undefined opcode) |
| 07 | Brak FPU |
| 08 | Double Fault |
| 09 | FPU Segment Overrun |
| 10 | Błąd Task Switch (Invalid TSS) |
| 11 | Segment nieobecny (segment not present) |
| 12 | Błąd segmentu stosu (Stack segment fault) |
| 13 | Ogólny błąd ochrony (General Protection Fault) |
| 14 | Błąd strony (page fault) |
| 15 | Zarezerwowane |
| 16 | Błąd operacji zmiennoprzecinkowej |
| 17 | Błąd alignacji (alignmen check error) |
| 18 | Machine Check |
| 19 | Błąd operacji SSE/SSE2/SSE3 |
| 20-31 | Zarezerwowane przez Intela |
Jest to część pierwsza mojego kursu pisania systemu operacyjnego. W następnej części zajmiemy się obsługą PIC i przerwań, a następnie napiszemy prosty shell z kilkoma komendami.
Autor: Fr3m3n, zamieszczanie gdziekolwiek bez mojej zgody zabronione. Kontakt - pytania, sugestie, znalezione błędy: fr3m3n małpa gmail.com
Konfiguracja Bochs
Konfiguracja Bochs jest zawarta w pliku bochsrc.txt. Można go skonfigurować ręcznie, edytując plik, albo użyć menu uruchamianego po starcie. Nie jest to szczególnie skomplikowana sprawa dlatego sądzę że instrukcje 'krok po kroku' są zbędne. Najlepiej po prostu użyc przykładowego pliku bochsrc.txt:
config_interface: textconfig
display_library: win32
megs: 4
romimage: file="BIOS-bochs-latest"
vgaromimage: file="VGABIOS-lgpl-latest"
boot: floppy
floppy_bootsig_check: disabled=0
floppya: 1_44="", status=inserted
# no floppyb
ata0: enabled=0
ata1: enabled=0
ata2: enabled=0
ata3: enabled=0
parport1: enabled=0
parport2: enabled=0
com1: enabled=1, mode=null, dev=""
com2: enabled=0
com3: enabled=0
com4: enabled=0
usb1: enabled=0
i440fxsupport: enabled=1
vga_update_interval: 100000
vga: extension=vbe
cpu: count=1, ips=10000000, reset_on_triple_fault=1
text_snapshot_check: enabled=0
private_colormap: enabled=0
clock: sync=none, time0=local
# no cmosimage
ne2k: enabled=0
pnic: enabled=0
sb16: enabled=0
# no loader
log: log.txt
logprefix: %t%e%d
debugger_log: -
panic: action=ask
error: action=report
info: action=report
debug: action=ignore
pass: action=fatal
keyboard_type: mf
keyboard_serial_delay: 250
keyboard_paste_delay: 100000
keyboard_mapping: enabled=0, map=
user_shortcut: keys=none
mouse: enabled=0, type=ps2
Można również stworzyć sobie plik srun.bat do szybkiego uruchamiania bochsa
(bez wyświetlania menu).
bochs.exe -q
Nagranie systemu na dyskietke i dysk
Jeśli chcemy sprawdzić nasz system w rzeczywistości, musimy go nagrać na dyskietkę albo dysk twardy*. Na systemach windows do nagrywania na dyskietkę pomocny będzie program rawwrite. Jest on okienkowy, jego obsługa nie powinna więc sprawiać problemów. Na systemach unixowych (*bsd, linux, ...) istnieje za to program o nazwie dd. Jego składnia to:dd [bs=SIZE[SUFFIX]] [count=BLOCKS] if=FILE of=FILE [seek=BLOCKS] [skip=BLOCKS] [--size] [--list] [--progress]
Instrukcja obsługi i krótki opis znajduje się na Wikipedii.
Port dd na system windows można znaleźć na http://www.chrysocome.net/dd
*Boot sektor twardego dysku wygląda trochę inaczej niż ten dla fdd, będziesz więc musiał sam
poszukać informacji na ten temat (albo poczekać na III część kursu, w której to opiszę).
Plik os.inc
os.inc - plik z przydatnymi makrami np. stdcall - jest to zmodyfikowany (bez niepotrzebych rzeczy) plik win32a.inc. Najlepiej umieścić w katalogu Include Fasma.include '/macro/if.inc'
include '/macro/struct.inc'
include '/macro/proc32.inc'
macro allow_nesting
{ macro pushd value
\{ match ,value \\{
pushx equ \\}
match =pushx =invoke proc,pushx value \\{
allow_nesting
invoke proc
purge pushd,invoke,stdcall,cinvoke,ccall
push eax
pushx equ \\}
match =pushx =stdcall proc,pushx value \\{
allow_nesting
stdcall proc
purge pushd,invoke,stdcall,cinvoke,ccall
push eax
pushx equ \\}
match =pushx =cinvoke proc,pushx value \\{
allow_nesting
cinvoke proc
purge pushd,invoke,stdcall,cinvoke,ccall
push eax
pushx equ \\}
match =pushx =ccall proc,pushx value \\{
allow_nesting
ccall proc
purge pushd,invoke,stdcall,cinvoke,ccall
push eax
pushx equ \\}
match =pushx,pushx \\{
pushd
pushx equ \\}
restore pushx \}
macro invoke proc,[arg]
\{ \reverse pushd
\common call [proc] \}
macro stdcall proc,[arg]
\{ \reverse pushd
\common call proc \}
macro cinvoke proc,[arg]
\{ \common size@ccall = 0
if ~ arg eq
\reverse pushd
size@ccall = size@ccall+4
match =double any,arg \\{ size@ccall = size@ccall+4 \\}
\common end if
call [proc]
if size@ccall
add esp,size@ccall
end if \}
macro ccall proc,[arg]
\{ \common size@ccall = 0
if ~ arg eq
\reverse pushd
size@ccall = size@ccall+4
match =double any,arg \\{ size@ccall = size@ccall+4 \\}
\common end if
call proc
if size@ccall
add esp,size@ccall
end if \} }
macro pushd value
{ match first=,more, value \{ \local ..continue
call ..continue
db value,0
..continue:
pushd equ \}
match pushd =addr var,pushd value \{ \local ..opcode,..address
virtual at 0
label ..address at var
mov eax,dword [..address]
load ..opcode from 0
end virtual
if ..opcode = 0A1h
push var
else
lea edx,[..address]
push edx
end if
pushd equ \}
match pushd =double [var],pushd value \{
push dword [var+4]
push dword [var]
pushd equ \}
match pushd =double =ptr var,pushd value \{
push dword [var+4]
push dword [var]
pushd equ \}
match pushd =double num,pushd value \{ \local ..high,..low
virtual at 0
dq num
load ..low dword from 0
load ..high dword from 4
end virtual
push ..high
push ..low
pushd equ \}
match pushd,pushd \{ \local ..continue
if value eqtype ''
call ..continue
db value,0
..continue:
else
push value
end if
pushd equ \}
restore pushd }
allow_nesting
Aby dodawać komentarze musisz być zalogowany!
