Page 3 of 3

Posted: Tue Jan 29, 2008 3:17 pm
by sancho1980
Hmm, now I'm confused.
I thought the ltr was there to tell the processor which task to execute on return from the interrupt. What do I have to do? Nested task flag? Task back? Could you explain the sequence?

Posted: Tue Jan 29, 2008 4:50 pm
by sancho1980
ok after a bit of readin in the intel manual, i changed the tss's as follows:

Code: Select all

	kernelstate: tss_t := tss_t:[IDX_TASK2TSS*@size(segdesc),0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,$ffff];

	task1state: tss_t := tss_t:[
					IDX_TASK2TSS*@size(segdesc),
					0,
					$fffffffc,
					IDX_TASK1STACK*@size(segdesc),
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					&task1,
					$4000,
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					IDX_KERNELDATA*@size(segdesc),
					0,
					IDX_KERNELCODE*@size(segdesc),
					0,
					0,
					0,
					IDX_KERNELDATA*@size(segdesc),
					0,
					IDX_MEMORY*@size(segdesc),
					0,
					IDX_VIDEO*@size(segdesc),
					0,
					0,
					0,
					0,
					0,
					$ffff
				];

	task2state: tss_t := tss_t:[
					IDX_TASK1TSS*@size(segdesc),
					0,
					$fffffffc,
					IDX_TASK2STACK*@size(segdesc),
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					&task2,
					$4000,
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					IDX_KERNELDATA*@size(segdesc),
					0,
					IDX_KERNELCODE*@size(segdesc),
					0,
					0,
					0,
					IDX_KERNELDATA*@size(segdesc),
					0,
					IDX_MEMORY*@size(segdesc),
					0,
					IDX_VIDEO*@size(segdesc),
					0,
					0,
					0,
					0,
					0,
					$ffff
				];
before enabling the clock interrupt i also do this:

Code: Select all

	pushfd();
	pop(eax);
	or($4000,eax);
	push(eax);
	popfd();
this way, a clock interrupt while the kernel task is executing should cause task2 to execute, a clock interrupt while task2 is executing should cause task1 to execute, and a clock interrupt while task 1 is executing should cause task2 to execute and so on...
heres my new clock handler:

Code: Select all

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

begin clock_int;
	push(eax);
	pushfd();
	mov(PIC_EOI,al);

	out(al,PIC1_COMMAND);
	popfd();
	pop(eax);

	iret();

end clock_int;
task1 and task 2 just look like this:

Code: Select all

procedure task1;
begin task1;
	forever
		cli;
		putstring("task1");
		sti;
	endfor;
end task1;

procedure task2;
begin task2;
	forever
		cli;
		putstring("task2");
		sti;
	endfor;
end task2;
i dont get a bochs error any more, but nothing happens either..id have expected to see many "task1"s and "task2"s printed to the screen!

:-(

Posted: Wed Jan 30, 2008 3:42 am
by AJ
Have a look at the lowest diagram on http://www.sandpile.org/ia32/tss.htm to see how backlinking works. Basically, you need to adjust the task's NT flag and set the backlink field to the desired incoming task. IRET will then perform the task switch for you.

I would suggest having a look through the Intel Manuals again. Software switching is only touched on in there, but the type of Hardware switching you are trying to accomplish is fully explained.

Cheers,
Adam

Posted: Wed Jan 30, 2008 4:19 am
by sancho1980
I really have read it. As it seems to me now, the situation is as follows:

If my current task has its NT flag set and a valid task selector in its backlink, it can execute an iret to return to the previous task. Ok.

But if during task execution an interrupt occurs (e.g. clock), the iret in the isr will simply return to the task, not to the task in the backlink of the task. This then means I can only return to the previous task with an explicit iret in the code of the task itself. But what I want is preemptive multitasking. Issuing an iret requires the task to be cooperative :-(

Posted: Wed Jan 30, 2008 4:50 am
by AJ
Ah - ok.

What you do, is modify the backlink and NT flags during your ISR. You do this for any task you want to make current. By doing this in the timer ISR, you make the tasks preemptible.

Posted: Wed Jan 30, 2008 5:33 am
by sancho1980
I think I got you, can you confirm:

In order for preemtive multitasking to work, my IDT entries need be TSS descriptors. They must not be "normal" code segment descriptors. Is that it?

Posted: Wed Jan 30, 2008 5:44 am
by AJ
No.

1) Timer interrupt fires and switches to ring 0 stack found in current task's TSS. You are still running 'Task 1' at this point, but have automatically made the transition to ring 0 to service the interrupt.
2) Your scheduler decides it is time for a task switch this time. Modify the backlink field and NT flag of Task 1, to point to Task 2.
3) IRET. Now you are running Task 2.
4) Timer fires and switches to ring 0 stack, but you don't need a task switch this time - just increment your tick counter and IRET with no modifications.
5) Timer fires and switched to ring 0 stack. You are still running Task 2 at this point, but have made the transition to ring 0 to service the interrupt.
6) We are due for another task switch this time. Modify the backlink field and NT flag of Task 2 to point to Task 1.
7) IRET. You are now running Task 1 again. No need for any IDT modifications.
....and so on.

