Memory protection and switching to ring 3

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
anon19287473
Member
Member
Posts: 97
Joined: Thu Mar 15, 2007 2:27 pm

Memory protection and switching to ring 3

Post by anon19287473 »

To get started, I am writing a mono-tasking system (to learn PM) and I want to set up the kernel stuff (int's etc.) and then switch to ring 3 and run a userland app. I'm a little shaky on exactly how the memory protection works; I was thinking that I would add separate GDT entries to userland (for code, data, stack etc.) which couldn't address below 1MG (roughly where I think the kernel is located), which would stop the app from meddling w/ the kernel and allow it to safely return control the kernel when it is done. Is this corrent? Should I allow the kernel to address above 1MG? Any help greatly appreciated.

Happy hacking :)
jnc100
Member
Member
Posts: 775
Joined: Mon Apr 09, 2007 12:10 pm
Location: London, UK
Contact:

Post by jnc100 »

I suppose the answer to your question depends upon your answer to "do you ever wish to enable paging?" as most OSs rely on page-level rather than segment-level protection. You would then use a 5 entry gdt:

Code: Select all

null
code0  base 0x0, limit 0xffffffff
data0       "               "
code3       "               "
data3       "               "
Obviously your kernel needs to be able to write outside its own code and data space, as otherwise it would not be able to load userland programs to wherever they should go.

Regards,
John.
anon19287473
Member
Member
Posts: 97
Joined: Thu Mar 15, 2007 2:27 pm

Post by anon19287473 »

Thanks. And no, I don't plan to implement paging any time soon, I just don't see myself needing more than 4GB of memory :)

Also, I read somewhere that I should use a linear selector (to access memory for stuff) in my GDT also, is this necessary?
User avatar
mystran
Member
Member
Posts: 670
Joined: Thu Mar 08, 2007 11:08 am

Post by mystran »

The segment descriptors in GDT serve several purposes at the same time. There are theoretically separate descriptors for code, data and stack segments, but you can reuse the data-segment as a stack segment usually, because the "default" data-segment type happens to have the same descriptor that the "default" stack-segment type uses.

Regardless of the type, a segment descriptor gives the starting address and the segment size. Any access through a segment register with a given descriptor loaded, will first check the logical (requested) address against the segment size-limit, and if it's within the segment bounds, the starting address is added to get the linear address.

Linear address then is what paging uses, or if no paging is enabled, it simply becomes the physical address. Since paging is portable, and easier to deal with, most systems simply use a set of "dummy" segments: the limit is such that the whole 4GB of linear addresses can be referenced (effectually there is no limit) and the base address is 0, so the logical and linear address become the same.

The other purpose the descriptors serve, is to control which ring you are executing your code in (CPL in CS) and from which ring are you allowed to access the segments (DPL in DS/SS). So even with dummies, one needs a duplicate set of descriptors so that one can have on ring0 CS, one ring0 DS for code running with the ring0 CS to use, and then one ring3 CS, and one ring3 DS for the code running in ring3 to use.

If you have paging, such a setup is all you need, because you can then futher limit access in your page tables. One of the advantages of paging is that the memory addresses allowed for your code doesn't need to continuous (there could be pages not mapped between pages mapped) and it's easy to give a program more memory (just map more pages in). This also makes it possible to organize process memory like this:

Code: Select all

  CODE|DATA|BSS (heap)--------break | unmapped | STACK
When heap needs to be grown, all that needs to be done is to move the break futher, and map some of the unmapped memory. But not only can heap be grown, you can also grow stack (backwards) by mapping more pages, as long as you have some unmapped memory in between.

With segmentation that's also possible (assuming you use separate DS and SS descriptors) but a compiler like GCC which assumes that SS and DS (and CS for that matter) co-exists (addresses inside one equal addresses inside another) will not work.
I was thinking that I would add separate GDT entries to userland (for code, data, stack etc.) which couldn't address below 1MG (roughly where I think the kernel is located), which would stop the app from meddling w/ the kernel and allow it to safely return control the kernel when it is done.
Yeah, that's possible to do, but the disadvantage is that you need to convert between addresses the userland sees, and the addresses your kernel sees. I think even for single tasking system, it's easier to just use flat segments and paging. All the complications of non-continuous physical memory, memory mapped IO regions, and what not, can be deal with when you initialize the paging system, and you can then map whatever normal free page at whatever address, with whatever access is necessary, without having to worry about system details.
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.
Post Reply