mov ax, 0x07C0 ; set up segments |
nasm kernel.asm -f bin -o kernel.bin3) Diskette formatieren (sonst gibt es eine Windows-Fehlermeldung), falls Sie noch ein Diskettenlaufwerk am PC besitzen.
Mit der Option-f
wird festgelegt, dass die übersetze Datei ein Programm aus nur einem Segment (maximal 64 KB) ist, in dem sich Code, Daten und
Stack befinden. Parameter-o
legt fest, dass man den Namen der assemblierten Datei selbst bestimmt.
partcopy kernel.bin 0 200 -f04) In Bochs das File "bochsrc-sample.txt" suchen.
romimage:
file=$BXSHARE/BIOS-bochs-latest cpu: count=1, ips=10000000, reset_on_triple_fault=1 megs: 32 vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest vga: extension=vbe floppya: 1_44=a:, status=inserted ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15 ata2: enabled=0, ioaddr1=0x1e8, ioaddr2=0x3e0, irq=11 ata3: enabled=0, ioaddr1=0x168, ioaddr2=0x360, irq=9 boot: floppy floppy_bootsig_check: disabled=0 log: bochsout.txt panic: action=ask error: action=report info: action=report debug: action=ignore debugger_log: - parport1: enabled=1, file="parport.out" vga_update_interval: 300000 keyboard_serial_delay: 250 keyboard_paste_delay: 100000 mouse: enabled=0 private_colormap: enabled=0 keyboard_mapping: enabled=0, map= i440fxsupport: enabled=0 |
Wichtiger Hinweis: Falls Sie kein Diskettenlaufwerk am PC haben oder kein Sicherheitsrisiko mit partcopy o.ä. eingehen wollen, können Sie unter bochs auch direkt die binäre Datei starten. Anstelle von: floppya: 1_44=a:, status=inserted verwenden Sie nicht den Laufwerksbuchstaben a:, sondern im Austausch den Pfad zur Binärdatei, bei mir z.B.: floppya: 1_44=G:\OSDev\Test\kernel.bin, status=inserted |
|
mov
ax,
0x07C0 ; set up segments mov ds, ax mov es, ax mov si, welcome call print_string loop: mov si, prompt call print_string mov di, buffer call get_string mov si, buffer cmp byte [si], 0 ; blank line? je loop ; yes, ignore it mov di, cmd_hi ; "hi" command call strcmp jz .helloworld mov si, buffer mov di, cmd_help ; "help" command call strcmp jz .help mov si, buffer mov di, cmd_questionmark ; "?" command call strcmp jz .help mov si, buffer mov di, cmd_exit ; "exit" command call strcmp jz .exit mov si,badcommand call print_string jmp loop .helloworld: mov si, msg_helloworld call print_string jmp loop .help: mov si, msg_help call print_string jmp loop .exit: mov si, msg_exit call print_string jmp 0xffff:0x0000 ; Reboot welcome db 'HenkesSoft 0.01 (version from Mar 14, 2009)', 13, 10, 0 msg_helloworld db 'Hello World!', 13, 10, 0 badcommand db 'Command unknown.', 13, 10, 0 prompt db '>', 0 cmd_hi db 'hi', 0 cmd_help db 'help', 0 cmd_questionmark db '?', 0 cmd_exit db 'exit', 0 msg_help db 'Commands: hi, help, ?, exit', 13, 10, 0 msg_exit db 'Reboot starts now.', 13, 10, 0 buffer times 32 db 0 ; ================ ; calls start here ; ================ print_string: lodsb ; grab a byte from SI or al, al ; logical or AL by itself jz .done ; if the result is zero, get out mov ah, 0x0E int 0x10 ; otherwise, print out the character! jmp print_string .done: ret get_string: xor cl, cl .loop: xor ah, ah ; mov ah, 0 int 0x16 ; wait for keypress cmp al, 8 ; backspace pressed? je .backspace ; yes, handle it cmp al, 13 ; enter pressed? je .done ; yes, we're done cmp cl, 31 ; 31 chars inputted? je .loop ; yes, only let in backspace and enter mov ah, 0x0E int 0x10 ; print out character stosb ; put character in buffer inc cl jmp .loop .backspace: or cl, cl ; zero? (start of the string) jz .loop ; if yes, ignore the key dec di mov byte [di], 0 ; delete character dec cl ; decrement counter as well mov ax, 0x0E08 int 0x10 ; backspace on the screen mov al, ' ' int 0x10 ; blank character out mov al, 8 int 0x10 ; backspace again jmp .loop ; go to the main loop .done: mov al, 0 ; null terminator stosb mov ax, 0x0E0D int 0x10 mov al, 0x0A int 0x10 ; newline ret strcmp: .loop: mov al, [si] ; fetch a byte from SI cmp al, [di] ; are SI and DI equal? jne .done ; if no, we're done. or al, al ; zero? jz .done ; if yes, we're done. inc di ; increment DI inc si ; increment SI jmp .loop ; goto .loop .done: ret times 510-($-$$) hlt ; as alternative to db 0 db 0x55 ; boot signature check (byte 511 in sector 1) db 0xAA ; boot signature check (byte 512 in sector 1) |
AX = AH + AL AkkumulatorWeitere 16-Bit-Register:
BX = BH + BL Basisregister
CX = CH + CL Counter
DX = DH + DL Datenregister für Eingabe/Ausgabeoperationen
BP Base Pointer (Basisregister)Steuer- und Status-Register:
SP Stack Pointer (Zeiger auf Stack)
SI Source Index (Indexregister für Quell-Operanden)
DI Destination Index (Indexregister für Ziel-Operanden)
IP Instruction Pointer (Offset-Adresse für den nächsten Befehl)
F Flag Register (enthält 16 einzelne Bits, die Informationen geben wie z.B.: NZ (Non Zero), ZR (Zero), NC (Non Carry), CY (Carry).
Segment-/Adressregister:
ES Extra Segment (arbeitet mit DI)Heutige Prozessoren beruhen immer noch auf den gleichen Prinzipien, allerdings gibt es seit dem 386er echte 32-Bit-Register, denen stellt man noch ein E vorne weg, also EAX anstelle AX, etc.
SS Stack Segment (arbeitet mit SP)
DS Daten Segment (Segmentregister für weitere Daten)
CS Code Segment (Segmentregister für Instruktionen)
AH=0x0E Schreiben und Kursor weiterbewegenEine Gesamtübersicht über die BIOS-Interrupts findet sich an dieser Stelle.
AL zu schreibendes Zeichen im ASCII-Code
BH Seitennummer
BL Farbe (nur Grafikmodi)
MOV AH,00 ; Keyboard Read: auf Tastendruck warten
INT 0x16 ; AL = ASCII Code (=0 für Sondertasten), AH = Scancode
Hinweis: Für die Initialisierung
eines Registers mit dem Wert Null verwendet man performant anstelle der
Instruktion MOV die exklusive ODER-Verknüpfung (XOR).
MOV
AX,0 erzeugt drei Bytes Maschinencode, während XOR
AX,AX nur zwei Byte Code erzeugt. Bei den 32-Bit-Registern
wirkt sich dieser Unterschied sogar verstärkt aus (sechs anstelle drei
Byte Maschinencode). Dies ergibt eine Speicherplatzeinsparung und einen
Zeitgewinn bei der Abarbeitung. Dies erfolgt leider auf Kosten der
Verständlichkeit. Dafür nutzt man dann die leichter lesbare Anweisung
im Kommentar. |
org 0x7C00
; set up start address
; setup a stack; start mov [bootdrive], dl ; boot drive from DL call load_kernel ; load kernel ; jump to kernel jmp 0x1000:0x0000 ; address of kernel bootdrive db 0 ; boot drive loadmsg db "bootloader message: loading kernel ...",13,10,0 ; print string print_string: lodsb ; grab a byte from SI or al, al ; NUL? jz .done ; if the result is zero, get out mov ah, 0x0E int 0x10 ; otherwise, print out the character! jmp print_string .done: ret ; read kernel from floppy disk load_kernel: mov dl,[bootdrive] ; select boot drive xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel ; trouble? try again load_kernel1: mov ax, 0x1000 mov es, ax ; ES:BX = 0x10000 xor bx, bx ; mov bx, 0 ; set parameters for reading function ; 8-Bit-wise for better overview mov dl,[bootdrive] ; select boot drive mov al,10 ; read 10 sectors mov ch, 0 ; cylinder = 0 mov cl, 2 ; sector = 2 mov dh, 0 ; head = 0 mov ah, 2 ; function "read" int 0x13 jc load_kernel1 ; trouble? try again ; show loading message mov si,loadmsg call print_string ret times 510-($-$$) hlt db 0x55 db 0xAA |
Hinweis:
Intel Prozessoren
verwenden die Little Endian (Intel-Format)
Byte-Reihenfolge im Gegensatz zu Big Endian (Motorola-Format),
d.h. im Speicher werden die
Bytes in der Reihenfolge beginnend vom niedrigwertigsten Byte abgelegt.
Zu Beginn der CPU-Entwicklung hatte Little Endian den Vorteil, dass man bei jeder Operation automatisch das niederwertigste Byte bereits laden konnte. Während dieses Vorgangs konnte der Befehl genau dekodiert werden. Falls für den Befehl notwendig wurden die höherwertigen Daten im nächsten Taktschritt ebenfalls bearbeitet. Dies war sehr effizient. Heute spielt dieser Zeitvorteil aufgrund breiter Datenbusse und hoher Taktraten nicht mehr diese entscheidende Rolle. Daher müssen wir z.B. times 510-($-$$) db 0 dw 0xAA55 ; dw = define word schreiben, wenn wir die Byte-Folge 55AA als Bootsignatur abspeichern wollen! |
AH | 0X02 |
AL | Sectors To Read Count |
CX | Cylinder + Sector |
DH | Head |
DL | Drive |
ES:BX | Buffer Address Pointer |
Hinweis: Die
Verwendung
des
High
und
Low Byte eines Registers in Verbindung mit
dem Befehl mov, wenn genau so gut das gesamte 16-Bit-Register mit einem
Befehl bedient werden könnte, ist eine Verschwendung von Speicherplatz
und Prozessortakten (in diesem Fall: ein Byte und max. z.B. auf
einem 486 ein Takt zusätzlich)! Dies geschieht in obigem Fall nur der
besseren
Übersicht wegen. Performanter Programmierstil ist dies nicht. Also anstelle mov al,10 (mov al, 0x0A) mov ah, 2 verwendet man performant mov ax, 0x020A |
mov ax, 0x1000 ; set up segments mov ds, ax mov es, ax mov si, welcome call print_string loop: mov si, prompt call print_string ... (siehe oben) .done: stc ; equal, set the carry flag ret times 512-($-$$) hlt ; no boot signature |
Hinweis: Bei offener
A20-Adressleitung werden im Real Mode weitere 64
KB minus 16 Byte erreichbar. Diesen Speicherbereich von FFFF:0010h bis FFFF:FFFFh nennt man High Memory Area (HMA). |
Physische Adresse = 16 * Segment + Offset |
Speicherinhalt |
relative
(segmentierte) Adresse |
absolute
Adresse |
Bootloader-Programm | 0000:7c00h | 07c00h |
Kernel-Programm |
1000:0000h |
10000h |
Stack-Bereich |
9000:0000h | 90000h |
Real Mode | "Zeiger", z.B. CS:IP, auf die physische Adresse im Speicher |
16-Bit-Protected Mode | "Zeiger auf Zeiger" (Deskriptor
-> Phys. Basis)
und "Zeiger" (Offset) auf die physische Adresse im Speicher |
32-Bit-Protected Mode | "Zeiger auf Zeiger auf Zeiger"
(Selektor ->
Deskriptor -> Page -> Phys. Basis) und "Zeiger" (Offset) auf die physische Adresse im Speicher |
1) GDT anlegen
2) Laden des GDTR mit GDT-Adresse
3) Protected Mode aktivieren: Setzen von Bit 0 ("PE-Bit") des Registers CR0
4) Durchführen eines "FAR-JMP" zur Leerung der Warteschlange
Wir werden zusätzlich die A20-Leitung aktivieren, damit kein 8088-kompatibler "wrap" der Speicher-Adressieruing erfolgt.
Das folgende OS schaltet nun vom Real Mode (RM) in den Protected Mode (PM) um.
Zuerst wird der Code des überarbeiteten Bootloaders, Kernels und makefile dargestellt.
Anschließend gehen wir detailliert auf die durchgeführten Änderungen und Ergänzungen ein.
org
0x7C00 ; set up start address of bootloader ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; setup a stack and segment regs ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; xor ax, ax mov ds, ax mov es, ax mov ss, ax mov sp, ax ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; read kernel from floppy disk ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov [bootdrive], dl ; boot drive stored by BIOS in DL. ; we save it to [bootdrive] to play for safety. load_kernel: xor ax, ax ; mov ax, 0 => function "reset" int 0x13 jc load_kernel ; trouble? try again mov bx, 0x8000 ; set up start address of kernel ; set parameters for reading function ; 8-bit-wise for better overview mov dl,[bootdrive] ; select boot drive mov al,10 ; read 10 sectors mov ch, 0 ; cylinder = 0 mov cl, 2 ; sector = 2 mov dh, 0 ; head = 0 mov ah, 2 ; function "read" int 0x13 jc load_kernel ; trouble? try again ; show loading message mov si,loadmsg call print_string ;;;;;;;;;;;;;;;;;; ; jump to kernel ; ;;;;;;;;;;;;;;;;;; jmp 0x0000:0x8000 ; address of kernel. "jmp bx" might also work. ;;;;;;;;;;;;;;;;;;;;;;; ; call "print_string" ; ;;;;;;;;;;;;;;;;;;;;;;; print_string: mov ah, 0x0E ; VGA BIOS fnct. 0x0E: teletype .loop: lodsb ; grab a byte from SI test al, al ; NUL? jz .done ; if the result is zero, get out int 0x10 ; otherwise, print out the character! jmp .loop .done: ret ;;;;;;;; ; data ; ;;;;;;;; bootdrive db 0 ; boot drive loadmsg db "bootloader message: loading kernel ...",13,10,0 times 510-($-$$) hlt db 0x55 db 0xAA |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Global Descriptor Table (GDT) ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; NULL_Desc: dd 0 dd 0 CODE_Desc: dw 0xFFFF ; segment length bits 0-15 ("limit") dw 0 ; segment base byte 0,1 db 0 ; segment base byte 2 db 10011010b ; access rights db 11001111b ; bit 7-4: 4 flag bits: granularity, default operation size bit, ; 2 bits available for OS ; bit 3-0: segment length bits 16-19 db 0 ; segment base byte 3 DATA_Desc: dw 0xFFFF ; segment length bits 0-15 dw 0 ; segment base byte 0,1 db 0 ; segment base byte 2 db 10010010b ; access rights db 11001111b ; bit 7-4: 4 flag bits: granularity, ; big bit (0=USE16-Segm., 1=USE32-Segm.), 2 bits avail. ; bit 3-0: segment length bits 16-19 db 0 ; segment base byte 3 gdtr: Limit dw 24 ; length of GDT Base dd NULL_Desc ; base of GDT ( linear address: RM Offset + Seg<<4 ) |
Die oben definierten drei Deskriptoren beschreiben nun die
"Segmente" für "NULL", Code und Daten im Hauptspeicher. Diese
Deskriptoren verwenden wir nur im Protected
Mode. Real Mode hat Segmente mit konstanter Größe von 64 KByte. Jeder Deskriptor besteht aus 8 Byte.
Die Deskriptoren CODE_Desc und DATA_Desc liefern Informationen über
Größe, Position, Zugriffsberechtigungen und Verwendungstyp des Code-
und Daten-Segmentes. Die GDT
(Global Descriptor Table) kann maximal 8192 solcher Deskriptoren
aufnehmen und steht jedem Prozess
zur Verfügung, daher das "global". Die detaillierte Untersuchung des
Inhaltes dieser Tabelle findet sich hier.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; HenkesSoft 0.03 (version from Mar 22, 2009) ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; org 0x8000 ; code's start offset [BITS 16] ; 16 Bit Code ;;;;;;;;;;;;; ; Real Mode ; ;;;;;;;;;;;;; RealMode: xor ax, ax ; set up segments mov es, ax mov ds, ax mov ss, ax mov sp, ax mov si, welcome call print_string add sp, -0x40 ; make room for input buffer (64 chars) loop_start: mov si, prompt ; show prompt call print_string mov di, sp ; get input call get_string jcxz loop_start ; blank line? -> yes, ignore it mov si, sp mov di, cmd_hi ; "hi" command call strcmp je .helloworld mov si, sp mov di, cmd_help ; "help" command call strcmp je .help mov si, sp mov di, cmd_questionmark ; "?" command call strcmp je .help mov si, sp mov di, cmd_exit ; "exit" command call strcmp je .exit mov si, sp mov di, cmd_pm ; "pm (protected mode)" command call strcmp je .pm mov si,badcommand ; unknown command call print_string jmp loop_start .helloworld: mov si, msg_helloworld call print_string jmp loop_start .help: mov si, msg_help call print_string jmp loop_start .exit: mov si, msg_exit call print_string xor ax, ax int 0x16 ; Wait for keystroke jmp 0xffff:0x0000 ; Reboot .pm: call clrscr mov si, msg_pm call print_string call Waitingloop cli ; clear interrupts lgdt [gdtr] ; load GDT via GDTR (defined in file "gtd.inc") ; we actually only need to do this ONCE, but for now it doesn't hurt to do this more often when ; switching between RM and PM in al, 0x92 ; switch A20 gate via fast A20 port 92 cmp al, 0xff ; if it reads 0xFF, nothing's implemented on this port je .no_fast_A20 or al, 2 ; set A20_Gate_Bit (bit 1) and al, ~1 ; clear INIT_NOW bit (don't reset pc...) out 0x92, al jmp .A20_done .no_fast_A20: ; no fast shortcut -> use the slow kbc... call empty_8042 mov al, 0xD1 ; kbc command: write to output port out 0x64, al call empty_8042 mov al, 0xDF ; writing this to kbc output port enables A20 out 0x60, al call empty_8042 .A20_done: mov eax, cr0 ; switch-over to Protected Mode or eax, 1 ; set bit 0 of CR0 register mov cr0, eax ; jmp 0x8:ProtectedMode ; http://www.nasm.us/doc/nasmdo10.html#section-10.1 ;;;;;;;;; ; Calls ; ;;;;;;;;; empty_8042: call Waitingloop in al, 0x64 cmp al, 0xff ; ... no real kbc at all? je .done test al, 1 ; something in input buffer? jz .no_output call Waitingloop in al, 0x60 ; yes: read buffer jmp empty_8042 ; and try again .no_output: test al, 2 ; command buffer empty? jnz empty_8042 ; no: we can't send anything new till it's empty .done: ret print_string: mov ah, 0x0E .loop_start: lodsb ; grab a byte from SI test al, al ; test AL jz .done ; if the result is zero, get out int 0x10 ; otherwise, print out the character! jmp .loop_start .done: ret get_string: xor cx, cx .loop_start: xor ax, ax int 0x16 ; wait for keypress cmp al, 8 ; backspace pressed? je .backspace ; yes, handle it cmp al, 13 ; enter pressed? je .done ; yes, we're done cmp cl, 63 ; 63 chars inputted? je .loop_start ; yes, only let in backspace and enter mov ah, 0x0E int 0x10 ; print out character stosb ; put character in buffer inc cx jmp .loop_start .backspace: jcxz .loop_start ; zero? (start of the string) if yes, ignore the key dec di mov byte [di], 0 ; delete character dec cx ; decrement counter as well mov ah, 0x0E int 0x10 ; backspace on the screen mov al, ' ' int 0x10 ; blank character out mov al, 8 int 0x10 ; backspace again jmp .loop_start ; go to the main loop .done: mov byte [di], 0 ; null terminator mov ax, 0x0E0D int 0x10 mov al, 0x0A int 0x10 ; newline ret strcmp: .loop_start: mov al, [si] ; grab a byte from SI cmp al, [di] ; are SI and DI equal? jne .done ; no, we're done. test al, al ; zero? jz .done ; yes, we're done. inc di ; increment DI inc si ; increment SI jmp .loop_start ; loop! .done: ret clrscr: mov ax, 0x0600 xor cx, cx mov dx, 0x174F mov bh, 0x07 int 0x10 ret ;;;;;;;;;;;;;;;;;; ; Protected Mode ; ;;;;;;;;;;;;;;;;;; [Bits 32] ProtectedMode: mov ax, 0x10 mov ds, ax ; data descriptor --> data, stack and extra segment mov ss, ax mov es, ax xor eax, eax ; null desriptor --> FS and GS mov fs, ax mov gs, ax mov esp, 0x200000 ; set stack below 2 MB limit call clrscr_32 mov ah, 0x01 .endlessloop: call Waitingloop inc ah and ah, 0x0f mov esi, msg_pm2 ; 'OS currently uses Protected Mode.' call PutStr_32 cmp dword [PutStr_Ptr], 25 * 80 * 2 + 0xB8000 jb .endlessloop mov dword [PutStr_Ptr], 0xB8000 ; text pointer wrap-arround jmp .endlessloop Waitingloop: mov ebx,0x9FFFF .loop_start: dec ebx jnz .loop_start ret PutStr_32: mov edi, [PutStr_Ptr] .nextchar: lodsb test al, al jz .end stosw jmp .nextchar .end: mov [PutStr_Ptr], edi ret clrscr_32: mov edi, 0xb8000 mov [PutStr_Ptr], edi mov ecx, 40 * 25 mov eax, 0x07200720 ; two times: 0x07 => white text & black background 0x20 => Space rep stosd ret PutStr_Ptr dd 0xb8000 ; You load the address you want to output to in [PutStr_Ptr], ; the address of the string (has to be NUL terminated) ; you want to print in esi and the attributes in ah ; lodsb loads one byte from esi into al, then increments esi, ; then it checks for a NUL terminator, ; then it moves the char into the write position in video memory, ; then increments edi and writes the attributes, ; loops until it finds NUL pointer at which point it breaks ... ;;;;;;;;;;; ; Strings ; ;;;;;;;;;;; welcome db 'HenkesSoft 0.03 (version from Mar 22, 2009)', 13, 10, 0 msg_helloworld db 'Hello World!', 13, 10, 0 badcommand db 'Command unknown.', 13, 10, 0 prompt db '>', 0 cmd_hi db 'hi', 0 cmd_help db 'help', 0 cmd_questionmark db '?', 0 cmd_exit db 'exit', 0 cmd_pm db 'pm', 0 msg_help db 'Commands: hi, help, ?, pm, exit', 13, 10, 0 msg_exit db 'Reboot starts now. Enter keystroke, please.', 13, 10, 0 msg_pm db 'Switch-over to Protected Mode.', 13, 10, 0 msg_pm2 db 'OS currently uses Protected Mode. ', 0 ;;;;;;;;;;;; ; Includes ; ;;;;;;;;;;;; %include "gdt.inc" ;;;;;;;;;;;;;;;;;;; ; Set Bits to HLT ; ;;;;;;;;;;;;;;;;;;; times 1024-($-$$) hlt |
all: nasmw -O32 -f bin -o boot.bin boot.asm nasmw -O32 -f bin -o kernel.bin kernel.asm cmd /c copy /b boot.bin + kernel.bin MyOS.bin partcopy MyOS.bin 0 600 -f0 |
Bit |
Meaning |
0 |
1 |
7 |
P (Present Bit) | Descriptor
is
undefined |
Descriptor
contains
a
valid
base
and limit |
6 |
DPL
(High) |
see below | see below |
5 |
DPL
(Low) |
see below | see below |
4 |
S (Segment Bit) | System
Descriptor |
Code,
Data
or
Stack
Descriptor |
3 |
Type |
see below |
see below |
2 |
Type |
see below | see below |
1 |
Type |
see below | see below |
0 |
A (Accessed Bit) | Segment
not
accessed |
Segment
has
been
accessed |
Das Ergebnis unser bisherigen Bemühungen sollte im Emulator Bochs nun hoffentlich auch bei Ihnen wie folgt aussehen:
Bochs emuliert leider nur relativ langsam, weshalb man es nur in Grenzen als Alternative zu MS Virtual PC oder Sun VirtualBox sieht.
Die Software VMware Workstation ist noch kostenpflichtig, kann aber als 30 Tage-Prüfversion bezogen werden.
Im Bereich des OS Developments hat Bochs einen festen Platz, vielleicht gerade, weil es "langsam" ist.
Der interne Bochs-Debugger trägt sicher ebenfalls zu dem soliden Ruf des Systems bei.
Das Programm Bochs (gesprochen "box") wurde von Kevin Lawton in C++ zunächst als kommerzielle Software entwickelt und später als open
source für viele Plattformen angeboten. Dazu gehören z.B. Windows, Linux, MacOS, Solaris, BeOS, IRIX, Digital Unix und AIX.
Diese geniale Software "emuliert" einen Computer des Typs Intel x86.
Man kann damit das Booten des selbst entwickelten OS und die Interaktion mit Tastatur, Maus, Grafikkarte, Laufwerke etc. austesten. Dies spart gegenüber dem
"echten" Testen eine Menge Arbeitszeit. Man kann diese Emulations-Software als 386er, 486er, Pentium oder x86-64 CPU einstellen.
Hierbei werden auch MMX, SSEx und 3DNow! Instruktionen berücksichtigt, falls erwünscht.
Bochs wird neben anderen Anwendungen zum Debuggen bei der Entwicklung von Betriebssystemen verwendet.
Hierbei kann man sich bequem die Inhalte des Speichers und der CPU Register anschauen oder die Verwendungshäufigkeit und Leistungsfähigkeit einzelner
Programmabschnitte testen.
Die Verhaltenseigenschaften von Bochs werden über eine config-Datei eingestellt.
Beispiel: cpu: count=1,
ips=10000000, reset_on_triple_fault=1
Hier ist eingestellt, dass ein Prozessor mit 10 Mips (Million Instructions Per Second) emuliert wird. Den berühmten Triple Fault
werden Sie bei der OS-Entwicklung häufig erleben. Hierbei tritt eine "Exception" (Fehlermeldung) auf, während die CPU versucht den Double
Fault Exception Handler aufzurufen, der wiederum Exceptions behandelt, während er den regulären Exception Handler aufruft. Hier hilft nur noch die Fehlersuche.
Wenn Sie Fehler suchen oder einfach nur den Ablauf detailliert verfolgen wollen, benötigen Sie den Bochs Debugger (.../Bochs-2.3.7/bochsdbg.exe).
Die in diesem Rahmen zur Verfügung stehenden Befehle findet man hier: .../Bochs-2.3.7/docs/user/internal-debugger.html
c continue executing
continue
s [count] execute count instructions, default is 1
step [count]
stepi [count]
Ctrl-C stop execution, and return to command line prompt
Ctrl-D if at empty line on command line, exit
q quit debugger and execution
quit
exit
info cpu List of all CPU registers and their contents
Wenn wir über den Protected Mode sprechen, beginnen wir praktisch sicher nicht unter einem 80386, da wir heuzutage zumindest von 32-Bit-Systemen ausgehen.
Die Entwicklung vom 8088 zu den heutigen modernen CPUs ist allerdings von vielen historischen Zwängen geplagt, die man aus Gründen der Kompatibilität
nicht mehr los wird, obwohl man sie schon lange nicht mehr braucht. So ein Relikt ist das A20-Gate.
Die Geschichte ist ganz einfach. Rechnet man die logische Adresse FFFF:FFFF (Real Mode) in eine physische Adresse um,
so erhält man mit der Formel 0xFFFF * 0x10 + 0xFFFF = 0xFFFF0 + 0xFFFF = 0x10FFEF = 1114095.
Das übersteigt den adressierbaren Speicherbereich von 1 MB = 1048576 Byte = 0x100000 Byte.
Im 8088 ergab dies einen "wrap", so dass z.B. aus 0x10FFEF einfach 0x0FFEF wurde. Beim 80286, der nun 24 Adressleitungen bot,
musste man daher die Adressleitung A20 künstlich lahm legen, damit er kompatibel zum 8088 war. Dafür benutzte man einen freien Pin am Keyboard Controller.
Wenn der PC heute im Real Mode startet ist genau dieses A20-Gate, die es immer noch gibt, abgeschaltet.
Daher muss diese Leitung im Protected Mode bei Zugriffen oberhalb 1 MB aktiviert werden, denn im Protected Mode kann man problemlos mit 32 Bit arbeiten.
Daher muss man das A20-Gate wieder aktivieren, da ansonsten bei bestimmten Speicherzugriffen Fehler auftreten, wenn die 21. Adressleitung deaktiviert ist.
Das bedeutet, das man auf eine andere Speicherstelle zugreifen kann als beabsichtigt.
Daher schaltet man das A20-Gate ein, bevor man den Kernel und weitere Programmteile startet.
Das A20-Gate ist wirklich ein lästiges Relikt aus grauer Vorzeit, das aus heutiger Sicht überflüssigen Codes notwendig macht.
Es gibt mehrere Methoden, diesen Einschaltvorgang durchzuführen:
1) Keyboard Controller
2) System Control Port A
3) Modernes BIOS
Im Falle unseres Kernel-Sourcecodes wählen wir die schnelle Variante via System Control Port A und, falls diese schief geht, die langsame via Keyboard Controller.
Der Befehl in bedeutet input from port. Nachfolgend wird somit der Wert des Ports 0x92 in das Register AL geladen.
in al, 0x92 ; switch A20 gate via fast A20 port 92
Da ältere Systeme die schnelle System Control Port A - Variante nicht bieten, muss man evtl. auf die langsame Keyboard Controller - Variante ausweichen.
Das wird hier geprüft.
cmp al, 0xff ; if it reads 0xFF, nothing's implemented on this port
je .no_fast_A20
Falls der schnelle Weg via Port 0x92 funktioniert, setzt man Bit 1 (A20_Gate_Bit) und löscht aber sicherheitshalber Bit 0 (Reset).
Anschließend sendet man den Wert mit dem Befehl out vom Register zum Port 0x92. Das ist der schnelle und einfache Weg.
or al, 2 ; set A20_Gate_Bit (bit 1)
and al, ~1 ; clear INIT_NOW bit (don't reset pc...)
out 0x92, al
jmp .A20_done
Der langsame Weg führt über Port 0x60 und Port 0x64 des Keyboard Controllers (Mainboard) und den Tastatur-Chip (Tastatur).
Man schreibt 0xD1 (11010001b) nach Port 0x64, damit das naechste Byte auf Port 0x60 beim Output-Port landet.
Darüber kann man dann seriell Informationen an die Tastatur schicken. Wir senden 0xDF zur Aktivierung des A20 Gate.
Der Port 0x64 des Keyboard Controller hat folgende Funktionen:
Bit 0 bewirkt einen Reset der CPU, falls dieses auf 0 gesetzt wird.
Bit 1 kontrolliert das A20 Gate. Bei 1 ist es eingeschaltet, bei 0 ausgeschaltet.
Den gewüschten Wert des Output Port schreibt man also nach Port 0x60:
0xDD = 11011101b (disable A20) bzw.
0xDF = 11011111b (enable A20).
.no_fast_A20: ; no fast shortcut -> use the slow keyboard controller command
call empty_8042
mov al, 0xD1 ; keyboard controller command: write to output port
out 0x64, al
call empty_8042
mov al, 0xDF ; writing this to kbc output port enables A20
out 0x60, al
call empty_8042
.A20_done: ... Hier geht es anschließend weiter mit der Umschaltung zum Protected Mode.
Die Subroutine empty_8042 kümmert sich um das Input-Handling des Keyboard Controllers, das etwas Zeit benötigt.
Daher die Warteschleifen.
empty_8042:
call Waitingloop
in al, 0x64
cmp al, 0xff ; ... no real kbc at all?
je .done
test al, 1 ; something in input buffer?
jz .no_output
call Waitingloop
in al, 0x60 ; yes: read buffer
jmp empty_8042 ; and try again
.no_output:
test al, 2 ; command buffer empty?
jnz empty_8042 ; no: we can't send anything new till it's empty
.done:
ret
Hier einige vertiefende Links zu diesem Thema:
http://www.elektronik-kompendium.de/sites/com/0811181.htm
http://de.wikipedia.org/wiki/A20-Gate
http://lowlevel.brainsware.org/wiki/index.php/A20-Gate
http://www.win.tue.nl/~aeb/linux/kbd/A20.html
Hinweis:
Ich empfehle diese DJGPP-Toolchain (gcc 3.1, ld 2.13, ...), da bei Einsatz der MinGW (GCC) Compiler Suite über Linker-Probleme mit dem in diesem Tutorial erfolgreich verwendeten NASM Output-Format a.out (assembler output)-Objektdatei-Format (inzwischen durch COFF und ELF abgelöst) berichtet wurde. Inzwischen wurde im zweiten Ansatz (siehe Teil 2 dieses Tutorials) ein Work-around gefunden: http://www.henkessoft.de/OS_Dev/OS_Dev2.htm#mozTocId42018 http://www.c-plusplus.de/forum/viewtopic-var-p-is-1736328.html#1736328 |
Innerhalb DJGPP
befindet sich eine Auswahl der sogenannten GNU Binutils. Hier
eine Gesamtübersicht mit kurzen Erläuterungen:
The GNU Binutils are a collection of binary tools. The main ones are:
But they also include:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; HenkesSoft 0.04 (version from Mar 26, 2009) ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;org 0x8000 ; code's start offset [BITS 16] ; 16 Bit Code [global RealMode] [extern _main] ; this is in the c file ;;;;;;;;;;;;; ; Real Mode ; ;;;;;;;;;;;;; RealMode: ... |
;;;;;;;;;;;;;;;;;; ; Protected Mode ; ;;;;;;;;;;;;;;;;;; [Bits 32] ProtectedMode: mov ax, 0x10 mov ds, ax ; data descriptor --> data, stack and extra segment mov ss, ax mov es, ax xor eax, eax ; null desriptor --> FS and GS mov fs, ax mov gs, ax mov esp, 0x200000 ; set stack below 2 MB limit call clrscr_32 mov ah, 0x01 .endlessloop: call Waitingloop inc ah and ah, 0x0f mov esi, msg_pm2 ; 'OS currently uses Protected Mode.' call PutStr_32 cmp dword [PutStr_Ptr], 25 * 80 * 2 + 0xB8000 jb .endlessloop mov dword [PutStr_Ptr], 0xB8000 ; text pointer wrap-arround ;jmp .endlessloop call _main ; ->-> C-Kernel jmp $ |
// ckernel.c void k_clear_screen() { char* vidmem = (char*) 0xb8000; unsigned int i=0; while(i<(80*2*25)) { vidmem[i] = ' '; ++i; vidmem[i] = 0x07; ++i; }; }; unsigned int k_printf(char* message, unsigned int line) { char* vidmem = (char*) 0xb8000; unsigned int i = line*80*2; while(*message!=0) { if(*message==0x2F) { *message++; if(*message==0x6e) { line++; i=(line*80*2); *message++; if(*message==0){return(1);}; }; }; vidmem[i]=*message; *message++; ++i; vidmem[i]=0x7; ++i; }; return 1; }; inline void outportb(unsigned int port,unsigned char value) { asm volatile ("outb %%al,%%dx"::"d" (port), "a" (value)); }; void update_cursor(int row, int col) { unsigned short position=(row*80) + col; // cursor LOW port to vga INDEX register outportb(0x3D4, 0x0F); outportb(0x3D5, (unsigned char)(position&0xFF)); // cursor HIGH port to vga INDEX register outportb(0x3D4, 0x0E); outportb(0x3D5, (unsigned char)((position>>8)&0xFF)); }; int main() { k_clear_screen(); k_printf("Welcome to HenkesSoft OS.", 0); k_printf("The C kernel has been loaded.", 2); update_cursor(3, 0); return 0; }; |
OUTPUT_FORMAT("binary") ENTRY(RealMode) SECTIONS { .text 0x8000 : { *(.text) } .data : { *(.data) } .bss : { *(.bss) } } |
all: nasmw -O32 -f bin boot.asm -o boot.bin nasmw -O32 -f aout kernel.asm -o kernel.o gcc -c ckernel.c -o ckernel.o ld -T kernel.ld kernel.o ckernel.o rename a.out ckernel.bin cmd /c copy /b boot.bin + ckernel.bin MyOS.bin partcopy MyOS.bin 0 1000 -f0 |
Output | Input |
- Grafikkarte / Monitor - Soundkarte / Lautsprecher / Verstärker - Printer / Plotter |
-
Tastatur - Maus - Trackball, Joystick, Pad, ... - Soundkarte / Mikrofon, Line-In - TV/Video-In-Karte |
Read
only |
Read & Write |
- CD-ROM - DVD-ROM - Scanner |
- Floppy Disk - Festplatten - CD-/DVD-Brenner - "Solid State Disk" (SSD) - Flash-Speicherkarten |
Computer | Netzwerk /
Kommunikation |
- Mainboard, Bios, Chipsatz - SATA, IDE, SCSI - USB, Firewire - Serielle Schnittstelle - Parallele Schnittstelle - Gameport |
- Netzwerkkarte &
Protokolle - Telefon, Fax - Modem, ISDN, DSL - WebCam, DigiCam |
void k_clear_screen() { |
char* vidmem = (char*) 0xb8000; unsigned int i=0; while(i<(80*2*25)) { vidmem[i] = ' '; // Space (ASCII: 0x20) ++i; vidmem[i] = 0x07; // white on black ++i; }; }; |
clrscr_32: mov edi, 0xb8000 mov [PutStr_Ptr], edi mov ecx, 40 * 25 ; we address two screen characters mov eax, 0x07200720 ; two times (eax has 4 byte): 0x07 => white text & black background 0x20 => Space rep stosd |
void
k_clear_screen() { unsigned int* vidmem = (unsigned int*) 0xb8000; unsigned int i=0; while(i<(80*25)) { vidmem[i] = 0x0720; // white on black, Space (0x20) ++i; }; }; |
void
k_clear_screen() { unsigned long* vidmem = (unsigned long*) 0xb8000; unsigned long i=0; while(i<(40*25)) { vidmem[i] = 0x07200720; // white on black, Space (0x20) ++i; }; }; |
unsigned
int k_printf(char* message,
unsigned int line) { char* vidmem = (char*) 0xb8000; unsigned int i = line*80*2; while(*message!=0) { if(*message==0x2F) { *message++; if(*message==0x6e) { line++; i=(line*80*2); *message++; if(*message==0){return(1);}; }; }; vidmem[i]=*message; *message++; ++i; vidmem[i]=0x7; ++i; }; return 1; }; |
unsigned int k_printf(char *message, unsigned int line) |
inline void outportb(unsigned int port,unsigned
char value) { asm volatile ("outb %%al,%%dx"::"d" (port), "a" (value)); }; void update_cursor(int row, int col) { unsigned short position = (row*80) + col; // cursor HIGH port to vga INDEX register outportb(0x3D4, 0x0E); outportb(0x3D5, (unsigned char)((position>>8)&0xFF)); // cursor LOW port to vga INDEX register outportb(0x3D4, 0x0F); outportb(0x3D5, (unsigned char)(position&0xFF)); }; |
Der Compiler gcc bietet die Möglichkeit, im
Assemblercode Werte zu verarbeiten, die nicht direkt im Assemblerstring
stehen, sondern in C-Variablen gespeichert sind. Diese
Möglichkeit wurde in der Funktion outportb(...)
genutzt. Die Syntax ist allgemein wie folgt:
asm ( "outb %%reg1, %%reg2" : "d" (port) : "a" (value) );
Hier sind die allgemeinen Regeln für dieses Vorgehen:
int main() { k_clear_screen(); k_printf(" ************************************************", 0); k_printf(" * *", 1); k_printf(" * Welcome to HenkesSoft OS. *", 2); k_printf(" * *", 3); k_printf(" * The C kernel has been loaded. *", 4); k_printf(" * *", 5); k_printf(" ************************************************", 6); update_cursor(8, 0); return 0; }; |
all: nasmw -O32 -f bin boot.asm -o boot.bin nasmw -O32 -f aout kernel.asm -o kernel.o gcc -c ckernel.c -o ckernel.o ld -T kernel.ld kernel.o ckernel.o -o ckernel.bin cmd /c copy /b boot.bin + ckernel.bin MyOS.bin partcopy MyOS.bin 0 800 -f0 del kernel.o del ckernel.o del ckernel.bin del boot.bin |
// Modul ckernel.c extern void k_clear_screen(); extern unsigned int k_printf(char* message, unsigned int line, char attribute); extern void outportb(unsigned int port,unsigned char value); extern void update_cursor(int row, int col); int main() { k_clear_screen(); k_printf(" ************************************************", 0, 0xA); k_printf(" * *", 1, 0xA); k_printf(" * Welcome to HenkesSoft OS. *", 2, 0xA); k_printf(" * *", 3, 0xA); k_printf(" * The C kernel has been loaded. *", 4, 0xA); k_printf(" * *", 5, 0xA); k_printf(" ************************************************", 6, 0xA); update_cursor(8, 0); return 0; }; |
// video.c void k_clear_screen() { unsigned long* vidmem = (unsigned long*) 0xb8000; unsigned long i=0; while(i<(40*25)) { vidmem[i] = 0x0A200A20; // intensive green on black, Space (0x20) ++i; }; }; unsigned int k_printf(char* message, unsigned int line, char attribute) { char* vidmem = (char*) 0xb8000; unsigned int i = line*80*2; while(*message!=0) { if(*message=='\n') // check for a new line { line++; i=(line*80*2); *message++; } else { vidmem[i]=*message; *message++; ++i; vidmem[i]=attribute; ++i; } }; return 1; }; inline void outportb(unsigned int port,unsigned char value) { asm volatile ("outb %%al,%%dx"::"d" (port), "a" (value)); }; void update_cursor(int row, int col) { unsigned short position=(row*80) + col; // cursor HIGH port to vga INDEX register outportb(0x3D4, 0x0E); outportb(0x3D5, (unsigned char)((position>>8)&0xFF)); // cursor LOW port to vga INDEX register outportb(0x3D4, 0x0F); outportb(0x3D5, (unsigned char)(position&0xFF)); }; |
all: nasmw -O32 -f bin boot.asm -o boot.bin nasmw -O32 -f aout kernel.asm -o kernel.o gcc -c ckernel.c -o ckernel.o gcc -c video.c -o video.o ld -T kernel.ld kernel.o ckernel.o video.o -o ckernel.bin --verbose cmd /c copy /b boot.bin + ckernel.bin MyOS.bin del video.o del kernel.o del ckernel.o del ckernel.bin del boot.bin partcopy MyOS.bin 0 800 -f0 |
objdump -D kernel.o >objdump_kernel_o_.txt objdump_kernel_o_text objdump -D ckernel.o >objdump_ckernel_o_.txt objdump_ckernel_o_.txt |
all: nasmw -O32 -f bin boot.asm -o boot.bin nasmw -O32 -f aout kernel.asm -o kernel.o gcc -c ckernel.c -o ckernel.o -O1 gcc -c video.c -o video.o -O1 ld -T kernel.ld kernel.o ckernel.o video.o -o ckernel.bin --verbose cmd /c copy /b boot.bin + ckernel.bin MyOS.bin del video.o del kernel.o del ckernel.o del ckernel.bin del boot.bin partcopy MyOS.bin 0 800 -f0 |
void k_itoa(int value, char* valuestring)
{ int tenth, min_flag; char swap, *p; min_flag = 0; if (0 > value) { *valuestring++ = '-'; value = -INT_MAX > value ? min_flag = INT_MAX : -value; } p = valuestring; do { tenth = value / 10; *p++ = (char)(value - 10 * tenth + '0'); value = tenth; } while (value != 0); if (min_flag != 0) { ++*valuestring; } *p-- = '\0'; while (p > valuestring) { swap = *valuestring; *valuestring++ = *p; *p-- = swap; } } void k_i2hex(unsigned int val, unsigned char* dest, int len) { unsigned char* cp; char x; unsigned int n; n = val; cp = &dest[len]; while (cp > dest) { x = n & 0xF; n >>= 4; *--cp = x + ((x > 9) ? 'A' - 10 : '0'); } return; } |
inline
unsigned inportb(unsigned port) { unsigned ret_val; asm volatile ("inb %w1,%b0" : "=a"(ret_val) : "d"(port)); return ret_val; } inline void outportb(unsigned port, unsigned val) { asm volatile ("outb %b0,%w1" : : "a"(val), "d"(port)); } |
Eine
PC Tastatur ist ein eigenständiges „Computer“ System, d.h. ein
Mikrocontroller
Chip überwacht (scan) ständig die Betätigung der Tasten. Durch diesen
Prozess
gehen durch die "Unabhängigkeit" der Tastatur keine Anschläge verloren,
nur weil
der Prozessor des PC beschäftigt ist. Die Programmlogik der Tastatur
kümmert
sich durch geeignete Scanzeiten (im 10 Millisekunden-Bereich) um das
Unterdrücken der Tastenprellung (keybounce). Zusätzlich muss auch das
andauernde Drücken einer Taste verarbeitet werden. Die noch heute
gültige Basis mit mindestens 101 verschiedenen Tasten ist das „IBM
PC/AT 101 Key
Enhanced Keyboard“.
ASCII
(American Standard Code
for Information Interchange) stellt einen 7-Bit-Code
(dezimal: 0 ...127) für Zeichen dar, der 1968 durch ANSI
standardisiert wurde. Nachfolgend eine kompakte tabellarische
Darstellung mit
Hexzahlen:
0 1 2 3 4 5 6 7 8 9 A B C D E F
--------------------------------------------------------------------
0 NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
1 DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US
2 SP ! " # $ % & ' ( ) * + , - . /
3 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
4 @ A B C D E F G H I J K L M N O
5 P Q R S T U V W X Y Z [ \ ] ^ _
6 ` a b c d e f g h i j k l m n o
7 p q r s t u v w x y z { | } ~ DEL
Der Großbuchstaben 'A' besitzt den ASCII 0x41, während der
Kleinbuchstabe 'a' mit 0x61 codiert ist.
Als Ergänzung zu diesen 128 grundlegenden Zeichen wurde später der
"Extended ASCII Character Set" geschaffen.
Dieser umfasst weitere 128 Zeichen (128 ... 255).
unsigned int FetchScancode() { return( inportb(0x60)); // port 0x60: get scan code from the keyboard } |
unsigned int FetchAndAnalyzeScancode() { unsigned int scancode; // For getting the keyboard raw scancode while(1) // Loop until a key to be pressed { // Wait for the key while ( !(inportb(0x64)&1) ); // 0x64: read keyboard µC status register scancode = FetchScancode(); if ( scancode & 0x80 ) // Key released? Check bit 7 (10000000b = 0x80) of scan code for this { scancode &= 0x7F; // Key was released, compare only low seven bits: 01111111b = 0x7F if ( scancode == KRLEFT_SHIFT || scancode == KRRIGHT_SHIFT ) // A key was released, shift key up? ShiftKeyDown = 0; // yes, it is up --> NonShift continue; // Loop } // Key was pressed. Capture scan code of shift key, if pressed if ( scancode == KRLEFT_SHIFT || scancode == KRRIGHT_SHIFT ) { ShiftKeyDown = 1; // It is down, use asciiShift characters continue; // Loop, so it will not return a scan code for the shift key } return scancode; } } |
#ifndef
KEYBOARD_H #define KEYBOARD_H #define NULL 0 #define ESC 27 #define BACKSPACE '\b' #define TAB '\t' #define ENTER '\n' #define RETURN '\r' #define NEWLINE ENTER // Non-ASCII special scancodes // Esc in scancode is 1 #define KESC 1 #define KF1 0x80 #define KF2 (KF1 + 1) #define KF3 (KF2 + 1) #define KF4 (KF3 + 1) #define KF5 (KF4 + 1) #define KF6 (KF5 + 1) #define KF7 (KF6 + 1) #define KF8 (KF7 + 1) #define KF9 (KF8 + 1) #define KF10 (KF9 + 1) #define KF11 (KF10 + 1) #define KF12 (KF11 + 1) // Cursor Keys #define KINS 0x90 #define KDEL (KINS + 1) #define KHOME (KDEL + 1) #define KEND (KHOME + 1) #define KPGUP (KEND + 1) #define KPGDN (KPGUP + 1) #define KLEFT (KPGDN + 1) #define KUP (KLEFT + 1) #define KDOWN (KUP + 1) #define KRIGHT (KDOWN + 1) // "Meta" keys #define KMETA_ALT 0x0200 // Alt is pressed #define KMETA_CTRL 0x0400 // Ctrl is pressed #define KMETA_SHIFT 0x0800 // Shift is pressed #define KMETA_ANY (KMETA_ALT | KMETA_CTRL | KMETA_SHIFT) #define KMETA_CAPS 0x1000 // CapsLock is on #define KMETA_NUM 0x2000 // NumLock is on #define KMETA_SCRL 0x4000 // ScrollLock is on // Other keys #define KPRNT ( KRT + 1 ) #define KPAUSE ( KPRNT + 1 ) #define KLWIN ( KPAUSE + 1 ) #define KRWIN ( KLWIN + 1 ) #define KMENU ( KRWIN + 1 ) #define KRLEFT_CTRL 0x1D #define KRRIGHT_CTRL 0x1D #define KRLEFT_ALT 0x38 #define KRRIGHT_ALT 0x38 #define KRLEFT_SHIFT 0x2A #define KRRIGHT_SHIFT 0x36 #define KRCAPS_LOCK 0x3A #define KRSCROLL_LOCK 0x46 #define KRNUM_LOCK 0x45 #define KRDEL 0x53 #define MAXKEYBUFFER 64 // max keyboard buffer // Keymaps: US International // Non-Shifted scan codes to ASCII: static unsigned char asciiNonShift[] = { NULL, ESC, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', BACKSPACE, TAB, 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', ENTER, 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, 0, 0, ' ', 0, KF1, KF2, KF3, KF4, KF5, KF6, KF7, KF8, KF9, KF10, 0, 0, KHOME, KUP, KPGUP,'-', KLEFT, '5', KRIGHT, '+', KEND, KDOWN, KPGDN, KINS, KDEL, 0, 0, 0, KF11, KF12 }; // Shifted scan codes to ASCII: static unsigned char asciiShift[] = { NULL, ESC, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', BACKSPACE, TAB, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', ENTER, 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '\"', '~', 0, '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, 0, 0, ' ', 0, KF1, KF2, KF3, KF4, KF5, KF6, KF7, KF8, KF9, KF10, 0, 0, KHOME, KUP, KPGUP, '-', KLEFT, '5', KRIGHT, '+', KEND, KDOWN, KPGDN, KINS, KDEL, 0, 0, 0, KF11, KF12 }; #endif |
extern
void k_clear_screen(); extern unsigned int k_printf(char* message, unsigned int line, char attribute); extern void update_cursor(int row, int col); extern unsigned char const k_getch(); extern void k_itoa(int value, char* valuestring); extern void k_i2hex(unsigned int val, unsigned char* dest, int len); int main() { k_clear_screen(); k_printf(" ************************************************", 0, 0xA); k_printf(" * *", 1, 0xA); k_printf(" * Welcome to HenkesSoft OS. *", 2, 0xA); k_printf(" * *", 3, 0xA); k_printf(" * The C kernel has been loaded. *", 4, 0xA); k_printf(" * *", 5, 0xA); k_printf(" ************************************************", 6, 0xA); update_cursor(8, 0); unsigned char KeyGot=0; char bufferKEY[10]; char bufferASCII[10]; char bufferASCII_hex[10]; int i; for(i=0;i<10000000;++i) { int j; for(j=0;j<1000000;++j)/*do nothing*/; KeyGot = k_getch(); // port 0x60 -> scancode + shift key -> ASCII bufferKEY[0] = KeyGot; k_itoa(KeyGot,bufferASCII); k_i2hex(KeyGot,bufferASCII_hex,2); k_clear_screen(); k_printf(bufferKEY, 0,0xA); // the ASCII character k_printf(bufferASCII, 1,0xA); // ASCII decimal k_printf(bufferASCII_hex,2,0xA); // ASCII hexadecimal }; return 0; }; |
00 | System Clock (Ticks alle 18,2 sec) |
01 | Tastatur |
02 | Programmierbarer Interrupt-Controller |
03 | Serielle Schnittstelle COM2 (E/A-Bereich 0x02F8) |
04 | Serielle Schnittstelle COM1 (E/A-Bereich 0x03F8) |
05 | Frei, oft Soundkarte bzw. LPT2 |
06 | Diskettenlaufwerk |
07 | Parallele Schnittstelle LPT1 (E/A-Bereich 0x0378) |
08 | Echtzeitsystemuhr |
09 | Frei |
10 | Frei |
11 | Frei |
12 | PS/2-Mouse |
13 | Mathematischer Coprozessor |
14 | Primärer IDE-Kanal |
15 | Sekundärer IDE-Kanal |
/*
Message
string
corresponding
to
the exception number 0-31:
exception_messages[interrupt_number] */ unsigned char* exception_messages[] = { "Division By Zero", "Debug", "Non Maskable Interrupt", "Breakpoint", "Into Detected Overflow", "Out of Bounds", "Invalid Opcode", "No Coprocessor", "Double Fault", "Coprocessor Segment Overrun", "Bad TSS", "Segment Not Present", "Stack Fault", "General Protection Fault", "Page Fault", "Unknown Interrupt", "Coprocessor Fault", "Alignment Check", "Machine Check", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved" }; |
//
IDT
entry struct idt_entry { unsigned short base_lo; unsigned short sel; unsigned char always0; unsigned char flags; unsigned short base_hi; }__attribute__((packed)); //prevent compiler optimization struct idt_ptr { unsigned short limit; unsigned int base; }__attribute__((packed)); //prevent compiler optimization // Declare an IDT of 256 entries and a pointer to the IDT struct idt_entry idt[256]; struct idt_ptr idt_register; idt_register.limit = (sizeof (struct idt_entry) * 256)-1; idt_register.base = (unsigned int) &idt; k_memset(&idt, 0, sizeof(struct idt_entry) * 256); // Clear out the entire IDT static void idt_load(){ asm volatile("lidt %0" : "=m" (idt_register)); } // load IDT register (IDTR) |
Bit |
Meaning |
0 |
1 |
7 |
P (Present Bit) | Descriptor
is
undefined |
Descriptor
contains
a
valid
base
and limit |
6 |
DPL
(High) |
see below | see below |
5 |
DPL
(Low) |
see below | see below |
4 |
S (Segment Bit) | System
Descriptor |
Code,
Data
or
Stack
Descriptor |
3 |
Type |
see below |
see below |
2 |
Type |
see below | see below |
1 |
Type |
see below | see below |
0 |
Type | see below | see below |
Type (in Flags) |
Bedeutung |
---|---|
0000b | |
0001b | 80286-TSS |
0010b | Local Descriptor Table (LDT) |
0011b | aktives 80286-TSS |
0100b | 80286 Call Gate |
0101b | Task Gate |
0110b | 80286 Interrupt Gate |
0111b | 80286 Trap Gate |
1000b | reserviert |
1001b | 80386-TSS |
1010b | reserviert |
1011b | aktives 80386-TSS |
1100b | 80386 Call Gate |
1101b | reserviert |
1110b | 80386 Interrupt Gate |
1111b | 80386 Trap Gate |
static void
idt_load(){ asm volatile("lidt %0" : "=m" (idt_register)); } // load IDT register (IDTR) |
//
Put
an
entry
into
the IDT void idt_set_gate(unsigned char num, unsigned long base, unsigned short sel, unsigned char flags) { idt[num].base_lo = (base & 0xFFFF); idt[num].base_hi = (base >> 16) & 0xFFFF; idt[num].sel = sel; idt[num].always0 = 0; idt[num].flags = flags; } |
/*
Array
of
function
pointers
handling custom IRQ handlers for a given IRQ
*/ void* irq_routines[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* Implement a custom IRQ handler for the given IRQ */ void irq_install_handler(int irq, void (*handler)(struct regs* r)) {irq_routines[irq] = handler;} /* Clear the custom IRQ handler */ void irq_uninstall_handler(int irq) {irq_routines[irq] = 0;} |
|
Command |
Data
|
|
PIC1 |
Master |
0x20 |
0x21 |
PIC2 | Slave |
0xA0 |
0xA1 |
Master
PIC |
0 to 7 | 0x08
-
0x0F |
Slave PIC | 8
to 15 |
0x70 - 0x77 |
/*
Remap:
IRQ0
to
IRQ15
have to be remapped to IDT entries 32 to 47 */ void irq_remap(void) { // starts the initialization sequenceoutportb(0x20, 0x11); outportb(0xA0, 0x11); // define the PIC vectors outportb(0x21, 0x20); outportb(0xA1, 0x28); // continue initialization sequence outportb(0x21, 0x04); outportb(0xA1, 0x02); outportb(0x21, 0x01); outportb(0xA1, 0x01); outportb(0x21, 0x00); outportb(0xA1, 0x00);
|
/*
After
remap
of
the
interrupt controllers the appropriate ISRs are
connected to the correct entries in the IDT. */ void irq_install() { irq_remap(); idt_set_gate(32, (unsigned) irq0, 0x08, 0x8E); idt_set_gate(33, (unsigned) irq1, 0x08, 0x8E); idt_set_gate(34, (unsigned) irq2, 0x08, 0x8E); idt_set_gate(35, (unsigned) irq3, 0x08, 0x8E); idt_set_gate(36, (unsigned) irq4, 0x08, 0x8E); idt_set_gate(37, (unsigned) irq5, 0x08, 0x8E); idt_set_gate(38, (unsigned) irq6, 0x08, 0x8E); idt_set_gate(39, (unsigned) irq7, 0x08, 0x8E); idt_set_gate(40, (unsigned) irq8, 0x08, 0x8E); idt_set_gate(41, (unsigned) irq9, 0x08, 0x8E); idt_set_gate(42, (unsigned) irq10, 0x08, 0x8E); idt_set_gate(43, (unsigned) irq11, 0x08, 0x8E); idt_set_gate(44, (unsigned) irq12, 0x08, 0x8E); idt_set_gate(45, (unsigned) irq13, 0x08, 0x8E); idt_set_gate(46, (unsigned) irq14, 0x08, 0x8E); idt_set_gate(47, (unsigned) irq15, 0x08, 0x8E); } |
/* EOI command to the
controllers. If you don't send them, any more IRQs cannot be raised */ void irq_handler(struct regs* r) { /* This is a blank function pointer */ void (*handler)(struct regs* r); /* Find out if we have a custom handler to run for this IRQ, and then finally, run it */ handler = irq_routines[r->int_no - 32]; if (handler) { handler(r); } /* If the IDT entry that was invoked was greater than 40 (IRQ8 - 15), * then we need to send an EOI to the slave controller */ if (r->int_no >= 40) { outportb(0xA0, 0x20); } /* In either case, we need to send an EOI to the master interrupt controller too */ outportb(0x20, 0x20); } |
; Interrupt Service Routine isr0 ...
isr32 global _isr0 ... global _isr31 ; 0: Divide By Zero Exception _isr0: cli push byte 0 push byte 0 jmp isr_common_stub ... ; 31: Reserved _isr31: cli push byte 0 push byte 31 jmp isr_common_stub ; Call of the C function fault_handler(...) extern _fault_handler ; Common ISR stub saves processor state, sets up for kernel mode segments, ; calls the C-level fault handler, and finally restores the stack frame. isr_common_stub: pusha push ds push es push fs push gs mov ax, 0x10 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov eax, esp push eax mov eax, _fault_handler call eax pop eax pop gs pop fs pop es pop ds popa add esp, 8 iret global _irq0 ... global _irq15 ; 32: IRQ0 _irq0: cli push byte 0 push byte 32 jmp irq_common_stub ... ; 47: IRQ15 _irq15: cli push byte 0 push byte 47 jmp irq_common_stub extern _irq_handler irq_common_stub: pusha push ds push es push fs push gs mov ax, 0x10 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov eax, esp push eax mov eax, _irq_handler call eax pop eax pop gs pop fs pop es pop ds popa add esp, 8 iret |
#include
"os.h" unsigned long timer_ticks = 0; unsigned long eticks; void timer_handler(struct regs* r) { ++timer_ticks; if (eticks) --eticks; //TEST char bufferTimerticks[20]; k_itoa (timer_ticks, bufferTimerticks); k_printf(" ", 6, 0x0B); k_printf(bufferTimerticks, 6, 0x0B); char bufferWaitticks[20]; k_itoa (eticks, bufferWaitticks); k_printf(" ", 7, 0x0B); k_printf(bufferWaitticks, 7, 0x0B); //TEST } void timer_wait (unsigned long ticks) { timer_uninstall(); eticks = ticks; timer_install(); // busy wait... while (eticks) { k_printf("waiting time runs", 8, 0x0B); /* do nothing */; }; k_printf("waiting time has passed", 9, 0x0B); } void sleepSeconds (unsigned long seconds) { // based upon timer tick frequence of 18.222 Hz timer_wait((unsigned long)18.222*seconds); } void timer_install() { /* Enable 'timer_handler' by IRQ0 */ irq_install_handler(0, timer_handler); } void timer_uninstall() { /* Disable 'timer_handler' by IRQ0 */ irq_uninstall_handler(0); } |
#include
"keyboard.h" #include "os.h" int ShiftKeyDown; // Variable for Shift Key Down /* Wait until buffer is empty */ void keyboard_init() { while (inportb(0x64)&1) inportb(0x60); }; unsigned int FetchAndAnalyzeScancode() { unsigned int scancode; // For getting the keyboard scancode while(TRUE) // Loop until a key (w/o shift key) has been pressed { scancode = inportb(0x60); // 0x60: get scan code from the keyboard // ACK: toggle bit 7 at port 0x61 unsigned char port_value = inportb(0x61); outportb(0x61,port_value | 0x80); // 0->1 outportb(0x61,port_value &~ 0x80); // 1->0 if ( scancode & 0x80 ) // Key released? Check bit 7 (10000000b = 0x80) of scan code for this { scancode &= 0x7F; // Key was released, compare only low seven bits: 01111111b = 0x7F if ( scancode == KRLEFT_SHIFT || scancode == KRRIGHT_SHIFT ) // A key was released, shift key up? { ShiftKeyDown = 0; // yes, it is up --> NonShift } } else // Key was pressed { // Capture scan code of shift key, if pressed if ( scancode == KRLEFT_SHIFT || scancode == KRRIGHT_SHIFT ) { ShiftKeyDown = 1; // It is down, use asciiShift characters continue; // Loop, so it will not return a scan code for the shift key } } break; // Leave the loop } return scancode; } |
#include "os.h" #include <stdarg.h> UCHAR csr_x = 0; UCHAR csr_y = 0; UCHAR saved_csr_x = 0; UCHAR saved_csr_y = 0; UCHAR attrib = 0x0F; USHORT* vidmem = (USHORT*) 0xb8000; void k_clear_screen() { k_memsetw (vidmem, 0x20 | (attrib << 8), 80 * 25); csr_x = 0; csr_y = 0; update_cursor(); } void settextcolor(UCHAR forecolor, UCHAR backcolor) { // Top 4 bytes: background, bottom 4 bytes: foreground color attrib = (backcolor << 4) | (forecolor & 0x0F); } void move_cursor_right() { ++csr_x; if(csr_x>=80) { ++csr_y; csr_x=0; } } void move_cursor_left() { if(csr_x) --csr_x; if(!csr_x && csr_y>0) { csr_x=79; --csr_y; } } void move_cursor_home() { csr_x = 0; update_cursor(); } void move_cursor_end() { csr_x = 79; update_cursor(); } void set_cursor(UCHAR x, UCHAR y) { csr_x = x; csr_y = y; update_cursor(); } void update_cursor() { USHORT position = csr_y * 80 + csr_x; // cursor HIGH port to vga INDEX register outportb(0x3D4, 0x0E); outportb(0x3D5, (UCHAR)((position>>8)&0xFF)); // cursor LOW port to vga INDEX register outportb(0x3D4, 0x0F); outportb(0x3D5, (UCHAR)(position&0xFF)); }; void putch(UCHAR c) { USHORT* pos; UINT att = attrib << 8; if(c == 0x08) // backspace: move the cursor back one space and delete { if(csr_x) { --csr_x; putch(' '); --csr_x; } if(!csr_x && csr_y>0) { csr_x=79; --csr_y; putch(' '); csr_x=79; --csr_y; } } else if(c == 0x09) // tab: increment csr_x (divisible by 8) { csr_x = (csr_x + 8) & ~(8 - 1); } else if(c == '\r') // cr: cursor back to the margin { csr_x = 0; } else if(c == '\n') // newline: like 'cr': cursor to the margin and increment csr_y { csr_x = 0; ++csr_y; } /* Any character greater than and including a space, is a printable character. * Index = [(y * width) + x] */ else if(c >= ' ') { pos = vidmem + (csr_y * 80 + csr_x); *pos = c | att; // Character AND attributes: color ++csr_x; } if(csr_x >= 80) // cursor reaches edge of the screen's width, a new line is inserted { csr_x = 0; ++csr_y; } /* Scroll the screen if needed, and finally move the cursor */ scroll(); update_cursor(); } void puts(UCHAR* text) { for(; *text; putch(*text), ++text); } void scroll() { UINT blank, temp; blank = 0x20 | (attrib << 8); if(csr_y >= 25) { temp = csr_y - 25 + 1; k_memcpy (vidmem, vidmem + temp * 80, (25 - temp) * 80 * 2); k_memsetw (vidmem + (25 - temp) * 80, blank, 80); csr_y = 25 - 1; } } void k_printf(UCHAR* message, UINT line, UCHAR attribute) { // Top 4 bytes: background, bottom 4 bytes: foreground color settextcolor(attribute & 0x0F, attribute >> 4); csr_x = 0; csr_y = line; update_cursor(); puts(message); }; /* Lean version of printf: printformat(...): supports %u, %d, %x/%X, %s, %c */ void printformat (char *args, ...) { va_list ap; va_start (ap, args); int index = 0, d; UINT u; char c, *s; char buffer[100]; while (args[index]) { switch (args[index]) { case '%': ++index; switch (args[index]) { case 'u': u = va_arg (ap, UINT); k_itoa(u, buffer); puts(buffer); break; case 'd': case 'i': d = va_arg (ap, int); k_itoa(d, buffer); puts(buffer); break; case 'X': case 'x': d = va_arg (ap, int); k_i2hex(d, buffer,8); puts(buffer); break; case 's': s = va_arg (ap, char*); puts(s); break; case 'c': c = (char) va_arg (ap, int); putch(c); break; default: putch('%'); putch('%'); break; } break; default: putch(args[index]); //printf("%c",*(args+index)); break; } ++index; } } void save_cursor() { cli(); saved_csr_x = csr_x; saved_csr_y = csr_y; sti(); } void restore_cursor() { cli(); csr_x = saved_csr_x; csr_y = saved_csr_y; sti(); } |
#include
"keyboard.h" #include "os.h" UCHAR ShiftKeyDown = 0; // Variable for Shift Key Down UCHAR KeyPressed = 0; // Variable for Key Pressed UCHAR scan = 0; // Scan code from Keyboard /* Wait until buffer is empty */ void keyboard_init() { while( inportb(0x64)&1 ) inportb(0x60); }; UCHAR FetchAndAnalyzeScancode() { if( inportb(0x64)&1 ) scan = inportb(0x60); // 0x60: get scan code from the keyboard // ACK: toggle bit 7 at port 0x61 UCHAR port_value = inportb(0x61); outportb(0x61,port_value | 0x80); // 0->1 outportb(0x61,port_value &~ 0x80); // 1->0 if( scan & 0x80 ) // Key released? Check bit 7 (10000000b = 0x80) of scan code for this { KeyPressed = 0; scan &= 0x7F; // Key was released, compare only low seven bits: 01111111b = 0x7F if( scan == KRLEFT_SHIFT || scan == KRRIGHT_SHIFT ) // A key was released, shift key up? { ShiftKeyDown = 0; // yes, it is up --> NonShift } } else // Key was pressed { KeyPressed = 1; if( scan == KRLEFT_SHIFT || scan == KRRIGHT_SHIFT ) { ShiftKeyDown = 1; // It is down, use asciiShift characters } } return scan; } UCHAR k_getch() // Scancode --> ASCII { UCHAR retchar; // The character that returns the scan code to ASCII code scan = FetchAndAnalyzeScancode(); // Grab scancode, and get the position of the shift key if( ShiftKeyDown ) retchar = asciiShift[scan]; // (Upper) Shift Codes else retchar = asciiNonShift[scan]; // (Lower) Non-Shift Codes if( ( !(scan == KRLEFT_SHIFT || scan == KRRIGHT_SHIFT) ) && ( KeyPressed == 1 ) ) //filter Shift Key and Key Release return retchar; // ASCII version else return 0; } void keyboard_handler(struct regs* r) { UCHAR KEY; KEY = k_getch(); restore_cursor(); switch(KEY) { case KINS: break; case KDEL: move_cursor_right(); putch('\b'); //BACKSPACE break; case KHOME: move_cursor_home(); break; case KEND: move_cursor_end(); break; case KPGUP: break; case KPGDN: break; case KLEFT: move_cursor_left(); break; case KUP: break; case KDOWN: break; case KRIGHT: move_cursor_right(); break; default: printformat("%c",KEY); // the ASCII character break; } save_cursor(); } void keyboard_install() { /* Installs 'keyboard_handler' to IRQ1 */ irq_install_handler(1, keyboard_handler); keyboard_init(); } |
#include
"os.h" ULONG const FREQ = 100; // 100 "ticks" per second ULONG timer_ticks = 0; ULONG eticks; void timer_handler(struct regs* r) { ++timer_ticks; if (eticks) --eticks; } void timer_wait (ULONG ticks) { timer_uninstall(); eticks = ticks; timer_install(); // busy wait... while (eticks) { update_cursor(); } } void sleepSeconds (ULONG seconds) { timer_wait(FREQ*seconds); } void sleepMilliSeconds (ULONG ms) { timer_wait(FREQ * ms/1000UL); } static void systemTimer_setFrequency( ULONG freq ) { ULONG divisor = 1193180 / freq; //divisor must fit into 16 bits // Send the command byte outportb(0x43, 0x36); // Send divisor outportb(0x40, (UCHAR)( divisor & 0xFF )); // low byte outportb(0x40, (UCHAR)( (divisor>>8) & 0xFF )); // high byte } void timer_install() { /* Installs 'timer_handler' to IRQ0 */ irq_install_handler(0, timer_handler); systemTimer_setFrequency( FREQ ); // FREQ Hz, meaning a tick every 1000/FREQ milliseconds } void timer_uninstall() { /* Uninstalls IRQ0 */ irq_uninstall_handler(0); } |