The one circumstance where you may want your IDT to contain a task gate is the double fault. This will ensure you are running on a clean stack.

Cheers,
Adam

Posted: Wed Jan 30, 2008 6:06 am
by sancho1980
Ok, suppose I want to achieve the following:

My kernel runs and I have ltr'ed the kernel tss. When the first timer interrupt occurs, I want a task switch to task 2. On the second timer interrupt, I want a task switch to task 1. After that task 1 and 2 keep alternating on every timer interrupt. I can only try this out when I get home from work but do you think I have a chance that this will finally work:

Code: Select all

procedure clock_int; @nodisplay; @noalignstack; @noframe;
begin clock_int;
	push(eax);
	pushfd();
	pop(eax);//popping the flags so I can set the NT bit
	or($4000,eax);
	push(eax);

	streg(ax);
	if (ax = IDX_KERNELTSS*@size(segdesc)) then 
		mov(IDX_TASK2TSS*@size(segdesc),kernelstate.backlink);
	elseif (ax = IDX_TASK1TSS*@size(segdesc)) then
		mov(IDX_TASK2TSS*@size(segdesc),task1state.backlink);
	else
		mov(IDX_TASK1TSS*@size(segdesc),task2state.backlink);
	endif;
	mov(PIC_EOI,al);
	out(al,PIC1_COMMAND);

	popfd();
	pop(eax);
	iret();
end clock_int;

Posted: Wed Jan 30, 2008 6:12 am
by AJ
I'm very unfamiliar with that assembler syntax (I think you have mentioned HLA in the past), but yes - that all looks good. Of course, once you have that working, you can then replace that middle bit with your full scheduler.

Cheers,
Adam

Posted: Wed Jan 30, 2008 5:30 pm
by sancho1980
I *seem* to be on the right track, but this keeps giving me errors.
When the iret is executed, bochs gives me "TSS selector points to bad TSS".

I'll paste my initialisation of task2 tss again:

Code: Select all

	task2state: tss_t := tss_t:[
					0,
					0,
					$fffffffc,
					IDX_TASK2STACK*@size(segdesc),
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					&task2,
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					IDX_KERNELDATA*@size(segdesc),
					0,
					IDX_KERNELCODE*@size(segdesc),
					0,
					0,
					0,
					IDX_KERNELDATA*@size(segdesc),
					0,
					IDX_MEMORY*@size(segdesc),
					0,
					IDX_VIDEO*@size(segdesc),
					0,
					0,
					0,
					0,
					0,
					104
				];
of type:

Code: Select all

type tss_t:
	record;
		backlink: word;
		zeroes1: word;
		sp0: dword;
		ss0: word;
		zeroes2: word;
		sp1: dword;
		ss1: word;
		zeroes3: word;
		sp2: dword;
		ss2: word;
		zeroes4: word;
		cr3_reg: dword;
		eip_reg: dword;
		eflag_reg: dword;
		eax_reg: dword;
		ecx_reg: dword;
		edx_reg: dword;
		ebx_reg: dword;
		esp_reg: dword;
		ebp_reg: dword;
		esi_reg: dword;
		edi_reg: dword;
		es_reg: word;
		zeroes5: word;
		cs_reg: word;
		zeroes6: word;
		ss_reg: word;
		zeroes7: word;
		ds_seg: word;
		zeroes8: word;
		fs_seg: word;
		zeroes9: word;
		gs_seg: word;
		zeroes10: word;
		ldt_sel: word;
		zeroes11: word;
		debug: byte; //set lowest bit TRUE to raise a debug exception on a task switch
		zeroes12: byte;
		iomap_base: word;
	endrecord;
