There's a major difference between evicting the "best" page that the current pager controls, and evicting the "best" page in the whole system (regardless of which pager). How L4 gets around this I've no idea - I can imagine a system where all pagers nominate their best page to evict and give it a score, and the page with the highest score is the one that's actually evicted, but it does make me wonder if L4 evicts the "best" page in the whole system, or if it compromises.
Page eviction algorithms can in general be divided into the two categories 'local' (least frequently used page for a single task) and 'global' (whole system). For an OS that supports multiple pagers a third category could be introduced that tries to find the LRU page for all the tasks that use a pager.
Locally limited (local, pager) algorithms can perform pretty well but have problems when the size of a working-set varies during the life-span of a process. Some communication between the pagers is therefore needed in order to coordinate and make sure that the pages are shared fair among all tasks. The protocol for this is not part of the L4 kernel but has to be based on a user-space standard.
How it's done exactly in L4 is out of my knowledge, I can't even guaranty you that it already supports something like this because the kernel is still more of an "experimental" nature..
You're talking of memory mapped files? For App B I wouldn't use memory mapped files at all, but despite this you're making a good argument for allowing the block size to be adjustable and for file IO priorities. For example "int madvise (void *addr, size_t length, int advice, int prefered_block_size, int IO_priority)". I've probably missed something, but I'm not seeing a need for multiple pagers here.
I actually meant real files (readme.txt) which are also an abstraction by the kernel that is redundant for some tasks. Without them and the associated overhead (eg caching police), the video-capturing task might get less dropped frames.
Maybe it was a bit too radical an example, but in exo-kernels (L4 is in between micro- and exo-kernel) there are indeed no files but only sectors that can be written to freely.
Back to the topic..
By adding fields for block-size and IO-priority to your system-call, you parameterized it and thus started to slowly move the policy from kernel to user:
The more parameters you add, the more control is passed to the user and eventually your kernel will be entirely policy free. In fact the development from early monolithic systems to the still "exotic" exo-kernels can be seen as an evolutionary process:
*monolithic-kernel*
much policy in the kernel, few user control through paramters for system-calls
*1st gen ?-kernels (mach)*
policy partly outsourced (eg window sub-system), more user control through both parameters for system-calls and user-space managers
*2nd gen ?-kernels (L4, QNX)*
almost policy free, apps can decide almost everything by user their prefered manager
*exo-kernels*
no kernel abstractions, apps have full control over policies, user-space managers is reduced to the minimum so that apps have even more liberties
When giving apps more liberties by offering additional parameters to specify their requirements, problems occure pretty soon when malicious/buggy/ignorant software exploit these new liberties (eg waste resources of any kind). It is therefore neccessary that a central instance acts as a neutral arbitrator that ensures some minmum policy needed to make sure that every app is treated fair. This is what user-space managers are all about..
- The kernel is policy free and only offers mechanisms that are protected so that they can only be used by authorized tasks
- User-Space Managers ensure some basic policy to make sure that all apps can run
- Apps have much more liberties and can define more precisly what they need
(post goes on..)