ngx wrote:And at first the tree is empty, but with every allocation we add at which address and how many pages were allocated. So to find a new page which could be given out to the user, we just go from the root and then I can't understand - should we follow the decrease route or increase route(e.g. go to the branch which contains addresses that are lower or higher then current one each time)? And also should I use(return to the user) the page right after the last page in the route I am following ?
Binary trees have the property that an in-order walk of the tree returns all nodes in sorted order. Therefore you can do an in-order walk, recording the predecessor of each node. That way, you know exactly the size of the hole between the two allocations. which means it becomes easy to test if that hole fits the requirements of the mapping.
OK, so what do we need? First, the in-order walk function:
Code: Select all
int walk_tree(struct node* root, int (*callback)(const struct node*, void*), void* ctx) {
if (root->left)
if (walk_tree(root->left, callback, ctx))
return 1;
if (callback(root, ctx))
return 1;
if (root->right)
if (walk_tree(root->right, callback, ctx))
return 1;
return 0;
}
So far, so standard. The return value is only there to facilitate early return.
Now, what does the visitor have to do? If there is no predecessor, it has to test if the allocation fits between the minimum address and the start of the current VMA. Else it has to test if the allocation fits between the current and the previous node. So basically the same, but different address for "end of predecessor". If it does fit, it has to return the start address it found to the top of the caller and abort the walk. Else it has to continue the walk. If the current node has a start address past the maximum for this allocation, it can also abort the walk, since nothing fitting will be found anymore.
Also, we need to return the maximum allocated address to the caller, so the caller can test if maybe there is a suitable address in the space between the last allocation and the maximum address.
Putting it all together:
Code: Select all
struct alloc_ctx {
uintptr_t size, align, min, max;
struct node *predecessor;
uintptr_t ret_address;
};
static int alloc_visitor(const struct node* vma, void* _ctx)
{
struct alloc_ctx *ctx = _ctx;
uintptr_t end_of_pred = ctx->min;
if (ctx->predecessor)
end_of_pred = ctx->predecessor->start + ctx->predecessor->length;
end_of_pred = (end_of_pred + ctx->align - 1) & -ctx->align;
if (end_of_pred >= ctx->min && end_of_pred + ctx->size < vma->start && end_of_pred + ctx->size < ctx->max)
{
ctx->ret_address = end_of_pred;
return 1;
}
ctx->predecessor = vma;
if (vma->start + vma->length >= ctx->max)
return 1;
return 0;
}
Awesome. Now for the actual allocation function, we only need to initialize this algorithm, do the final check for memory at the end, and bind it all together:
Code: Select all
static struct node *vma_root;
static uintptr_t find_free_vma(size_t size, size_t align, uintptr_t min, uintptr_t max)
{
assert(align >= PAGE_SIZE); /* align is non-zero and at least PAGE_SIZE */
assert(!(align & (align - 1))); /* align is also a power of two */
size = (size + align - 1) & -align;
struct alloc_ctx alloc_ctx = {size, align, min, max};
walk_tree(vma_root, alloc_visitor, &alloc_ctx);
if (alloc_ctx.ret_address)
return alloc_ctx.ret_address;
uintptr_t end_of_pred = min;
if (alloc_ctx.predecessor)
end_of_pred = alloc_ctx.predecessor->start + alloc_ctx.predecessor->length;
end_of_pred = (end_of_pred + align - 1) & -align;
if (end_of_pred >= min && end_of_pred + size < max)
return end_of_pred;
return 0;
}
OK, now the only thing left to do is to allocate a new node, fill it with information, and insert it into the tree. Which I shall leave to you.
ngx wrote:And also I wanted to ask - where should I store the initial VMM pages, because the pages I require at the start should map the kernel and I store kernel in the last 2GB, but mapping last 2 GB may be impossible or would take 2 MB in page tables. So is it a good idea to store the page tables in the data section like 512 x 8 sized arrays and also how could I not map the useless space if my kernel is less then 2GB (as I do not know the kernel size at runtime)?
Not quite sure what you mean. Do you mean your initial page tables? Anyway, if static allocation makes you waste memory, the answer is dynamic allocation. I also allocate my page tables at run time. But that is more of a job for the PMM at that point. As for how to not map useless space: In my case, the kernel itself is an ELF binary, and gets its segments mapped like a userspace program by the stage 1 kernels, which are themselves loaded by the booting method of choice (i.e. GRUB or UEFI). I don't know how your memory layout works, you will have to figure that out yourself.