OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Apr 18, 2024 6:55 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 14 posts ] 

How does your kernel/OS keep track of used memory regions in a process's address space?
Single Linked List 5%  5%  [ 1 ]
Double Linked List 32%  32%  [ 7 ]
Binary Tree 5%  5%  [ 1 ]
Other Tree (give details) 9%  9%  [ 2 ]
Static Array 9%  9%  [ 2 ]
Hash Table 5%  5%  [ 1 ]
Other Method (give details) 36%  36%  [ 8 ]
Total votes : 22
Author Message
 Post subject: Question about tracking memory regions in processes...
PostPosted: Tue May 01, 2007 7:49 am 
Offline
Member
Member

Joined: Sun Oct 22, 2006 5:31 am
Posts: 66
Location: Oxford, UK
I'm redesigning the address space support in my kernel, and I'm wondering which is the best/most popular option for tracking memory regions.

Discuss! :D

_________________
Code:
-----BEGIN GEEK CODE BLOCK-----
Version: 3.1
GCS/M/MU d- s:- a--- C++++ UL P L++ E--- W+++ N+ w++ M- V+ PS+ Y+ PE- PGP t-- 5- X R- tv b DI-- D+ G e h! r++ y+
------END GEEK CODE BLOCK------


Top
 Profile  
 
 Post subject: Re: Question about tracking memory regions in processes...
PostPosted: Tue May 01, 2007 12:24 pm 
Offline
Member
Member
User avatar

Joined: Sat Jan 15, 2005 12:00 am
Posts: 8561
Location: At his keyboard!
Hi,

senaus wrote:
I'm redesigning the address space support in my kernel, and I'm wondering which is the best/most popular option for tracking memory regions.

Discuss! :D


I tend to use the flags in page table entries as much as possible. For "present" pages there's at least 3 available bits that are combined to give one of the following page types:
    0 = normal RAM page
    1 = locked RAM page
    2 = locked RAM page that's part of a DMA buffer
    3 = normal RAM page that's part of a memory mapped file (see note below)
    4 = special mapping (ROMs, video display memory, etc)
For "not present" pages there's at least 31 bits that are available. These are combined into a 31 bits number, where:
    0x00000000 = normal not present page
    0x00000001 = "allocate on demand" not present page
    0x00000002 = not present part of a memory mapped file

All other values mean "not present page that was sent to swap", and contains the "block number" in swap space. For 32-bit paging this gives me a maximum (combined) swap space size of "(2^31 - 3) * 4 KiB", or almost 8192 GiB of swap space. For PAE and long mode, page table entries are 64-bit and the maximum amount of swap space is insanely huge (it's almost 34359738368 TiB).

For memory mapped files the page table flags only indicate if a page is part of a memory mapped file or not. There will be seperate structures for working out which file is mapped where (probably using simple linked lists).

For swap space the only thing missing is a way to work out which pages should be sent to swap if the OS runs out of RAM (e.g. an LRU page eviction algorithm). I need to think about this more, but I want a global page eviction algorithm rather than per process page eviction algorithm (i.e. when a page needs to be sent to swap, it could be any page in any process not just a page in the currently running process). This means some sort of structure in kernel space for managing when pages where last used.

On top of that I have regions that behave differently - one region for "process space", one for "thread space", and one for "kernel space". The boundaries between these regions are all at fixed addresses, except for the boundary between process space and thread space which is determined by a "per process" variable that's initialised from the executable file's header. There's also flags for each process that effect the behaviour of process space, and flags for each thread that effect the behaviour of thread space (mostly, if allocation on demand is enabled as default or not).

Process space itself is split into seperate areas - one for code and read only data, one for read/write data and the remainder for unititialised data. The size of these areas are determined from the executable file's header too. Thread space is always treated as uninitialised data. For normal pages these areas determine page protection flags (read only, read/write, executeable).

That's it.

There are a few deficiencies with this system - it can't handle shared memory areas, and doesn't allow for DLLs or shared libraries. This is fine for my OS where shared memory is deliberately avoided (as processes may be running on different computers), and where DLLs or shared libraries should be implemented as either static libraries and/or services that run as seperate processes.

It also takes a little juggling in the linear memory manager to make it work. For example, if a process says "make this area allocation on demand" when the default is "allocation on demand disabled", then the memory manager needs to pre-allocate page tables for that area so it can remember. It also means that if a process says "make this area locked" then the linear memory manager has to pre-allocate pages because there is no "not present locked RAM" page type.

Of course for large areas the same thing can be done with flags in page directory entries (and page directory pointer table entries, etc). This means (for e.g. and assuming 32-bit paging) if a process wants to change 1 GB of space to "allocation on demand" then the linear memory manager can just set flags in the page directory for most of it, and only pre-allocate page tables if the area doesn't start or end on a 4 MB boundary.


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.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 01, 2007 1:30 pm 
Offline
Member
Member
User avatar

Joined: Wed Oct 18, 2006 3:45 am
Posts: 9301
Location: On the balcony, where I can actually keep 1½m distance
I'm building an exokernel. Hence my memory tracking mechanism consists of the paging structures (1024 or 512-way tree), and a mirror tree in the kernel that keeps reference counts (also 512 way as if it were a alternate format PAE structure) This allows userland applications to manage physical and virtual memory with mostly O(1), lock-free and (under conditions) realtime calls to the kernel.

I'm currently trying to find a way to layer a page allocator on top of that without breaking the properties of the other calls. I've got some concepts, but i'm trying to mentally work out a proof of correctness so to make sure it will actually work 8)

_________________
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]


