This is a description of my framework for generic event notification
within the NetBSD kernel, called `kevent'.
BASIC MODEL
-----------
The kevent framework has the notion of `event servers' and `event clients'.
Things which wait for events to happen are clients. Things which notify
the clients that the event has occured are servers.
For each event type, there is one event server. Event clients are bound
to specific servers, and thus can receive only one event type. If the
owner of a client wishes to receive notification of more than one event
type, it must use multiple clients bound to the corresponding event servers.
A message may accompany the notification of an event. For example, a
network driver may be waiting for "power-status" events. A "power-status"
event message may include one of the following message payloads:
"suspend"
"resume"
"line"
"battery"
"level" <int:72>
The format of any message payloads are defined by the owner of the server.
The message payload is opaque to the kevent framework.
DATA STRUCTURES
---------------
kevent_message:
This structure defines an event message which is sent to
clients. A kevent_message is always send to a client,
even though there may be no payload.
The one public member of a kevent_message is "kmsg_len",
which is the length of the payload portion of the message.
If there is no payload, "kmsg_len" is 0.
Payload in the kevent_message is aligned to the system's
alignment requirements, as if the ALIGN() macro were used.
The KEVENT_MESSAGE_INITIALIZER constant exists to initialize
a kevent_message to 0s by assignment, e.g.:
void foo(void)
{
struct kevent_message kmsg =
KEVENT_MESSAGE_INITIALIZER;
...
}
The KEVENT_DATA() macro returns a void * to the payload
portion of the message.
The KEVENT_SIZE() macro computes the total size of a
kevent_message structure, given the payload length, suitable
for allocating storage for the message either as an automatic
variable (i.e. char kmsg_stor[KEVENT_SIZE(100)];) or via some
dynamic allocation mechanism.
kevent_server:
This structure describes an event server. The structure has
no public members, and users of the kevent framework should
not access the internals of this structure.
kevent_client:
This structure describes an event client. The structure has
no public members, and users of the kevent framework should
not access the internals of this structure.
SOFTWARE INTERFACE
------------------
void kevent_init(void);
This function exists to initialize the kevent framework. It
is called by main() early in the bootstrapping process, immediately
after the memory allocation subsystems are initialized.
int kevent_server_create(const char *name, struct kevent_server **ksrvrpp);
This function creates an event server. The event type is defined by
"name". The handle to the server is returned in "ksrvrpp".
If the "name" is longer than KEVENT_MAXNAMELEN, including the
terminating NUL, this function returns EINVAL.
If a server of type "name" already exists, this function returns
EEXIST.
Otherwise, the value 0 is returned, and *ksrvrpp points to a valid
kevent_server structure. This value is used as a handle by which
event notifications are sent to clients.
If a previously orphaned server of type "name" is found, that server
will be reconnected, owned by the new owner, with all remaining
clients. No other special action is taken in this case. If a
server owner wishes to notify clients that it has reconnected the
server, it may do so. See kevent_server_destroy() below.
void kevent_server_destroy(struct kevent_server *ksrvr);
This function destroys an event server. If the server still has
clients bound to it, the server becomes `orphaned'. This allows
a detaching subsystem to notify all clients that it has gone away,
but allows the clients to detach from the server at their leisure.
Once the last client reference is gone, the kevent_server structure
will be freed back to the system.
int kevent_client_create(const char *name,
void (*func)(void *arg, const struct kevent_message *kmsg),
void *arg, struct kevent_client **kclntpp);
This function creates an event client. The server the client
is to be bound to is defined by "name". The callback function
which will receive event notifications is defined by "func". An
opaque argument to pass to this function is defined by "arg". The
handle to the client is returned in "kclntpp".
If the specified server does not exist, this function returns ESRCH.
Otherwise, the value 0 is returned, and *kcnltpp points to a valid
kevent_client structure. This may later be used to destroy the
client.
void kevent_client_destroy(struct kevent_client *kclnt);
This function destroys an event client.
void kevent_send(struct kevent_server *ksrvr,
const struct kevent_message *kmsg);
This function sends notifications of kernel events. A
kevent_message must be included, although its payload may
be 0. If a client wishes to keep a copy of the message, it
is responsible for copying it to client-local storage. This
allows messages to be allocated as automatic variables by
the sender.
Clients which receive event notification may not block, as
notification may occur in interrupt context. Senders are
responsible for blocking interrupts, as appropriate.
MISC NOTES
----------
As a proof of concept, I have replaced "shutdownhooks" in my
source tree with kevents. main() creates a kevent_server called
"shutdown", and cpu_reboot() sends a zero-payload message on this
event server where doshutdownhooks() would have normally been
called. Drivers which previously registered shutdownhooks now
create clients of the "shutdown" event server.
-----
It is intended that userland programs may be clients of the
framework, as well.
My initial inclination is to create a new protocol family,
PF_KEVENT, which allows programs to receive atomic datagrams
containing kevent_messages.
My idea here is to have the following:
struct sockaddr_kevent {
u_char skev_len; /* total length */
u_char skev_family; /* address family */
char skev_name[KEVENT_MAXNAMELEN];
/* event type */
};
struct sockaddr_kevent skev;
int s;
...
s = socket(PF_KEVENT, SOCK_DGRAM, 0);
...
skev.skev_len = sizeof(skev);
skev.skev_len = AF_KEVENT;
strcpy(skev.skev_name, "nfslock");
bind(s, (struct sockaddr *)&skev, sizeof(skev));
...and that program can now listen for "nfslock" event
messages from the kernel. (Note, this is the message path
required to implement NFS locking, which is why I used it
as an example :-)
The act of binding a socket to an event server would cause
that socket's kevent_pcb to create a client for that server.
In order to hang messages off of socket buffers, we'd have to
define a NETISR_KEVENT, because all of that stuff has to happen
at splsoftnet. But that is an implementation detail.
-----
Also, some sort of "feedback" mechanism may be desired for the
userland -> kernel path, although I haven't thought too much about
this yet.
-- Jason R. Thorpe <thorpej@nas.nasa.gov>