Thank you for all your replies.
If the process wants to reserve an area and/or dynamically allocate areas, then that's a user-space problem that should be implemented in a user-space library
No matter where, it still should have been done. How do you think is the best way to do it? And also, is this library involved in process creation, when you need to reserve blocks for its image sections, stacks, pools etc?
If you need a specific location or a large size, you should probably have a way to reserve early or the general-purpose reservation shouldn’t ever reserve in the special area.
Orherwise, the simplest is to keep available regions in a balanced tree, ordered by size (size being a page or multiple pages, e.g. 4 KB to 64KB). With this you can quickly find if you can satisfy reservation and where (e.g. find the region big enough and possibly split it).
That was tempting, but merging free blocks becomes a performance disaster.
First, as this is not an in-place representation (not where free blocks reside), you cannot check adjacent blocks on their status directly. Only through page table entries.
So you end up with something like this sequence (the lower block check example):
1) check its last pte for some flag indicating used/unused. this pte is right before our new block pte.
2) if it is "not used", go to the previous pte until pte is used, this gives an address and size of the previous block.
3) find the item describing the block in a list/tree. for the list it's just a traversal linear by the number of blocks, bad, for the tree, its relatively quick since you find by the size and then find next until the address matches.
4) now finally modify this item increasing its size (key) and make increase-key operation restoring BST property if it's a tree.
1 is constant, 2 depends linearly on the previous block size in pages, might be huge (it's a free block) or much worse - might even not have page tables allocated, resulting in at least a great wastage of time. 3 depends linearly on the number of blocks with the same size and logarithmical on the number of blocks. 4 is just O(log(n)), n - number of blocks.
2 looks kind of not exactly inspiring.
Once you have the balanced tree¹ reserving regions becomes easy: Just store the size of the largest available region in the inner nodes of this tree. Then during reservation you only descend into nodes that have enough space left. This means that the kernel is able to satisfy non-fixed reservation requests at no additional overhead - in fact, if you implement the same functionality in user-space like Brendan suggested, you will have some non-zero overhead.²
This sounds interesting and honestly I need to clarify it yet. As well as learn what radix trees are.

Thank you for the link. Still, the key moment is availability of the address P at the moment of the call to the reservation algorithm. But how do you find P?