Page 1 of 2
I'm Multitasking! (not sure why :) )
Posted: Sun Oct 09, 2005 7:04 pm
by Cjmovie
Well, I considered it a longshot, but I made my kernel multi-task in 10 minutes
.
I simply installed a new IRQ handler for the PIT and set it to go at 100 hz.
Then, inside the IRQ handler, it switches what task it's on by changing values in the register state passed to it (as a pointer in the stack). That way, when I return to the ASM part of the Interrupt, it POPs the values into the real registers.
Anyways, as I'm guessing I'll have to ask a question to pertain, I've also the question I was going to ask about it halfway through:
It's multitasking fine for a few seconds (actually, in bochs, it's a lot less. Don't see it with your eyes). Then it pauses for around 1/4 a minute, and resets (guessing a triple fault, cause maybe by lack of stack space?).
The only thing is that I never get an exception from the CPU, real or bochs. What could cause that pause, and what would triple fault without any exception? I know I'm on a longshot here without any code to show, but if you need it, feel free to ask. Again, I'm asking for general help.
Re:I'm Multitasking! (not sure why :) )
Posted: Sun Oct 09, 2005 10:57 pm
by JoeKayzA
Hmm..
The pause could be a recursive loop, causing a stack overflow...and then triple fault. Is the stack in a place where it could overwrite parts of your IDT or page tables? (the latter one seems more likely to be the case) If so, try putting a guard page directly below stack space, then you can intercept this issue (and this is a good idea anyway in kernel mode)
Always check (and print) the new esp before you return to the next task, ensuring it is in a valid location. Check this out, if it doesn't help, some code would be interesting.
cheers Joe
Re:I'm Multitasking! (not sure why :) )
Posted: Mon Oct 10, 2005 12:52 am
by proxy
there is a problem with your multitasking code. When trying to figure out stack swapping i tried just what you are talking about and unfortunately it doesn't pan out in the long run. The problem is that while 7 of the general purpose registers will have your new state and CS/EIP/EFLAGS will too. ESP will not be correct for your new task. If you read the intel docs on a popa esp on the stack is NOT popped into the esp register (as this would make the popa fail further pops). So now the problem is that you are running code from task B with a stack from task A. If either of them have local vars on the stack...well consider them buh-by.
The way that i do it is really simple. My asm ISR handler calls a c/c++ function which does the actual work. This c/c++ function will return the esp of the next task. Then when i return to the asm stub, the next code loads eax into esp thus switching to the next tasks stack. Then as you planned all the general purpose registers look as they should for the new task.
It's super easy once you get your brain around it...until then well it's a little confusing, so good luck and I hope this helped.
proxy
Re:I'm Multitasking! (not sure why :) )
Posted: Mon Oct 10, 2005 9:20 am
by JoeKayzA
proxy wrote:
The problem is that while 7 of the general purpose registers will have your new state and CS/EIP/EFLAGS will too. ESP will not be correct for your new task. If you read the intel docs on a popa esp on the stack is NOT popped into the esp register (as this would make the popa fail further pops). So now the problem is that you are running code from task B with a stack from task A. If either of them have local vars on the stack...well consider them buh-by.
Eh - I don't really understand: Assuming you are pusha'ing task A's state to task A's stack (and save ESP seperately then), then switch stack to task B (that means: replace ESP with task B's saved ESP), and then popa the state again, you have task B's state as well as it's valid ESP. I can't see a problem here.. ???
cheers Joe
Re:I'm Multitasking! (not sure why :) )
Posted: Mon Oct 10, 2005 11:27 am
by proxy
JoeKayzA, what you described sound fine, but it NOT the same as Cjmovie described, read it carefully...
Then, inside the IRQ handler, it switches what task it's on by changing values in the register state passed to it (as a pointer in the stack). That way, when I return to the ASM part of the Interrupt, it POPs the values into the real registers.
he is not changing esp, he is merely changing the saved register values on the stack.
think of it like this: (this is psuedo code so not precise)
Code: Select all
int_00:
push es
push ds
push fs
push gs
pusha
call int_handler
popa
pop gs
pop fs
pop ds
pop es
iret
/*----------c code -----------*/
voit int_handler(context c) {
/* call scheduler */
c.eax = next->eax;
c.ebx = next->ebx;
c.ecx = next->ecx;
c.edx = next->edx;
/*etc etc*/
}
from what i read in his oriingal question, he is modifying the saved register state and NOT swapping tasks.
proxy
Re:I'm Multitasking! (not sure why :) )
Posted: Mon Oct 10, 2005 1:23 pm
by JoeKayzA
@proxy: You are right, I guess I'm already too accomodated to stack-based task state saving and switching.... ::)
cheers Joe
Re:I'm Multitasking! (not sure why :) )
Posted: Mon Oct 10, 2005 2:40 pm
by Cjmovie
To be exact, I am pushing all the registers in the ISR stub. Then a pointer to that area of the stack is pushed, which can be accessed by C code like a pointer to a structure. Then, after it changes the values in the stack, it returns to the ISR. The ISR then perfoms POPS for all the registers it earlier pushed, only the C code has changed them. That way the pop's actually 'install' the new task. If you wish to see code, you may below. It is actually super simple
.
Also, I would like to add that I tested it on my my working machine. It works perfectly, and ran for minutes without problem. I ended up turning it off, as I could see no trouble. So it appears that either my original *real* testbed is messed up or my new computer is more forgiving.
TaskSwitch.c:
Code: Select all
TaskState NextTask;
TaskState TaskList[2];
int CurrentTask = 0;
void SetTask(int a, TaskState b){
TaskList[a] = b;
}
void TaskSwitcher(RegStateExt* r){
if(CurrentTask == 0)CurrentTask = 1;
else CurrentTask = 0;
memcpy((UCHAR*)r, (UCHAR*)&TaskList[CurrentTask], 64);
}
void StartMultitask(){
IRQ_InstallHandler(0, TaskSwitcher);
}
This is the 'main.c' (really, it's Kernel.c). KernelEntry is where control is passed to it.
Code: Select all
void BeepThis(){
for(;;)PrintString("* TASK1! *");
}
void BeepThat(){
for(;;)PrintString("+ TASK2! +");
}
TaskState BeepThisTask = {
0x10, 0x10, 0x10, 0x10,
0x00, 0x00, 0x00, 0x100000, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
(UINT)BeepThis, 0x08, 0x00,
};
TaskState BeepThatTask = {
0x10, 0x10, 0x10, 0x10,
0x00, 0x00, 0x00, 0x200000, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
(UINT)BeepThat, 0x08, 0x00,
};
void KernelEntry(LoaderInfoBlock* argv){
int i, x;
int NextMsgLine = 0;
char a;
SetSystemMemory(16);
SetSystemBitmap(0x7E0000, 0xFFFE0000);
GDT_Init();
GDT_Install();
IDT_Install();
ISR_Install();
IRQ_Install();
PIT_Install();
Keyboard_Install();
EnableInterrupts();
ClearScreenTo(0x00);
PrintString("--DEMI OS-- (Version 0.0.5b)\n");
PrintString(" + Memory Page Manager\n");
PrintString(" + GDT\n");
PrintString(" + IDT\n");
PrintString(" + ISR\n");
PrintString(" + IRQ\n");
PrintString(" + Default Module: Console(CGA)\n\n");
PrintString("Time it took to get to here: 0x");
PrintHex(PIT_GetTime());
SetTask(0, BeepThisTask);
SetTask(1, BeepThatTask);
StartMultitask();
for(;;);
}
(Note: Some non-pertaining code has been ommited.)
ISR stub:
Code: Select all
;Common stub for all ISRs. Pushes all registers, then pointer to that structure
IsrStub:
pushad ;Push this stuff, in opposite order of memory (stack grows DOWN!)
push ds
push es
push fs
push gs
mov ax, 0x10 ;Kernel Data Segment!
mov ds, ax ;Restore data segment
mov es, ax
mov fs, ax
mov gs, ax
mov eax, esp ;Get stack pointer
push eax ;Push it. Pointer to structure formed in memory above
call _IsrEntry ;Call C handler
pop eax ;Pop back the standard registers
pop gs
pop fs
pop es
pop ds
popad
add esp, 8 ;Clean up pushed error code and ISR number
iret ;Return to interrupted whatever
Re:I'm Multitasking! (not sure why :) )
Posted: Mon Oct 10, 2005 2:44 pm
by Cjmovie
Almost forgot:
Code: Select all
typedef struct{
unsigned int gs, fs, es, ds;
unsigned int edi, esi, ebp, esp, ebx, edx, ecx, eax;
unsigned int IdNum, ErrorCode;
unsigned int eip, cs, eflags, useresp, ss;
} __attribute__ ((packed)) RegStateExt;
And:
Re:I'm Multitasking! (not sure why :) )
Posted: Mon Oct 10, 2005 8:50 pm
by proxy
Cjmovie, thank you for confirming my suspisions about your technique. But it is still broken for the reasons I gave. the popa instruction will NOT load the esp from your state. According to the intel docs, a popa is fuctionally equivalent to this:
Code: Select all
pop edi
pop esi
pop ebp
add $4, esp
pop ebx
pop edx
pop ecx
pop eax
do you see the problem now? you never load a new esp for your new task, which will firstly give you bad data for any stack variables in your tasks, and will in the long run lead the stack overflows since all your "threads" are sharing the same stack effectivly.
proxy
Re:I'm Multitasking! (not sure why :) )
Posted: Tue Oct 11, 2005 2:35 pm
by Cjmovie
Oooh, ya....
Intel doc's sometimes can be hard to read when your tired
.
Oh well, it's an easy fix at least! I just need to add one more push and change my structure around. I'll let you guys know how it turns out.
And I think I know why it worked on one real computer: It seemed to be timed, somehow, just perfect. Each time it finished a call to "PrintString" it switch. That way the stack was in essence 'clean' at the task switch, meaning it could do whatever. I noticed that the tasks never wrote characters right in between each other, in otherwords:
"*TASK1*+TASK2+*TASK1*+TASK2+"
instead of:
"*TAS+TASKK1**2++Task2Tas" etc.
Re:I'm Multitasking! (not sure why :) )
Posted: Tue Oct 11, 2005 3:38 pm
by Cjmovie
Cjmovie wrote:
Oooh, ya....
Intel doc's sometimes can be hard to read when your tired
.
Oh well, it's an easy fix at least! I just need to add one more push and change my structure around. I'll let you guys know how it turns out.
And I think I know why it worked on one real computer: It seemed to be timed, somehow, just perfect. Each time it finished a call to "PrintString" it switch. That way the stack was in essence 'clean' at the task switch, meaning it could do whatever. I noticed that the tasks never wrote characters right in between each other, in otherwords:
"*TASK1*+TASK2+*TASK1*+TASK2+"
instead of:
"*TAS+TASKK1**2++Task2Tas" etc.
Ya, right! Not so easy, is it me? lol
The main problem is that ESP has to be the LAST to be popped, so that the rest of the POPs don't pop on the wrong stack. But the processor, on returning from the interrupt (IRET) will do more pops. So I guess back to the 'drawing board'...
Time to get a TSS!
Re:I'm Multitasking! (not sure why :) )
Posted: Tue Oct 11, 2005 9:43 pm
by proxy
no no no, you are going about it all wrong. it really is simple.
Basically you want something like this (keep in mind psuedo code so non critical details may be omited)
Code: Select all
irq_00:
push %ds
push %es
push %fs
push %gs
pusha
mov $0x10, %ax
mov %ax, %ds
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
push %esp /*this allows the function to return the oriiginal esp if neccessary */
call IsrEntry /* this will return the stack kernel esp of the new task */
add $4, %esp /* counter that push esp */
mov %eax, %esp /* set esp to what _IsrEntry returned */
/* at this point we are running on the NEW esp, pop all the other registers */
popa
pop %gs
pop %fs
pop %es
pop %ds
add $8, %esp
iret
and the C code would look something like this:
Code: Select all
uint32_t IsrEntry(uint32_t old_esp) {
Thread *oldThread = current;
Thread *newThread = pickNextThread();
if(oldThread != newThread) {
/* update TSS's esp0 entry for new thread */
/* maybe update cr3 if neccessary */
return newThread->esp0;
} else {
return oldThread->esp0;
}
}
see, this way when you want to create a new thread, you simply allocate it's kernel stack and push what looks like your iret frame and you just add it to the scheduler.
a lot simpler than it sounds right?
proxy
Re:I'm Multitasking! (not sure why :) )
Posted: Wed Oct 12, 2005 12:44 am
by distantvoices
It's as proxy says: dead simple and straight forward once one's got the brain wrapped around.
Cjmovie: Don't modify registers in saved away stacks: simply switch the stackpointer to the new esp0 stack. Registers of the saved state are simply popped off by the isr stub, and at last you're back in your good old esp3 stack, eh?
look at this pseudo code, it describes what proxy says:
taskA ... running
Interrupt/schedule
isr handler called by stub returns TaskB.esp0 (that's stuffed into eax)
isrstub sets the new esp0 value by moving eax to esp
pop pop pop (we want popcorn) all the registers.
IRET -> pops off eflags,cs,eip,esp,ss
taskB ... running
Mark that with this method it doesn't matter to which task you return. you can as well resume taskA again.
stay safe.
Re:I'm Multitasking! (not sure why :) )
Posted: Wed Oct 12, 2005 2:33 pm
by Cjmovie
Ya, and if I follow, To create a task:
Allocate stack space for process
add SizeOfStack to that pointer (pointer is at start, stack grows down)
subtract 4 (size of one push, as we are on edge of next memory page if we don't)
Then do
*StackPointer-- = ThisValueThatWillNeedToBePopped.
etc. for each that will have to be popped.
And then just treat them all the same when task-switching
Re:I'm Multitasking! (not sure why :) )
Posted: Wed Oct 12, 2005 4:40 pm
by Cjmovie
Still having a little trouble. Here's my code:
Code: Select all
typedef struct{
UINT esp0;
UINT esp3;
} __attribute__ ((packed)) Task;
extern void ASM_TaskStub();
Task TaskList[25];
int CurrentTask = -1;
void MT_ScheduleThread(int a, void (*thread)()){
UINT *Stack;
TaskList[a].esp0 = (UINT)AllocPage()+4096; //Stack for when kernel is called
TaskList[a].esp3 = (UINT)AllocPage()+4096; //Stack for process itself
TaskList[a].esp0--; //Offset kernel stack into actual 4kb page
TaskList[a].esp3--; //Offset user/app stack into actual 4kb page
Stack = (UINT*)TaskList[a].esp3; //Get stack pointer for process
//IRET
*Stack-- = 0x0202; //EFLAGS
*Stack-- = 0x08; //CS
*Stack-- = (UINT)thread; //EIP
//PUSHa / POPa
*Stack-- = 0; //EAX
*Stack-- = 0; //ECX
*Stack-- = 0; //EDX
*Stack-- = 0; //EBX
*Stack-- = 0; //Temp/Unused
*Stack-- = 0; //EBP
*Stack-- = 0; //ESI
*Stack-- = 0; //EDI
//PUSH ds, es, fs, gs / POP gs, fs, es, ds
*Stack-- = 0x10; //DS
*Stack-- = 0x10; //ES
*Stack-- = 0x10; //FS
*Stack-- = 0x10; //GS
TaskList[a].esp3 = (UINT)Stack; //Update stack position in process info struct
}
UINT MT_SwitchTask(UINT esp){
if(CurrentTask != -1)
TaskList[CurrentTask].esp3 = esp;
else
CurrentTask = 0;
CurrentTask++;
if(CurrentTask > 1)CurrentTask = 0;
return TaskList[CurrentTask].esp3;
}
void MT_Start(){
IDT_InstallHandler(32, (UINT)ASM_TaskStub, 0x08, 0);
} /*32 is mapped to IRQ 0 */
ASM code (NASM syntax, not GAS):
Code: Select all
global _ASM_TaskStub
extern _MT_SwitchTask
;When called by an IRQ from processor, allows task switches
_ASM_TaskStub:
pushad ;Push all general purpose registers
push ds ;Push data segment
push es ;Push data segment 'e'
push fs ;Push data segment 'f'
push gs ;Push data segment 'g'
mov eax, esp ;Get current stack pointer
push eax ;Push it as argument to C function
call _MT_SwitchTask ;Call 'C code' task scheduler
add esp, 4 ;Clean up pushed argument to 'C code'
mov esp, eax ;Set new task's stack pointer. (EAX from C code return value)
pop gs ;Pop off data segment 'g'
pop fs ;Pop off data segment 'f'
pop es ;Pop off data segment 'e'
pop ds ;Pop off data segmemt
popad ;Pop all general purpose registers
iret ;Interrupt Return. Installs EIP etc. of new task
I'm getting from bochs:
fetch_raw_descriptor: LDTR.valid=0
Usually, as I find, that means invalid combination of pushes and pops. So, I'm guessing it has to do with me setting up the stack in the MT_ScheduleThread(). If in the C part of the switcher I just return the current ESP, it's fine (stays in kernel code).
Also, Both are ring0, so the CPU isn't pushing UserESP or SS.