Getting to Ring 3: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
(Removed JamesM ripoff)
No edit summary
Line 1: Line 1:

====Introduction====
====Introduction====
As fun as making a kernel and such is, eventually we all have to get outside the kernel to really appreciate and use it though. This involves getting to ring 3. Now, I'm sure all of us wish that 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:
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? -->
* Setup a barebones TSS with an ESP0 stack
* Set up a barebones TSS with an ESP0 stack. <!-- What is this? Why is it necessary? -->
* Setup an IDT entry for ring 3 system call interrupts(optionally actually)
* 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'm 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.
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's split into bit-fields)
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
// ...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'll tell the CPU where our TSS is:
// 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 can't get back to ring 0 for system calls or faults or even IRQs. That is where the TSS comes in.
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'll end up overwriting yourself.
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.