Hi...
I know when processor is running in ring 0 that means
it is for system use,and when running in ring 3 that
means
it's for user aplication am I right?
let me tell you that the only thing I know about ring
levels so would someone explain to me about them
and tell me how can i switch between ring 0, ring 3
and what i need to do that?TSS, software multitasking,
whatever...
also any links,tutorials would be so helpfull.
Thanx.
ring 0 ring 3 what about them?
- Combuster
- Member
- Posts: 9301
- Joined: Wed Oct 18, 2006 3:45 am
- Libera.chat IRC: [com]buster
- Location: On the balcony, where I can actually keep 1½m distance
- Contact:
Originally, Ring 0 is meant for kernel use, and ring 3 is for userspace. The main difference between various rings is the amount of exerted control over the running program: 0 means a minimal amount of control, 1 and 2 means some control, 3 means maximal control. If you want to, lets say, run your kernel in ring 3 and userspace in ring 0, you are free to do so. (wether it makes sense is another matter), For comparison, there exist kernels that use the intermediate rings, or run everything in ring 0
There are 4 common ways of changing between rings:
- 'directed' instructions: far jump, far call, far return, iret, sysexit, sysret (these will generally only allow you to go to lower-privileged rings)
- 'trap' instructions: int, syscall, sysenter (these are meant to allow controlled access to higher-privilege rings)
- exceptions
- external interrupts
I suggest you read the intel manuals on how these work. (you really should have a copy)
There are 4 common ways of changing between rings:
- 'directed' instructions: far jump, far call, far return, iret, sysexit, sysret (these will generally only allow you to go to lower-privileged rings)
- 'trap' instructions: int, syscall, sysenter (these are meant to allow controlled access to higher-privilege rings)
- exceptions
- external interrupts
I suggest you read the intel manuals on how these work. (you really should have a copy)
- Happy Egghead
- Member
- Posts: 32
- Joined: Thu Oct 19, 2006 5:25 am
- Location: Adelaide, Australia
- Contact:
Hello,
I am just starting to add task switching to my kernel and have decided to go with software task switching as recommended in several threads.
One thing I have found is that regardless of what ring you are in, on a task switch you will ALWAYS be in ring0. Therefore you only need one set of code to do the switching.
As an example I have this code pointed to by the timer interrupt :-
Then the reschedule code :-
The tasks are stored in a linked list and all the reschedular does is save the ESP of the current task to the current task slot and load a new one from another. It also checks to see if a new page directory needs to be loaded.
The code's definately not cut-and-pastable into your own OS (due to massive dependancies/defines etc) but this should give you a fair idea of what is needed to support Ring0 + Ring3 tasks and threads.
All my tasks / system services are/will be ring 3. Only Device Drivers will run at ring 0. Anyway....
On the subject of interrupt gates etc, each one has a certain level of access, so the timer interrupt gate I made in the IDT would have a ring 0 interrupt gate pointing to the shedular interrupt code.
A syscall / kernel trap used by an application would still point to kernel code but have a ring 3 interrupt gate allowing the ring 3 application to access it. The processor switches to ring 0 automatically, switches to the stack defined in a/the TSS (see below) automatically, and begin running ring 0 code.
Finally, for the task switching described only one TSS is needed and it only needs one field - ESP0 set, pointing to where the kernel stack is when you switch from ring0 to ring3. I cheat by aiming ESP0 to the end of the tasks process table so regardless of priviledge level, they all look the same to me.
Yes this is a long post but I figured if I got it all out my head while I'm thinking about this stuff, it helps me get it right in my mind. And it wouldn't be too bad if it helped you too eh?
I am just starting to add task switching to my kernel and have decided to go with software task switching as recommended in several threads.
One thing I have found is that regardless of what ring you are in, on a task switch you will ALWAYS be in ring0. Therefore you only need one set of code to do the switching.
As an example I have this code pointed to by the timer interrupt :-
Code: Select all
scheduler_irq_handler:
cld
pushad
push ds
push es
push fs
push gs
mov ax, SYSTEM_DATA_SELECTOR
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp ;Push pointer to all the stuff we just pushed
call reschedule ;Call C code
mov esp, eax ;Replace the stack with what the C code gave us
pop gs
pop fs
pop es
pop ds
popad ;Put the standard registers back - Switch to another task
iretd
Code: Select all
uint_t reschedule(uint_t OldESP)
{
#ifdef debug_scheduler
kprintf("RESCHEDULER\n");
#endif
// update that timer thing
// technically this should be adjusted for the speed of the processor
timer_ticks++;
#ifdef debug_scheduler
kprintf("current task_pointer = 0x%X.\n", (uint_t)current_task_pointer);
kprintf("OldESP = 0x%X.\n", OldESP);
#endif
// common for all
if (system_started)
{
current_task_pointer->kernel_stack_top = OldESP;
// bill the task
current_task_pointer->times_process_scheduled++;
}
else
{
system_started = 1;
// but don't save the stupid pointer to knowhere
}
#ifdef debug_scheduler
kprintf("kernel_stack_top = 0x%X.\n", current_task_pointer->kernel_stack_top);
#endif
if (current_task_pointer->process_next_ready != NIL_PROC)
{
#ifdef debug_scheduler
kprintf("There's a task on the ready task list - switching!\n");
kprintf("old task_pointer = 0x%X.\n", (uint_t)current_task_pointer);
#endif
current_task_pointer = current_task_pointer->process_next_ready;
#ifdef debug_scheduler
kprintf("new task_pointer = 0x%X.\n", (uint_t)current_task_pointer);
#endif
}
else
{
#ifdef debug_scheduler
kprintf("No more tasks on the task list - going to beginning again.\n");
kprintf("old task_pointer = 0x%X.\n", (uint_t)current_task_pointer);
#endif
current_task_pointer = process_ready_list;
#ifdef debug_scheduler
kprintf("new task_pointer = 0x%X.\n", (uint_t)current_task_pointer);
#endif
}
#ifdef debug_scheduler
kprintf("kernel_stack_top = 0x%X.\n", current_task_pointer->kernel_stack_top);
kprintf("should be the same as what we entered with!.\n");
#endif
outb(0x20,0x20); // ack the timer interrupt
// skip context switching for Device Driver's
if (current_task_pointer->process_type == PT_DEVICE_DRIVER)
return(current_task_pointer->kernel_stack_top); // new ESP0 of process
// we have a new process_pointer that isn't a Device Driver, do we have to change the context???
// not necessary for threads though as they share the previous context as their parent
if (current_task_pointer->process_page_directory != current_task_page_directory)
{
current_task_page_directory = current_task_pointer->process_page_directory;
write_cr3((uint_t*)current_task_page_directory);
// a new task also needs it's kernel stack pointer in the TSS set correctly to it knows where
// to store data later
// point it to the end of the process table
process_tss_p->esp0 = (current_task_pointer->kernel_stack_top & 0xFFFFF000) + 0xFFC;
}
// Done
return(current_task_pointer->kernel_stack_top); // new ESP0 of process
}
The code's definately not cut-and-pastable into your own OS (due to massive dependancies/defines etc) but this should give you a fair idea of what is needed to support Ring0 + Ring3 tasks and threads.
All my tasks / system services are/will be ring 3. Only Device Drivers will run at ring 0. Anyway....
On the subject of interrupt gates etc, each one has a certain level of access, so the timer interrupt gate I made in the IDT would have a ring 0 interrupt gate pointing to the shedular interrupt code.
A syscall / kernel trap used by an application would still point to kernel code but have a ring 3 interrupt gate allowing the ring 3 application to access it. The processor switches to ring 0 automatically, switches to the stack defined in a/the TSS (see below) automatically, and begin running ring 0 code.
Finally, for the task switching described only one TSS is needed and it only needs one field - ESP0 set, pointing to where the kernel stack is when you switch from ring0 to ring3. I cheat by aiming ESP0 to the end of the tasks process table so regardless of priviledge level, they all look the same to me.
Yes this is a long post but I figured if I got it all out my head while I'm thinking about this stuff, it helps me get it right in my mind. And it wouldn't be too bad if it helped you too eh?
Hi,
I do something very similar, except my interrupt also handles the tick counter. I only need eax for this, so I just push eax and nothing else.
After this, I check whether or not it is time to reschedule (still in the asm handler). Not until this point do I acually restore eax, do the pusha, call the scheduler and then iret.
This means that (at the moment), 9 times out of 10, the interrupt handler does not call the C code. I am still playing around with the exact intervals to get it right.
This way of doing it adds very minimal overhead to the tick counter (there is much less overhead than if you were doing the tick count in C!), and the task registers do not get pushed unless I am actually switching to a new task.
Cheers,
Adam
I do something very similar, except my interrupt also handles the tick counter. I only need eax for this, so I just push eax and nothing else.
After this, I check whether or not it is time to reschedule (still in the asm handler). Not until this point do I acually restore eax, do the pusha, call the scheduler and then iret.
This means that (at the moment), 9 times out of 10, the interrupt handler does not call the C code. I am still playing around with the exact intervals to get it right.
This way of doing it adds very minimal overhead to the tick counter (there is much less overhead than if you were doing the tick count in C!), and the task registers do not get pushed unless I am actually switching to a new task.
Cheers,
Adam
- Happy Egghead
- Member
- Posts: 32
- Joined: Thu Oct 19, 2006 5:25 am
- Location: Adelaide, Australia
- Contact:
Hi, apologies for co-opting thread with multitasking talk,
I can see how your interrupt works by checking the time to run and only switching when needed.
My multitasker is basically round-robin at the moment with a task switch every clock tick (120 times per second) giving me an accuracy of 1/120 of a second (roughly/kind of). As such the "timer_ticks++" in my reschedular is a count of how many times the timer has fired since the OS has started.
If the timer interrupt fires a task switch MUST occur.
My interrupt code does not at any time allow the timer interrupt to be disabled. The reasoning is, if a task breaks then another task will always be available (hopefully to remove the bad one). Pre-emption and therefore control over processes is enforced.
My code has a task list consisting of tasks ready to run (process_ready_list) and processes can be 'readied' multiple times on the list by a yield(to process) function.
So let's say your database app needs to load data, it can setup the transaction and then yieldto(file_manager). This ends the time-slice for the database and gives the file_manager a higher priority (its on the list twice).
If another app also wants file access it can yieldto(file_manager) for a third time giving the file_manager more CPU time, not necessarily runnning consecutively.
As a result everything is still "round-robin" but with variable timeslices (nice processes yielding unused CPU time to others) and prioritization (services are prioritized based on application demands). If apps want more graphics they get more graphics, if they want more network they get more network and so forth. The other services are there they just aren't as important in the eyes of the apps.
Of course the OS can run a serialize_run_list() to remove multiple instances of services when things get out of hand. A service that realizes it has nothing to do can serialize itself giving more time to apps.
That's the plan anyway. I just have to write the floppy driver, the file system server, the network driver, interface code etc. etc. etc..........
I can see how your interrupt works by checking the time to run and only switching when needed.
My multitasker is basically round-robin at the moment with a task switch every clock tick (120 times per second) giving me an accuracy of 1/120 of a second (roughly/kind of). As such the "timer_ticks++" in my reschedular is a count of how many times the timer has fired since the OS has started.
If the timer interrupt fires a task switch MUST occur.
My interrupt code does not at any time allow the timer interrupt to be disabled. The reasoning is, if a task breaks then another task will always be available (hopefully to remove the bad one). Pre-emption and therefore control over processes is enforced.
My code has a task list consisting of tasks ready to run (process_ready_list) and processes can be 'readied' multiple times on the list by a yield(to process) function.
So let's say your database app needs to load data, it can setup the transaction and then yieldto(file_manager). This ends the time-slice for the database and gives the file_manager a higher priority (its on the list twice).
If another app also wants file access it can yieldto(file_manager) for a third time giving the file_manager more CPU time, not necessarily runnning consecutively.
As a result everything is still "round-robin" but with variable timeslices (nice processes yielding unused CPU time to others) and prioritization (services are prioritized based on application demands). If apps want more graphics they get more graphics, if they want more network they get more network and so forth. The other services are there they just aren't as important in the eyes of the apps.
Of course the OS can run a serialize_run_list() to remove multiple instances of services when things get out of hand. A service that realizes it has nothing to do can serialize itself giving more time to apps.
That's the plan anyway. I just have to write the floppy driver, the file system server, the network driver, interface code etc. etc. etc..........