I'm Multitasking! (not sure why :) )
I'm Multitasking! (not sure why :) )
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.
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 :) )
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
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 :) )
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
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 :) )
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.. ???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.
cheers Joe
Re:I'm Multitasking! (not sure why :) )
JoeKayzA, what you described sound fine, but it NOT the same as Cjmovie described, read it carefully...
think of it like this: (this is psuedo code so not precise)
from what i read in his oriingal question, he is modifying the saved register state and NOT swapping tasks.
proxy
he is not changing esp, he is merely changing the saved register values on the stack.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.
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 :) )
@proxy: You are right, I guess I'm already too accomodated to stack-based task state saving and switching.... ::)
cheers Joe
cheers Joe
Re:I'm Multitasking! (not sure why :) )
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:
This is the 'main.c' (really, it's Kernel.c). KernelEntry is where control is passed to it.
(Note: Some non-pertaining code has been ommited.)
ISR stub:
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);
}
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(;;);
}
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 :) )
Almost forgot:
And:
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;
Code: Select all
typedef RegStateExt TaskState;
Re:I'm Multitasking! (not sure why :) )
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:
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
Code: Select all
pop edi
pop esi
pop ebp
add $4, esp
pop ebx
pop edx
pop ecx
pop eax
proxy
Re:I'm Multitasking! (not sure why :) )
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.
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 :) )
Ya, right! Not so easy, is it me? lolCjmovie 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.
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 :) )
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)
and the C code would look something like this:
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
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
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;
}
}
a lot simpler than it sounds right?
proxy
-
- Member
- Posts: 1600
- Joined: Wed Oct 18, 2006 11:59 am
- Location: Vienna/Austria
- Contact:
Re:I'm Multitasking! (not sure why :) )
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.
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.
... the osdever formerly known as beyond infinity ...
BlueillusionOS iso image
BlueillusionOS iso image
Re:I'm Multitasking! (not sure why :) )
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
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 :) )
Still having a little trouble. Here's my code:
ASM code (NASM syntax, not GAS):
I'm getting from bochs:
Also, Both are ring0, so the CPU isn't pushing UserESP or SS.
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 */
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
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).fetch_raw_descriptor: LDTR.valid=0
Also, Both are ring0, so the CPU isn't pushing UserESP or SS.