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;