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 über

    Call 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.


Log in to reply