I've spend a few hours on this tonight and came to the conclusion that IPC and completion ports can't be combined. It doesn't makes sense to try to merge the concepts of IPC endpoints and completion ports. Even though the APIs might look similar, they are orthogonal concepts.
So what I think needs to happen here is basic IPC primitives:
- ipc_create_endpoint()
- ipc_send(endpoint, message, lenMessage) - async call
- ipc_wait(endpoint, buffer, lenBuffer, timeout) - blocking call, open wait
- ipc_receive(endpoint, message, lenMessage, timeout) - blocking call, closed wait
And then have IO port primitives:
- port_create()
- port_wait(port, completion_info*, timeout) - blocking call waiting for completion(s) on the port
- port_post(port, status, completion key) - post completion status to the port
There is no need to associate file (or other) handles with completion ports: where to post the completion status can just be part of the payload of the "read file command".
For example:
Code:
struct FileReadCommand
{
int command;
int file;
int lenBuffer;
void* buffer;
int completionPort;
void* completionKey;
};
- The client would send the FileReadCommand() using ipc_send() to the file server.
- The file server would receive the request using ipc_wait().
- The file server would read data from the file into the specified buffer.
- The file server would then post the result to the completion port (status + completion key) using port_post().
- The client can retrieve the completion status at any timeusing port_wait().
- If the client doesn't care about the completion status, it could just set completionPort to 0.
Anything missing here and/or possible simplifications?
Right now I would need 8 registers for the system call (6 for the structure members + 1 for the endpoint + 1 for the syscall number). It would be nice if this could be reduced as the x86_64 ABI only allows 6 parameters in registers. Perhaps "buffer + lenBuffer" can be replaced to a handle to a registered I/O buffer. Maybe other things can be done as well.