Task Switch über Interrupt mit NT-Bit
-
Hallo zusammen,
ich habe 2 einfache kleine Programme für die ich je ein TSS angelegt habe.
Leider funktioniert der Task-Switch bei mir nur wenn ich am Ende jeder "Runde" im Programm einen JMP zum anderen TSS ausführe.Ich erinnere mich gelesen zu haben das bei einem Taskwechsel die CPU das NT Flag
(Nested Task) im EFLAG Register setzt. Dadurch kann z.B. nach einem Timerinterrupt mittels IRET zum nächsten Task geschaltet werden dessen TSS im Backlinkfeld des aktuellen eingetragen ist.Mein ISR für den Timer besteht lediglich aus
mov al, 0x20 out 0x20, al sti iret
in den beiden Backlinkfeldern habe ich das jeweilige TSS des anderen Task eingetragen. Was fehlt für einen Taskswitch mittels IRET noch?
Wieso braucht man ein Task-Gate wenn man direkt das TSS anspringen kann?
Vielleicht kann jemand Licht ins Dunkel bringen..
Gruß, Nicky
-
Mit Hardware-Multitasking kenne ich mich nicht sehr gut aus. Aber ich meine, aus den Intel-Manuals herauszulesen, dass die CPU nicht das NT-Flag bei einem Taskwechsel setzt, sondern dass sie bei gesetztem NT-Flag bei einem Iret den naechsten Task anspringt. Zusaetzlich loescht die CPU dann das NT-Flag im gesicherten EFLAGS-Register des alten Tasks, wenn der Switch ueber iret ging. Bei den Calls/Jumps bleibt das Flag unveraendert. (Intel Manual 3 Kap. 7.3)
Habs aber nur ueberflogen, ohne Gewaehr. Probier also mal das NT-Flag explizit vor jedem iret zu setzen.Gibts eigentlich einen bestimmten Grund, warum du Hardware-Multitasking benutzen musst? Gut implementiertes Software-Multitasking ist fast genauso schnell, und du umgehst Probleme wie dieses.
-
Hallo Jonas,
Danke für die Info. Da hab ich es wohl verdreht.
Ich denke das dies leichter ist, da die Task's ja verknüpft sind.Was mich noch brennend interessiert ist folgendes.
Ich kann einen Taskswitch auch überCall 0x40:12345
aufrufen.
Bei einem Call werden noch CS und EIP auf den Stack gelegt.
Wenn ich das 500x mache läuft mein Stack doch über.Wie macht das Windows, das ein Thread einfach mit "ret" beendet werden kann?
Legt es zu jedem Task ein extra Stack an damit der Task weiß wo er am Ende
hinspringen soll?Nicky
-
Wie macht das Windows, das ein Thread einfach mit "ret" beendet werden kann?
Legt es zu jedem Task ein extra Stack an damit der Task weiß wo er am Ende
hinspringen soll?Eine andere Lösung fällt mir spontan nicht ein.
Bei einem Call werden noch CS und EIP auf den Stack gelegt.
Wenn ich das 500x mache läuft mein Stack doch über.Ja, das haben Stacks so ansich (zum Lachen: https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920 ) - zumindest solange niemand den Stack wieder entleert oder den Stackpointer neu setzt (was dann ein eventuelles RET unmöglich macht)
-
Wie macht das Windows, das ein Thread einfach mit "ret" beendet werden kann? Legt es zu jedem Task ein extra Stack an damit der Task weiß wo er am Ende hinspringen soll?
Dass CreateThread einen Parameter dwStackSize hat, spricht wohl fuer sich. Allgemein wuerde es aber nicht viel Sinn machen, allen Threads den gleichen Stack zu zuteilen, selbst im gleichen Prozess. Thread A ruft Funktion X auf, deren Ruecksprungadresse liegt auf dem Stack und ihre Argumente. Jetzt wird A von B unterbrochen, welches seinerseits eine Funktion Y aufruft, die aber auch vor Vollendung wieder von A unterbrochen wird. Jetzt wuerde X in A mit dem Stackinhalt von Y arbeiten. Das wird schon schoen, wenn dann aufeinmal lokale Variablen und Argumente andere Werte haben. Richtig schoen wirds, wenn er dann zurueckspringen will und als Return-Adresse irgendwelche Argumente o. lokale Variablen von B vor sich hat.
Bezueglich des Calls: Ich denke, dieser Mechanismus ist eher fuer Syscalls gedacht (ein Taskwechsel kann glaube ich einen Wechsel in einen hoeher Priviligierten Modus ausfuehren, bin aber gerade zu faul, das nachzupruefen). Fuer einen reinen Wechsel ist entweder der iret-Ansatz oder ein Jump eine gute Wahl.
-
und nochmal ich...
habe jetzt versucht das NT-BIT (im EFLAG Bit Nr. 14) mittels OR-Verknüpfung
zu setzen...
Da ich nicht genau wusste welches EFLAG, das auf dem Stack oder das aktuell
im Register liegt habe ich mal beide Varianten ausprobiert...Leider kein Erfolg!
Ich schreibe mal meine beiden "Tasks" hier rein:
Task 1:proc1: sti jmp beg_proc1 farbe db 00001111b grenze dd 0xb8640 string1 db "Supernicky",0,0 beg_proc1: mov esi, string1 vv1: mov edi, 0xb8000 task1_start: mov ah, [farbe] schreiben1: lodsb stosw xor ebx, ebx mov ebx, 10000000d nochmal1: dec ebx jnz nochmal1 cmp edi, [grenze] jge vv1 or al, al jnz schreiben1 mov esi, string1 jmp 0x28:123456 jmp task1_start ret
Task 2:
proc2: sti jmp beg_proc2 farbe2 db 01110000b grenze2 dd 0xb8c80 string2 db "C-PlusPlus",0,0 beg_proc2: mov esi, string2 vv2: mov edi, 0xb8640 task2_start: mov ah, [farbe2] schreiben2: lodsb stosw xor ebx, ebx mov ebx, 10000000d nochmal2: dec ebx jnz nochmal2 cmp edi, [grenze2] jge vv2 or al, al jnz schreiben2 mov esi, string2 jmp 0x18:123456 jmp task2_start ret
Diese beiden machen nichts anderes als einen String einmal in der oberen Bildschirmhälfte und einmal in der unteren in einer Endlosschleife auszugeben.
Nach jedem Buchstabe der ausgegeben wird erfolgt ein Sprung in den nächsten Task.
jmp 0x18:123456
Der Timerinterrupt wird wie gewohnt 18,2 mal pro Sekunde aufgerufen.
Darin habe ich einmal das versucht:IRQ_32: ;Timer Interrupt IRQ 0 cli mov al, 0x20 out 0x20, al push ebp ;basepointer sichern mov ebp, esp ;ESP nach EBP kopieren zum zeigen auf den Stack add ebp, 12 ;ESP zeigt auf EFLAG auf dem Stack mov ebx, [ebp] ;Eflag nach ebx kopieren or ebx, 100000000000000b mov [ebp], ebx ;EBX zurück ins Eflag kopieren auf dem Stack pop ebp ;EBP wiederherstellen sti iret
und einmal das hier:
IRQ_32: ;Timer Interrupt IRQ 0 cli mov al, 0x20 out 0x20, al pushfd pop ebx or ebx, 100000000000000b push ebx popfd sti iret
In beiden Varianten wird das Bit Nr. 14 vor dem IRET gesetzt.
Ein Sprung findet jedoch nicht statt.Ich hoffe es ist noch jemand wach und kann mir etwas helfen
Gruß, Nicky
-
Ich habe mich im Intel-Manual wohl etwas verlesen. Das NT-Bit wird durchaus bei einem Task-Wechsel gesetzt; zumindest steht in der Beschreibung des Flags
Intel Manual 1 3.4.3.3 schrieb:
Nested task flag — Controls the chaining of interrupted and called tasks. Set when the
current task is linked to the previously executed task; cleared when the current task is not
linked to another task.Und da er sich beim Task-Switch den ehemaligen Task in einem Feld des TSS merkt, interpretiere ich das so, dass das Flag eigentlich gesetzt sein muesste.
Genauso steht aber im Manual, dass iret das Flag gesetzt braucht.
Intel Manual 3A 7.1.3 schrieb:
Software or the processor can dispatch a task for execution in one of the following ways:
- [...]
- Nested task flag — Controls the chaining of interrupted and called tasks. Set when the
current task is linked to the previously executed task; cleared when the current task is not
linked to another task.
Du machst also schonmal nichts falsch, wenn du das Flag setzt. Meines Erachtens nach ist hier das Flag im EFLAGS-Register gemeint, nicht im gesicherten EFLAGS auf dem Stack. Allerdings sollte er das theortisch automatisch machen, sobald du einen Task-Switch explizit durch einen call, oder implizit durch einen Interrupt machst (wobei hier nur dann ein Task-Switch stattfindet, wenn das Task-Segment als Handler-Segment in der IDT angegeben ist).
Das bei dir dann kein Task-Switch stattfindet, wundert mich. Versuch mal im TSS explizt den Previous Task zu setzen und das Busy-Flag zu clearen. Allgemein scheint diese Art des Task-Switchings aber nur fuer System-Calls oder Interrupts, die selbst einen Task-Switch verursachen (TSS-Selektor als Selektor des Handlers in der IDT), deswegen heisst das Flag wohl auch Nested Task. Das koennte auch dein Problem sein, denn laut Manual darf sich ein Task nicht rekursiv aufrufen.
Fuer reines Scheudeling scheint eher ein Jump zum einem Task-Gate oder TSS-Deskriptor gedacht zu sein. Wie man das jetzt aber gescheit in einen Interrupt einbaut, weiss ich nicht.Ich wuerde jetzt erstmal versuchen, das NT-Flag zu setzen, wie du es schon gedacht hast, den Previous Task explizit zu setzen und noch das BSY-Flag zu clearen.
-
Danke für die Info's...
ich werde mal versuchen den Taskswitch selbst zu organisieren im IRQ des Timers.
Ich hoffe ich liege mit dem Vorgehen richtig die GDT nach verfügbaren TSS Segmenten zu durchsuchen und dann jeweils den nächsten Task mittels eines JMP zu starten. Davor werde ich aber sicherheitshalber noch 12 Byte vom aktuellen Stack nehmen (EFLAG, CS und EIP) damit es hier keine Probleme gibt.
Bevor ich mich nun weiter an die Arbeit machen hätte ich gern noch ein paar Info's.
Ist es erforderlich für jeden Task einen eigenen Stack für die 4 PL anzulegen? Also auch wenn alle auf PL 0 laufen?
Vielen Dank
Nicky
-
EDIT: Hier stand Mist.
Intel Manual 3A 7.2.1 schrieb:
Privilege level-0, -1, and -2 stack pointer fields — These stack pointers consist of a logical address made
up of the segment selector for the stack segment (SS0, SS1, and SS2) and an offset into the stack (ESP0,
ESP1, and ESP2). Note that the values in these fields are static for a particular task; whereas, the SS and ESP
values will change if stack switching occurs within the task.Die werden also nur verwendet, wenn du innerhalb deines Tasks in einen hoeher priviligierten Ring wechselst. Da du eh schon auf Ring 0 bist, sollte es reichen, SS und ESP, sicherheitshalber ggf. noch SS0 und ESP0, aufzusetzen. Moeglich, dass SS und ESP nur im Ring 3 beachtet werden.