Task Switching Doubt

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
Jeko
Member
Member
Posts: 500
Joined: Fri Mar 17, 2006 12:00 am
Location: Napoli, Italy

Task Switching Doubt

Post by Jeko »

I have a doubt about task switching.

If I switch from a kernel task to an user task, I must change esp0 in the TSS to the kernel stack.
If I switch from a kernel task to another kernel task, I must change nothing within the TSS.
If I switch from an user task to a kernel stack, I must change nothing within the TSS.
And if I switch from an user task to another user task?
User avatar
prajwal
Member
Member
Posts: 154
Joined: Sat Oct 23, 2004 11:00 pm
Contact:

Post by prajwal »

in my understanding, user task runs at lower privilage level (say ring 3) than kernel task which runs at higher privilage level (say ring 0). So, when user task switches to kernel task (usually thru call gate), the stack pointer is changed to corresponding esp(x) from TSS of user task. That is,
if the switch is from ring 3 to ring 2 task, then esp2 of ring 3 task TSS is used for esp, similarly switch to ring1 -> esp1 and ring0 -> esp0.

Further, I would like to make a point that there are 2 kinds of switches.
1-> where in the entire TSS of the next process is used. that is, either the TR register is changed to point to a different TSS Selector or the existing TSS (one currently being pointed by the TR register) is replaced by the next process's TSS

2-> When one task calls / enters into one more task through gates. Use of esp(x) in the current task will come into picture in this kind of switch.

When switches among tasks of same privilage level happens, then the current tasks esp is used in the next process...
User avatar
mystran
Member
Member
Posts: 670
Joined: Thu Mar 08, 2007 11:08 am

Post by mystran »

If you are doing software switching (like it seems) and only ever use ring3/ring0 code (for kernel/user split) then you only have to check that before you return to userspace, you set esp0 in TSS to correct value for the current thread. Assuming your userthreads each have a kernel stack, ofcourse.

Easiest thing is to have a common entry point into kernel, through which everything goes, and have that take care of such issues. The downside is that you more or less need an indirect jump (or actually in my case it's call).
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
User avatar
Jeko
Member
Member
Posts: 500
Joined: Fri Mar 17, 2006 12:00 am
Location: Napoli, Italy

Post by Jeko »

mystran wrote:If you are doing software switching (like it seems) and only ever use ring3/ring0 code (for kernel/user split) then you only have to check that before you return to userspace, you set esp0 in TSS to correct value for the current thread. Assuming your userthreads each have a kernel stack, ofcourse.
yes, I'm doing software switching.
I set esp0 in the TSS only when the new task is an user task. Is that good?
User avatar
mystran
Member
Member
Posts: 670
Joined: Thu Mar 08, 2007 11:08 am

Post by mystran »

MarkOS wrote:
mystran wrote:If you are doing software switching (like it seems) and only ever use ring3/ring0 code (for kernel/user split) then you only have to check that before you return to userspace, you set esp0 in TSS to correct value for the current thread. Assuming your userthreads each have a kernel stack, ofcourse.
yes, I'm doing software switching.
I set esp0 in the TSS only when the new task is an user task. Is that good?
Well, the only difference with simply saving it every time is that it saves at least one memory access when you switch between kernel tasks.
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
User avatar
Kevin McGuire
Member
Member
Posts: 843
Joined: Tue Nov 09, 2004 12:00 am
Location: United States
Contact:

Post by Kevin McGuire »

If you wanted a quick and dirty way to switch tasks you could always do just push the current registers to the stack, switch stacks, and pop the new registers out.

Use a structure like this to setup the stack.

Code: Select all

struct __attribute__((__packed__)) tPushPopAd{
        /// Allows a thread to start from a interrupt return instruction, and exit to a retfunc with retfuncarg as the argument.
        /// Arg is the argument for the kernel thread to take.
	&u32 edi, esi, ebp, esp, ebx, edx, ecx, eax, eip;
	u32 cs;
        u32 eflags, retfunc, arg, retfuncarg;
}; typedef struct tPushPopAd PUSHPOPAD; typedef PUSHPOPAD* PPUSHPOPAD;
I have no idea why I named it tPushPopAd, but never the less just:

Code: Select all

void schedTaskEnd(u32 argument){
        /// handle removing task from scheduler...
        /// (never return from this function)
        for(;;);
}
int schedTaskCreate(...){
        PPUSHPOPAD *ppad;
        /// allocate some stack space
        ppad = (PPUSHPOPAD)(function to get some space);
        ppad->esp = (u32)ppad;
        ppad->cs = THE_USER_CS;
        ppad->eflags = 0x200;  // enable interrupts
        ppad->eip = ENTRYPOINT;
        ppad->ebp = ppad->esp;
        ppad->retfunc = &schedTaskEnd
        ppad->retfuncarg = <some identification of this task/thread whatever>
        /// insert it into the scheduler's whatever linked list.. array...
}
Write out some assembler code.



; /////// first entry //////
; You might like to make the scheduler just load the first task..
; ////// normal entry /////
; Save the current task by using a pushad or some other mechanism.
; ////// load next task /////
; You might want to load the appropriate segments. (ss, ds)
; These might be stored in you're task structure.

popad ; pop the general purpose registers
iretd ; interrupt return which will pop eip, cs, and eflags.

; the retfunc and retfuncarg will be remaining on the stack



You might like to change the ss and ds to represent user segments since the cs is changed using the iretd instruction. You would most likely not have to save them but instead just load them.

If you used a call gate or interrupt for a system call you can just iretd, and it will pop the user level cs from the stack when you exit it. When entering into the system call change ss and ds to reflect the kernel privledge level and change esp to a kernel stack from you're task structure.

Code: Select all

struct aExampleTaskStructure{
     unsigned int espKernel;
     .......
}
And as I have though of it you might just push SS and DS onto the stack during a task save and pop them for a task load so that the scheduler needs to know little about the transition from user and kernel space.

Code: Select all

struct __attribute__((__packed__)) tPushPopAd{
        /// Allows a thread to start from a interrupt return instruction, and exit to a retfunc with retfuncarg as the argument.
        /// Arg is the argument for the kernel thread to take.
	&u32 edi, esi, ebp, esp, ebx, edx, ecx, eax, 
        /// Set these to comply with privilege transition state.
        u32 ss, ds;
        u32 eip, cs, eflags, retfunc, arg, retfuncarg;
}; typedef struct tPushPopAd PUSHPOPAD; typedef PUSHPOPAD* PPUSHPOPAD;
Post Reply