David van Moolenbroek 910831cb5c PM: generic process event publish/subscribe system
Now that there are services other than PM and VFS that implement
userland system calls directly, these services may need to know about
events related to user processes.  In particular, signal delivery may
have to interrupt blocking system calls, and certain cleanup tasks may
have to be performed after a user process exits.

This patch aims to implement a generic, lasting solution for this
problem, by allowing services to subscribe to "signal delivered"
and/or "process exit" events from PM.  PM publishes such events by
sending messages to its subscribed services, which must then reply an
acknowledgment message.

For now, only the two aforementioned events are implemented, and only
the IPC service makes use of the process event facility.

The new process event publish/subscribe system replaces the previous
VM notify-sig/watch-exit/query-exit system, which was unsound: 1) it
allowed subscription to events from individual processes, and suffered
from fundamental race conditions as a result; 2) it relied on "not too
many" processes making use of the IPC server functionality in order to
avoid loss of notifications.  In addition, it had the "ipc" process
name hardcoded, did not distinguish between signal delivery and exits,
and added a roundtrip to VM for all events from all processes.

Change-Id: I75ebad4bc54e646c6433f473294cb4003b2c3430
2016-01-16 14:04:10 +01:00

145 lines
3.4 KiB
C

#include "inc.h"
/*
* The call table for this service.
*/
#define CALL(n) [((n) - IPC_BASE)]
static int (* const call_vec[])(message *) = {
CALL(IPC_SHMGET) = do_shmget,
CALL(IPC_SHMAT) = do_shmat,
CALL(IPC_SHMDT) = do_shmdt,
CALL(IPC_SHMCTL) = do_shmctl,
CALL(IPC_SEMGET) = do_semget,
CALL(IPC_SEMCTL) = do_semctl,
CALL(IPC_SEMOP) = do_semop,
};
static int verbose = 0;
/*
* Initialize the IPC server.
*/
static int
sef_cb_init_fresh(int type __unused, sef_init_info_t * info __unused)
{
/*
* Subscribe to PM process events. While it might be tempting to
* implement a system that subscribes to events only from processes
* that are actually blocked (or using the SysV IPC facilities at all),
* this would result in race conditions where subscription could happen
* "too late" for an ongoing signal delivery, causing the affected
* process to deadlock. By issuing this one blocking subscription call
* at startup, we eliminate all possibilities of such race conditions,
* at the cost of receiving notifications for literally all processes.
*/
proceventmask(PROC_EVENT_EXIT | PROC_EVENT_SIGNAL);
return OK;
}
static void
sef_cb_signal_handler(int signo)
{
/* Only check for termination signal, ignore anything else. */
if (signo != SIGTERM) return;
/*
* Check if there are still IPC keys around. If not, we can safely
* exit immediately. Otherwise, warn the system administrator.
*/
if (is_sem_nil() && is_shm_nil())
sef_exit(0);
printf("IPC: exit with unclean state\n");
}
static void
sef_local_startup(void)
{
/* Register init callbacks. */
sef_setcb_init_fresh(sef_cb_init_fresh);
sef_setcb_init_restart(sef_cb_init_fresh);
/* Register signal callbacks. */
sef_setcb_signal_handler(sef_cb_signal_handler);
/* Let SEF perform startup. */
sef_startup();
}
int
main(int argc, char ** argv)
{
message m;
unsigned int call_index;
int r, ipc_status;
/* SEF local startup. */
env_setargs(argc, argv);
sef_local_startup();
/* The main message loop. */
for (;;) {
if ((r = sef_receive_status(ANY, &m, &ipc_status)) != OK)
panic("IPC: sef_receive_status failed: %d", r);
if (verbose)
printf("IPC: got %d from %d\n", m.m_type, m.m_source);
if (is_ipc_notify(ipc_status)) {
printf("IPC: ignoring notification from %d\n",
m.m_source);
continue;
}
if (m.m_source == PM_PROC_NR && m.m_type == PROC_EVENT) {
/*
* Currently, only semaphore handling needs to know
* about processes being signaled and exiting.
*/
sem_process_event(m.m_pm_lsys_proc_event.endpt,
m.m_pm_lsys_proc_event.event == PROC_EVENT_EXIT);
/* Echo the request as a reply back to PM. */
m.m_type = PROC_EVENT_REPLY;
if ((r = asynsend3(m.m_source, &m, AMF_NOREPLY)) != OK)
printf("IPC: replying to PM process event "
"failed (%d)\n", r);
continue;
}
/* Dispatch the request. */
call_index = (unsigned int)(m.m_type - IPC_BASE);
if (call_index < __arraycount(call_vec) &&
call_vec[call_index] != NULL) {
r = call_vec[call_index](&m);
} else
r = ENOSYS;
/* Send a reply, if needed. */
if (r != SUSPEND) {
if (verbose)
printf("IPC: call result %d\n", r);
m.m_type = r;
/*
* Other fields may have been set by the handler
* function already.
*/
if ((r = ipc_sendnb(m.m_source, &m)) != OK)
printf("IPC: send error %d\n", r);
}
/* XXX there must be a better way to do this! */
update_refcount_and_destroy();
}
/* NOTREACHED */
return 0;
}