Page 1 of 1
Problem with task switching
Posted: Mon Oct 16, 2006 1:01 am
by satyajit
OK. I'm doing something really stupid here.
Have coded an OS (in GNU gas assembler and C) to switch from the kernel code to a task. The task is represented as a regulation TSS descriptor in the GDT, which in turns points to the tss structure (in standard IA-32 tss format) for the new task. Both the task and the kernel share the same GDT entries, one for system code, one for system data (32bit, max limit, zero base, 0 DPL). Everything seems to be loading correctly. Have checked that umpteen times.
The problem is that the system crashes on the lcall. (Actually, the real problem is my poor knowledge of gas syntax for operands.)
Tried to load an empty TSS so that the processor has a place to dump the out-going cpu status stuff. Did that with:
__asm__ __volatile__ ("movw %0, %%ax\n"
"ltr &&ax" : : "m" (selector) );
where selector is the GDT index of the "empty" TSS (i.e., pointing to the empty tss) multiplied by 8. That may or may not be working correctly since I don't know how to read the TR.
So the question is: What's going on? What really is the syntax in gas for doing the silly lcall and ljmp task switches? I have tried every possible permutation I can think of. Also, is there a possibility of something else being wrong? Have tried
call_task(unsigned int gdt_index)
{
unsigned int sel[2];
sel[0] = 0x0
sel[1[ = gdt_index << 3;
__asm__ __volatile__ ( "lcall *(%0)" : : "p" ((unsigned int)sel) );
}
This works in a tutirial that I have, but still crashes in my code. Also it above makes no sense to me.
Would really appreciate help here! Thanks.
Posted: Mon Oct 16, 2006 6:21 pm
by Mikae
I think that you can not use empty selector for TR. When you are trying to switch to another task, using 'call' or 'jmp' instruction, one of CPU requirements is that TR must contain correct selector, pointing to TSS segment. I think that this is your problem.
I don't know AT&T syntax too, but you can switch to intel syntax, as
chase mentioned:
here.
Posted: Tue Oct 17, 2006 6:01 am
by satyajit
Thanks. Every bit helps.
Loaded the TR with LTR so that the TR points initially to the empty TSS. Then checked the TR with STR, and yeah, the TR is pointing correctly. Then I do the lcall to another TSS holding the task, and bang! The systems still crashes. GDT is good and GDTR loaded correctly. All format are as per IA-32 specs. Memory allocated ok. Selector is correct.
If I understand Intel's manual correctly, the operand to lcall is just the selector (i.e., the GDT indes * 8 for DPL 0). But there seems to a bit of vaguness in the manual. Getting a bit frustrated on this.
Just checked out Chase's suggestion about using Intel format with inline gas. Will give it a try. Thanks again!
Posted: Tue Oct 17, 2006 8:45 am
by JAAman
also:
"crashes my code" isnt very discriptive
i assume that it is tripple-faulting? but on which exception
hard-switching is very complicated, and can fail for many reasons (which is one reason most people use soft-switching)
the intel manuals list which exceptions are caused at which points in the task-switch, but you didnt say what exceptions were being generated
Posted: Tue Oct 17, 2006 11:26 pm
by satyajit
Thanks for the input. Sorry for being vague.
The system works fine until it execiutes the lcall, at which time the machine reboots, symptomatically a triple fault.
From the Intel manual (Vol 2A), the problem may be most likely: "If the segment descriptor pointed to by the segment selector in the destination operand is not for a conforming-code segment, nonconforming-code segment, call gate, task gate, or task state segment." In other words, a bad operand.
From Intel manual (Vol 3). "The processor transfers execution to another task. . . (when) . . .The current program, task, or procedure executes a JMP or CALL instruction to a TSS descriptor in the GDT. . . (i.e., it). . obtains the TSS segment selector for the new task as the operand of the JMP or CALL instruction" So it seems that the operand should just be the segment selectory for the TSS (i.e., its GDT index times eight). This gives the truple faullt.
(FYI, the segment selector for the task's TSS is 0x30 and for the TSS pointing to the emty tss struct is 0x28. TR is pointing to 0x28 before lcall)
Posted: Wed Oct 18, 2006 12:52 am
by Brendan
Hi,
satyajit wrote:From the Intel manual (Vol 2A), the problem may be most likely: "If the segment descriptor pointed to by the segment selector in the destination operand is not for a conforming-code segment, nonconforming-code segment, call gate, task gate, or task state segment." In other words, a bad operand.
IMHO this probably isn't the problem - judging purely by what you've described already, I think you've got this right.
The CPU also does some messing about with the busy bit (in the GDT entry), a "nested task" bit (in eflags) and the backlink field (in the TSS). This is what I'm assuming isn't right.
First, you're using the "far call" instruction - why?
Most of the time "far jump" is used, because when far call is used the CPU leaves the busy bit set, then does the task switch, then sets the NT (nested task) flag, the busy flag and the backlink for the new task. This mostly means that the new task needs to do a "far return" to return the CPU back to the calling task (and clear it's NT flag). For far call, the CPU leaves the caller's busy flag set to prevent recursive calls.
If you use far jump instead, then the CPU clears the busy flag, does the task switch, then sets the busy flag in the new task. There is no need for the new task to return to the old task - each task can jump to any other task without problems.
The other thing you'd need to be careful of is a task switching to itself - this never works. If it's possible for a task to switch to itself (likely with most schedulers I've seen) then you need to detect it and skip the task switch. For example:
Code: Select all
if( new_task != current_task ) {
current_task = new_task;
do_far_jump( new_task );
}
BTW if you do use far jumps, it will look funny because the instruction after the jump will be the first instruction that is executed when the CPU starts running the task again. For example:
Code: Select all
task1:
<stuff>
jmp second_task:_ignored_
<more stuff>
jmp task1 ; <- endless loop
It can look strange, but it works and you do get used to it after a little while....
Cheers,
Brendan
Posted: Thu Oct 19, 2006 10:54 pm
by satyajit
Thanks Brendan. You really know your stuff. My hat is off to you. Your comments were a tremendous help to us.
Ok. Have been checking all the flags, access bits, etc. So far, everything seems to be as it should be. In the GDT entry pointing to the empty TSS, attribute bits 47-40 before the LTR are 0x89. After the LTR, 0x8B. This means that the system is finding the entry correclty and setting its Busy bit. Attribute bits 55-52 are all zero before and after the LTR. The NT flag, before and after the LTR, remains zero which is right because this dummy task is not nested. So the search continues.
Thanks again.