alux
alux copied to clipboard
Inter-process communication
I am in the process of figuring out how my microkernel should implement inter-process communication. Here are the two types of IPC I want to consider:
- Shared memory IPC
- A region of memory would be shared between the two tasks and would be continually overwritten as a sort of socket buffer. The connected tasks should be able to negotiate the size of this buffer.
- I have to worry about paging shared memory to disk: I'd like unprivileged tasks to get all of their memory from a privileged "memory daemon" that is responsible for paging memory.
- Unprivileged tasks must be able to use some other kind of IPC to communicate with this memory daemon; tasks can't use shared memory to communicate with the memory daemon since this would be a circular dependency.
- Basic IPC
- This form of IPC cannot depend on any user-space daemon—it depends on the kernel itself.
- Because memory used by basic IPC cannot be paged to disk, tasks should not be able to use basic IPC to transmit large amounts of information at a time.
- Privileged device drivers should be able to use basic IPC to send physical addresses between each other for fast handling of data from a network card or some other device.
A System for Basic IPC
Since basic IPC should allow very limited memory usage, I'm considering a simple "write & signal" approach. In this approach, a basic IPC connection has a 32-byte buffer associated with it that can be read/written with a system call. A task may wakeup the remote end of a basic IPC connection to notify it of a data change. A task can also poll one or more basic IPC connections at once to await data. Multiple threads on a task cannot poll on the same basic IPC connection at the same time.
This IPC technique would serve best if it was primarily used underneath shared memory IPC. For example, task A would connect to task B and the tasks would perform a basic handshake over basic IPC. Then, both task A and task B would use their connection to the memory daemon to negotiate a shared memory region. From there, task A and task B would communicate as follows: one task would write a relatively large piece of data to the shared memory region and use its basic IPC connection to signal the other task of the data change.
Essentially, the basic IPC connection would be used as a signaling mechanism for the larger shared memory region.
Thread-Local IPC
An IPC connection can only be polled or signaled by one thread at a time. My first instinct is to decide that only the main Dart Isolate or VM thread can access the IPC mechanism. However, this is unreasonable because multithreaded VMs will probably need to access the memory daemon from multiple threads at once.
To deal with this issue, I came up with the idea of "thread-local IPC". When you open a connection to another task, you will specify whether you want to connect with a full-task connection, or with a thread-local connection. Thread-local file descriptors will be negative and will be specific to a given thread. Thread-local connections are closed when a thread is terminated, similarly to how full-task connections are closed when the task exits.
When you run a PollAll() system call, that will only poll full-task connections. I have not yet decided if PollAll() should poll thread-local connections on the thread that made the call.
Things I am still flaky about:
- How will two tasks negotiate to share memory with the memory daemon?
- Should the receiver of a connection have to approve shared memory for each remote connection it receives?
- What system calls will be necessary for the memory daemon to verify that two tasks are really connected and both want to share memory?
- Should received connections all be full-task connections?
- Will exactly one thread have to be designated as the connection receiver?
- Should I expose thread-local connections to regular programs running in the VM?
Additionally, I'd like to have some form of sockets for privileged tasks to communicate with the kernel. For example, I'd want a type of connection that would transmit page faults to the memory daemon. Additionally, I want a way that the kernel's arch-specific code could talk to user-space to send it things like hardware interrupts.
I have recently decided that all IPC will be thread local. A task will serve other tasks by allocating its own ports and passing them to some sort of PushToListenQueue()
method. I will prevent DOS in the future by creating a permissions bitmap so that a task can block or unblock any given user ID from connecting.