Top
 Profile  
 
 Post subject: Re: Question about tracking memory regions in processes...
PostPosted: Tue May 01, 2007 3:17 pm 
Offline
Member
Member
User avatar

Joined: Thu Mar 08, 2007 11:08 am
Posts: 670
Brendan wrote:
All other values mean "not present page that was sent to swap", and contains the "block number" in swap space. For 32-bit paging this gives me a maximum (combined) swap space size of "(2^31 - 3) * 4 KiB", or almost 8192 GiB of swap space. For PAE and long mode, page table entries are 64-bit and the maximum amount of swap space is insanely huge (it's almost 34359738368 TiB).


I don't think swap space size is the biggest issue with the above scheme. I think the biggest issue is that if you need to bring a page in from swap for read, but it's never modified, you end up writing them back into swap anyway if you don't keep track of the swap block when a page is present. In a sense every page then is dirty (assuming you map pages on demand.. if not, then any non-zero page is always dirty).

If every page is dirty, there will never be any pages to free "fast" from processes, because removing a dirty page from a process involves writing it to disk. On the other hand, if the swap-space allocation is remembered even for in-memory pages, you can clean pages when you don't have anything else to do, such that hopefully you always have some clean pages that you can remove from processes essentially instantly.

Once you have a way to remember swap blocks for in-memory pages, you can use the same method for doing memory mapped files. If several processes map the same file (and you allow this) you'll (in a sane implementation) end up having shared memory as well, which allows shared libraries as a special case (though allowing CoW as well can simplify them).

_________________
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 01, 2007 3:32 pm 
Offline
Member
Member
User avatar

Joined: Thu Mar 08, 2007 11:08 am
Posts: 670
Oh, and my own kernel doesn't do that kind of stuff yet, but I've got a few alternatives in mind. Most likely I'll do region management with either (resizeable) arrays or binary trees (that's easy to abstract though, so experimentation with different schemes is possible), to allow binary search to sparse address spaces. Each region mapped into an address space will then map into another region in some file, anonymous swap space being handled by special swapfs which isn't visible mounted in the normal directory tree. Memory mapped devices can likewise be handled by generalized files. Copy-on-write will need special support in the region structures, but that's pretty much it.

It'll take a while though, before I get into writing that, since VFSv3 is under development, I want some networking first, and I'm planning to write a new "fair" scheduler. Until then my process memory management is simple: there's a "break" which you can move around, and system will serve page faults by mapping zeroed pages below break, never removing any page below break.

_________________
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.


Last edited by mystran on Tue May 01, 2007 3:33 pm, edited 1 time in total.

