tss on stack???
tss on stack???
hi all, ok im back and ready for multi-tasking, i understand that tss is for storing a tasks state but how? do i, at a switch, push all registers on a stack pointing to my tss table, then move stack pointer to new table, pop registers + iret???, thx
Re:tss on stack???
Hi,
For hardware task switching you need one TSS for each task (and some GDT entries, called "task gates" pointing to them).
For software tasks switching you only need one TSS that's shared by all tasks (and mostly unused), with one task gate in the GDT.
You might find this page useful while you're deciding:
http://www.osdev.org/osfaq2/index.php/C ... 0Switching
Cheers,
Brendan
First, decide if you're going to use hardware task switching or software task switching.GLneo wrote:hi all, ok im back and ready for multi-tasking, i understand that tss is for storing a tasks state but how? do i, at a switch, push all registers on a stack pointing to my tss table, then move stack pointer to new table, pop registers + iret???, thx
For hardware task switching you need one TSS for each task (and some GDT entries, called "task gates" pointing to them).
For software tasks switching you only need one TSS that's shared by all tasks (and mostly unused), with one task gate in the GDT.
You might find this page useful while you're deciding:
http://www.osdev.org/osfaq2/index.php/C ... 0Switching
Cheers,
Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Re:tss on stack???
i'm going for software task switching, but what do you mean 1 place to store all those redgisters???
Re:tss on stack???
When you do software task switching, you use the TSS only to tell the processor what kernel stack pointer and segment to use when it makes a ring3->ring0 transition. You save registers elsewhere.GLneo wrote: i'm going for software task switching, but what do you mean 1 place to store all those redgisters???
Re:tss on stack???
Hi,
Forget about the TSS for now, and think of a normal function or sub-routine that modifies all of the CPUs registers - what does it do? It just pushes everything on the stack. For software task switching, we do the same thing.
Conceptually, at the start of the task switch you push everything on the stack, then you change stacks (to a different task's stack), and then you pop everything off of the new tasks stack.
Of course nothing is this simple in real life. First, you need somewhere to store each task's ESP, so that you can switch from one stack to another. It's not the only information the OS needs to keep for each task, so it's best to have some form of "Task Structure" that describes the task, and store ESP in that.
Next, some (or maybe all) tasks have different address spaces, which means that you need to store CR3 somewhere. Usually the task's stack can only be accessed when the task's address space is in use, so you couldn't store CR3 on the tasks stack (you'd need CR3 to get CR3 off of the stack again). This is easy to fix - put CR3 in that "Task Structure", just like ESP.
Then, for reasons probably best left for later, it's a good idea to store the CPU's FPU/MMX/SSE/SSE2 state in the "Task Structure" instead of on the stack, because this allows the FPU/MMX/SSE/SSE2 state saving/loading to be postponed or even skipped if some tasks don't use FPU/MMX/SSE/SSE2 instructions. I recommend that you ignore the FPU/MMX/SSE/SSE2 state for now - it just confuses things and it's easy to add to a working scheduler later.
Combining all of this (but ignoring FPU/MMX/SSE/SSE2 state), you end up with something like:
You may have noticed that saving and restoring the segment registers on the stack is optional. Because of all the protection checks loading a segment register is slow, which is one of the reasons why most people use a flat memory model (so data segment registers never need to be changed). Even if you do allow the segment registers to be changed you must remember that the task switching code is within the kernel. If CPL=3 code uses different segment registers they are often changed to segments that the kernel expects when CPL=3 code enters the CPL=0 kernel, which is what happens automatically for the CPL=3 CS and SS anyway. For these reasons, for almost all OS's, none of the segment registers need to be saved/restored during a task switch.
[continued]
Ahh - with software task switching you don't store all the registers in the TSS.GLneo wrote:i'm going for software task switching, but what do you mean 1 place to store all those redgisters???
Forget about the TSS for now, and think of a normal function or sub-routine that modifies all of the CPUs registers - what does it do? It just pushes everything on the stack. For software task switching, we do the same thing.
Conceptually, at the start of the task switch you push everything on the stack, then you change stacks (to a different task's stack), and then you pop everything off of the new tasks stack.
Of course nothing is this simple in real life. First, you need somewhere to store each task's ESP, so that you can switch from one stack to another. It's not the only information the OS needs to keep for each task, so it's best to have some form of "Task Structure" that describes the task, and store ESP in that.
Next, some (or maybe all) tasks have different address spaces, which means that you need to store CR3 somewhere. Usually the task's stack can only be accessed when the task's address space is in use, so you couldn't store CR3 on the tasks stack (you'd need CR3 to get CR3 off of the stack again). This is easy to fix - put CR3 in that "Task Structure", just like ESP.
Then, for reasons probably best left for later, it's a good idea to store the CPU's FPU/MMX/SSE/SSE2 state in the "Task Structure" instead of on the stack, because this allows the FPU/MMX/SSE/SSE2 state saving/loading to be postponed or even skipped if some tasks don't use FPU/MMX/SSE/SSE2 instructions. I recommend that you ignore the FPU/MMX/SSE/SSE2 state for now - it just confuses things and it's easy to add to a working scheduler later.
Combining all of this (but ignoring FPU/MMX/SSE/SSE2 state), you end up with something like:
Code: Select all
switch_tasks:
pushad
pushfd
cli
<push segment registers if necessary>
<store ESP in the previous task's Task Structure>
<get new task's CR3 from it's Task Structure>
<change address spaces if necessary>
<get new task's ESP from it's Task Structure>
<switch to the new task's stack>
<pop segment registers if necessary>
popfd
popad
ret
[continued]
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Re:tss on stack???
[continued]
So why do you need a single TSS? The problem is that when the CPU changes from CPL=3 to CPL=0 it changes from the CPL=3 stack to a CPL=0 stack (and for 32 bit protected mode there is no way to avoid it). The CPU is hardwired to load the CPL=0 stack from the TSS (specifically the "SS0" and "ESP0" fields in the TSS).
Therefore (if you support CPL=3 code) you must have one TSS where the only fields that are actually used are the "SS0" and "ESP0" fields. If you also allow CPL=1 or CPL=2 code (for e.g. for device drivers or something) then you must also use the corresponding "SS1", "ESP1", "SS2" and "ESP2" fields in the TSS, but most people do not use these privelge levels and can skip them.
Because each task has it's own CPL=0 stack, you need to correct the ESP0 field in the TSS during the task switch. The "SS0" field is usually set once during boot and left alone after that (it's the same for all of the kernel's stacks). I guess I should also point out that the "ESP0" value in the TSS should not be confused with ESP - ESP0 is always be the address for the top of the stack, where ESP (when you're running at CPL=0) rarely equals the address of the top of the stack.
This gives something like:
Each task's "Task Structure" would contain the following items:
I usually make the "Task Structure" 4 KB big and put the task's CPL=0 stack in it. It means each "kernel" stack is less than 3.5 KB and you'd need to be carefull with your kernel's code (don't use more stack than you've got), but for me this is easy because I'm doing a micro-kernel written in assembly. For a monolithic kernel in C++ you'd probably want much larger kernel stacks.
Cheers,
Brendan
So why do you need a single TSS? The problem is that when the CPU changes from CPL=3 to CPL=0 it changes from the CPL=3 stack to a CPL=0 stack (and for 32 bit protected mode there is no way to avoid it). The CPU is hardwired to load the CPL=0 stack from the TSS (specifically the "SS0" and "ESP0" fields in the TSS).
Therefore (if you support CPL=3 code) you must have one TSS where the only fields that are actually used are the "SS0" and "ESP0" fields. If you also allow CPL=1 or CPL=2 code (for e.g. for device drivers or something) then you must also use the corresponding "SS1", "ESP1", "SS2" and "ESP2" fields in the TSS, but most people do not use these privelge levels and can skip them.
Because each task has it's own CPL=0 stack, you need to correct the ESP0 field in the TSS during the task switch. The "SS0" field is usually set once during boot and left alone after that (it's the same for all of the kernel's stacks). I guess I should also point out that the "ESP0" value in the TSS should not be confused with ESP - ESP0 is always be the address for the top of the stack, where ESP (when you're running at CPL=0) rarely equals the address of the top of the stack.
This gives something like:
Code: Select all
switch_tasks:
pushad
pushfd
cli
<push segment registers if necessary>
<store ESP in previous task's task structure>
<get new task's CR3 from it's task structure>
<change address spaces if necessary>
<get new task's top of CPL=0 stack address from it's task structure>
<set ESP0 in the TSS for the new task>
<get new task's ESP from it's task structure>
<switch to the new task's stack>
<pop segment registers if necessary>
popfd
popad
ret
- ESP
ESP0
CR3
A FPU/MMX/SSE/SSE2 state save area (512 bytes IIRC)
I usually make the "Task Structure" 4 KB big and put the task's CPL=0 stack in it. It means each "kernel" stack is less than 3.5 KB and you'd need to be carefull with your kernel's code (don't use more stack than you've got), but for me this is easy because I'm doing a micro-kernel written in assembly. For a monolithic kernel in C++ you'd probably want much larger kernel stacks.
Cheers,
Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Re:tss on stack???
thx, very informative , i think i got the idea:
but how do i put it together???, what do i do at a switch???
Code: Select all
struct task_state
{
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
unsigned int edi;
unsigned int esi;
unsigned int cs;
unsigned int eflags;
unsigned int eip;
unsigned int esp;
};
struct task_data
{
unsigned char name[32]
unsigned int esp;
unsigned int time;
unsigned int priority;
struct task_state *state;
};
unsigned int create_task(void (*function)(), unsigned int priority)
{
struct task_data *new_task = (struct task_data *)malloc(sizeof(struct task_data));
new_task[0].esp = getesp();
new_task[0].time = 10;
new_task[0].task_priority = priority;
new_task[0].state = (struct task_state *)malloc(sizeof(struct task_state));
new_task[0].state->eax = 0;
new_task[0].state->ebx = 0;
new_task[0].state->ecx = 0;
new_task[0].state->edx = 0;
new_task[0].state->edi = 0;
new_task[0].state->esi = 0;
new_task[0].state->cs = 0x8;
new_task[0].state->eflags = 0x202;
new_task[0].state->eip = (unsigned)function;
new_task[0].state->esp = (unsigned int)new_task[0].state;
return (unsigned int) new_task;
}
Re:tss on stack???
Hi,
In assembly this is 2 instructions - "mov <somewhere>,esp" and "mov esp,<somewhere>". Of course what "<somewhere>" is will depend on your OS, but for you it looks like it would be the "esp" field in the "task_data" structures.
I'm no C expert, but I doubt it's possible to do the task switch without some (inline?) assembly because C doesn't allow the stack (or ESP) to be manually messed with.
You would also need to be very careful with C's stack frames, input parameters and local variables (unless you do the entire task switch routine in assembly). The reason for this is that the input parameters and local variables will be on the previous task's stack, which you'd stop using half way through the task switch.
To complicate this the compiler's optimizer may store some of these values in the CPUs registers, so that after switching stacks you won't know which input parameters and local variables are still current, and you won't be able to rely on tested behaviour because it could change every time the code is changed (or the compiler/optimizer's settings, or the compiler itself).
Cheers,
Brendan
The key to doing the task switch is swapping from one task's stack to another task's stack (everything else, like changing address spaces, etc, can be added to this after it works).GLneo wrote:but how do i put it together???, what do i do at a switch???
In assembly this is 2 instructions - "mov <somewhere>,esp" and "mov esp,<somewhere>". Of course what "<somewhere>" is will depend on your OS, but for you it looks like it would be the "esp" field in the "task_data" structures.
I'm no C expert, but I doubt it's possible to do the task switch without some (inline?) assembly because C doesn't allow the stack (or ESP) to be manually messed with.
You would also need to be very careful with C's stack frames, input parameters and local variables (unless you do the entire task switch routine in assembly). The reason for this is that the input parameters and local variables will be on the previous task's stack, which you'd stop using half way through the task switch.
To complicate this the compiler's optimizer may store some of these values in the CPUs registers, so that after switching stacks you won't know which input parameters and local variables are still current, and you won't be able to rely on tested behaviour because it could change every time the code is changed (or the compiler/optimizer's settings, or the compiler itself).
Cheers,
Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
-
- Member
- Posts: 1600
- Joined: Wed Oct 18, 2006 11:59 am
- Location: Vienna/Austria
- Contact:
Re:tss on stack???
indeed you need some assembly in order to switch stacks.
Go here to find an example of how it can be done:
http://www.distantvoices.org/html/multitasking.html
Hope this helps. I'm lazy in that article about other needed nitty gritty because I just concentrate on multitasking stuff.
important: you don't need to reload cr3 upon each end every task switch, only at those where new cr3 != old cr3. If you're doing your threads the fine way, you can increase overall performance quite nice.
Go here to find an example of how it can be done:
http://www.distantvoices.org/html/multitasking.html
Hope this helps. I'm lazy in that article about other needed nitty gritty because I just concentrate on multitasking stuff.
important: you don't need to reload cr3 upon each end every task switch, only at those where new cr3 != old cr3. If you're doing your threads the fine way, you can increase overall performance quite nice.
... the osdever formerly known as beyond infinity ...
BlueillusionOS iso image
BlueillusionOS iso image
- Colonel Kernel
- Member
- Posts: 1437
- Joined: Tue Oct 17, 2006 6:06 pm
- Location: Vancouver, BC, Canada
- Contact:
Re:tss on stack???
And this is why you don't use a do_task_switch() function if you're writing your OS in C.Brendan wrote:You would also need to be very careful with C's stack frames, input parameters and local variables (unless you do the entire task switch routine in assembly). The reason for this is that the input parameters and local variables will be on the previous task's stack, which you'd stop using half way through the task switch.
To complicate this the compiler's optimizer may store some of these values in the CPUs registers, so that after switching stacks you won't know which input parameters and local variables are still current, and you won't be able to rely on tested behaviour because it could change every time the code is changed (or the compiler/optimizer's settings, or the compiler itself).
beyond_infinity's example is pretty close to what I do -- do task switching in the asm stubs that surround every ISR.
Top three reasons why my OS project died:
- Too much overtime at work
- Got married
- My brain got stuck in an infinite loop while trying to design the memory manager
Re:tss on stack???
OK, my kernel is based on bkerndev, so for isr i have:
so i dont know if that counts as surrounding the irq???
and if so what do i switch around???, thx
Code: Select all
_irq0:
cli
push byte 0
push byte 32
jmp irq
Code: Select all
irq:
pusha
push ds
push es
push fs
push gs
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov eax, esp
push eax
mov eax, _irq_handler
call eax
pop eax
pop gs
pop fs
pop es
pop ds
popa
add esp, 8
iret
Code: Select all
void irq_handler(struct registers *r)
{
void (*handler)(struct registers *r);
handler = irq_pointers[r->int_no - 32];
if (handler)
{
handler(r);
}
if (r->int_no >= 40)
{
outport(0xA0, 0x20);
}
outport(0x20, 0x20);
}
Code: Select all
void timer_handler()
{
if(cur_task_time() > 0)
{
dec_cur_task_time();
if(cur_task_time() < 1)
schedule();
}
}
and if so what do i switch around???, thx
Re:tss on stack???
Hmm... I have a question. What are you going to do if you schedule() and the EOI code never gets executed?
Re:tss on stack???
End Of Interrupt
irq 0-7 -> outb (0x20, 0x20);
irq 8-f -> outb (0x20, 0x20); and outb (0xa1, 0x20);
irq 0-7 -> outb (0x20, 0x20);
irq 8-f -> outb (0x20, 0x20); and outb (0xa1, 0x20);
Re:tss on stack???
all schedule() does is deturmin what task gets run next, then returns
does EOI
Then when it returns back though:
it should pop registers and iret, the question is what should schedule() contain???
Code: Select all
if (r->int_no >= 40)
{
outport(0xA0, 0x20);
}
outport(0x20, 0x20);
}
Then when it returns back though:
Code: Select all
irq:
pusha
push ds
push es
push fs
push gs
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov eax, esp
push eax
mov eax, _irq_handler
call eax
pop eax
pop gs
pop fs
pop es
pop ds
popa
add esp, 8
iret