Page 1 of 1

eip not backed up right on hardware task switch

Posted: Sat Feb 02, 2008 5:10 pm
by sancho1980
hi

i decided to open a new post for this, as its not really got to do with the original problem of the old post any more

still stuck with hardware multitasking

i have my 2 tasks, task1 and task2
they just run in a loop:

Code: Select all

procedure task1;
begin task1;
	forever
		inc(somevariable);
	endfor;
end task1;
my "scheduler" is set up as follows:

-kernel executes hlt with interrupts set
-clock occurs, sets backlink of kernel tss to task1, sets busy flag for task1, sets nt flag in flags register so that when interrupt returns:
-task1 executes
-clock occurs, sets backlink of task1 tss to task1, sets busy flag for task2, sets nt flag in flags register so that when interrupt returns:
-task2 executes
-clock occurs, sets backlink of task2 tss to task1, sets busy flag for task1, sets nt flag in flags register so that when interrupt returns:
-task1 executes
-and so on, task1 and task2 alternating

the problem is i get only as far as to task 2.
once task2 executes for the first time and gets interrupted by the clock, the iret instruction (back to task1) makes bochs crash with error:

Code: Select all

 WARNING: Encountered an unknown instruction (signalling illegal instruction)
i checked that as follows:

just before the clock interrupt switches from task2 to task1, i added

-one line of code that prints out the eip value stored in task1 tss
-another line of code that prints out the address of task1

because task1 is so small and executes in a loop, these two values should be very close to each other, but they are not!
it tells me that task1 starts at address 2088 and the eip values stored in the eip is 3229 (decimal values)!

how is that possible?

see below the clock handler:

Code: Select all

procedure clock_int; @nodisplay; @noalignstack; @noframe;

begin clock_int;

	push(eax);
	push(ebx);

	pushfd();



	streg(ax);

	if (ax = IDX_KERNELTSS*@size(segdesc)) then

		mov(IDX_TASK1TSS*@size(segdesc),kernelstate.backlink); //set the backlink
		mov(GDT_BASE,eax); //set
		add(IDX_TASK1TSS*@size(segdesc),eax); //busy
		fseg: mov((type segdesc [eax]).props1,bl); //flag
		or(%00000010,bl); //for incoming
		fseg: mov(bl,(type segdesc [eax]).props1); //task

	elseif (ax = IDX_TASK1TSS*@size(segdesc)) then
		mov(IDX_TASK2TSS*@size(segdesc),task1state.backlink); //set the backlink
		mov(GDT_BASE,eax); //set
		add(IDX_TASK2TSS*@size(segdesc),eax); //busy
		fseg: mov((type segdesc [eax]).props1,bl); //flag
		or(%00000010,bl); //for incoming
		fseg: mov(bl,(type segdesc [eax]).props1); //task
	else
		mov(IDX_TASK1TSS*@size(segdesc),task2state.backlink); //set the backlink
		mov(GDT_BASE,eax); //set
		add(IDX_TASK1TSS*@size(segdesc),eax); //busy
		fseg: mov((type segdesc [eax]).props1,bl); //flag
		or(%00000010,bl); //for incoming
		fseg: mov(bl,(type segdesc [eax]).props1); //task
		putunsint(&task1); //print address of task1
		putchar(LF);
		putunsint(task1state.eip_reg); //print backed-up eip
		putchar(LF);
	endif;

	mov(PIC_EOI,al);

	out(al,PIC1_COMMAND);


	pop(eax); //popping the flags so I can set the NT bit
	or($4000,eax);

	push(eax);

	popfd();
	pop(ebx);

	pop(eax);

	iret();

end clock_int;
any ideas??


edit: i just found something else out: the address that gets stored in the esp field of the tss segment on a hardware task switch is the address just after the iret in the clock handler

this doesnt help of course

so this means i must not execute the iret in the clock handler, but rather in the code of the task itself...but this is useless, because how am i going to implement preemptive multitasking if i have to rely on each task to execute iret???

Posted: Sun Feb 03, 2008 3:28 am
by sancho1980
is there any example of hardware multitasking out there?
everything i found is software multitasking (seriously)

Posted: Sun Feb 03, 2008 4:23 am
by Combuster
Yes there is, and it's been around from even before when I learned to program assembly. But it's indeed hard to get by even when you know the google keywords.

Its actually part of a whole series. You probably want to start here: (dos app)
http://ringzero.free.fr/os/protected%20mode/Pm/PM8.ASM

Posted: Sun Feb 03, 2008 1:49 pm
by sancho1980
i finally got preemptive hardware multitasking to work
it seems the only way to do it is to hook the clock interrupt as a task gate
this way, whenever the clock interrupts the currently running task, the clock handler will be called as a task with the previously executing task being placed in the backlink field..so all the clock handler has to do is modify the back link field and iret to beam itself whereever it wants to be
it also needs to be written as an endless loop, with the iret instruction just at the end of the loop, so that next time the clock handler is called, it starts right at the top again:

Code: Select all

procedure clock_int; @nodisplay; @noalignstack; @noframe;