Top
 Profile  
 
 Post subject: Re: Question about tracking memory regions in processes...
PostPosted: Tue May 01, 2007 3:33 pm 
Offline
Member
Member
User avatar

Joined: Tue Oct 17, 2006 6:06 pm
Posts: 1437
Location: Vancouver, BC, Canada
mystran wrote:
I don't think swap space size is the biggest issue with the above scheme. I think the biggest issue is that if you need to bring a page in from swap for read, but it's never modified, you end up writing them back into swap anyway if you don't keep track of the swap block when a page is present. In a sense every page then is dirty (assuming you map pages on demand.. if not, then any non-zero page is always dirty).


This would be a pretty big problem... In my MM design (yet to be implemented for lack of time :roll:), I store the block number in the PTE as well, but copy it to the corresponding entry in the page frame DB (my physical memory manager's main data structure) when bringing a page in from swap. BCOS uses a linked list of pages for its PMM I believe... Brendan, how would you handle this case that mystran pointed out?

mystran wrote:
Once you have a way to remember swap blocks for in-memory pages, you can use the same method for doing memory mapped files. If several processes map the same file (and you allow this) you'll (in a sane implementation) end up having shared memory as well, which allows shared libraries as a special case (though allowing CoW as well can simplify them).


I think the lack of shared memory and shared library support in BCOS is a feature, not a bug (Brendan, correct me if I'm wrong). Like Singularity, it is designed as a "sealed process architecture" (although with traditional hardware protection, unlike Singularity).

The paper on the sealed process architecture is here:

ftp://ftp.research.microsoft.com/pub/tr/TR-2006-51.pdf

_________________
Top three reasons why my OS project died:
  1. Too much overtime at work
  2. Got married
  3. My brain got stuck in an infinite loop while trying to design the memory manager
Don't let this happen to you!


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 01, 2007 3:38 pm 
Offline
Member
Member
User avatar

Joined: Thu Mar 08, 2007 11:08 am
Posts: 670
Yeah I didn't mean it'd be bug to not allow shared memory. I just pointed out that if you allow memory mapping a single file into several processes at the same time, you end up with either shared memory, or a huge synchronization issue.

I know the Singularity stuff. ;)

Anyway, I remember having read that Windows NT style for the in-memory swap address problem is to use a separate "shadow page directory" with the swap addresses. No idea about the details though, so I could have understood it totally wrong.

_________________
The real problem with goto is not with the control transfer, but with environments. Properly tail-recursive closures get both right.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 01, 2007 4:28 pm 
Offline
Member
Member
User avatar

Joined: Tue Oct 17, 2006 6:06 pm
Posts: 1437
Location: Vancouver, BC, Canada
mystran wrote:
Anyway, I remember having read that Windows NT style for the in-memory swap address problem is to use a separate "shadow page directory" with the swap addresses. No idea about the details though, so I could have understood it totally wrong.


I wouldn't really call it a "shadow page directory"... actually, the PFDB in my kernel is based on NT's. You might be thinking of "prototype PTEs", which are like "shadow PTEs" I guess. They're used to help keep PTEs that point to shared pages consistent. I never really understood the details... I'm not planning to implement shared memory either. :)

_________________
Top three reasons why my OS project died:
  1. Too much overtime at work
  2. Got married
  3. My brain got stuck in an infinite loop while trying to design the memory manager
Don't let this happen to you!


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 01, 2007 5:42 pm 
Offline
Member
Member

Joined: Sat Apr 14, 2007 12:13 pm
Posts: 58
i just keep a static array of 4 or more memory regions (program data, heap, stack and IPC area (really just the other half of the heap but i keep it separate because it makes things easier))

BUT, i keep my own page tables separately from the "real" page tables
this makes things alot easier, since i can store arbitrary data with any MM object (too much overhead is not good of course, but 12 bytes per page is acceptable in my opinion)

these structures keep track of shared memory, CoW and all that stuff
keeping them in sync might seem troublesome but most of that is invisible outside the MM


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 01, 2007 8:01 pm 
Offline
Member
Member

