Lowlevel
Lowlevel => OS-Design => Thema gestartet von: TheThing am 19. March 2009, 12:15
-
Hi,
hab gestern versucht (Hardware-)Multitasking in meinen Kernel einzubauen, was in einem GPF geendet hat. Dann hab ich gelesen, das im Long-Mode Hardware-Multitasking sowieso nicht mehr geht.
Mein Problem ist, das ich KEINE Ahnung hab, wie man software-multitasking implementiert.
Software-Multitasking funktioniert (wenn ich das richtig verstanden habe) so:
Task1 wird von Timer unterbrochen
Kernel pusht Daten (Register usw.) auf den Stack von Task1
Kernel läd Stack von Task2
Kernel holt Daten vom Stack
So, wie springt jetzt aber der Kernel zurück zum Task?
Habt ihr da Tutorials, code oder so was für mich?
-
Das machst du dann durch iret.
Du unterbrichst den Prozess durch einen IRQ und springst damit durch iret zurück
-
Du musst darauf achten, dass du beim Laden der Daten vom Stack des 2. Tasks zuletzt die Rücksprungadresse (+ segment glaub ich) auf den Stack legst, damit iret funktioniert
-
schonmal danke für die Antworten, aber ich hab noch en paar Fragen:
Was mache ich wenn der Task das erste Mal gestartet werden soll?
Könnt ihr mir evtl. sagen wo ich Beispielcode finde?
-
Da gibt es mehrere Möglichkeiten.
Das einfachste wäre, wenn du elf-Dateien benutzt.
Am besten du schaust dir im Coding-Bereich meinen Thread an.
Da habe ich die selben Fragen gehabt, da ich auch gerade Multitasking einbaue.
-
ELF hat damit erstmal nichts zu tun, das ist ja nur ein Binärformat, in dem man Programme ablegen kann. Ein Task kann auch ein Kernelthread sein, also letztlich einfach eine Funktion im Kernel. Genau so wird Multitasking ja auch meistens zuerst getestet: Zwei Kernelthreads geben jeweils 'a' oder 'b' aus und in der Ausgabe sollte dann ein Wechsel sichtbar sein.
Was hier beim Multitasking an sich beim ersten Start gilt, ist, daß man den Stack genau so vorbereitet, wie er aussehen würde, wenn der laufende Task unterbrochen worden wäre. Statt den dann gesicherten Werten gibt man beim ersten Start eben den Anfangszustand rein.
Quellcode gibt es wie immer im tyndur-Repository (http://git.tyndur.org/?p=tyndur.git;a=blob;f=src/kernel2/src/tasks/thread.c;h=d869595c016c55abaa71e7ad659a9b9c8cddec72;hb=HEAD). ;)
-
danke :-)
hm, C <ironie>meine Lieblingssprache</ironie>
ich glaub das ich das irgendwie hinkrieg ;)
-
Ich habe die Funktion task switch (siehe Kap. 9, James Molloy) eingebaut. Klappt auch gut, wenn ich an der richtigen Stelle im Code umschalte. Erledige ich das via System Timer Handler, also an rein zufälliger Stelle, so erhalte ich einen General Protection Fault. Wie muss der Scheduler arbeiten, um dies zu vermeiden? Gibt es da ein Vorbild?
-
Dann machst du bei der Interruptverarbeitung was falsch. Der Userspace-Prozeß sollte an jeder beliebigen Stelle unterbrochen werden können. Wenn du eine bestimmte Stelle zum Umschalten brauchst, deutet das darauf hin, daß du den Zustand des Prozesses in deinem Interrupthandler nicht richtig oder nicht vollständig speicherst.
Du kannst dir mal den Interrupthandler-Stub (http://git.tyndur.org/?p=tyndur.git;a=blob;f=src/kernel2/src/arch/i386/interrupts/int_stubs.S;h=2672f3ea8bac614ae86ffae2f4043b0444a24259;hb=HEAD) von tyndur anschauen. Der sichert den gesamten Zustand auf den Stack und ruft anschließend den eigentlichen Interrupthandler in C auf.
-
Danke für den Hinweis. So sieht der task_switch aus:
void task_switch()
{
if (!current_task) return;
ULONG esp, ebp, eip;
asm volatile("mov %%esp, %0" : "=r"(esp));
asm volatile("mov %%ebp, %0" : "=r"(ebp));
eip = read_eip();
if (eip == 0x12345) return;
current_task->eip = eip;
current_task->esp = esp;
current_task->ebp = ebp;
current_task = current_task->next;
if (!current_task) current_task = ready_queue;
eip = current_task->eip;
esp = current_task->esp;
ebp = current_task->ebp;
current_directory = current_task->page_directory;
set_kernel_stack(current_task->kernel_stack+KERNEL_STACK_SIZE);
asm volatile(" \
cli; \
mov %0, %%ecx; \
mov %1, %%esp; \
mov %2, %%ebp; \
mov %3, %%cr3; \
mov $0x12345, %%eax; \
sti; \
jmp *%%ecx "
: : "r"(eip), "r"(esp), "r"(ebp), "r"(current_directory->physicalAddr));
}
-
Den eigentlichen Taskswitch solltest du nicht in C machen, Assembler ist besser geeignet. Der richtige Ort dafür sind die Interrupthandler. Der Interrupthandler speichert den alten Zustand auf den Stack (bevor C-Funktionen die Register verändern), ruft Kernelcode für das Interrupthandling auf (der dann möglicherweise den aktuellen Task ändert), stellt den Zustand des jetzt aktuellen Tasks wieder her und springt zurück. Jeder Task hat seinen eigenen Kernelstack, so daß der Zustand immer auf diesen Stack liegen bleibt und erst wieder abgebaut und geladen ist, wenn tatsächlich in diesen Task gesprungen werden soll.
Hast du dir den verlinkten tyndur-Code angeschaut? Der macht genau das.
Edit: Was man bei deinem Code noch erwähnen sollte, ist vielleicht, daß der Zustand eines Tasks aus mehr als eip und esp besteht. Da gehören wirklich alle Register dazu. Wenn die nicht gesichert werden, passieren interessante Dinge, aber sicher nicht, was man möchte.
-
Hast du dir den verlinkten tyndur-Code angeschaut? Der macht genau das.
Ja, das klingt überzeugend. Ich habe mich an James Molloy's Code gehalten, da mir C leichter fällt, leider hört er dort auf, wo die Probleme anfangen. Daher kann ich nicht beurteilen, inwieweit sein Code wirklich brauchbar ist. Wie gesagt bisher GPF im Timer Handler. Werde mir Tyndur nun mal genauer anschauen.
Du kannst dir mal den Interrupthandler-Stub von tyndur anschauen. Der sichert den gesamten Zustand auf den Stack und ruft anschließend den eigentlichen Interrupthandler in C auf.
Ich habe auch einen Interrupthandler in asm, der C ISR aufruft. Einen Software Interrupt habe ich bisher nur für system calls eingesetzt. Welchen interrupt verwendet tyndur für das Schalten der Tasks, oder läuft das beim Timer Handler (IRQ0) mit?
-
Das läuft normal über den Timer. Oder der Task kann natürlich vorzeitig über einen Syscall abgeben.
-
Ich habe den Code in task_switch mit putch('!'); gecheckt. Der Fehler liegt im hinteren Teil im asm-code. Dort tritt der GPF auf.
-
Mir ist dieser ganze Ansatz sehr suspekt. Ob das ganze möglicherweise funktionieren kann, hängt wohl auch davon ab, wo task_switch() aufgerufen wird. Es sollte wohl direkt von einem Interrupthandler sein. Kannst du ein bißchen mehr Code herzeigen?
-
void timer_handler(struct regs* r)
{
++timer_ticks;
if (eticks)
--eticks;
//TEST
static ULONG c = 0;
++c;
if(c>200)
{
task_switch();
settextcolor(getpid(),0);
c=0;
}
//TEST
}
das ist der Code im timer handler, der bei IRQ0 angestoßen wird. Ändert man den Wert von c:
Manchmal gelingt ein task_switch, manchmal keiner. Mal wird mit GPF beendet, mal mit Absturz ohne Fehlermeldung. Also echt chaotisches Verhalten.
-
Hm, gut, theoretisch könnte das tun. Voraussetzung ist, daß kein Register das asm() in task_switch() überlebt, sondern gcc alles auf den Stack packt. Du könntest das vielleicht erreichen, indem du die nötigen Register in die Clobber-Liste reinnimmt. Ich bin mir im Moment selbst nicht sicher, welche das alles sind, ebx dürfte eins davon sein.
Ich glaube aber wirklich, daß du dir einen Gefallen tust, wenn du es ähnlich wie in tyndur machst. Dort gibt es viel weniger Dinge, die man beim Sichern der Register beachten muß, weil kein C-Compiler reinspielt.
-
indem du die nötigen Register in die Clobber-Liste reinnimmt.
Clobber-Liste, noch nie gehört. Hier kann man ja richtig was lernen. :-D
Ich habe das gefunden:
asm volatile ( Anweisungen : Outputs : Inputs : Clobbers)
Wenn ich das richtig sehe, ist die Clobbers-Liste z.Z. leer:
asm volatile(" ..." : : "r"(eip), "r"(esp), "r"(ebp), "r"(current_directory->physicalAddr));
Wie muss das genau aussehen hinter einem dritten Doppelpunkt? kenne mich da leider gar nicht gut aus mit der Syntax.
Ich habe mal vor dem Assemblercode in task_switch bochs abgefangen mit:
/// TEST
cli();
asm volatile("hlt;");
for(;;);
/// TEST
asm volatile(" \
01368000000p[WGUI ] >>PANIC<< POWER button turned off.
01368000000i[CPU0 ] CPU is in protected mode (halted)
01368000000i[CPU0 ] CS.d_b = 32 bit
01368000000i[CPU0 ] SS.d_b = 32 bit
01368000000i[CPU0 ] EFER = 0x00000000
01368000000i[CPU0 ] | RAX=000000000000008f RBX=000000000000d502
01368000000i[CPU0 ] | RCX=00000000000b8000 RDX=00000000000003d5
01368000000i[CPU0 ] | RSP=000000000018ff1c RBP=000000000018ff44
01368000000i[CPU0 ] | RSI=000000000018ffd8 RDI=000000000018fff0
01368000000i[CPU0 ] | R8=0000000000000000 R9=0000000000000000
01368000000i[CPU0 ] | R10=0000000000000000 R11=0000000000000000
01368000000i[CPU0 ] | R12=0000000000000000 R13=0000000000000000
01368000000i[CPU0 ] | R14=0000000000000000 R15=0000000000000000
01368000000i[CPU0 ] | IOPL=0 id vip vif ac vm rf nt of df if tf sf zf af pf cf
01368000000i[CPU0 ] | SEG selector base limit G D
01368000000i[CPU0 ] | SEG sltr(index|ti|rpl) base limit G D
01368000000i[CPU0 ] | CS:0008( 0001| 0| 0) 00000000 000fffff 1 1
01368000000i[CPU0 ] | DS:0010( 0002| 0| 0) 00000000 000fffff 1 1
01368000000i[CPU0 ] | SS:0010( 0002| 0| 0) 00000000 000fffff 1 1
01368000000i[CPU0 ] | ES:0010( 0002| 0| 0) 00000000 000fffff 1 1
01368000000i[CPU0 ] | FS:0010( 0002| 0| 0) 00000000 000fffff 1 1
01368000000i[CPU0 ] | GS:0010( 0002| 0| 0) 00000000 000fffff 1 1
01368000000i[CPU0 ] | MSR_FS_BASE:0000000000000000
01368000000i[CPU0 ] | MSR_GS_BASE:0000000000000000
01368000000i[CPU0 ] | RIP=000000000000d3f9 (000000000000d3f9)
01368000000i[CPU0 ] | CR0=0xe0000011 CR1=0x0 CR2=0x0000000000000000
01368000000i[CPU0 ] | CR3=0x00306000 CR4=0x00000000
01368000000i[CPU0 ] >> add esp, 0x00000010 : 83C410
01368000000i[CMOS ] Last time is 1240781579 (Sun Apr 26 23:32:59 2009)
01368000000i[ ] restoring default signal behavior
01368000000i[CTRL ] quit_sim called with exit code 1
Das stand dann in bochsout.txt.
-
Clobber-Liste, noch nie gehört. Hier kann man ja richtig was lernen. :-D
Das ist Sinn und Zweck des Forums. ;)
Ich habe das gefunden:
asm volatile ( Anweisungen : Outputs : Inputs : Clobbers)
Wenn ich das richtig sehe, ist die Clobbers-Liste z.Z. leer:
asm volatile(" ..." : : "r"(eip), "r"(esp), "r"(ebp), "r"(current_directory->physicalAddr));
Wie muss das genau aussehen hinter einem dritten Doppelpunkt? kenne mich da leider gar nicht gut aus mit der Syntax.
Genau, und dahinter die Registernamen, die geclobbert werden. Also ungefähr so: asm("" : : : "ebx", "esi", "edi", "ebp");
01368000000p[WGUI ] >>PANIC<< POWER button turned off.
Ich benutze ja eher qemu als bochs und kenne daher die typischen Meldungen nicht. Aber für mich klingt das nach ganz normalem Ausschalten der VM und nicht nach Fehlermeldung.
-
Aber für mich klingt das nach ganz normalem Ausschalten der VM und nicht nach Fehlermeldung.
Ja, genau. Das ist vor dem Fehler.
can't find a register in class `GENERAL_REGS' while reloading `asm'
asm volatile(" \
cli; \
mov %0, %%ecx; \
mov %1, %%esp; \
mov %2, %%ebp; \
mov %3, %%cr3; \
mov $0x12345, %%eax; \
sti; \
jmp *%%ecx "
: : "r"(eip), "r"(esp), "r"(ebp), "r"(current_directory->physicalAddr) : "ebx", "edx", "esi", "edi" );
Ich verwende gcc.exe (Version 3.1) und ld.exe (Version 2.13) wegen Linkens des aout-Formats von NASM.
Mit : "ebx", "edx" ); compiliert es, gibt aber wieder Absturz.
esi oder edi mag er nicht an dieser Stelle. :-D
-
Nun bekomme ich nur noch Page Faults. Offenbar erhält der EIP unter gewissen Umständen einen falschen Wert.
Erster Task Switch nach 20 sec (eingestellt im Time Handler):
Page Fault (page not present) at 0040FC20h - EIP: 0040FC20h
Sehr viele Task switches pro Sekunde:
Page Fault (page not present) at 0F000123h - EIP: 0F000123h
Aber immerhin. Offenbar hat die Clobber List den GPF gegen PF getauscht. :-)
Funktion read_eip() ist in Assembler codiert (process.asm):
global _read_eip
_read_eip:
pop eax ; Get the return address
jmp eax ; Return. Can't use RET because return
; address popped off the stack.
Vielleicht liegt hier der Hund begraben.
Ich habe diese gegen
global _read_eip
_read_eip:
pop eax
push eax
ret
ausgetauscht. Sieht irgendwie besser aus, hilft aber nicht. :-P
-
can't find a register in class `GENERAL_REGS' while reloading `asm'
Ich verwende gcc.exe (Version 3.1) und ld.exe (Version 2.13) wegen Linkens des aout-Formats von NASM.
Kann sein, daß er esi und edi schon benutzt, um deine Variablen zwischenzuspeichern. Durch das "g" ist das alles etwas unbestimmt, nichts genaues weiß man nicht. Du kannst jetzt entweder versuchen, deinen Kernel hinterher zu disassemblieren und dir mal genau anzuschauen, ob der Zustand komplett und korrekt gespeichert wird. Oder du wirfst dieses Modell über den Haufen und machst den Switch im Assembler-Stub wie ich dir seit einigen Posts vorschlage. ;)
-
Ich bin didaktisch interessiert, daher muss ich erst verstehen, was überhaupt falsch läuft. Clobbering ebx und edx, das leider noch zum Page Fault führt, gibt im objdump einen Hinweis, warum man nicht auch edi und esi clobbern kann:
35f: fa cli
360: 89 f1 mov %esi,%ecx
362: 89 fc mov %edi,%esp
364: 89 cd mov %ecx,%ebp
366: 0f 22 d8 mov %eax,%cr3
369: b8 45 23 01 00 mov $0x12345,%eax
36e: fb sti
36f: ff e1 jmp *%ecx
-
Erster Task Switch nach 20 sec (eingestellt im Time Handler):
Page Fault (page not present) at 0040FC20h - EIP: 0040FC20h
Sollte denn diese Adresse gemappt sein?
-
Nicht mehr genau dieses Programm:
002E0000h 11111111111111111111111111111111
00300000h 11111111111111111111111111111111
00320000h 11111111111111111111111111111111
00340000h 11111111111111111111111111111111
00360000h 11111111111111111111111111111111
00380000h 11111111111111111111111111111111
003A0000h 11111111111111111111111111111111
003C0000h 11111111111111111111111111111111
003E0000h 11111111111111111111111111111111
00400000h 11111111111111111111111111111111
00420000h 11111111100000000000000000000000
00440000h 00000000000000000000000000000000
00460000h 00000000000000000000000000000000
00480000h 00000000000000000000000000000000
004A0000h 00000000000000000000000000000000
004C0000h 00000000000000000000000000000000
004E0000h 00000000000000000000000000000000
Ich denke ja. :?
Im Timer Handler komme ich noch nicht klar damit. Um zu verstehen, was wirklich wichtig ist, habe ich task_switch() an eine Stelle in einer while-Schleife im Hauptprogramm verlagert, in der es "stabil" nach jedem sechsten Tastenanschlag umschaltet (ich benutze den PID als Farbgeber).
Ohne Clobbern geht das. Sobald ich aber ebx und edx clobbere, gibt es einen PF:
task_switch before asm: eip: 0000D4AFh esp: 0018FFD8h ebp: 0018FFF0h cur_dir->phys: 0041E000h
Page Fault (page not present) at 0040FCA0h - EIP: 0040FCA0h
-
clobbering ebx and edx ==> Page Fault:
asm volatile(" \
cli; \
mov %0, %%ecx; \
mov %1, %%esp; \
mov %2, %%ebp; \
mov %3, %%cr3; \
mov $0x12345, %%eax; \
sti; \
jmp %%ecx; "
: : "r"(eip), "r"(esp), "r"(ebp), "r"(current_directory->physicalAddr)
: "ebx","edx" );
objdump task.o -D > task.txt
35f: fa cli
360: 89 f1 mov %esi,%ecx
362: 89 fc mov %edi,%esp
364: 89 cd mov %ecx,%ebp
366: 0f 22 d8 mov %eax,%cr3
369: b8 45 23 01 00 mov $0x12345,%eax
36e: fb sti
36f: ff e1 jmp *%ecx
Man sieht hier, warum man edi und esi nicht clobbern kann.
Leere clobber list:
351: fa cli
352: 89 d9 mov %ebx,%ecx
354: 89 f4 mov %esi,%esp
356: 89 fd mov %edi,%ebp
358: 0f 22 d8 mov %eax,%cr3
35b: b8 45 23 01 00 mov $0x12345,%eax
360: fb sti
361: ff e1 jmp *%ecx
clobbert man ebx:
35f: fa cli
360: 89 f1 mov %esi,%ecx
362: 89 fc mov %edi,%esp
364: 89 d5 mov %edx,%ebp
366: 0f 22 d8 mov %eax,%cr3
369: b8 45 23 01 00 mov $0x12345,%eax
36e: fb sti
36f: ff e1 jmp *%ecx
clobbering edx:
351: fa cli
352: 89 d9 mov %ebx,%ecx
354: 89 f4 mov %esi,%esp
356: 89 fd mov %edi,%ebp
358: 0f 22 d8 mov %eax,%cr3
35b: b8 45 23 01 00 mov $0x12345,%eax
360: fb sti
361: ff e1 jmp *%ecx
-
35f: fa cli
360: 89 f1 mov %esi,%ecx
362: 89 fc mov %edi,%esp
364: 89 cd mov %ecx,%ebp
366: 0f 22 d8 mov %eax,%cr3
369: b8 45 23 01 00 mov $0x12345,%eax
36e: fb sti
36f: ff e1 jmp *%ecx
Hier ist das Register %ecx doppelt belegt. Am Anfang ist da als Eingabe der Basepointer drin. Dann wird %ecx mit %esi (dem Page Directory) überschrieben. Zwei Zeilen später wird %ecx nach %ebp kopiert. %ebp enthält jetzt also das Page Directory, und nicht den Stack Pointer. Du solltest auf benannte Register umsteigen:
asm volatile(" \
cli; \
mov %%ebx %%esp; \
mov %%edx, %%ebp; \
mov %%eax, %%cr3; \
mov $0x12345, %%eax; \
sti; \
jmp %%ecx; "
: : "c"(eip), "b"(esp), "d"(ebp), "a"(current_directory->physicalAddr) );
Ist natürlich jetzt die Frage, ob das was bringt, oder ob dein ursprünglicher Code nicht genau das gleiche tut.
Außerdem gehören Register natürlich nicht auf die Clobber-List, wenn du sie (im Assembler Code) gar nicht benutzt. Wenn GCC die Register benutzt, weiß er schon selbst, was er tut. (Naja gut bei deinem alten GCC weiß man das nicht^^) Also ist nur eine leere Clobber-List korrekt.
-
Außerdem gehören Register natürlich nicht auf die Clobber-List, wenn du sie (im Assembler Code) gar nicht benutzt. Wenn GCC die Register benutzt, weiß er schon selbst, was er tut. (Naja gut bei deinem alten GCC weiß man das nicht^^) Also ist nur eine leere Clobber-List korrekt.
Er benutzt die Register aber implizit. gcc weiß nicht, daß hier ein Taskwechsel stattfindet und könnte noch Zeug aus dem alten Task in den Registern stehen haben. Wenn man es unbedingt in C machen will, muß man also dafür sorgen, daß gcc alle Register auf den Stack packt und die Register das asm() nicht überleben. Dazu kann man die Clobber-Liste nehmen.
-
Das Problem liegt leider auch noch an anderer Stelle. SS ist nicht korrekt. Momentan schlagen alle Schwächen gleichzeitig zu. JM's Code ist im TaskSwitch Bereich nicht besonders brauchbar. Das wird mir immer klarer.
-
Wieso sollte der Taskswitch-Code ss ändern müssen? Er ist doch vorher und nachher im Kernelcode?
-
Ja, völlig richtig. Das Problem liegt tiefer, nämlich im Interrupt asm und C Code. JM's Code ist da nur in Ansätzen - sprich als erste Demo - hilfreich, aber in der Praxis versagt und verwirrt er konkret, wie bei mir geschehen. Daher mache ich bezüglich des Task Switch nun einen kompletten Roll-back.
Mal noch zwei konkrete Fragen zu diesem Thema:
1) Sollte man auch die Flags/EFlags sichern? Ist das überhaupt notwendig?
2) Im GCC 3.1 geht in asm(...) nur pushf/popf, pushfd/popfd wird nicht erkannt (das könnte man natürlich nach NASM auslagern und zurück linken). Liegt das an dem Alter dieser GCC-Version (2002, ich verwende diese wegen aout, weil coff keinen gemischten 16/32-bit-code akzeptiert)? Die 386 Instruktionen sind doch schon uralt dagegen.
-
1) Sollte man auch die Flags/EFlags sichern? Ist das überhaupt notwendig?
Ja, die werden aber automatisch bei einem Interrupt aus den Stack gelegt.
2) Im GCC 3.1 geht in asm(...) nur pushf/popf, pushfd/popfd wird nicht erkannt (das könnte man natürlich nach NASM auslagern und zurück linken). Liegt das an dem Alter dieser GCC-Version (2002, ich verwende diese wegen aout, weil coff keinen gemischten 16/32-bit-code akzeptiert)? Die 386 Instruktionen sind doch schon uralt dagegen.
Warum brauchst du 16bit Instruktionen überhaupt? :-o
Abgesehen davon ist das offensichtlich keine dauerhaft Lösung...
-
Warum brauchst du 16bit Instruktionen überhaupt?
http://www.henkessoft.de/OS_Dev/OS_Dev1.htm#mozTocId412221
Dort finden sich [Bits 16] und [Bits 32] in einer Übersetzungseinheit (Übergang von RM nach PM). Vielleicht kann man das so verändern, so dass man COFF verwenden kann?
-
Ich weiß nicht, warum das in vielen Tutorials immer so kompliziert gemacht wird, und 16 und 32 Bit Code vermischt wird ... Man kann doch einfach den Kernel komplett in 32 Bit halten (und im Bootloader natürlich weiterhin nur 16 Bit benutzen). Am Ende des Bootloader muss man den Sprung in den Protected Mode dann mit dem Sprung in den Kernel kombinieren.
-
Im Binärformat kann man auf jeden Fall den Code vermischen. Aber ob dir das reicht weiß ich nicht. Müsste man halt eventuell noch ein Feld am Anfang unterbringen, in dem die Adresse vom Entry-Point steht oder so.
Ansonsten hat Jidder natürlich Recht, aber das hab ich ja oben auch mehr oder weniger gesagt...
-
In diesem Bild sieht man die Zusammenhänge:
http://www.henkessoft.de/OS_Dev/Bilder/make_process.PNG
Der Übergang von RM nach PM erfolgt in kernel.asm (16/32 bit).
Damit das Linken klappt, erzeuge ich aout-Format, was problemlos von der Kombination gcc 3.1 / ld 2.13 (in DJGPP) geschluckt wird.
Naja, vielleicht sollte mir das einfach egal sein. In anderen Tutorials habe ich bisher nur kompliziertere Wege gesehen als im oben genannten Tut.
Um GRUB führt auf Dauer sowieso kein Weg vorbei, werde ich in Teil 3 gezwungermaßen wohl angehen müssen. MS Windows User mögen dieses Teil allerdings überhaupt nicht, weil sie GRUB nicht haben. Eine selbst entwickelte Alternative fehlt. Unter Vista gibt es doch auch einen Bootloader? (verwende selbst noch Win XP)
-
In diesem Bild sieht man die Zusammenhänge:
http://www.henkessoft.de/OS_Dev/Bilder/make_process.PNG
Der Übergang von RM nach PM erfolgt in kernel.asm (16/32 bit).
Damit das Linken klappt, erzeuge ich aout-Format, was problemlos von der Kombination gcc 3.1 / ld 2.13 (in DJGPP) geschluckt wird.
Naja, vielleicht sollte mir das einfach egal sein. In anderen Tutorials habe ich bisher nur kompliziertere Wege gesehen als im oben genannten Tut.
Ein Sprung aus dem Binär-Format nach C-Funktionen war nicht erlaubt. Daher konnte ich kernel.asm nicht als bin verwenden. ich musste auf o, dann war da aber das 16/32 bit Problem beim Linken, was ich via aout (es lebe hoch) lösen konnte. :-)
Um GRUB führt auf Dauer sowieso kein Weg vorbei, werde ich in Teil 3 gezwungermaßen wohl angehen müssen. MS Windows User mögen dieses Teil allerdings überhaupt nicht, weil sie GRUB nicht haben. Eine selbst entwickelte Alternative fehlt. Unter Vista gibt es doch auch einen Bootloader? (verwende selbst noch Win XP)
-
Ein Sprung aus dem Binär-Format nach C-Funktionen war nicht erlaubt. Daher konnte ich kernel.asm nicht als bin verwenden.
Jo das ist klar, ich dachte auch eher an das Ausgabeformat .bin für den ges. gelinkten Kernel, was du aber offensichtlich schon verwendest.
ich musste auf o, dann war da aber das 16/32 bit Problem beim Linken, was ich via aout (es lebe hoch) lösen konnte. :-)
Wie bereits gesagt, 16bit Code mit 32Bit C-Code mischen ist nicht optimal. Dafür habe ich auch keine gescheite Lösung gesehen/gefunden.
Unter Vista gibt es doch auch einen Bootloader? (verwende selbst noch Win XP)
Kommt gut, wenn man sein OS mal weitergeben möchte. :wink: Außerdem bist du dann an das gebunden was dieser Bootloader an Ausgabeformaten unterstützt. Der ist sicher nicht so flexibel wie grub.
-
Unter Vista gibt es doch auch einen Bootloader? (verwende selbst noch Win XP)
Soweit ich weiß, ist der Bootloader ziemlich der gleiche seit NT. Der kann allerdings auch nur Windows laden, oder chain loading, also eine Datei als Bootsektor in den Speicher nach 0x00007c00 laden.
-
Falls du das so meinst, dass erst der Vistabootloader chain loaden kann, liegst du falsch den das macht auch der NTLDR von NT-Systemen. Einfach einen Eintrag in der Boot.ini Datei, dabei muss sich der bootsector in einer datei auf der C-Partition im Haubtverzeichniss befinden: z.B. C:\BOOTSECT.DOS, wenn die Datei BOOTSECT.DOS heist.
-
Mal wieder ne Frage von mir:
Ich les mir gerade den source von Linux 0.01 durch. Meine Frage: Nutzt die Version des Kernels Software oder Hardware Multitasking?
-
Hardware.
-
ok, und ab welcher Version nutzt Linux Software Multitasking?
-
Das werde ich sicherlich nicht nachschauen, außerdem glaube ich nicht, dass jemand Linux so genau kennt. :roll:
Du solltest lieber eine Frage zu Software-Multitasking stellen, wenn du da was nicht verstanden hast. Außerdem gibt es genug Hobby-OS mit SW-Multitasking (tyndur, lightOS, pedigree um nur einige zu nennen), bei denen du eventuell mehr lernen könntest.
-
Ich habe noch nicht verstanden, wie Tyndur den SW Taskswitch im IRQ Stub durchführt.
Ich verwende folgenden asm Stub z.B. für den Timer Handler, in dem dann auch der Task Switch ablaufen soll, den ich in C versucht habe und der mit einer Exception (z.B. PF, GPF) oder mit einem Absturz endet:
extern _irq_handler
irq_common_stub:
;pusha
push edi
push esi
push ebp
push esp
push ebx
push edx
push ecx
push eax
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
pop eax
pop ecx
pop edx
pop ebx
pop esp
pop ebp
pop esi
pop edi
add esp, 8
sti
iret
Ist da etwas falsch, was die Exception auslöst?
Task-Switch in C/inline-asm:
void task_switch()
{
// If we haven't initialised tasking yet, just return.
if(!current_task) return;
cli();
// Read esp, ebp now for saving later on.
ULONG esp, ebp, eip;
eip = read_eip();
if(eip == 0x12345) return; // Have we just switched tasks?
asm volatile("mov %%esp, %0" : "=r"(esp));
asm volatile("mov %%ebp, %0" : "=r"(ebp));
//old_task
current_task->eip = eip;
current_task->esp = esp;
current_task->ebp = ebp;
//--------------------------
//old_task ==> new_task
current_task = current_task->next;
if(!current_task) current_task = ready_queue;
// new_task
current_directory = current_task->page_directory;
set_kernel_stack(current_task->kernel_stack+KERNEL_STACK_SIZE);
tss_entry.eip = eip = current_task->eip; //TEST
tss_entry.cr3 = current_directory->physicalAddr; //TEST
esp = current_task->esp;
ebp = current_task->ebp;
//TSS_log(&tss_entry); //TEST
asm volatile(" \
cli; \
mov %%ebx, %%esp; \
mov %%edx, %%ebp; \
mov %%eax, %%cr3; \
mov $0x12345, %%eax; \
sti; \
jmp *%%ecx; "
: : "c"(eip), "b"(esp), "d"(ebp), "a"(current_directory->physicalAddr):"%edi","%esi" );
}
Ideen, wie ich das doch noch zum Laufen bekommen kann? Ich verwende nur eine TSS.
Wie macht Tyndur den eigentlichen Task-Switch im Timer-Handler?
-
Du hast uns versprochen, dass du es mal anders versuchst ;)
Daher mache ich bezüglich des Task Switch nun einen kompletten Roll-back.
Was ich ja interessant finde, ist dass du esp als Parameter übergibst, aber gar nicht benutzt.
tyndur macht das so:
irq_common:
1. Register sichern (ähnlich wie du)
2. esp der C-Routine übergeben (push esp)
3. C-Routine aufrufen (call handle_int)
4. Rückgabewert nach esp laden (mov eax, esp)
5. Register wiederherstellen, Stack ausgleichen
6. iret
// Prinzip der C-Routine:
unsigned int handle_int(unsigned int esp)
{
if(kein Taskwechsel nötig)
return esp;
// Taskwechsel:
current_task->esp = esp; // esp sichern
current_task = schedule(); // neuen Task bestimmen
return current_task->esp; // esp zurückgeben
}
Der Code nutzt aus, dass eip und ebp bereits auf dem Stack liegen. Die werden wie alle Register gesichert und wiederhergestellt. Außerdem ist es einfacher nicht solchen verzwickten Assembler-Code mit C-Code zu mischen. Voraussetzung dafür sind konsistente Interrupt-Handler bezüglich des Stack-Layouts, das heißt alle deine Stubs müssen "kompatibel" zueinander sein. Das "add esp, 8" deutet an, dass dein Kernel das wohl bereits so macht.
-
Und der "eigentliche Taskswitch" ist dann das mov %eax, %esp im allgemeinen Interrupthandler-Code.
-
Du hast uns versprochen, dass du es mal anders versuchst
Ich war die letzte Zeit noch mit dem Thema Memory Management beschäftigt. Das läuft jetzt halbwegs stabil.
Nun möchte ich ein stabiles und transparentes Tasking System aufbauen. Daher vielen Dank für die obige Darstellung! Diese Interrupt-Geschichte ist bei mir leider noch ziemlich verworren, funktioniert aber.
Ich habe es mit eurer Routine versucht, hat aber wie erwartet noch nicht geklappt, weil der Handler-Mechanismus mit dem return esp bzw. return current_task->esp noch eingebaut werden muss.
Könnte bitte jemand prüfen, ob die Intel Syntax im asm-Stub so überhaupt richtig übertragen wurde:
extern _irq_handler
irq_common_stub:
push eax
push gs
push fs
xor eax,eax
mov ax, es
push eax
mov ax,ds
push eax
push ebp
push edi
push esi
push edx
push ecx
push ebx
cld
mov ax, 0x10
mov ds, ax
mov es, ax
push esp
call _irq_handler
mov esp, eax
pop ebx
pop ecx
pop edx
pop esi
pop edi
pop ebp
pop eax
mov ds,ax
pop eax
mov es,ax
pop fs
pop gs
pop eax
add esp, 8
iret
Das mit dem handle_int(esp) leuchtet mir ein. Aber wo gehört dies genau hin? Ich habe in meinem OS einen irq_handler, der momentan so aussieht, und vom asm-Stub aufgerufen wird:
void irq_handler(struct regs* r)
{
void (*handler)(struct regs* r);
handler = irq_routines[r->int_no - 32];
if (handler) { handler(r); }
if (r->int_no >= 40) { outportb(0xA0, 0x20); }
outportb(0x20, 0x20);
}
Die Verbindung von dort zum Timer Handler:
void irq_install_handler(int irq, void (*handler)(struct regs* r))
{
irq_routines[irq] = handler;
}
void timer_install()
{
irq_install_handler(0, timer_handler);
systemTimer_setFrequency( 100 );
}
void timer_handler(struct regs* r)
{
++timer_ticks;
task_switch(); // Das gehört ja jetzt wohl an andere Stelle?
}
Wie baut man da eure Rückgabe des alten bzw. neuen esp ein?
Die struct regs sieht so aus:
/* This defines what the stack looks like after an ISR was running */
typedef struct regs
{
ULONG gs, fs, es, ds;
ULONG edi, esi, ebp, esp, ebx, edx, ecx, eax;
ULONG int_no, err_code;
ULONG eip, cs, eflags, useresp, ss;
}registers_t;
Die muss wohl auch umgebaut werden, damit man esp auch wirklich erwischt. Das ist leider einer meiner schwächsten Ecken. :roll:
Daher bitte möglichst viele Details, damit es möglichst bald klappt.
Wenn man diese regs Strukutur richtig aufbaut, wird das r->esp doch bis in den Timer Handler getragen. Der ist aber void. :?
Und der "eigentliche Taskswitch" ist dann das mov %eax, %esp im allgemeinen Interrupthandler-Code.
Dieser Mechanismus leuchtet mir ein, ich habe aber noch Probleme dies ohne Komplettumbau in mein Gefrickel zu übertragen.
-
Du hast kein task_switch(), das auf der Stelle umschaltet. Du kannst z.B. ein current_task = current_task->next machen und wenn dein _irq_handler dann return current_task->esp macht, könnte es langsam Formen annehmen.
-
Sorry, aber das mit dem "auf der Stelle umschalten" verstehe ich konzeptionell leider noch nicht. Ich möchte doch im timer_handler umschalten und nicht bei jedem interrupt, oder sehe ich das verkehrt?
-
Jein. Du möchtest wirklich umschalten, wenn du einen Timer kriegst, eventuell bei IRQs und vermutlich auch bei manchen Syscalls. Dort machst du dann die Änderung von current_task. Bei allen anderen Fällen bleibt current_task eben wie es ist und dein Interrupthandler schaltet wieder zu dem gleichen Task um, in dem du sowieso schon warst.
-
OK, ich versuche es auf meinen Code zu übertragen:
In diese Funktion ...
void irq_handler(struct regs* r)
{
void (*handler)(struct regs* r);
handler = irq_routines[r->int_no - 32];
if (handler) { handler(r); }
if (r->int_no >= 40) { outportb(0xA0, 0x20); }
outportb(0x20, 0x20);
}
... würde esp mit eurem asm Stub als erster Parameter in regs übertragen, weil dieses Argument zuletzt auf den Stack geschoben wurde, richtig? Hat tyndur auch so eine Register Struktur zur Auswertung von exceptions? Diese könnte ich ja übernehmen, damit alles zusammen passt.
-
ja, hat es. struct int_stack_frame (http://git.tyndur.org/?p=tyndur.git;a=blob;f=src/kernel/include/intr.h;h=f07bf6133421c2ba620a710e20316fd3aefe3b61;hb=master#l54). Das entspricht wohl genau deinem struct regs. Allerdings eignet sich das nicht, um esp zu ändern, weil bei einem popa esp nicht geladen wird. Deswegen hat handle_int den Rückgabewert für esp.
Dein irq_common_stub passt übrigens nicht zu deinem struct regs. esp fehlt, eax ist an anderer stelle. Um push ds/es musst du auch nicht herumbasteln. Das funktioniert und pusht ein 32 bit Wort, mit den oberen 16 Bits auf 0.
-
Dein irq_common_stub passt übrigens nicht zu deinem struct regs.
Nein, das war nicht meines, sondern das "übersetzte" von tyndur. Ich hatte darum gebeten zu prüfen, ob ich von AT&T sauber nach Intel übersetzt habe.
Meines sieht so aus:
extern _irq_handler
irq_common_stub:
;pusha
push eax
push ecx
push edx
push ebx
push esp
push ebp
push esi
push edi
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
pop edi
pop esi
pop ebp
pop esp
pop ebx
pop edx
pop ecx
pop eax
add esp, 8
iret
Das passt doch zu
typedef struct regs
{
ULONG gs, fs, es, ds;
ULONG edi, esi, ebp, esp, ebx, edx, ecx, eax;
ULONG int_no, err_code;
ULONG eip, cs, eflags, useresp, ss;
}registers_t;
Wenn allerdings ein regs log bei einer exception stattfindet, stimmen die Werte nicht zusammen, keine Ahnung warum. :?
-
Um push ds/es musst du auch nicht herumbasteln. Das funktioniert und pusht ein 32 bit Wort, mit den oberen 16 Bits auf 0.
Das ist euer tyndur code. :-o
-
Allerdings eignet sich das nicht, um esp zu ändern, weil bei einem popa esp nicht geladen wird. Deswegen hat handle_int den Rückgabewert für esp.
Moment, das ist wichtig. Du meinst, wenn ich pusha/popa durchführe, ohne esp wie bei euch als ersten Parameter auf dem Stack an eine C-Funktion zu geben, hat eine esp-Änderung im irq_handler keine Wirkung, weil durch popa das alte esp wieder zurück geladen wird? Daher habt ihr doch auf push esp / pop esp komplett verzichtet und das selbst "außen" erledigt, habe ich das richtig verstanden?
Noch eine Frage zu meiner regs struct:
Ist das die Reihenfolge der gepushten register bis zum irq_handler oder hat dies seine eigene konstante Regel, so dass dies bei einem Interrupt immer gleich aussieht. ich stelle nämlich beim Loggen fest, dass Register vermischt sind. Kann natürlich auch vom falschen task_switch kommen.
Eure Struktur sieht ja genauso aus wie meine, sieht nach einer allgemeinen von Intel bestimmten Regel aus.
Ich habe mal selbst gewühlt und in James Molloy's Code folgende Kommentare gefunden:
popa ; Pops edi,esi,ebp...
add esp, 8 ; Cleans up the pushed error code and pushed ISR number
sti
iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
Demnach ist es eine Mischung aus den selbst gepushten Registern und dem vom Interrupt-Mechanismus gepushten Daten, richtig? Das letzte ESP ist das useresp.
Warum er vor dem iret ein sti setzt, ist mir nicht klar.
-
Wenn ich eure Technik verwende, kann ich dann nach dem ersten Parameter esp die regs zusätzlich abfangen?
ULONG irq_handler1(ULONG esp, struct regs* r){...}
:mrgreen:
-
Und der "eigentliche Taskswitch" ist dann das mov %eax, %esp im allgemeinen Interrupthandler-Code.
... und was ist mit eip?
-
Nein, das war nicht meines, sondern das "übersetzte" von tyndur.
Hm, aus kernel2... mit dem hab ich nichts zu tun, deswegen hab ich das nicht erkannt..
Ich hatte darum gebeten zu prüfen, ob ich von AT&T sauber nach Intel übersetzt habe.
Meines sieht so aus:
[...]
Sorry, hab ich übersehen. Sieht aber in Ordnung aus.
Wenn allerdings ein regs log bei einer exception stattfindet, stimmen die Werte nicht zusammen, keine Ahnung warum. :?
Du musst beachten, dass manche Exceptions einen Fehlercode auf den Stack legen, und manche nicht. (Und IRQs auch nicht.) Bei Exceptions ohne Fehlercode und IRQs solltest du direkt am Anfang 0 auf den Stack legen. Irgendwie so sollte das bei dir aussehen:
; Beispielsweise legt Exception 0 (Division durch 0) keinen Fehlercode
; auf den Stack, deswegen machen wir das.
exception_stub_0:
push dword 0 ; Ersatz für Fehlercode
push dword 0 ; Nummer des Interrupts
jmp irq_common_stub
...
; Bei Exception 8 legt die CPU selbst einen Fehlercode auf den Stack, also machen wir das *nicht*
exception_stub_8:
push dword 8 ; Nummer des Interrupts
jmp irq_common_stub
...
; IRQs legen keinen Fehlercode auf den Stack, deswegen machen wir das.
irq_stub_0:
push dword 0 ; Ersatz für Fehlercode
push dword 32 ; Nummer des Interrupts (IRQ 0 ist z.B. nach Interrupt 32 gemappt)
jmp irq_common_stub
...
Diese kurzen 2- bis 3-zeiligen Stubs springen zum "größeren" Stub, der dann die ganzen Register sichert, und zur C-Routine springt. Die hab ich mal "irq_common_stub" genannt, weil die aus deinem vorherigen Post (vermutlich) genau passen sollte (wichtig ist das add esp, 8, das die pushs wieder rückgängig macht).
Ich hoffe das ist so einigermaßen verständlich.
Allerdings eignet sich das nicht, um esp zu ändern, weil bei einem popa esp nicht geladen wird. Deswegen hat handle_int den Rückgabewert für esp.
Moment, das ist wichtig. Du meinst, wenn ich pusha/popa durchführe, ohne esp wie bei euch als ersten Parameter auf dem Stack an eine C-Funktion zu geben, hat eine esp-Änderung im irq_handler keine Wirkung, weil durch popa das alte esp wieder zurück geladen wird?
Nein, der Grund ein anderer. PUSHA legt zwar die Werte der 8 Register inkl. ESP auf den Stack, aber POPA überspringt ESP einfach beim auslesen. In den Intel Manuals (Volume 2 - Instruction Set Reference) ist das so beschrieben:
POPA:
EDI <- Pop();
ESI <- Pop();
EBP <- Pop();
Increment ESP by 4; (* Skip next 4 bytes of stack *)
EBX <- Pop();
EDX <- Pop();
ECX <- Pop();
EAX <- Pop();
POPA lädt ESP also gar nicht. Das liegt zwischen EBP und EBX, aber wird einfach übersprungen.
Ich weiß nicht mehr, ob ich das war, der die Interrupt Handler (in kernel(1)) geschrieben hat, aber wenn ich es gewesen wäre, dann hätte ich kein POP ESP verwendet, weil mir das zu unübersichtlich geworden wäre. Einfach direkt den Stack wechseln mittels des Rückgabewertes an einer Stelle, die wenig Probleme verursacht, ist hingegen recht einfach.
Direkt nach dem Aufruf der C-Routine ist halt nichts besonderes auf dem Stack, und dies erleichert es später für die Erstellung von neuen Tasks den Stack "aus dem Nichts" aufzubauen. Der Code dafür (http://git.tyndur.org/?p=tyndur.git;a=blob;f=src/kernel/src/task.c;h=a86abab9c527697e4447ac5570285e23e6e182e9;hb=master#l213) in tyndur ist natürlich mit der Zeit gewachsen, dennoch spiegelt er imo gut den Aufbau des Stacks wider. Vor allem muss er nicht mit irgendwelchen Tricks arbeiten um zu berücksichtigen, dass in der Mitte von irgendwas (C-Code, POP-Serie, ...) der Stack gewechselt wird.
Noch eine Frage zu meiner regs struct:
Ist das die Reihenfolge der gepushten register bis zum irq_handler oder hat dies seine eigene konstante Regel
Teils, teils. CS, EIP, EFLAGS sind immer dabei. SS und ESP nur, wenn der Interrupt nicht im Kernel Mode (also Ring 1-3) auftrat. (Jep, die Beiden sind die Werte aus dem User Mode.) Außerdem legt die CPU selbst manchmal den Fehlercode auf den Stack. Auf diese 3 bis 6 Werte und deren Anordnung hast du keinen direkten Einfluss.
Die anderen Register (General Purpose Register, Segment Register), kannst du in beliebiger Reihenfolge nach den anderen Registern auf den Stack sichern (oder auch nicht).
Der Aufbau der Stackframes ergibt sich wohl einfach daraus, dass sich eine Art natürliche Ordnung ergibt: Zuerst werden die "flüchtigsten" Register (CS:EIP, SS:ESP) gesichert, dann die "weniger flüchtigen" (EAX, ...), dann erst die Segmentregister, die sich sehr selten ändern.
Warum er vor dem iret ein sti setzt, ist mir nicht klar.
Mir ebenfalls nicht. Interrupts und deren Sperrung im Kernel sind eine Geschichte für sich ...
Ich komm ja kaum hinterher ...
Wenn ich eure Technik verwende, kann ich dann nach dem ersten Parameter esp die regs zusätzlich abfangen?
ULONG irq_handler1(ULONG esp, struct regs* r){...}
Viel besser: esp ist gleich r, denn der Parameter esp zeigt auf den Stack, genauer gesagt auf die ganzen Register. Der C-Handler sieht in tyndur im Prinzip so aus:
unsigned int handle_int(unsigned int esp)
{
struct regs * regs = *((struct regs **)esp);
// und hier kannst du mit regs weiter arbeiten.
}
Und der "eigentliche Taskswitch" ist dann das mov %eax, %esp im allgemeinen Interrupthandler-Code.
... und was ist mit eip?
Das liegt auf dem Stack.
-
@PorkChicken: danke für die Infos. Du hast ein Superwissen. Das mit popa wusste ich bisher nicht.
Ich habe meinen Code versuchsweise umgebaut, lande aber noch bei einem GPF, war vielleicht ein Bisschen zu viel auf einmal.
Ich poste es mal der Reihe nach, vielleicht seht ihr den Fehler:
asm stub:
irq_common_stub:
push eax
push ecx
push edx
push ebx
push ebp
push esi
push edi
push ds
push es
push fs
push gs
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp ; parameter of _irq_handler1
call _irq_handler1 ;
mov esp, eax ; return value: changed or unchanged esp
pop gs
pop fs
pop es
pop ds
pop edi
pop esi
pop ebp
pop ebx
pop edx
pop ecx
pop eax
add esp, 8
iret
versuchsweise neuer irq_handler:
ULONG irq_handler1(ULONG esp)
{
ULONG retVal;
struct regs* r = *((struct regs **)esp);
if(pODA->ts_flag==0)
{
retVal = esp;
}
else
{
// task_switch1:
retVal = task_switch1(esp);
}
/* 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);
return retVal;
}
task_switch1:
ULONG task_switch1(ULONG esp)
{
// If we haven't initialised tasking yet, just return.
if(!current_task) return esp;
current_task->esp = esp; // save esp
//old_task ==> new_task
current_task = current_task->next;
if(!current_task) current_task = ready_queue;
// new_task
current_directory = current_task->page_directory;
set_kernel_stack(current_task->kernel_stack+KERNEL_STACK_SIZE);
tss_entry.cr3 = current_directory->physicalAddr;
return current_task->esp; // return esp
}
Ich habe eine task struct:
typedef struct task
{
int id; // Process ID.
ULONG esp, ebp; // Stack and base pointers.
ULONG eip; // Instruction pointer.
page_directory_t* page_directory; // Page directory.
ULONG kernel_stack; // Kernel stack location.
struct task* next; // The next task in a linked list.
} task_t;
die tss_entry bildet ein TSS nach. Ich benutze nur ein einziges. Da blicke ich aber noch nicht durch, was ich selbst setzen muss und was die CPU setzt.
Super Forum hier. Ein dickes Lob! :-)
Wenn ihr mir helft, diesen Multitasking - Kram zum Laufen zu bringen, werde ich hier Stammgast und schreibe euch das wiki voll.
Fehler mit pODA->ts_flag == 0 :
Welcome to PrettyOS 0.07
initial_esp: 001FFED8h
GDT, IDT, ISRS, IRQ, timer, keyboard install
paging install
< bricht vor dem tasking install ab, sti ist gesetzt >
< Das kommt beim regs->log heraus: >
err_code: 00000000h address(eip): 00000001h edi: 000B8FDCh esi: 001FFE68h ebp: 001FFE5Ch eax: FFFFFFFFh ebx: 00000010h ecx:
00000064h edx: 001FFE68h cs: 8 ds: 16 es: 16 fs: 16 gs 16 ss 40361
int_no 13 eflags 00010016h useresp 00000010h
General Protection Fault >>> Exception. System Halted! <<<
-
Hast du beachtet, dass du nicht einfach irq_common_stub in die IDT eintragen darfst, weil du da nicht den Fehlercode sowie die Interruptnummer auf den Stack legst, sondern du einen Stub ähnlich wie diesen verwenden musst? So einen Stub meine ich:
irq_stub_0:
push dword 0 ; Ersatz für Fehlercode
push dword 32 ; Nummer des Interrupts (IRQ 0 ist z.B. nach Interrupt 32 gemappt)
jmp irq_common_stub
Sonst ist dein Stack nicht wie erwartet aufgebaut, und du bekommst Probleme beim Auslesen der Register, und spätestens beim IRET einen GPF.
Dass in der Parameter esp auch als Zeiger auf dein Register-Struct zuverwenden ist, hab ich vielleicht auch nicht ganz gut rübergebracht. Ich meinte das in etwa so:
ULONG irq_handler1(ULONG esp)
{
struct regs* r = (struct regs*)esp;
...
Ansonsten hilft nur den Fehler einzugrenzen. Es hilft manchmal mittels Breakpoints an wichtigen Stellen sich die Register genau anzugucken, und zu überlegen, ob sie die erwarteten Werte enthalten. Und wenn ja, musst du überlegen, ob die Erwartung sinnvoll ist. Außerdem kannst du natürlich deinen Code kürzen, indem du Behandlung des PICs sowie das Paging rauswirfst.
-
Und IRQs (zumindest der Timer-IRQ) sollten auch funktionieren, wenn du sie direkt mit INT 32 (o.ä.) aufrufst. Falls du den PIC ignorierst, ist das ganz hilfreich.
-
Das mit cr3 im TSS dürfte auch nicht funktionieren, oder? Wenn ich mich recht erinnere werden da für Software-Multitasking nur die Stack-Einträge (also esp und ss jeweils für Kernel- und Userspace) benutzt. Du solltest also stattdessen irgendwo gegen Ende des C-Interrupt-Handlers manuell cr3 neu laden (nur wenn es wirklich einen Taskwechsel gab, das ist teuer!)
-
so sieht das im asm stub aus:
_irq0:
cli
push byte 0
push byte 32
jmp irq_common_stub
-
Das cli kannst du dir sparen, wenn du den Gate-Typ richtig gesetzt hast (Interrupt Gate statt Trap Gate), das ist dann automatisch dabei. Ansonsten kommt mir ein byte-Push etwas klein vor - ich hätte sogar gedacht, daß man überhaupt nicht byteweise pushen kann. dword wie PorkChicken es geschrieben hat wäre wohl sinnvoller (und beim Fehlercode ist das auch, was die CPU pusht, wenn sie es selber macht).
-
So ich habe jetzt mal user mode, vfs, RAM disk weg gelassen. Mit pODA->ts_flag ==0 kann ich mittels IRQ0 eine Zeitschleife realisieren, also sollte der IRQ-Mechanismus gehen. Das geht:
k_clear_screen();
settextcolor(getpid(),0);
printformat("SS: %x, ESP: %x, EBP: %x, CS: %x, DS: %x\n", fetchSS(),fetchESP(),fetchEBP(),fetchCS(),fetchDS());
sti();
sleepSeconds(5);
//pODA->ts_flag = 1;
ULONG c=0;
while(TRUE)
{
if( k_checkKQ_and_print_char() )
{
++c;
if(c>5)
{
c=0;
settextcolor(4,0);
printformat("\nT: %x H: %x WRITE: %i Read: %i ", pODA->pTailKQ, pODA->pHeadKQ, pODA->KQ_count_write, pODA->KQ_count_read);
printformat("*T: %c %i *H: %c %i\n", *(pODA->pTailKQ),*(pODA->pTailKQ),*(pODA->pHeadKQ),*(pODA->pHeadKQ));
settextcolor(2,0);
settextcolor(getpid(),0);
}
}
}
return 0;
Schon mal ein guter Anfang.
-
Das cli kannst du dir sparen, wenn du den Gate-Typ richtig gesetzt hast (Interrupt Gate statt Trap Gate), das ist dann automatisch dabei.
Ich verwende IDT.
Ansonsten kommt mir ein byte-Push etwas klein vor - ich hätte sogar gedacht, daß man überhaupt nicht byteweise pushen kann. dword wie PorkChicken es geschrieben hat wäre wohl sinnvoller (und beim Fehlercode ist das auch, was die CPU pusht, wenn sie es selber macht).
Ich habe mir das aus Bran's und James Molloy's Tutorial abgeschaut. Ging aber die ganze Zeit, sollte also nicht das Problem sein.
-
Mit gesetztem ts_flag gibt es leider einen GPF:
jksdghfjkdghdfjkgh: 0018FFE0h, EBP: 0018FFF0h, CS: 00000008h, DS: 00000010h
T: 0000DAF6h H: 0000DAF6h WRITE: 18 Read: 18 *T: 0 *H: 0100
TSS log:
prev_tss: 00000000h esp0: 40105800h ss0: 00000010h esp1: 00000000h ss1: 00000000h esp2: 00000000h ss2: 00000000h cr3: 0041E000h eip: 00000000h eflags: 00000000h eax: 00000000h ecx: 00000000h edx: 00000000h ebx: 00000000h esp: 00000000h ebp: 00000000h esi: 00000000h edi: 00000000h es: 00000013h cs: 0000000Bh ss: 00000013h ds: 00000013h fs: 00000013h gs: 00000013h ldt: 00000000h trap: 00000000h iomap_base: 00000000h
err_code: 0000DAD0h address(eip): 00008497h
edi: 000B8FDCh esi: 000082ABh ebp: 00190000h eax: 0018FFD8h ebx: 00000000h ecx: 000B800Ah edx: 00000020h cs: 8 ds: 16 es: 16 fs: 16 gs 16 ss 753674 int_no 13 eflags 00010016h useresp 0000DAD0h
General Protection Fault >>> Exception. System Halted! <<<
-
Ich poste mal den Code, da ich momentan nicht weiß, wo es am meisten hängt, werde aber dennoch Fehlereingrenzung versuchen. Vielleicht seht ihr das Problem mit einem Blick. Würde mich freuen, wenn ihr mir aus dem Tiefpunkt heraus helft. Vielleicht fehlt mir auch noch der Durchblick. Die Thematik ist komplex aber auch sehr interessant. :-)
http://www.henkessoft.de/OS_Dev/Downloads/36i.zip
Leider hat mir euer Code aber auch nicht zum Multitasking geholfen.
Es ist echt verhext.
Falls ihr den Code compilieren wollt:
http://www.osdever.net/downloads/compilers/DJGPP-Installer-nocpp.exe
Ich verwende den Compiler gcc.exe (Version 3.1) und Linker ld.exe (Version 2.13) wegen des Formats aout für den Linker. Nur so konnte ich den Übergang 16/32 Bit schaffen, wollte aus didaktischen Gründen keinen GRUB bootloader (die Diskussion hatten wir schon, ihr habt mich überzeugt, bitte nicht nachsetzen).
-
Ich habe nun folgendes getestet: Umschalten von task A nach task A, um mal zu sehen, ob der ganze Mechanismus überhaupt läuft, und es geht!
ULONG task_switch1(ULONG esp)
{
// If we haven't initialised tasking yet, just return.
if(!current_task) return esp;
current_task->esp = esp; // save esp
//old_task ==> new_task
printformat("ts ");
/*
current_task = current_task->next;
if(!current_task) current_task = ready_queue;
*/
current_task = current_task; //same task TEST TEST TEST
// new_task
current_directory = current_task->page_directory;
tss_entry.cr3 = current_directory->physicalAddr;
set_kernel_stack(current_task->kernel_stack+KERNEL_STACK_SIZE);
return current_task->esp; // return esp
}
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts t
s ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts t
s ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts t
s ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts t
s ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts t
s ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts t
s ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts t
s ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts t
s ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts ts
:-)
Nun gilt es heraus zu finden, woran es liegt, dass das Umschalten von task A nach task B nicht fehlerfrei gelingt. Wie läuft das bei tyndur? Wie sieht da die task-Struktur aus?
Was muss genau beachtet werden, dass dies klappt?
current_task = current_task->next;
if(!current_task) current_task = ready_queue;
-
Ich habe das jetzt so aufgesetzt, dass ich die Daten des zweiten Tasks vor dem GPF gerade noch sehe:
ULONG task_switch1(ULONG esp)
{
// If we haven't initialised tasking yet, just return.
if(!current_task) return esp;
current_task->esp = esp; // save esp
//old_task ==> new_task
printformat("ts: ");
task_log(current_task);
printformat("\n\n");
current_task = current_task->next;
if(!current_task) current_task = ready_queue;
printformat("ts: ");
task_log(current_task);
printformat("\n\n");
//current_task = current_task; //same task TEST TEST TEST
// new_task
current_directory = current_task->page_directory;
tss_entry.cr3 = current_directory->physicalAddr;
set_kernel_stack(current_task->kernel_stack+KERNEL_STACK_SIZE);
return current_task->esp; // return esp
}
Screen Output:
SS: 00000010h, ESP: 0018FFE0h, EBP: 0018FFF0h, CS: 00000008h, DS: 00000010h
ts: id: 1 ebp: 00000000h esp: 0018FFC0h eip: 00000000h PD: 00000000h k_stack: 40101800h next: 40101814h
ts: id: 2 ebp: 0018FFF0h esp: 0018FFD8h eip: 0000D36Bh PD: 40102000h k_stack: 40105000h next: 40101844h
TSS log:
prev_tss: 00000000h esp0: 40105800h ss0: 00000010h esp1: 00000000h ss1: 00000000h esp2: 00000000h ss2: 00000000h cr3: 0041E000h eip: 00000000h eflags: 00000000h eax: 00000000h ecx: 00000000h edx: 00000000h ebx: 00000000h esp: 00000000h ebp: 00000000h esi: 00000000h edi: 00000000h es: 00000013h cs: 0000000Bh ss: 00000013h ds: 00000013h fs: 00000013h gs: 00000013h ldt: 00000000h trap: 00000000h iomap_base: 00000000h
err_code: 0000FFF8h address(eip): 00008497h edi: 000B8FDCh esi: 000082ABh ebp: 001FFFF8h eax: 0018FFD8h ebx: 00000000h ecx:
000B800Ah edx: 00000020h cs: 8 ds: 16 es: 16 fs: 16 gs 16 ss 0
int_no 13 eflags 00010002h useresp 001FFFF8h
General Protection Fault >>> Exception. System Halted! <<<
-
Ich hab deinen Code auch mal versucht zu debuggen. So ganz schlau, werd ich da gerade auch nicht. Bei mir tritt der GPF beim pop fs im Interrupt Handler auf. Seltsamerweise nicht beim pop gs davor. Außerdem tritt bei mir der Fehler nicht beim ersten Interrupt auf, sondern ein paar Interrupts später.
Irgendwas stimmt da mit dem Stack nicht (ein 0xd9a7 sollte da nicht einfach so draufliegen). Auszug aus dem Bochs Log:
00158736424-e-@0000849d-[CPU0 ] fetch_raw_descriptor: GDT: index (d9a7)1b34 > limit (2f)
Ich denke du suchst das Problem an der falschen Stelle. Mehr kann ich dir um diese Zeit leider auch noch nicht sagen. Am besten verlässt du dich nicht nur auf deine eigenen Ausgaben, sondern nutzt den Debugger deines Emulators.
-
ich habe jetzt ein paar posts übersprungen:
aber mir ist aufgefallen das der parameter für _irq_handler1 nicht
vom stack geholt wird:
mov gs, ax
push esp ; parameter of _irq_handler1
call _irq_handler1 ;
mov esp, eax ; return value: changed or unchanged esp
;>>> add esp, 4 <<<
pop gs
-
add esp, 4
Ich habe dies hinzu gefügt, dann stürzt er noch früher mit GPF ab.
Ich hab deinen Code auch mal versucht zu debuggen. So ganz schlau, werd ich da gerade auch nicht. Bei mir tritt der GPF beim pop fs im Interrupt Handler auf. Seltsamerweise nicht beim pop gs davor. Außerdem tritt bei mir der Fehler nicht beim ersten Interrupt auf, sondern ein paar Interrupts später.
Irgendwas stimmt da mit dem Stack nicht (ein 0xd9a7 sollte da nicht einfach so draufliegen). Auszug aus dem Bochs Log:
Zitat
00158736424-e-@0000849d-[CPU0 ] fetch_raw_descriptor: GDT: index (d9a7)1b34 > limit (2f)
Ich denke du suchst das Problem an der falschen Stelle. Mehr kann ich dir um diese Zeit leider auch noch nicht sagen. Am besten verlässt du dich nicht nur auf deine eigenen Ausgaben, sondern nutzt den Debugger deines Emulators.
Ich verwende Bochs. Wie wendest Du den Debugger genau an, damit Du diese Meldungen siehst? Mir fiel auf, dass beim Vergleich des eigenen error logs bei GPF und output von Bochs Unterschiede sind, und zwar bei eip, esi, ebp, eax, edx.
-
ich habe jetzt ein paar posts übersprungen:
aber mir ist aufgefallen das der parameter für _irq_handler1 nicht
vom stack geholt wird:
mov gs, ax
push esp ; parameter of _irq_handler1
call _irq_handler1 ;
mov esp, eax ; return value: changed or unchanged esp
;>>> add esp, 4 <<<
pop gs
Nein, das wäre falsch. Die Funktion gibt das korrekte esp in eax zurück und das wird ja sofort gesetzt.
-
Ich verwende Bochs. Wie wendest Du den Debugger genau an, damit Du diese Meldungen siehst?
Wenn meine bochs-Version nicht schon längst veraltet ist und sich alles verändert hat, brauchst du ungefähr sowas in deiner Konfigurationsdatei:
error: action=report
info: action=report
debug: action=report
-
error: action=report
info: action=report
debug: action=ignore
steht da bei mir.
Nach dem GPF:
RAX=000000000000001a RBX=0000000000000000
RCX=00000000000b8000 RDX=00000000000003d5
RSP=000000000018ff6c RBP=000000000018ff94
RSI=000000000018ffa0 RDI=0000000000000004
IOPL=0 id vip vif ac vm rf nt of df if tf sf zf af PF cf
CS:0008( 0001| 0| 0) 00000000 000fffff 1 1
DS:0010( 0002| 0| 0) 00000000 000fffff 1 1
SS:0010( 0002| 0| 0) 00000000 000fffff 1 1
ES:0010( 0002| 0| 0) 00000000 000fffff 1 1
FS:0010( 0002| 0| 0) 00000000 000fffff 1 1
GS:0010( 0002| 0| 0) 00000000 000fffff 1 1
RIP=0000000000009a07 (0000000000009a07)
CR0=0xe0000011
CR1=0x0
CR2=0x0000000000000000
CR3=0x00306000
CR4=0x00000000
jmp .+0xfffffffe (0x00009a07) : EBFE
... habe nun auch die byte gegen dword getauscht, macht aber keinen Unterschied. :-(
_irq0:
cli
push dword 0
push dword 32
jmp irq_common_stub
Ich habe mal nachgeschaut, was ein GPF im Rahmen des Switching bewirkt:
Faults can occur in the task state segment (TSS) structure when:
* switching to a busy task during a call or jump instruction
* switching to an available task during an interrupt return (IRET) instruction
* using a segment selector on a switch pointing to a TSS descriptor in the LDT
Hat jemand eine Idee, wo und wie ich genau ansetzen könnte, um das Problem zu lösen? Wie soll ich das schaffen, wenn selbst PorkChicken, der mich so hervorragend zur Umsetzung des tyndur-taskswitch-Mechanismus geführt hat, den Fehler nicht leicht findet. :?
Ich habe nochmals
von task A nach task A umgeschaltet:
id: 1 esp: 0018FFB8h k_stack: 40101800h next: 40101814h
id: 1 esp: 0018FFB8h k_stack: 40101800h next: 40101814h
id: 1 esp: 0018FFBCh k_stack: 40101800h next: 40101814h
id: 1 esp: 0018FFBCh k_stack: 40101800h next: 40101814h
Kann das sein, dass esp dabei ab und zu um 4 Byte hin und her springt?
Was muss im task_switch im TSS eingetragen werden?
-
error: action=report
info: action=report
debug: action=ignore
steht da bei mir.
Ja. ignore ist eben weniger Ausgabe als report.
RIP=0000000000009a07 (0000000000009a07)
Ich weiß nicht, wohin du deinen Kernel legst (du benutzt ja kein GRUB), aber normal würde ich das als einen kaputten Wert ansehen.
Ich habe mal nachgeschaut, was ein GPF im Rahmen des Switching bewirkt:
Faults can occur in the task state segment (TSS) structure when:
* switching to a busy task during a call or jump instruction
* switching to an available task during an interrupt return (IRET) instruction
* using a segment selector on a switch pointing to a TSS descriptor in the LDT
Du machst kein Hardware-Taskswitching, deswegen klingt das alles nicht so richtig relevant. Du kriegst deinen GPF entweder bei einem Interrupt oder beim Rücksprung aus einem Interrupt, würde ich sagen.
Kann das sein, dass esp dabei ab und zu um 4 Byte hin und her springt?
Das klingt auf jeden Fall komisch. Ich würde mal versuchen rauszufinden, woran das liegt. Dann kannst du wahrscheinlich auch sagen, ob es harmlos oder böse ist.
Was muss im task_switch im TSS eingetragen werden?
Nichts. Wie oben schonmal erwähnt wird beim Software-Taskswitchung der TSS nur benutzt, um ss und esp von Kernel und Userspace zu sichern. Alles andere interessiert den Prozessor nicht.
-
Momentan bin ich für jede Klarstellung dankbar, denn vor lauter lauter baut man zum Schluss noch mehr Blödsinn ein, so dass garnichts mehr geht.
Ich weiß nicht, wohin du deinen Kernel legst (du benutzt ja kein GRUB), aber normal würde ich das als einen kaputten Wert ansehen.
Der Kernel beginnt bei 0x8000h:
OUTPUT_FORMAT("binary")
ENTRY(RealMode)
phys = 0x00008000;
...
...wird beim Software-Taskswitchung der TSS nur benutzt, um ss und esp von Kernel und Userspace zu sichern. Alles andere interessiert den Prozessor nicht.
Was ist mit cr3?
ULONG task_switch1(ULONG esp)
{
// If we haven't initialised tasking yet, just return.
if(!current_task) return esp;
current_task->esp = esp; // save esp
current_task = current_task->next;
if(!current_task) current_task = ready_queue;
// new_task
current_directory = current_task->page_directory;
asm volatile("mov %0, %%cr3" : : "r" (current_directory->physicalAddr));
tss_entry.cr3 = current_directory->physicalAddr; //notwendig?
tss_entry.esp = current_task->esp;
tss_entry.esp0 = (current_task->kernel_stack)+KERNEL_STACK_SIZE;
return current_task->esp; // return esp
}
Ist das so in Ordnung?
-
Ah gut, dann kann das eip so natürlich stimmen. Ist nur eine ungewöhnliche Position.
-
error: action=report
info: action=report
debug: action=ignore
steht da bei mir.
Ja. ignore ist eben weniger Ausgabe als report.
Ich habe Report verwendet, ergibt mehrere 10000 Seiten Report. :mrgreen:
-
esp bei task A -> task A :?
0018FFC4h
0018FFC0h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFC4h
0018FFC8h
0018FFC4h
0018FFC4h
0018FFC8h
0018FFC4h
0018FFC0h
0018FFB8h
0018FFB8h
0018FFC0h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFC4h
0018FFC8h
0018FFB8h
0018FFC4h
0018FFC8h
0018FFC4h
0018FFC0h
0018FFB8h
0018FFC4h
0018FFC0h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFC4h
0018FFC8h
0018FFC4h
0018FFC4h
0018FFC8h
0018FFC4h
0018FFC0h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFC4h
0018FFC8h
0018FFB8h
0018FFC4h
0018FFC8h
0018FFC4h
0018FFC0h
0018FFB8h
0018FFC4h
0018FFC0h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFC4h
0018FFB8h
0018FFB8h
0018FFC4h
0018FFC8h
0018FFC4h
0018FFC0h
0018FFC8h
0018FFC4h
0018FFC0h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFB8h
0018FFC4h
0018FFC8h
0018FFB8h
Für mich kein Muster erkennbar.
Ich poste mal den gesamten Sourcecode:
http://www.henkessoft.de/OS_Dev/Downloads/36m.zip
Vielleicht kann mir mal jemand genau erklären, wie man mit dem Bochs Debugger heraus bekommt, wo und warum diese Ereignisse ablaufen?
Ich komme da mit der Schritt-Technik einfach nicht an die wirklich interessanten Stellen, z.B. im asm-Stub und im task_switch. Wie macht ihr das genau? Setzt man da irgendwelche Haltepunkte im Code selbst oder läuft das über Adressen? Wenn ja woher bekommt man diese?
Vielleicht liegt der Fehler auch in den 5 Gates (Code, Daten, Code, Daten, TSS): siehe gdt.c, flush.asm, descriptor_tables.c, task.c
void gdt_install()
{
/* Setup the GDT pointer and limit */
gdt_register.limit = (sizeof(struct gdt_entry) * NUMBER_GDT_GATES)-1;
gdt_register.base = (ULONG) &gdt;
/* GDT GATES - desriptors with pointers to the linear memory address */
gdt_set_gate(0, 0, 0, 0, 0); // NULL descriptor
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); // CODE, privilege level 0 for kernel code
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); // DATA, privilege level 0 for kernel code
//Kernel Mode:
gdt_set_gate(3, 0, 0xFFFFFFFF, 0x9A, 0xCF); // CODE, privilege level 0 for kernel code
gdt_set_gate(4, 0, 0xFFFFFFFF, 0x92, 0xCF); // DATA, privilege level 0 for kernel code
//User Mode:
//gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // CODE, privilege level 3 for user code
//gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // DATA, privilege level 3 for user code
write_tss (5, 0x10, 0x0); //num, ss0, esp0
gdt_flush((ULONG)&gdt_register); // inclusive gdt_load() in assembler code
tss_flush(); //privilege level 3 for user mode 0x2B // in flush.asm
//privilege level 0 for kernel mode 0x28
}
Gate 3 und 4 werden z.Z. noch im kernel mode (privilege 0) betrieben, sollen später aber auf 3 umgesetzt werden. TSS habe ich in flush.asm auch auf Privileg 0 gesetzt?! Mit 3 geht es aber auch nicht.
Syscalls gehen auch schon, daher wurde der user mode bereits getestet, muss aber auf Privileg 0 bleiben, da ich kernel code verwende und nicht nurFunktionen über syscall aufrufe. Mit den kernel stacks und user stacks komme ich konzeptionell auch noch nicht ganz klar.
Könnte da mal jemand rein schauen? Task switch erfolgt in task_switch1 (ganz unten in task.c).
-
Für mich kein Muster erkennbar.
Sollte man auch nicht.
Ich poste mal den gesamten Sourcecode:
http://www.henkessoft.de/OS_Dev/Downloads/36m.zip
Hm, da ist ja eine Menge Zeug drin. Das müsste man ausgemistet werden. Ich steig da auf jeden Fall nicht durch. Ich würde vorschlagen nicht alles aus den Tutorials/anderen Systemen in einziges Programm zu stecken.
Vielleicht kann mir mal jemand genau erklären, wie man mit dem Bochs Debugger heraus bekommt, wo und warum diese Ereignisse ablaufen?
Ich komme da mit der Schritt-Technik einfach nicht an die wirklich interessanten Stellen, z.B. im asm-Stub und im task_switch. Wie macht ihr das genau? Setzt man da irgendwelche Haltepunkte im Code selbst oder läuft das über Adressen? Wenn ja woher bekommt man diese?
Mit einer Map-Datei. Die bekommst du, wenn du ld den Parameter -Map kernel.map übergibst. Dann stehen in der Datei kernel.map alle globalen Symbole. Da kannst du dir z.B. irq0 oder task_switch (oder isr_common_stub/irq_common_stub, wenn du diese global deklarierst) raussuchen, und die Adresse merken. Bei mir ist irq0 zum Beispiel 0x0000816a.
Wenn du in bochs im Debugger bist, kannst du da dann einen breakpoint mittels "lb 0x816a" setzen. Mit c kommst du dahin, und kannst dann mit s durchsteppen.
-
-Map kernel.map ... globale Symbole. ... irq0 zum Beispiel 0x0000816a ... bochs im Debugger bist, ... breakpoint mittels "lb 0x816a" setzen. Mit c kommst du dahin, und kannst dann mit s durchsteppen.
Super! Danke für den Tipp mit -Map. Musste ich bisher noch nicht so intensiv machen.
Hm, da ist ja eine Menge Zeug drin.
Fast nichts. Das soll ein OS werden. :-)
Ich steig da auf jeden Fall nicht durch.
Schade. :-( Bisher hast Du mir auf jeden Fall gut weiter geholfen.
-
EIP=9a07 ...
Ich weiß nicht, wohin du deinen Kernel legst (du benutzt ja kein GRUB), aber normal würde ich das als einen kaputten Wert ansehen.
PorkChicken:
Bei mir ist irq0 zum Beispiel 0x0000816a.
:-D :-D :-D
Es scheint noch mehr Leute zu geben, die ihren Kernel ziemlich direkt hinter dem bootloader anbringen.
-
EIP=9a07 ...
Ich weiß nicht, wohin du deinen Kernel legst (du benutzt ja kein GRUB), aber normal würde ich das als einen kaputten Wert ansehen.
PorkChicken:
Bei mir ist irq0 zum Beispiel 0x0000816a.
:-D :-D :-D
Es scheint noch mehr Leute zu geben, die ihren Kernel ziemlich direkt hinter dem bootloader anbringen.
Ne, das ist dein OS. Nach dem Austausch deiner als Makefile getarnten Batch-Datei, war das doch recht einfach zum Laufen zu bringen ;)
-
war das doch recht einfach zum Laufen zu bringen
Na super. :-D leider nur die Version mit task A -> task A in task_switch1
Darf ich mal fragen mit welchem Compiler Du dabei arbeitest, weil wir noch das Problem mit dem notwendigen aout-Format und dem Linker haben.
Ich habe nun das Debugging beim echten task_switch entsprechend deinem Vorschlag durchgeführt, habe _irq0 und _task_switch1 als breakpoint gesetzt, bin aber nicht an die Stelle gekommen, wo der GPF direkt auftritt. Das bringt mich momentan irgendwie nicht weiter, sitze wirklich fest.
-
Ich verwende einen Cross Compiler für ELF unter Windows (gcc 4.3.1), den ich selbst gebaut habe. Ich habe bei mir deswegen auch bei den Assembler-Aufruf aout durch elf ersetzt.
edit: verlesen -.-
-
Ich verwende einen Cross Compiler für ELF unter Windows (gcc 4.3.1), den ich selbst gebaut habe. Ich habe bei mir deswegen auch bei den Assembler-Aufruf aout durch elf ersetzt.
Hast Du das irgendwo beschrieben, wie man das macht? Denn das wäre wirklich interessant.
-
Ich hab die Anleitung (http://wiki.osdev.org/GCC_Cross-Compiler) auf osdev.org befolgt. In unserem Wiki ist aber auch eine, die genauso gut ist: http://lowlevel.brainsware.org/wiki/index.php/Cross-Compiler
Beide sollten ohne große Änderungen in einer aktuellen MinGW/MSYS Umgebung funktionieren.
-
Danke für die Links!
Ich habe deinen Rat befolgt und entrümpelt (VFS, RAM disk, User Gates, Syscall alles weg): http://www.henkessoft.de/OS_Dev/Downloads/36n.zip
Die Reaktion ist noch gleich: GPF
Beim diesbezüglichen Error-Log stimmt etwas nicht mit den Daten in regs.
Nun gibt es nur noch paging, heap, tasking.
Vielleicht findest Du nun leichter durch und damit den entscheidenden Fehler.
-
Hm, mal schauen. Also ich hab natürlich noch viel mehr gefunden, was da raus kann. Ohne jetzt tief in deinem Code bohren zu wollen, hab ich einfach mal meine Entfernen-Taste etwas strapaziert und Funktion-/Block-/Zeilenweise deinen Code gelöscht. Deine main-Methode ist ein 10-Zeiler und kernel.asm ist auch nur noch ein Schatten seiner selbst.
Also ich habs soweit reduziert, dass für mich das fork() als der Übeltäter aussieht. Im Kernel forkt man nicht. Das macht auch irgendwie keinen Sinn. Ich sehe da außerdem nicht, wo du den Stack kopierst. Das kernel_directory->tables == src->tables ist auch falsch, weil die CPU in deinem aktuellen Page Directory das Dirty Flag setzen kann. (Oder wie auch immer das heißt.)
Im allgemeinen ist fork() sowieso zu kompliziert. Ein create_task tuts auch. (Erstellt einen neuen Task, für den nur der Einsprungspunkt vorgegeben ist, nicht der ganze Prozess)
Bei mir funktionieren folgende Änderungen (an 36n.zip):
ckernel.c
#include "os.h"
#include "kheap.h"
#include "task.h"
extern page_directory_t* current_directory;
extern task_t* current_task;
extern tss_entry_t tss_entry;
extern ULONG read_eip();
void moo()
{
while(1) printformat("MOO %d", getpid()); // <-- kuh
}
void baa()
{
while(1) printformat("BAA %d", getpid()); // <- schaf
}
int main()
{
printformat("Welcome to PrettyOS 0.07\n");
// GDT, IDT, ISRS, IRQ, timer, keyboard, paging, enable interrupts, RAM disk, multitasking
gdt_install();
idt_install();
isrs_install();
irq_install();
initODA();
timer_install();
keyboard_install();
sti();
paging_install();
tasking_install();
create_task(moo);
create_task(baa);
pODA->ts_flag = 1;
while(1) printformat("%d", getpid());
return 0;
}
task.c neue Funktion:
extern void irq_tail();
void create_task(void *entry)
{
task_t* parent_task = (task_t*)current_task;
page_directory_t* directory = clone_directory(current_directory);
task_t* new_task = (task_t*)k_malloc(sizeof(task_t),0,0);
new_task->id = next_pid++;
new_task->page_directory = directory;
new_task->kernel_stack = k_malloc(KERNEL_STACK_SIZE,1,0)+KERNEL_STACK_SIZE;
new_task->next = 0;
task_t* tmp_task = (task_t*)ready_queue;
while (tmp_task->next)
tmp_task = tmp_task->next;
tmp_task->next = new_task; // ... and extend it
unsigned int *kernel_stack = (unsigned int*)new_task->kernel_stack;
//*(--kernel_stack) = 0x23; // ss
//*(--kernel_stack) = USER_STACK_VIRT + USER_STACK_SIZE - 0x4;
*(--kernel_stack) = 0x0202; // eflags = interrupts aktiviert und iopl = 0
*(--kernel_stack) = 0x08; // cs
*(--kernel_stack) = (unsigned int)entry; // eip
*(--kernel_stack) = 0; // interrupt nummer
*(--kernel_stack) = 0; // error code
// general purpose registers
//*(--kernel_stack) = 0; // eins von denen ist esp
*(--kernel_stack) = 0;
*(--kernel_stack) = 0;
*(--kernel_stack) = 0;
*(--kernel_stack) = 0;
*(--kernel_stack) = 0;
*(--kernel_stack) = 0;
*(--kernel_stack) = 0;
// segment registers
*(--kernel_stack) = 0x10;
*(--kernel_stack) = 0x10;
*(--kernel_stack) = 0x10;
*(--kernel_stack) = 0x10;
new_task->ebp = 0xd00fc0de;
new_task->esp = (unsigned int)kernel_stack;
new_task->eip = irq_tail;
}
*hust* Stackaufbau aus tyndur ^^
In isr.asm in irq_common_stub vor das mov esp, eax
global irq_tail
irq_tail:
Edit: Sorry, dass ich schon wieder eine neue Funktion/Methode vorschlage. Aber beim OS-Dev in dem Stadium ist der Weg das Ziel.
-
Zuerst mal vielen Dank für Deine Mühe! Das ist echt super. :-)
Habe alles eingebaut und folgenden Ausdruck erhalten:
ts:
id: 1 ebp: 00000000h esp: 001FFE9Ch eip: 00000000h PD: 00000000h k_stack: 40101800h next: 40101814h
ts:
id: 2 ebp: D00FC0DEh esp: 401056E4h eip: 00008495h PD: 40102000h k_stack: 40105800h next: 40101844h
ts:
id: 3 ebp: D00FC0DEh esp: 401096BCh eip: 00008495h PD: 40106000h k_stack: 40109800h next: 00000000h
3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA
:-D
Manchmal sieht es auch so aus:
ts:
id: 3 ebp: D00FC0DEh esp: 401096E0h eip: 00008495h PD: 40106000h k_stack: 40109800h next: 00000000h
ts:
id: 1 ebp: 00000000h esp: 001FFEC0h eip: 00000000h PD: 00000000h k_stack: 40101800h next: 40101814h
ts:
id: 2 ebp: D00FC0DEh esp: 401056DCh eip: 00008495h PD: 40102000h k_stack: 40105800h next: 40101844h
ts:
id: 3 ebp: D00FC0DEh esp: 401096E0h eip: 00008495h PD: 40106000h k_stack: 40109800h next: 0000FC0DEh
ts:
id: 1 ebp: 00000000h esp: 001FFEC0h eip: 00000000h PD: 00000000h k_stack: 40101800h next: 40101814h
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
Ich wundere mich gerade, dass das MOO nicht auftaucht.
Wo kommt eigentlich diese Adresse her?
new_task->ebp = 0xd00fc0de;
Bin echt begeistert. Es "multitaskt" :-)
Vielen Dank für die Starthilfe bei diesem Thema.
-
MOO sollte in Kombinationen mit 2en aufreten. Du könntest mal in task_switch1 das task_log auskommentieren. Dann ist diese Zeile vielleicht leichter zu finden. Wenn sie gar nicht zu finden ist, haben wir noch ein Problem. ;)
Die Variable new_task->ebp wird zur Zeit (so wie ich das sehe) nicht verwendet, deswegen hab ich sie mit irgendwas deutlich ungültigem geladen. Dann knallts wenigstens laut, wenn sie doch mal verwendet wird. Diese Adresse ist übrigens in leetspeak kodiert ;)
-
11111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111
2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MO
O 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MO
BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA
3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3B11111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
O 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2M1111111111111111
O 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MO
O 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MO
O 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MO
A 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3B
AA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3B
AA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA
11111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111
O 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MO
O 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MO
O 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MO
3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA
3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA
3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 311111111111111111111111111111111111111
111111111111111111111111111111
No Problem. Doch kein DOOFCODE. :-P
-
Kannst Du dazu bitte noch etwas sagen?
*(--kernel_stack) = 0x0202; // eflags = interrupts aktiviert und iopl = 0
0x0202 = 1000000010
Warum wurde Bit 1 gesetzt?
Bit 0: Carry-Flag (CF)
Bit 1: unbelegt
Bit 2: Parity-Flag (PF)
Bit 3: unbelegt
Bit 4: Auxiliary-Flag (AF)
Bit 5: unbelegt
Bit 6: Zero-Flag (ZF)
Bit 7: Sign-Flag (SF)
Bit 8: Trap-Flag (TF)
Bit 9: Interrupt-Flag (IF)
Bit 10: Direction-Flag (DF)
Bit 11: Overflow-Flag (OF)
Bit 12 und 13: I/O-Privilege Field-Flag (IOPL)
Bit 14: Nested-Task-Flag (NT)
Bit 15: unbelegt
Bit 16: Resume-Flag (RF)
Bit 17: Virtual-8086-Mode-Flag (VM)
Bit 18: Alignment Check-Flag (AC)
Bit 19: Virtual-Interrupt-Flag (VIF)
Bit 20: Virtual-Interrupt-Pending-Flag (VIP)
Bit 21: Identification-Flag (IF)
Bit 22-31: unbelegt
Hier ist auch eine interessante Seite zum Thema Multitasking:
http://www.osdever.net/tutorials/multitasking.php
-
Bit 1 hat zwar keine besondere Bedeutung, aber es ist immer fest auf 1 gesetzt. Ich hätte auch 0x200 nehmen können, die CPU hätte trotzdem Bit 1 gesetzt.
-
Danke für die Hilfe und die Informationen.
Ich lade den funktionsfähigen Code dieser Multitasking-Versuchsversion hoch, falls andere da mal mit spielen wollen:
http://www.henkessoft.de/OS_Dev/Downloads/36p.zip
-
Wo kommt eigentlich diese Adresse her?
new_task->ebp = 0xd00fc0de;
Ich fordere Lizenzzahlungen, das ist meine! ;)
-
Baust du das mit dem Multitasking auch ein?
Dieses Thema ist absolut faszinierend, also gehört es ins PrettyOS Tutorial. Ich möchte es aber erst verarbeiten, wenn ich die Zusammenhänge komplett verstanden habe. Der Code von James Molloy ist für eigene Versuche nicht verwertbar. Daher wurde er hier zu Recht weitgehend auf den Müll geworfen. :-)
@PorkChicken: Du hast geschrieben, dass Du auch mein kernel.asm reduziert hast. Kannst Du das mal zeigen, oder hast Du ckernel.c gemeint?
Auf jeden Fall werde ich diesem Forum hier treu bleiben. Das Forum und die vorbildhafte Hilfestellung PorkChicken's habe ich hier gewürdigt:
http://www.henkessoft.de/OS_Dev/OS_Dev1.htm#mozTocId64850
-
@PorkChicken: Du hast geschrieben, dass Du auch mein kernel.asm reduziert hast. Kannst Du das mal zeigen, oder hast Du ckernel.c gemeint?
jo, das kann ich. Ich hab zur Zeit keinen Zugriff auf den PC mit dem Code, aber ich hab das mal versucht zu rekonstruieren:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; HenkesSoft 0.07 (version from Apr 18, 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:
xor ax, ax ; set up segments
mov es, ax
mov ds, ax
mov ss, ax
mov sp, ax
mov si, welcome
call print_string
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
Waitingloop:
mov ebx,0x9FFFF
.loop_start:
dec ebx
jnz .loop_start
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 _main ; ->-> C-Kernel
jmp $
;;;;;;;;;;;
; Strings ;
;;;;;;;;;;;
welcome db 'HenkesSoft 0.07 (Apr 18, 2009)', 13, 10, 0
;;;;;;;;;;;;
; Includes ;
;;;;;;;;;;;;
%include "gdt.inc"
Hab noch die welcome-Nachricht drinne gelassen, und nur das nötigste für den Wechsel in den Protected Mode. Das erleichtert das Testen, wenn man nicht immer "pm" eintippen muss, und dann warten darf, bis der Bildschirm voll mit buntem Text ist ;)
Achja, die Waitingloop hab ich aus dem [bits 32]- in den [bits 16]-Teil geschoben. Es ist ziemlich riskant 32 Bit Code aus 16 Bit Code heraus aufzurufen. In diesem Fall wird folgendes ausgeführt:
00000000 BBFFFF mov bx,0xffff
00000003 0900 or [bx+si],ax ; <--- Das sind die Überreste vom mov ebx, 0x0009ffff
00000005 4B dec bx
00000006 75FD jnz 0x5
00000008 C3 ret
Es kommt auf den Kontext an, ob die zusätzliche or-Instruktion problematisch ist, oder nicht ;)
@TheThing: Ich trenn mal dein Thema ab. Der Thread ist lang genug ;)
-
Ich habe das Thema Software-Multitasking hier als kleines Kapitel im Tutorial verarbeitet:
http://www.henkessoft.de/OS_Dev/OS_Dev2.htm#mozTocId114565
Hoffentlich habe ich alles richtig beschrieben.
Beim Recherchieren habe ich noch folgendes Tutorial gefunden, das relativ ähnlich arbeitet, aber schon ausgefeiltere task-Strukturen und -Funktionen hat:
http://www.osdever.net/tutorials/multitasking.php
Danke nochmals an PortChicken! Auch für die verkürzte Variante von kernel.asm. Ich schleppe da mein viertel Tutorial mit mir nach.
-
Ich habe da doch noch eine Nachfrage:
Bei mir sieht das nach Vorlage tyndur so aus:
*(--kernel_stack) = 0x0200; // eflags = interrupts aktiviert und iopl = 0
*(--kernel_stack) = 0x08; // cs
*(--kernel_stack) = (ULONG)entry; // eip
*(--kernel_stack) = 0; // interrupt nummer
*(--kernel_stack) = 0; // error code
Im Intel Manual Vol. 3, 5-18 habe ich Folgendes gefunden:
eflags, cs, eip, error code
Die Interrupt Nr. taucht da gar nicht auf. :?
-
Jo, die Reihenfolge in den Kommentaren stimmt nicht.
-
Also eflags, cs, eip, error code, interrupt nr? Warum steht da nix im Intel Manual von dieser Nr.?
-
Weil wir die Nummer selbst auf den Stack legen. Und das wiederrum tun wir, weil wir zu faul sind, 256 vollständige Interrupthandler zu schreiben. Lieber bloß jedem Interrupt einen 2- bis 3-zeiligen Stub geben, und dann zum richtigen Handler springen.
-
Ah, nun wird das klar. :-)