Getting to Ring 3: Difference between revisions
[unchecked revision] | [unchecked revision] |
(Removed JamesM ripoff) |
No edit summary |
||
Line 1: | Line 1: | ||
====Introduction==== |
====Introduction==== |
||
As fun as making a kernel |
As fun as making a kernel is, eventually we have to get outside the kernel into userspace. This involves getting from ring 0 to ring 3. I am sure all of us wish we could just make a GDT entry and - poof - ring 3 works, but Intel wants us to pull our hair out at least a little with their [[Task State Segment]]. So, in order to get to ring 3 we must do the following: |
||
* Get 2 new GDT entries(at least) configured for ring 3. |
* Get 2 new GDT entries (at least) configured for ring 3. <!-- Why? --> |
||
* |
* Set up a barebones TSS with an ESP0 stack. <!-- What is this? Why is it necessary? --> |
||
* |
* Set up an IDT entry for ring 3 system call interrupts (optional, actually). <!-- Again, please add some more info to make this a tutorial instead of a copy & paste template. --> |
||
====Requirements==== |
====Requirements==== |
||
I |
I am not going to go through making a whole kernel that can get to ring 3. I will assume you have a decent and usable ring 0 GDT and IDT, along with being able to handle IRQs properly. I also assume you will be multitasking, and so will cover switching ring 3 > ring 0 (switch task) > ring 3. |
||
====GDT==== |
====GDT==== |
||
This is my GDT struct I will be using(it |
This is my GDT struct I will be using (it is split into bit-fields) |
||
<pre> |
<pre> |
||
struct gdt_entry_bits |
struct gdt_entry_bits |
||
Line 17: | Line 16: | ||
unsigned int limit_low:16; |
unsigned int limit_low:16; |
||
unsigned int base_low : 24; |
unsigned int base_low : 24; |
||
//attribute byte split into bitfields |
// attribute byte split into bitfields |
||
unsigned int accessed :1; |
unsigned int accessed :1; |
||
unsigned int read_write :1; //readable for code, writable for data |
unsigned int read_write :1; // readable for code, writable for data |
||
unsigned int conforming_expand_down :1; //conforming for code, expand down for data |
unsigned int conforming_expand_down :1; // conforming for code, expand down for data |
||
unsigned int code :1; //1 for code, 0 for data |
unsigned int code :1; // 1 for code, 0 for data |
||
unsigned int always_1 :1; //should be 1 for everything but TSS and LDT |
unsigned int always_1 :1; // should be 1 for everything but TSS and LDT |
||
unsigned int DPL :2; //priveledge level |
unsigned int DPL :2; // priveledge level |
||
unsigned int present :1; |
unsigned int present :1; |
||
//and now into granularity |
// and now into granularity |
||
unsigned int limit_high :4; |
unsigned int limit_high :4; |
||
unsigned int available :1; |
unsigned int available :1; |
||
unsigned int always_0 :1; //should always be 0 |
unsigned int always_0 :1; // should always be 0 |
||
unsigned int big :1; //32bit opcodes for code, dword stack for data |
unsigned int big :1; // 32bit opcodes for code, dword stack for data |
||
unsigned int gran :1; //1 to use 4k page addressing, 0 for byte addressing |
unsigned int gran :1; // 1 to use 4k page addressing, 0 for byte addressing |
||
unsigned int base_high :8; |
unsigned int base_high :8; |
||
} __packed; //or __attribute__((packed)) |
} __packed; //or __attribute__((packed)) |
||
Line 36: | Line 35: | ||
We will be doing a simple setup, and I will assume you will later implement paging in your OS, so we will use only 2 ring 3 segments both with base of 0 and limit of 0xFFFFFFFF so we will setup our two GDT segments like this: |
We will be doing a simple setup, and I will assume you will later implement paging in your OS, so we will use only 2 ring 3 segments both with base of 0 and limit of 0xFFFFFFFF so we will setup our two GDT segments like this: |
||
<pre> |
<pre> |
||
// |
// ...insert your ring 0 segments here or whatever |
||
gdt_entry_bits *code; |
gdt_entry_bits *code; |
||
gdt_entry_bits *data; |
gdt_entry_bits *data; |
||
//I assume your ring 0 segments are in gdt[1] and gdt[2] (0 is null segment) |
// I assume your ring 0 segments are in gdt[1] and gdt[2] (0 is null segment) |
||
code=(void*)&gdt[3]; //gdt is a static array of gdt_entry_bits or equivalent |
code = (void*)&gdt[3]; // gdt is a static array of gdt_entry_bits or equivalent |
||
data=(void*)&gdt[4]; |
data = (void*)&gdt[4]; |
||
code->limit_low=0xFFFF; |
code->limit_low = 0xFFFF; |
||
code->base_low=0; |
code->base_low = 0; |
||
code->accessed=0; |
code->accessed = 0; |
||
code->read_write=1; //make it readable for code segments |
code->read_write = 1; // make it readable for code segments |
||
code->conforming=0; //don't worry about this.. |
code->conforming = 0; // don't worry about this.. |
||
code->code=1; //this is to signal its a code segment |
code->code=1; // this is to signal its a code segment |
||
code->always_1=1; |
code->always_1 = 1; |
||
code->DPL=3; //set it to ring 3 |
code->DPL = 3; // set it to ring 3 |
||
code->present=1; |
code->present = 1; |
||
code->limit_high=0xF; |
code->limit_high = 0xF; |
||
code->available=1; |
code->available = 1; |
||
code->always_0=0; |
code->always_0 = 0; |
||
code->big=1; //signal it's 32 bits |
code->big = 1; // signal it's 32 bits |
||
code->gran=1; //use 4k page addressing |
code->gran = 1; // use 4k page addressing |
||
code->base_high=0; |
code->base_high = 0; |
||
*data=*code; //copy it all over, cause most of it is the same |
*data = *code; // copy it all over, cause most of it is the same |
||
data->code=0; //signal it's not code; so it's data. |
data->code = 0; // signal it's not code; so it's data. |
||
install_tss(&gdt[5]); //we'll implement this function later... |
install_tss(&gdt[5]); // we'll implement this function later... |
||
//...go on to install GDT segments and such |
// ...go on to install GDT segments and such |
||
//after those are installed we |
// after those are installed we will tell the CPU where our TSS is: |
||
flush_tss(); //implement this later |
flush_tss(); // implement this later |
||
</pre> |
</pre> |
||
Ok, so now we have our two user mode segments. Now technically, we can get to user mode right now with these two segments. The problem is we |
Ok, so now we have our two user mode segments. Now technically, we can get to user mode right now with these two segments. The problem is we cannot get back to ring 0 for system calls or faults or even IRQs. That is where the TSS comes in. |
||
====Multitasking considerations==== |
====Multitasking considerations==== |
||
There are a lot of subtle things with user mode and task switching that you may not realize at first. First: Whenever a system call interrupt happens, the first thing that happens is the CPU changes to ESP0 stack. Then, it will push all the system information. So when you enter the interrupt handler, your working off of the ESP0 stack. This could become a problem with 2 ring 3 tasks going if all you do is merely push context info and change esp. Think about it. you will change the esp, which is the esp0 stack, to the other tasks esp, which is the same esp0 stack. So, what you must do is change the ESP0 stack(along with the interrupt pushed ESP stack) on each task switch, or you |
There are a lot of subtle things with user mode and task switching that you may not realize at first. First: Whenever a system call interrupt happens, the first thing that happens is the CPU changes to ESP0 stack. Then, it will push all the system information. So when you enter the interrupt handler, your working off of the ESP0 stack. This could become a problem with 2 ring 3 tasks going if all you do is merely push context info and change esp. Think about it. you will change the esp, which is the esp0 stack, to the other tasks esp, which is the same esp0 stack. So, what you must do is change the ESP0 stack (along with the interrupt pushed ESP stack) on each task switch, or you will end up overwriting yourself. |
||
[[Category:Tutorials]] |
[[Category:Tutorials]] |
Revision as of 08:04, 9 June 2009
Introduction
As fun as making a kernel is, eventually we have to get outside the kernel into userspace. This involves getting from ring 0 to ring 3. I am sure all of us wish we could just make a GDT entry and - poof - ring 3 works, but Intel wants us to pull our hair out at least a little with their Task State Segment. So, in order to get to ring 3 we must do the following:
- Get 2 new GDT entries (at least) configured for ring 3.
- Set up a barebones TSS with an ESP0 stack.
- Set up an IDT entry for ring 3 system call interrupts (optional, actually).
Requirements
I am not going to go through making a whole kernel that can get to ring 3. I will assume you have a decent and usable ring 0 GDT and IDT, along with being able to handle IRQs properly. I also assume you will be multitasking, and so will cover switching ring 3 > ring 0 (switch task) > ring 3.
GDT
This is my GDT struct I will be using (it is split into bit-fields)
struct gdt_entry_bits { unsigned int limit_low:16; unsigned int base_low : 24; // attribute byte split into bitfields unsigned int accessed :1; unsigned int read_write :1; // readable for code, writable for data unsigned int conforming_expand_down :1; // conforming for code, expand down for data unsigned int code :1; // 1 for code, 0 for data unsigned int always_1 :1; // should be 1 for everything but TSS and LDT unsigned int DPL :2; // priveledge level unsigned int present :1; // and now into granularity unsigned int limit_high :4; unsigned int available :1; unsigned int always_0 :1; // should always be 0 unsigned int big :1; // 32bit opcodes for code, dword stack for data unsigned int gran :1; // 1 to use 4k page addressing, 0 for byte addressing unsigned int base_high :8; } __packed; //or __attribute__((packed))
We will be doing a simple setup, and I will assume you will later implement paging in your OS, so we will use only 2 ring 3 segments both with base of 0 and limit of 0xFFFFFFFF so we will setup our two GDT segments like this:
// ...insert your ring 0 segments here or whatever gdt_entry_bits *code; gdt_entry_bits *data; // I assume your ring 0 segments are in gdt[1] and gdt[2] (0 is null segment) code = (void*)&gdt[3]; // gdt is a static array of gdt_entry_bits or equivalent data = (void*)&gdt[4]; code->limit_low = 0xFFFF; code->base_low = 0; code->accessed = 0; code->read_write = 1; // make it readable for code segments code->conforming = 0; // don't worry about this.. code->code=1; // this is to signal its a code segment code->always_1 = 1; code->DPL = 3; // set it to ring 3 code->present = 1; code->limit_high = 0xF; code->available = 1; code->always_0 = 0; code->big = 1; // signal it's 32 bits code->gran = 1; // use 4k page addressing code->base_high = 0; *data = *code; // copy it all over, cause most of it is the same data->code = 0; // signal it's not code; so it's data. install_tss(&gdt[5]); // we'll implement this function later... // ...go on to install GDT segments and such // after those are installed we will tell the CPU where our TSS is: flush_tss(); // implement this later
Ok, so now we have our two user mode segments. Now technically, we can get to user mode right now with these two segments. The problem is we cannot get back to ring 0 for system calls or faults or even IRQs. That is where the TSS comes in.
Multitasking considerations
There are a lot of subtle things with user mode and task switching that you may not realize at first. First: Whenever a system call interrupt happens, the first thing that happens is the CPU changes to ESP0 stack. Then, it will push all the system information. So when you enter the interrupt handler, your working off of the ESP0 stack. This could become a problem with 2 ring 3 tasks going if all you do is merely push context info and change esp. Think about it. you will change the esp, which is the esp0 stack, to the other tasks esp, which is the same esp0 stack. So, what you must do is change the ESP0 stack (along with the interrupt pushed ESP stack) on each task switch, or you will end up overwriting yourself.