What the heck am I still doing wrong?

Posted: Thu Jan 31, 2008 2:24 am
by Combuster
Would you *please* try and look for the answer yourself. That includes searching on the error message in the bochs sources:

Code: Select all

// AR byte must specify TSS, else #TS(new TSS selector)
// new TSS must be busy, else #TS(new TSS selector)

Posted: Thu Jan 31, 2008 4:28 am
by sancho1980
I'll (try to) do my best. :-)

I didn't know the busy flag needs to be set, so that may be the cause. I'll try out at home. The AR byte is definitely correctly set.

But let me get that straight: The error message tells me "something is wrong with the TSS", but a look into the source code tells me, what this really wants to say is "something is wrong with the descriptor"???

That really put me on the wrong track!

Posted: Sat Feb 02, 2008 9:46 am
by sancho1980
hi

i *kind of* got the multitasking to work

i had to modify my clocl handler still a little bit, it now looks like this:

Code: Select all

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

begin clock_int;

	push(eax);
	push(ebx);

	pushfd();
	pop(eax);//popping the flags so I can set the NT bit

	or($4000,eax);

	push(eax);

	streg(ax);

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

		mov(IDX_TASK1TSS*@size(segdesc),kernelstate.backlink);
		mov(GDT_BASE,eax);
		add(IDX_TASK1TSS*@size(segdesc),eax);
		fseg: mov((type segdesc [eax]).props1,bl);
		or(%00000010,bl);
		fseg: mov(bl,(type segdesc [eax]).props1);

	elseif (ax = IDX_TASK2TSS*@size(segdesc)) then

		mov(IDX_TASK1TSS*@size(segdesc),task2state.backlink);
		mov(GDT_BASE,eax);
		add(IDX_TASK1TSS*@size(segdesc),eax);
		fseg: mov((type segdesc [eax]).props1,bl);
		or(%00000010,bl);
		fseg: mov(bl,(type segdesc [eax]).props1);
	else
		mov(IDX_TASK2TSS*@size(segdesc),task1state.backlink);
		mov(GDT_BASE,eax);
		add(IDX_TASK2TSS*@size(segdesc),eax);
		fseg: mov((type segdesc [eax]).props1,bl);
		or(%00000010,bl);
		fseg: mov(bl,(type segdesc [eax]).props1);
	endif;

	mov(PIC_EOI,al);

	out(al,PIC1_COMMAND);
	popfd();
	pop(ebx);

	pop(eax);

	iret();

end clock_int;
So the sequence should be as follows:

-Kernel executes, clock interrupts
-clock switches to task1
-task1 executes
-clock switches to task2
-task2 executes
-clock switches to task1
-task1 executes
-clock switches to...
and so on

task1 and task2 simply output 1's and 2's respectively so I can see who's currently executing:

Code: Select all

procedure task1;
begin task1;
	forever
		cli;
		putstring("1");
		sti;
	endfor;
end task1;
when i run this the following happens:

i quickly see many 1's, then many 2's, then it hangs

so the first switch from kernel to task1 and from task1 to task2 execute fine

i suspect the problem lies somehow with the fact that i first iret'ed from task1 to task2 and then try to iret from task2 to task1

i checked in the intel manuals and found out that whenever i iret from task1 to task2, the busy flag of task1 is cleared, so I put also code in the clock handler to set the busy flag of the task i want to iret to (as you can see above)

but the problem remains, system hangs when trying to switch back from task2 to task1

i also tried the other way round: have the kernel first switch to task2 and then to task1 and then to task2 and so on

the result was the same, only that now the system hung duiring task1, so the problem clearly seems to lie with the fact that i am iretting TO a task that i iretted FROM just before that...

i cannot provide an error message since i have to use virtualbox now..after i found out that bochs didnt properly handle expand down stack segments, i tried virtualbox where it ran fine..but now i have no debugger :-(