Page 1 of 1
Context switching (and then some)
Posted: Fri Jan 11, 2008 11:50 pm
by adw3221
Hello all,
I'm developing a portable microkerenel and I'm stumped on the context switching. Now I got the basic idea but you guys being expirence war veterns on this stuff I thought I might post it here.
This is what I got going: (for the x86)
- 1.) Timer fires. CPU recives and looks up IDT, switches rings and calls my ISR
2.) ISR disables interrupts (for single CPU right now) saves the registers.
3.) Call the kernel code to save the registers in its own data structure
4.) Do some scheduling and the like
5.) Put the registers of the new thread (of whatever task) into their apporate registers
6.) Do an iret (to the new EIP)
First few questions: When exactly do I save the stack for the thread and the kernel? How can I avoid the TSS? Saving the registers, what would be the quickest and most practicle way of doing it?
(I just want to get it right the first time)[/code]
Posted: Sat Jan 12, 2008 2:37 am
by ucosty
You can't avoid the TSS if you want Ring3 support.
Use stack switching. It is simple and works nicely. Browse my SVN source (link in sig) for an idea.
Posted: Sat Jan 12, 2008 3:44 am
by adw3221
Alright. I was kinda leaning towards stack switching anyways.
How many TSS's will I need? I read I can just use one for task switching but I fail to see that. Maybye two?
Posted: Sat Jan 12, 2008 5:05 am
by ucosty
one per cpu
edit: you don't invoke the tss directly. you just store the kernel stack for the process you are switching to in it.
Posted: Sat Jan 12, 2008 7:15 am
by Jef
when irq0 fires, save immediately all registers (and of course the stack), do what ever you want for scheduling and then restore the registers of new task.
Then do an IRet.
Posted: Sat Jan 12, 2008 7:47 am
by ucosty
Jef wrote:when irq0 fires, save immediately all registers (and of course the stack), do what ever you want for scheduling and then restore the registers of new task.
Then do an IRet.
During the ISR you just push all the registers on to the stack. That way they are saved along with the stack data and you only need to manipulate the esp address.
Posted: Sat Jan 12, 2008 9:30 am
by Jef
ucosty wrote:Jef wrote:when irq0 fires, save immediately all registers (and of course the stack), do what ever you want for scheduling and then restore the registers of new task.
Then do an IRet.
During the ISR you just push all the registers on to the stack. That way they are saved along with the stack data and you only need to manipulate the esp address.
yes its more faster but (i think) if a driver or application or whatever damage the stack, the OS will crash. if you save in vars, you can kill the task that creates the exception without missing the other tasks data.
something like this:
Code: Select all
movzx eax, byte [CurrentProcess]
shl eax, 8 ;*256
add eax, dword [ProcessArray]
;save old data
mov dword [eax+tss.status], 0 ;not busy
mov dword [eax+tss.esp], esp
mov dword [eax+tss.ebx], ebx
mov dword [eax+tss.ecx], ecx
mov dword [eax+tss.edx], edx
mov dword [eax+tss.esi], esi
mov dword [eax+tss.edi], edi
mov dword [eax+tss.ebp], ebp
mov ebx, dword [tmp_eax]
mov dword [eax+tss.eax], ebx
mov ebx, cr3
mov dword [eax+tss.cr3], ebx
mov word [eax+tss.ds], ds
mov word [eax+tss.es], es
mov word [eax+tss.fs], fs
mov word [eax+tss.gs], gs
mov word [eax+tss.ss], ss
pushfd
pop ebx
mov dword [eax+tss.eflags], ebx
.find_next:
movzx eax, byte [CurrentProcess]
inc eax
cmp eax, 2 ;max processes
jne .ok
.init_multitask:
mov eax, 0
.ok
mov byte [CurrentProcess], al
shl eax, 8 ;*256
add eax, dword [ProcessArray]
cmp dword [eax+tss.status], 1 ;busy
je .find_next
mov ebx, dword [eax+tss.eax]
mov dword [tmp_eax], ebx ;keep eax for the end
push dword [eax+tss.eflags]
popfd
mov ebx, dword [eax+tss.cr3]
mov cr3, ebx
mov esi, dword [eax+tss.esi]
mov edi, dword [eax+tss.edi]
mov ebp, dword [eax+tss.ebp]
mov ebx, dword [eax+tss.ebx]
mov ecx, dword [eax+tss.ecx]
mov edx, dword [eax+tss.edx]
mov esp, dword [eax+tss.esp]
mov ds, word [eax+tss.ds]
mov es, word [eax+tss.es]
mov fs, word [eax+tss.fs]
mov gs, word [eax+tss.gs]
mov ss, word [eax+tss.ss]
mov dword [eax+tss.status], 1
mov eax, dword [tmp_eax]
this code works (almost good). I have not finished the multitasking code.
Posted: Sat Jan 12, 2008 10:32 am
by ucosty
Jef wrote:yes its more faster but (i think) if a driver or application or whatever damage the stack, the OS will crash. if you save in vars, you can kill the task that creates the exception without missing the other tasks data.
Not true. If your app screws up it's own stack (multitasking irrespective) it WILL CRASH. That being said the only Ring0 stack data is the kernel stack stuff pushed on by the ISR. That stack contents (that matter) for task switching are all set by the OS and not the app. There is no potential for for app to crash the OS. Seriously.
edit: And to add, the stack in question is the Kernel Stack. For Ring 3 tasks, you (usually) have a seperate kernel stack for each process. The kernel stack is independant of the actual process stack and is used for Ring 0 bits (like ISR's). The user code can't contaminate the processes' kernel stack.
Posted: Sat Jan 12, 2008 4:27 pm
by Jef
yes but if running in ring0 ?
maybe you are right, i am not so sure.
anyway, i think that my code is the one way, and the stack (you say) is the other way. You can choose,... and the result will be the same (for multitasking)
Posted: Sun Jan 13, 2008 6:59 pm
by elderK
You realize Mike, that you don't need to disable Interrupts yourself, right?
Unless the gate in the IDT is a TRAP Gate, the processor itself will automatically inhibit interrupts. You can learn more about this from the Intel System Developer's Manuals.
If you are clever, you will need only one Task state segment. However, it all depends on the grand design of your microkernel.
How micro is your kernel? How engrained is the notion of everything being a seperate process?
Some people seem to forget that process' do not need to be in different address spaces or privileges to be considered a seperate process. In my current project (z3-apoc : a proof of concept), The core kernel contains the scheduler, low level ipc and memory management - these are all running in seperate threads - they are all considered seperate processes.
There is no 'Kernel' as such. There is a cooperating group of lowlevel processes, these together form the core kernel.
You only need one TSS as you can dynamically update it's contents at will. If you want to switch the stack that dpl#3 will use, when the TSS next comes into effect (ie: when you return from interrupt ...), just change the TSS' esp2 field. If the next task requires a different address space, then update the cr3 field in the TSS too.
As for speed, Save only when you have to save and save only what you must.
Your ISRs should be as small as possible and optimized for utmost speed, especially the one servicing the timer. You dont want to end up with a high interrupt latency.
Hope this helps,
~Zeii.