OS paging in multiprocess environnement

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
MagicalTux
Posts: 22
Joined: Mon Dec 04, 2006 5:34 pm

OS paging in multiprocess environnement

Post by MagicalTux »

Hello,

I'm having some troubles getting paging working with many process.

Basically I need each process to have its own "view" of the physical memory, so they won't be able to access kernel's memory.

I tried to create many pde tables, and attach each one to one process, but in the end I had various problems. Basically I couldn't manage to get the system switch to a new PDE (for example when an exception happens) and I also need to be able to switch to a new stack *before* entering the interrupt. Eg. if I get an interrupt because the process' stack is full, I get a memory interrupt, need to enter kernel stack, then allocate an extra block of memory for the process.
I tried to fake it, but couldn't manage to get it working well.

Basically if anyone can provide a sample code, or some explanations on stuff like that, it'd be welcome.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: OS paging in multiprocess environnement

Post by Brendan »

Hi,
MagicalTux wrote:Basically I need each process to have its own "view" of the physical memory, so they won't be able to access kernel's memory.
Are you sure? Most OSs give each process it's own view of memory (it's own linear address space), but map the kernel into each address space the same so that all processes have the same view of the kernel's memory. Then they use protection to prevent processes from accessing anything in the kernel's part of the address space (e.g. set the "supervisor" flag in page directory entries and run processes at CPL=3).

The only real reason not to do things like this is if you want to give processes (almost) 4 GB of the address space (instead of splitting the address space, with 3 GB for processes and 1 GB for the kernel for e.g.).
MagicalTux wrote:I tried to create many pde tables, and attach each one to one process, but in the end I had various problems. Basically I couldn't manage to get the system switch to a new PDE (for example when an exception happens) and I also need to be able to switch to a new stack *before* entering the interrupt.
If you want to run the kernel in it's own 4 GB address space (with almost 4 GB of normal address spaces used by the processes), then you still need a little bit of kernel code in each address space to switch between the kernel's address space and the process' address space. In general this is bad for performance (2 complete TLB flushes for every system call) and most processes don't need more than 1 GB of space anyway.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
User avatar
AJ
Member
Member
Posts: 2646
Joined: Sun Oct 22, 2006 7:01 am
Location: Devon, UK
Contact:

Post by AJ »

Hi,

The following outlines one fairly common way of doing it - you may prefer something else:
  • * When you create a process, create a new page table - this just needs to be a copy of your existing kernel-space table. Map the kernel to 3GB (0xC0000000), for argument's sake.
    * Map enough physical RAM in to the correct location in your *new* page directory to load youe executable.
    * Load and relocate your executable image.
    * Whenever you switch tasks, load CR3 with the new page directory (remember you are using a *physical* pointer for this.
    * As kernel-space is mapped in to the same location in every page directory, you can run interrupts *without* switching CR3.
Of course, if your interrupt needs to terminate the user process, you will have to switch back to the kernel's CR3 before doing this!

Make sure that you map the kernel with the User Mode bit *cleared* and user-space programs with this bit set. This will protect your kernel from errant user code. This means you have to use interrupts (or SYSENTER et. al.) to access kernel services. You will also, further down the line, need some method of sharing memory spaces between processes.

I hope this helps,
Adam

[EDIT] The IA32 manuals also suggest having your kernel in a separate 4MB page with the global flag turned on. This will avoid flushing kernel code every time you switch memory space and the 4MB page TLB is separate from the 4KB TLB, meaning that you don't waste TLB entries. [/EDIT]
MagicalTux
Posts: 22
Joined: Mon Dec 04, 2006 5:34 pm

Post by MagicalTux »

sorry for the mistake.

Basically I need each process to have its own "view" of the physical memory, so they won't be able to access kernel's memory. <-- I mean "won't be able to access other process's memory"
User avatar
AJ
Member
Member
Posts: 2646
Joined: Sun Oct 22, 2006 7:01 am
Location: Devon, UK
Contact:

Post by AJ »

??

My previous suggestion achieves this. User programs will *not* be able to access kernel or other processes memory, because of the IA32 built in protection systems.

However, each process cannot have its own *physical* memory space without using segmentation - you want separate *virtual* address spaces with all physical management done by the kernel.

Adam
MagicalTux
Posts: 22
Joined: Mon Dec 04, 2006 5:34 pm

Post by MagicalTux »

Hello,

I finally resolved my problem.

I was linking the kernel using -N ld option, which means that the .text part is not readonly, and that .data is not page-aligned.

Removing the -N option allowed me to write a code which is able to map only the kernel .text & .rodata to processes, at address 0xc0000000

Once the kernel is called, it switches to its own memory map. This is required as for some reason the kernel still want to jump to addresses like 0x100000 (where it was initially loaded). I guess it's not relocable, still using this technique (when in kernel the kernel is mapped two times, once at 0x100000, and once at 0xc0000000) allows it to work (yay).

Now I need to implement correctly changes to cr3 when we switch from one to another.
User avatar
AJ
Member
Member
Posts: 2646
Joined: Sun Oct 22, 2006 7:01 am
Location: Devon, UK
Contact:

Post by AJ »

Nonononono...

If you're doing what I think you're doing, can I stop you before you go much further please?

If you have to map your kernel at 1MB and 3GB for it to work, it is still running at 0x100000 - regardless of the fact that you have it mapped to 0xC0000000 too. You may JMP to 0xC0000000, but the first time your kernel branches, it is calling code above the offset 0x100000.

When you get on to separate user space apps and try to load them at 0x100000, you will find one of two things - either they will overwrite kernel data (if you use the same PD), or they will not have a usable kernel mapped in to the same memory space (if you use separate PDs).

You need to link the kernel purely at 0xC0000000 and ensure the *whole thing* works there (even if you un-map lower memory). You can do this either through paging, or I believe there is a trick you can use with the GDT prior to setting up paging (it's probably in the wiki somewhere...).

Cheers,
Adam
MagicalTux
Posts: 22
Joined: Mon Dec 04, 2006 5:34 pm

Post by MagicalTux »

Haha don't worry, I know about that.

Well, the idea is to have the kernel mapped at 0xc0000000. When an interruption occurs, a piece of ASM code is called at this address. It switches cr3 to the kernel's PD (which have the kernel mapped at 0x100000) and gives control to the kernel, which works just fine.

The code to return to the process is also always called at address 0xc0000000 and will switch back to process' PD before returning to process.

As far as I know, there's no problem with this kind of implementation.
Tyler
Member
Member
Posts: 514
Joined: Tue Nov 07, 2006 7:37 am
Location: York, England

Post by Tyler »

The problem with that implementation is that if you use pre-emptive scheduling. If you clock out during an interupt, then back into the main address space at that location, you will be executing the wrong code.

Also, a question to anyone who knows. Can device drivers be interupted in between read and writes to a device? Wont this mess it up if the driver begins writing in the context of one address space then starts writing to it again in a different context?
User avatar
AJ
Member
Member
Posts: 2646
Joined: Sun Oct 22, 2006 7:01 am
Location: Devon, UK
Contact:

Post by AJ »

Hi,

Assuming you have a part of memory space (say, above 3GB) which is common to all processes, could you just assign a buffer for your driver there? Alternatively, you could have a device manager process - all reads and writes could happen synchroniously in the context of the device manager (I guess this could be slow?).

[disclaimer]I haven't implemented this myself - just speculating...[/disclaimer]

Cheers,
Adam
Post Reply