Joined: Thu Oct 21, 2004 11:00 pm
Posts: 248
I use an array of arrays. The higher-level arrays contain structures denoting the starting virtual address, access rights and containing the array of pages mapped at that virtual address with those permissions.


Top
 Profile  
 
 Post subject: Re: Question about tracking memory regions in processes...
PostPosted: Tue May 01, 2007 9:55 pm 
Offline
Member
Member
User avatar

Joined: Sat Jan 15, 2005 12:00 am
Posts: 8561
Location: At his keyboard!
Hi,

mystran wrote:
Brendan wrote:
All other values mean "not present page that was sent to swap", and contains the "block number" in swap space. For 32-bit paging this gives me a maximum (combined) swap space size of "(2^31 - 3) * 4 KiB", or almost 8192 GiB of swap space. For PAE and long mode, page table entries are 64-bit and the maximum amount of swap space is insanely huge (it's almost 34359738368 TiB).


I don't think swap space size is the biggest issue with the above scheme. I think the biggest issue is that if you need to bring a page in from swap for read, but it's never modified, you end up writing them back into swap anyway if you don't keep track of the swap block when a page is present. In a sense every page then is dirty (assuming you map pages on demand.. if not, then any non-zero page is always dirty).


Inside the kernel there's "kernel modules". One of these keeps track of page usage (a "page usage manager?"). It tells the linear memory manager which pages to evict from RAM (and which pages are "high usage" and should be corrected if a process is migrated from one NUMA node to another, but that's another story).

Because the page usage manager is responsible for deciding which pages get sent to swap, it needs to know about anything that can make one page a better choice than another. This means that if the physical memory manager brings a page in from swap for read then it needs to tell the page usage manager what it did (which process/thread, and which swap block was read at what linear address), and the linear memory manager can forget about that page after that. In this case the page usage monitor would remember the page is still in the swap, and if the page is modified (or if the system starts running out of swap space) it'd free the "swap block" and forget about the page.

