Msg Passing Architecture, Suggestions Welcome
Posted: Thu Aug 07, 2008 5:40 pm
So, this is the current client-side API and notes from my microkernel. I have a working prototype, but am in the process of making it 'solid', well at least solid enough to code against. I suspect I'll rewrite again after. It's very similar to other archs, but with my current pet-feature - kernel side closures.
Open a service with the given URL. This is handled in a near identical way to a standard open command, the URL is translated to a device node, which has an associated service registered. Return code stored in id, error, if any, is returned. All these functions return error code and save return result in pointer params.
Lookup a non predefined message with an associated namespace. Services can register namespaces, or register a name in the global namespace.
Send a message to the service associated with a given connection, store a 'msg status indicator' in msgid. The message type (kindof a method) is specified by msg. Arguments are according to the definition of the message type. This may return an error condition if the queue is plugged, but it will never block.
Wait for a given service to mark any message on its queue as complete. Stores top most ready message id in msgid.
Wait on a collection of connection descriptors for any to return a response. Stores the active connection and topmost msgid.
Wait for a specific message to get a response. Other messages may be received.
Run any ready closures without risking prolonged wait from incoming messages.
Obtain any return results from a message without clearing. Arguments depend upon the message type.
Obtain any return results from a message and clear the message immediately after. Arguments depend upon the message type.
Clear a received message response without grabbing return data. Clearing a non-complete message results in it never being sent.
Usage notes:
Every service runs in user space.
Connections have associated send and receive queues for the client.
Before sending a message, the client must obtain a selector for the desired op.
In the global namespace, predefined selectors exist for most operations (read, write, sync, etc...)
Selectors are reusable for the lifetime of a service connection, or for global selectors, always.
Messages are placed on the send queue.
When a service handles a message, it places that message and any return on the receive queue.
Messages may be of a variable, but strictly limited, size.
Messages involving large amounts of data may use "message blocks", a built in shared memory type.
Services may define kernel side closures.
The closure is defined by giving the kernel a block of (currently) LISP code, and data types/bound variable info.
The LISP code is executed in kernel space, in context of the process sending the message, when a wait state is allowed (called to service_msg_wait_*)
Closures can not enter wait states.
Clients may request any active (non-waited) closure to run, by using service_msg_perform_closure().
This design specifically allows clients to send multiple messages without need to worry about blocking. However, the send and receive queue sizes are limited, so a client must continue clearing responses, or risk being blocked from sending new messages or receiving responses. A client will not be allowed to send a message if the maximum response size for that message is not available in the receive queue (the message will stay in the send queue but not be visible to the service).
There are no guarantees on ordering of message responses - strictly ordered messages would have to be done by waiting for each response between message send commands.
Example interaction, this code has some issues in it's queue management (makes invalid assumptions, no error checking, etc...)
Code: Select all
error_t service_open(Connection *id, const char *URL);
Code: Select all
error_t service_msg_lookup(MessageType *msg, Namespace namespace, const char *method_string);
Code: Select all
error_t service_msg_send(Message *msgid, Connection fd, MessageType msg, ...);
Code: Select all
error_t service_msg_wait(Message *msgid, Connection fd);
Code: Select all
error_t service_msg_wait_sel(Connection *cid, Message *msgid, Connection fd[]);
Code: Select all
error_t service_msg_wait_specific(Connection fd, Message msgid);
Code: Select all
error_t service_msg_perform_closure(Message *msg, Connection fd);
Code: Select all
error_t service_msg_resp(Connection fd, Message msg, ...);
Code: Select all
error_t service_msg_resp_clear(Connection fd, Message msgid, ...);
Code: Select all
error_t service_msg_clear(Connection fd, Message msgid);
Usage notes:
Every service runs in user space.
Connections have associated send and receive queues for the client.
Before sending a message, the client must obtain a selector for the desired op.
In the global namespace, predefined selectors exist for most operations (read, write, sync, etc...)
Selectors are reusable for the lifetime of a service connection, or for global selectors, always.
Messages are placed on the send queue.
When a service handles a message, it places that message and any return on the receive queue.
Messages may be of a variable, but strictly limited, size.
Messages involving large amounts of data may use "message blocks", a built in shared memory type.
Services may define kernel side closures.
The closure is defined by giving the kernel a block of (currently) LISP code, and data types/bound variable info.
The LISP code is executed in kernel space, in context of the process sending the message, when a wait state is allowed (called to service_msg_wait_*)
Closures can not enter wait states.
Clients may request any active (non-waited) closure to run, by using service_msg_perform_closure().
This design specifically allows clients to send multiple messages without need to worry about blocking. However, the send and receive queue sizes are limited, so a client must continue clearing responses, or risk being blocked from sending new messages or receiving responses. A client will not be allowed to send a message if the maximum response size for that message is not available in the receive queue (the message will stay in the send queue but not be visible to the service).
There are no guarantees on ordering of message responses - strictly ordered messages would have to be done by waiting for each response between message send commands.
Example interaction, this code has some issues in it's queue management (makes invalid assumptions, no error checking, etc...)
Code: Select all
Connection fd;
MessageType setxy;
Message setxy_msg, write_msg;
char *buffer = "Hello World';
size_t buf_size = strlen(buffer);
int bytes_written;
/* setxy is a closure, execute immediately */
service_open(&fd, "^tty://console");
service_msg_lookup( &setxy, service_namespace(fd), "setxy");
service_msg_send( &setxy_msg, fd, setxy, 15, 15 );
service_msg_perform_closure (&setxy_msg, fd);
service_msg_clear (setxy_msg);
/* Perform blocking write */
service_msg_send( &write_msg, fd, MSG_WRITE, buffer, buf_size);
service_wait_specific (fd, write_msg);
service_msg_resp (fd, write_msg, &bytes_written);
service_msg_clear (fd, write_msg);