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?
Task Switching Doubt
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...
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...
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).
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.
yes, I'm doing software switching.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.
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.MarkOS wrote:yes, I'm doing software switching.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.
I set esp0 in the TSS only when the new task is an user task. Is that good?
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
- Kevin McGuire
- Member
- Posts: 843
- Joined: Tue Nov 09, 2004 12:00 am
- Location: United States
- Contact:
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.
I have no idea why I named it tPushPopAd, but never the less just:
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.
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.
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;
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...
}
; /////// 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;
.......
}
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;