Richard Sailer 637f688f0d PM: Convert K&R C -> ANSI C
Aditionally this removes all trailing whitespaces in pm server code
using: sed -i 's/[[:space:]]*$//' *.c

Change-Id: Ie44162fd56cd7042f4f0cc7bd7314b17ea128761
2016-07-08 21:24:33 +02:00

354 lines
11 KiB
C

/*
* This file implements a generic process event publish/subscribe facility.
* The facility for use by non-core system services that implement part of the
* userland system call interface. Currently, it supports two events: a
* process catching a signal, and a process being terminated. A subscribing
* service would typically use such events to interrupt a blocking system call
* and/or clean up process-bound resources. As of writing, the only service
* that uses this facility is the System V IPC server.
*
* Each of these events will be published to subscribing services right after
* VFS has acknowledged that it has processed the same event. For each
* subscriber, in turn, the process will be blocked (with the EVENT_CALL flag
* set) until the subscriber acknowledges the event or PM learns that the
* subscriber has died. Thus, each subscriber adds a serialized messaging
* roundtrip for each subscribed event.
*
* The one and only reason for this synchronous, serialized approach is that it
* avoids PM queuing up too many asynchronous messages. In theory, each
* running process may have an event pending, and thus, the serial synchronous
* approach requires NR_PROCS asynsend slots. For a parallel synchronous
* approach, this would increase to (NR_PROCS*NR_SUBS). Worse yet, for an
* asynchronous event notification approach, the number of messages that PM can
* end up queuing is potentially unbounded, so that is certainly not an option.
* At this moment, we expect only one subscriber (the IPC server) which makes
* the serial vs parallel point less relevant.
*
* It is not possible to subscribe to events from certain processes only. If
* a service were to subscribe to process events as part of a system call by
* a process (e.g., semop(2) in the case of the IPC server), it may subscribe
* "too late" and already have missed a signal event for the process calling
* semop(2), for example. Resolving such race conditions would require major
* infrastructure changes.
*
* A server may however change its event subscription mask at runtime, so as to
* limit the number of event messages it receives in a crude fashion. For the
* same race-condition reasons, new subscriptions must always be made when
* processing a message that is *not* a system call potentially affected by
* events. In the case of the IPC server, it may subscribe to events from
* semget(2) but not semop(2). For signal events, the delay call system
* guarantees the safety of this approach; for exit events, the message type
* prioritization does (which is not great; see the TODO item in forkexit.c).
*
* After changing its mask, a subscribing service may still receive messages
* for events it is no longer subscribed to. It should acknowledge these
* messages by sending a reply as usual.
*/
#include "pm.h"
#include "mproc.h"
#include <assert.h>
/*
* A realistic upper bound for the number of subscribing services. The process
* event notification system adds a round trip to a service for each subscriber
* and uses asynchronous messaging to boot, so clearly it does not scale to
* numbers larger than this.
*/
#define NR_SUBS 4
static struct {
endpoint_t endpt; /* endpoint of subscriber */
unsigned int mask; /* interests bit mask (PROC_EVENT_) */
unsigned int waiting; /* # procs blocked on reply from it */
} subs[NR_SUBS];
static unsigned int nsubs = 0;
static unsigned int nested = 0;
/*
* For the current event of the given process, as determined by its flags, send
* a process event message to the next subscriber, or resume handling the
* event itself if there are no more subscribers to notify.
*/
static void
resume_event(struct mproc * rmp)
{
message m;
unsigned int i, event;
int r;
assert(rmp->mp_flags & IN_USE);
assert(rmp->mp_flags & EVENT_CALL);
assert(rmp->mp_eventsub != NO_EVENTSUB);
/* Which event should we be concerned about? */
if (rmp->mp_flags & EXITING)
event = PROC_EVENT_EXIT;
else if (rmp->mp_flags & UNPAUSED)
event = PROC_EVENT_SIGNAL;
else
panic("unknown event for flags %x", rmp->mp_flags);
/*
* If there are additional services interested in this event, send a
* message to the next one.
*/
for (i = rmp->mp_eventsub; i < nsubs; i++, rmp->mp_eventsub++) {
if (subs[i].mask & event) {
memset(&m, 0, sizeof(m));
m.m_type = PROC_EVENT;
m.m_pm_lsys_proc_event.endpt = rmp->mp_endpoint;
m.m_pm_lsys_proc_event.event = event;
r = asynsend3(subs[i].endpt, &m, AMF_NOREPLY);
if (r != OK)
panic("asynsend failed: %d", r);
assert(subs[i].waiting < NR_PROCS);
subs[i].waiting++;
return;
}
}
/* No more subscribers to be notified, resume the actual event. */
rmp->mp_flags &= ~EVENT_CALL;
rmp->mp_eventsub = NO_EVENTSUB;
if (event == PROC_EVENT_EXIT)
exit_restart(rmp);
else if (event == PROC_EVENT_SIGNAL)
restart_sigs(rmp);
}
/*
* Remove a subscriber from the set, forcefully if we have to. Ensure that
* any processes currently subject to process event notification are updated
* accordingly, in a way that no services are skipped for process events.
*/
static void
remove_sub(unsigned int slot)
{
struct mproc *rmp;
unsigned int i;
/* The loop below needs the remaining items to be kept in order. */
for (i = slot; i < nsubs - 1; i++)
subs[i] = subs[i + 1];
nsubs--;
/* Adjust affected processes' event subscriber indexes to match. */
for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++) {
if ((rmp->mp_flags & (IN_USE | EVENT_CALL)) !=
(IN_USE | EVENT_CALL))
continue;
assert(rmp->mp_eventsub != NO_EVENTSUB);
/*
* While resuming a process could trigger new events, event
* calls always take place after the corresponding VFS calls,
* making this nesting-safe. Check anyway, because if nesting
* does occur, we are in serious (un-debuggable) trouble.
*/
if ((unsigned int)rmp->mp_eventsub == slot) {
nested++;
resume_event(rmp);
nested--;
} else if ((unsigned int)rmp->mp_eventsub > slot)
rmp->mp_eventsub--;
}
}
/*
* Subscribe to process events. The given event mask denotes the events in
* which the caller is interested. Multiple calls will each replace the mask,
* and a mask of zero will unsubscribe the service from events altogether.
* Return OK on success, EPERM if the caller may not register for events, or
* ENOMEM if all subscriber slots are in use already.
*/
int
do_proceventmask(void)
{
unsigned int i, mask;
/* This call is for system services only. */
if (!(mp->mp_flags & PRIV_PROC))
return EPERM;
mask = m_in.m_lsys_pm_proceventmask.mask;
/*
* First check if we need to update or remove an existing entry.
* We cannot actually remove services for which we are still waiting
* for a reply, so set their mask to zero for later removal instead.
*/
for (i = 0; i < nsubs; i++) {
if (subs[i].endpt == who_e) {
if (mask == 0 && subs[i].waiting == 0)
remove_sub(i);
else
subs[i].mask = mask;
return OK;
}
}
/* Add a new entry, unless the given mask is empty. */
if (mask == 0)
return OK;
/* This case should never trigger. */
if (nsubs == __arraycount(subs)) {
printf("PM: too many process event subscribers!\n");
return ENOMEM;
}
subs[nsubs].endpt = who_e;
subs[nsubs].mask = mask;
nsubs++;
return OK;
}
/*
* A subscribing service has replied to a process event message from us, or at
* least that is what should have happened. First make sure of this, and then
* resume event handling for the affected process.
*/
int
do_proc_event_reply(void)
{
struct mproc *rmp;
endpoint_t endpt;
unsigned int i, event;
int slot;
assert(nested == 0);
/*
* Is this an accidental call from a misguided user process?
* Politely tell it to go away.
*/
if (!(mp->mp_flags & PRIV_PROC))
return ENOSYS;
/*
* Ensure that we got the reply that we want. Since this code is
* relatively new, produce lots of warnings for cases that should never
* or rarely occur. Later we can just ignore all mismatching replies.
*/
endpt = m_in.m_pm_lsys_proc_event.endpt;
if (pm_isokendpt(endpt, &slot) != OK) {
printf("PM: proc event reply from %d for invalid endpt %d\n",
who_e, endpt);
return SUSPEND;
}
rmp = &mproc[slot];
if (!(rmp->mp_flags & EVENT_CALL)) {
printf("PM: proc event reply from %d for endpt %d, no event\n",
who_e, endpt);
return SUSPEND;
}
if (rmp->mp_eventsub == NO_EVENTSUB ||
(unsigned int)rmp->mp_eventsub >= nsubs) {
printf("PM: proc event reply from %d for endpt %d index %d\n",
who_e, endpt, rmp->mp_eventsub);
return SUSPEND;
}
i = rmp->mp_eventsub;
if (subs[i].endpt != who_e) {
printf("PM: proc event reply for %d from %d instead of %d\n",
endpt, who_e, subs[i].endpt);
return SUSPEND;
}
if (rmp->mp_flags & EXITING)
event = PROC_EVENT_EXIT;
else if (rmp->mp_flags & UNPAUSED)
event = PROC_EVENT_SIGNAL;
else {
printf("PM: proc event reply from %d for %d, bad flags %x\n",
who_e, endpt, rmp->mp_flags);
return SUSPEND;
}
if (m_in.m_pm_lsys_proc_event.event != event) {
printf("PM: proc event reply from %d for %d for event %d "
"instead of %d\n", who_e, endpt,
m_in.m_pm_lsys_proc_event.event, event);
return SUSPEND;
}
/*
* Do NOT check the event against the subscriber's event mask, since a
* service may have unsubscribed from an event while it has yet to
* process some leftover notifications for that event. We could decide
* not to wait for the replies to those leftover notifications upon
* unsubscription, but that could result in problems upon quick
* resubscription, and such cases may in fact happen in practice.
*/
assert(subs[i].waiting > 0);
subs[i].waiting--;
/*
* If we are now no longer waiting for any replies from an already
* unsubscribed (but alive) service, remove it from the set now; this
* will also resume events for the current process. In the normal case
* however, let the current process move on to the next subscriber if
* there are more, and the actual event otherwise.
*/
if (subs[i].mask == 0 && subs[i].waiting == 0) {
remove_sub(i);
} else {
rmp->mp_eventsub++;
resume_event(rmp);
}
/* In any case, do not reply to this reply message. */
return SUSPEND;
}
/*
* Publish a process event to interested subscribers. The event is determined
* from the process flags. In addition, if the event is a process exit, also
* check if it is a subscribing service that died.
*/
void
publish_event(struct mproc * rmp)
{
unsigned int i;
assert(nested == 0);
assert((rmp->mp_flags & (IN_USE | EVENT_CALL)) == IN_USE);
assert(rmp->mp_eventsub == NO_EVENTSUB);
/*
* If a system service exited, we have to check if it was subscribed to
* process events. If so, we have to remove it from the set and resume
* any processes blocked on an event call to that service.
*/
if ((rmp->mp_flags & (PRIV_PROC | EXITING)) == (PRIV_PROC | EXITING)) {
for (i = 0; i < nsubs; i++) {
if (subs[i].endpt == rmp->mp_endpoint) {
/*
* If the wait count is nonzero, we may or may
* not get additional replies from this service
* later. Those will be ignored.
*/
remove_sub(i);
break;
}
}
}
/*
* Either send an event message to the first subscriber, or if there
* are no subscribers, resume processing the event right away.
*/
rmp->mp_flags |= EVENT_CALL;
rmp->mp_eventsub = 0;
resume_event(rmp);
}