begin clock_int;
	forever
		if (clockintstate.backlink = IDX_TASK1TSS*@size(segdesc)) then
			mov(IDX_TASK2TSS*@size(segdesc),clockintstate.backlink);
			mov(GDT_BASE,eax); //set
			add(IDX_TASK2TSS*@size(segdesc),eax); //busy
			fseg: mov((type segdesc [eax]).props1,bl); //flag
			or(%00000010,bl); //for incoming
			fseg: mov(bl,(type segdesc [eax]).props1); //task 
		else
			mov(IDX_TASK1TSS*@size(segdesc),clockintstate.backlink);
			mov(GDT_BASE,eax); //set
			add(IDX_TASK1TSS*@size(segdesc),eax); //busy
			fseg: mov((type segdesc [eax]).props1,bl); //flag
			or(%00000010,bl); //for incoming
			fseg: mov(bl,(type segdesc [eax]).props1); //task 
		endif;
		mov(PIC_EOI,al);

		out(al,PIC1_COMMAND);
		iret();
	endfor;

end clock_int;

Posted: Mon Feb 04, 2008 2:46 am
by AJ
Hi,

Whenever the clock interrupt occurs, shouldn't execution begin at the top of the ISR anyway? I don't understand why you need the loop. Having said that, it shouldn't cause any problems (unless your assembler does anything funny with the stack to create a loop - but hopefully it just does a 'JMP clock_int'.

Cheers,
Adam

Posted: Mon Feb 04, 2008 4:28 am
by sancho1980
No!
After the iret, the address of the instruction just after the iret is placed in the eip field of the clock handler tss. So when the clock interrupts next time, you're falling out of the procedure. With the loop, you just jump back to the start.

Posted: Mon Feb 04, 2008 4:32 am
by AJ
Sorry - I didn't spot that your interrupts were actually being handled by separate tasks. I just use trap gates.

Cheers,
Adam

Posted: Mon Feb 04, 2008 5:04 am
by sancho1980
yeah, as I said, that only works fine if you use software multitasking :-)

Posted: Mon Feb 04, 2008 5:09 am
by AJ
Hi,

That's what comes of trying to respond to one of your later posts without thoroughly reading the first one - sorry.

A long time ago, I have definitely have HW task switching working with a trap gate. I'm just looking trough my archives now to see if I can find it.

Cheers,
Adam

Posted: Mon Feb 04, 2008 5:24 am
by sancho1980
If you could only tell me how this is supposed to work in theory
I really see no possible way, lest you're talking about cooperative multitasking, but with preemptive multitasking, I think you'll always run into the problem I described in my first post in this thread

Posted: Mon Feb 04, 2008 5:54 am
by AJ
I'm afraid I can't find my HW task code at the moment (I wasn't very neat about archiving back then, so the whole thing's a mess), but here, I think, is how it works in theory.
  • Task 1 Running - Gets interrupted by the clock.
    CPU Pushes SS, ESP, EFLAGS, EIP and CS and runs your handler.
    You manually switch to Task 2 - our exact current system state gets stored in TSS1.
    Tick! Task 2 interrupted - again, SS, ESP, EFLAGS and CS are pushed.
    You manually switch to Task 1 - the previous saved state should be exactly loaded from TSS1 and Task 1 will continue running the second half of the task handler, including a proper IRET.
There is nothing in there that indicates the task switch should not work. This leads me to one of a couple of conclusions based on the fact that EIP only gets loaded at one of two points.

1) There is some stack trashing going on there (EIP is trashed on IRET - so IRET does really work, but you just aren't loading the correct EIP from the stack). Your tasks still need separate ring 0 stacks and ring 3 stacks even with HW multitasking. Perhaps some stack creep is going on?
2) The other place where EIP is loaded is on the actual task switch. The only way this can be invalid is if your TSS data is getting trashed. I think this is much less likely than option 1.

The fact is a hw task switch should be possible from inside a trap gate and if you sort that out with the 'use a task gate instead' idea, there may be some bug lurking that will rear its head when servicing other interrupts / IRQ's.

Cheers,
Adam

Posted: Mon Feb 04, 2008 6:17 am
by sancho1980
AJ wrote:I'm afraid I can't find my HW task code at the moment (I wasn't very neat about archiving back then, so the whole thing's a mess), but here, I think, is how it works in theory.
  • Task 1 Running - Gets interrupted by the clock.
    CPU Pushes SS, ESP, EFLAGS, EIP and CS and runs your handler.
    You manually switch to Task 2 - our exact current system state gets stored in TSS1.
Yes, our exact current system, including eip, which currently points to somewhere in the clock handler! Thats not where I want to return to!

Posted: Mon Feb 04, 2008 6:21 am
by AJ
Why not? Execution should then continue with the rest of the clock handler and the clock handler will IRET back to your task.

Cheers,
Adam

Posted: Mon Feb 04, 2008 6:42 am
by sancho1980
No:

-task1 executes
-clock: clock handler starts, clock handler adjusts backlink field in task1 tss to task2 and executes iret, this stores the address of the next instruction (the instruction just after the iret in the clock handler because task1 is still active) in eip field of task1 tss and switches over to task 2
-now task2 executes
-clock: clock handler starts, clock handler adjusts backlink field in task2 tss to task1 and executes iret, this stores the address of the next instruction (the instruction just after the iret in the clock handler because task2 is still active) in eip field of task2 tss and switches over to task 2
-now task 1 is active again but we're still in the clock handler because this was the eip value that was backed up just before we left task1..we could somehow fiddle with the stack return to where the clock interrupted stopped task 1 in the first place, but that would be a software task switch :-(

Posted: Mon Feb 04, 2008 7:01 am
by AJ
Dammit - I see what you mean. Thinking about it, that seems like quite an odd design on Intel's part. I really don't remember that difficulty from when I implemented it before - it just worked. Perhaps I messed with the stack somewhere.

Oh well - just goes to show software task switching is the way forward :wink:

Cheers,
Adam