Basically, for each "in use swap block", either the physical memory manager keeps track of it (if the data isn't in RAM) or the page usage manager keeps track of it (if the data is in RAM).

To be honest I haven't spent much time thinking about the interface between the linear memory manager and the page usage manager. The nice thing about my linear memory management is that I don't actually need to think about the page usage manager until after I've got basic linear memory management working - everything except swap space and memory mapped files can be done with page table flags and nothing else (and swap space and memory mapped files can be added later). ;)

mystran wrote:
Once you have a way to remember swap blocks for in-memory pages, you can use the same method for doing memory mapped files. If several processes map the same file (and you allow this) you'll (in a sane implementation) end up having shared memory as well, which allows shared libraries as a special case (though allowing CoW as well can simplify them).


A file can be opened as read-only any number of times. If a file is opened as read/write then (conceptually) a virtual copy of the file is created that is atomically written to the file system as a new version of the old file when the file is closed. The same file can be opened as read/write any number of times to create any number of new versions of that file. For legacy file systems (file systems that don't support versioning, like FAT) the OS refuses to allow the file to be opened as read/write if it's already opened as read-only or read/write, and refuses to allow it to be open as read-only if it's already opened as read/write.

For memory mapped files, creating the memory mapping is the same as opening the file, and unmapping the file is the same as closing the file. It doesn't make any difference which process or thread opens or closes what (for e.g. a single thread could open the same file as read/write 6 times and still end up creating 6 new versions of the old file). Lastly, file handles can't be transfered - if a thread opens a file and gets a file handle, then other threads in the same process can't use that file handle.


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.


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 02, 2007 7:09 pm 
Offline
Member
Member

Joined: Sun Oct 22, 2006 5:31 am
Posts: 66
Location: Oxford, UK
Thanks everyone, some very interesting ideas there. Now it's time for me to explain my design... here goes!

Basically, I use three structures alongside the page tables - MemoryRegions, BackingStores and Pages. I'll explain these first.

A MemoryRegion defines an arbitrary sized range of virtual addresses in a process's address space. I am currently trying to work out a way of storing these structures efficiently.

A Page structure represents a physical page frame. These structures are stored in a large array, allocated upon initialization, where each element represents its corresponding page frame.

A BackingStore is an object (managed by my object manager) which represents the physical backing storage behind MemoryRegions - a MemoryRegion can be a 'window' into a BackingStore (this is optional, a MemoryRegion with no BackingStore will be treated as heap). They also contain a 'Page Cache Table' - a hash table of pointers to Page structures representing the parts of the BackingStore currently resident in memory.

The page tables are the link entities between MemoryRegions and Pages; when a page frame is committed to a MemoryRegion, a page table entry is filled which links the two.

All memory allocation is done on-demand, and can be satisfied from the Page Cache. When a page fault comes in for a MemoryRegion with a BackingStore (could be a memory mapped file), the kernel does one of four things:

    If the fault was caused by a write, and the MemoryRegion is persistent read-write (meaning changes must be flushed back to BackingStore), the mapped page frame is moved/copied(if shared) to the dirty cache.

    If the fault was caused by a write, and the MemoryRegion is read-write, the page is moved/copied out of the Page Cache.

    If the page is not present, the kernel looks down the BackingStore's Page Cache Table to find the relevant page. If not found, a fresh page is allocated and the data is read in (whilst the faulting thread is blocked).

    If none of the above conditions are satisfied, an exception is thrown for the process.


Pages in the dirty cache are locked and written to BackingStore lazily. Once written, they are moved back to the clean cache.

The design of this system prohibits a BackingStore to be mapped persistent RW once only; this can be done whilst there are non-persistent mappings on the BackingStore - however, no more mappings can be made on a BackingStore once it is mapped as persistent RW.

I hope that all makes sense :?
It is late here, and I'll probably be suitably embarrassed once I re-read this in the morning, but what the hell. I'll be glad to hear some constructive criticism on this!

Cheers,
Senaus

_________________
Code:
-----BEGIN GEEK CODE BLOCK-----
Version: 3.1
GCS/M/MU d- s:- a--- C++++ UL P L++ E--- W+++ N+ w++ M- V+ PS+ Y+ PE- PGP t-- 5- X R- tv b DI-- D+ G e h! r++ y+
------END GEEK CODE BLOCK------


Top
 Profile  
 
 Post subject:
PostPosted: Sat May 19, 2007 8:06 pm 
Offline
Member
Member

Joined: Thu Oct 21, 2004 11:00 pm
Posts: 248
Since I'm writing a microkernel, I've decided to add L4-style map/unmap/grant virtual memory management. Anyone know an efficient implementation of an L4-style mapping database?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 30, 2007 10:07 pm 
Offline
Member
Member

Joined: Thu Aug 30, 2007 9:09 pm
Posts: 102
:oops: I'm still marking down the specifications for mine.

I've been planning to modify reiser4 to achieve Hans' file system semantics, and implement a multi-entry-point, multi-threading executable file format on top of it with a privileges security framework.

My line of thought was to allow a "program" to have multiple entry points, possibly for different threads, and therefore multiple code and stack segments. To accomplish this, "programs" need to provide information (generated at compile time) about the initial size of each of these arrays, as well as their maximum size. This information allows "the OS" to allocate pages to the "program" marked with the appropriate access bits.

The initial size of the code and data segments tends to be accurate, while the stack and dynamic arrays tend to expand.
Quote:
For this reason the dynamic arrays are placed in the middle of the (64-bit) address space and the stack at the top 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF. This makes it impossible to achieve a stack overflow condition with rationally chosen program maximums (which are tested on allocation of real resources, and exist to detect overflows, infinite recursion, etc.


On second thought, there are multiple stacks. Allocate them like dynamic arrays. Provide no guarantees about the address space on stacks other than that ESP and EBP will be adjusted accordingly. Re-address stack pages and adjust ESP and EBP if an address space collision occurs. Guarantee dynamic array addresses except across allocations.

An added benefit of this mechanism seems to be that one can, if sparsely addressed, not only append space, but often enough, prepend it for large dynamically allocated arrays.

_________________
There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies.
- C. A. R. Hoare


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 14 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: No registered users and 33 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group