FORK # 01
EXEC_NEWMEM # 03
WILLEXIT # 05
- NOTIFY_SIG # 39
GETRUSAGE # 47
;
io NONE; # No I/O range allowed
{ "GETPHYS", VM_GETPHYS },
{ "GETREF", VM_GETREF },
{ "RS_SET_PRIV", VM_RS_SET_PRIV },
- { "QUERY_EXIT", VM_QUERY_EXIT },
- { "WATCH_EXIT", VM_WATCH_EXIT },
- { "NOTIFY_SIG", VM_NOTIFY_SIG },
{ "INFO", VM_INFO },
{ "RS_UPDATE", VM_RS_UPDATE },
{ "RS_MEMCTL", VM_RS_MEMCTL },
#define PM_REBOOT (PM_BASE + 37)
#define PM_SVRCTL (PM_BASE + 38)
#define PM_SPROF (PM_BASE + 39)
-/* PM call number 40 is currently unused. */
+#define PM_PROCEVENTMASK (PM_BASE + 40)
#define PM_SRV_FORK (PM_BASE + 41)
#define PM_SRV_KILL (PM_BASE + 42)
#define PM_EXEC_NEW (PM_BASE + 43)
*===========================================================================*/
#define COMMON_RQ_BASE 0xE00
+#define COMMON_RS_BASE 0xE80
/* Field names for system signals (sent by a signal manager). */
#define SIGS_SIGNAL_RECEIVED (COMMON_RQ_BASE+0)
/* Common fault injection ctl request to all processes. */
#define COMMON_REQ_FI_CTL (COMMON_RQ_BASE+2)
+/* Process event message from PM. */
+#define PROC_EVENT (COMMON_RQ_BASE+3)
+
+/* Reply to process event message to PM. */
+#define PROC_EVENT_REPLY (COMMON_RS_BASE+0)
+
/*===========================================================================*
* Messages for VM server *
*===========================================================================*/
# define VM_RS_BUF m2_l1
# define VM_RS_SYS m2_i2
-#define VM_QUERY_EXIT (VM_RQ_BASE+38)
-
-#define VM_NOTIFY_SIG (VM_RQ_BASE+39)
-# define VM_NOTIFY_SIG_ENDPOINT m1_i1
-# define VM_NOTIFY_SIG_IPC m1_i2
-
#define VM_INFO (VM_RQ_BASE+40)
/* VM_INFO 'what' values. */
# define VM_RS_CTL_ADDR m2_p1
# define VM_RS_CTL_LEN m2_i3
-#define VM_WATCH_EXIT (VM_RQ_BASE+43)
-
#define VM_REMAP_RO (VM_RQ_BASE+44)
/* same args as VM_REMAP */
} mess_lsys_pm_getprocnr;
_ASSERT_MSG_SIZE(mess_lsys_pm_getprocnr);
+typedef struct {
+ unsigned int mask;
+
+ uint8_t padding[52];
+} mess_lsys_pm_proceventmask;
+_ASSERT_MSG_SIZE(mess_lsys_pm_proceventmask);
+
typedef struct {
uid_t uid;
gid_t gid;
} mess_lsys_vm_map_phys;
_ASSERT_MSG_SIZE(mess_lsys_vm_map_phys);
-typedef struct {
- endpoint_t ret_pt;
- int is_more;
- uint8_t padding[48];
-} mess_lsys_vm_query_exit;
-_ASSERT_MSG_SIZE(mess_lsys_vm_query_exit);
-
typedef struct {
endpoint_t endpt;
vir_bytes addr;
} mess_lsys_vm_vmremap;
_ASSERT_MSG_SIZE(mess_lsys_vm_vmremap);
-typedef struct {
- endpoint_t ep;
- uint8_t padding[52];
-} mess_lsys_vm_watch_exit;
-_ASSERT_MSG_SIZE(mess_lsys_vm_watch_exit);
-
typedef struct {
size_t oldlen;
uint8_t padding[52];
} mess_pm_lsys_getprocnr;
_ASSERT_MSG_SIZE(mess_pm_lsys_getprocnr);
+typedef struct {
+ endpoint_t endpt;
+ unsigned int event;
+
+ uint8_t padding[48];
+} mess_pm_lsys_proc_event;
+_ASSERT_MSG_SIZE(mess_pm_lsys_proc_event);
+
typedef struct {
int num;
mess_lsys_pci_busc_get_bar m_lsys_pci_busc_get_bar;
mess_lsys_pm_getepinfo m_lsys_pm_getepinfo;
mess_lsys_pm_getprocnr m_lsys_pm_getprocnr;
+ mess_lsys_pm_proceventmask m_lsys_pm_proceventmask;
mess_lsys_pm_srv_fork m_lsys_pm_srv_fork;
mess_lsys_sched_scheduling_start m_lsys_sched_scheduling_start;
mess_lsys_sched_scheduling_stop m_lsys_sched_scheduling_stop;
mess_lsys_vm_getref m_lsys_vm_getref;
mess_lsys_vm_info m_lsys_vm_info;
mess_lsys_vm_map_phys m_lsys_vm_map_phys;
- mess_lsys_vm_query_exit m_lsys_vm_query_exit;
mess_lsys_vm_rusage m_lsys_vm_rusage;
mess_lsys_vm_unmap_phys m_lsys_vm_unmap_phys;
mess_lsys_vm_update m_lsys_vm_update;
mess_lsys_vm_vmremap m_lsys_vm_vmremap;
- mess_lsys_vm_watch_exit m_lsys_vm_watch_exit;
mess_mib_lc_sysctl m_mib_lc_sysctl;
mess_mmap m_mmap;
mess_net_netdrv_dl_conf m_net_netdrv_dl_conf;
mess_pm_lexec_exec_new m_pm_lexec_exec_new;
mess_pm_lsys_getepinfo m_pm_lsys_getepinfo;
mess_pm_lsys_getprocnr m_pm_lsys_getprocnr;
+ mess_pm_lsys_proc_event m_pm_lsys_proc_event;
mess_pm_lsys_sigs_signal m_pm_lsys_sigs_signal;
mess_pm_sched_scheduling_set_nice m_pm_sched_scheduling_set_nice;
mess_pty_ptyfs_req m_pty_ptyfs_req;
#define COPYFD_TO 1 /* copy file descriptor to remote process */
#define COPYFD_CLOSE 2 /* close file descriptor in remote process */
+/*
+ * These are also the event numbers used in PROC_EVENT messages, but in order
+ * to allow for subscriptions to multiple events, they form a bit mask.
+ */
+#define PROC_EVENT_EXIT 0x01 /* process has exited */
+#define PROC_EVENT_SIGNAL 0x02 /* process has caught signal */
+int proceventmask(unsigned int mask);
+
#endif /* _SYSLIB_H */
void *vm_map_phys(endpoint_t who, void *physaddr, size_t len);
int vm_unmap_phys(endpoint_t who, void *vaddr, size_t len);
-int vm_notify_sig(endpoint_t ep, endpoint_t ipc_ep);
int vm_set_priv(endpoint_t ep, void *buf, int sys_proc);
int vm_update(endpoint_t src_e, endpoint_t dst_e, int flags);
int vm_memctl(endpoint_t ep, int req, void** addr, size_t *len);
int vm_prepare(endpoint_t src_e, endpoint_t dst_e, int flags);
-int vm_query_exit(endpoint_t *endpt);
-int vm_watch_exit(endpoint_t ep);
int minix_vfs_mmap(endpoint_t who, off_t offset, size_t len,
dev_t dev, ino_t ino, int fd, u32_t vaddr, u16_t clearend, u16_t
flags);
mapdriver.c \
optset.c \
panic.c \
+ proceventmask.c \
safecopies.c \
sched_start.c \
sched_stop.c \
vm_info.c \
vm_map_phys.c \
vm_memctl.c \
- vm_notify_sig.c \
vm_prepare.c \
vm_procctl.c \
- vm_query_exit.c \
vm_set_priv.c \
vm_update.c
--- /dev/null
+#include "syslib.h"
+
+#include <string.h>
+
+/*
+ * Subscribe to a certain set of process events from PM. Subsequent calls will
+ * replace the set, and the empty set (a zero mask) will unsubscribe the caller
+ * altogether. Usage restrictions apply; see PM's event.c for details. Return
+ * OK or a negative error code.
+ */
+int
+proceventmask(unsigned int mask)
+{
+ message m;
+
+ memset(&m, 0, sizeof(m));
+ m.m_lsys_pm_proceventmask.mask = mask;
+
+ return _taskcall(PM_PROC_NR, PM_PROCEVENTMASK, &m);
+}
+++ /dev/null
-
-#include "syslib.h"
-
-#include <string.h>
-#include <minix/vm.h>
-
-/*===========================================================================*
- * vm_notify_sig *
- *===========================================================================*/
-int vm_notify_sig(endpoint_t ep, endpoint_t ipc_ep)
-{
- message m;
- int result;
-
- memset(&m, 0, sizeof(m));
- m.VM_NOTIFY_SIG_ENDPOINT = ep;
- m.VM_NOTIFY_SIG_IPC = ipc_ep;
-
- result = _taskcall(VM_PROC_NR, VM_NOTIFY_SIG, &m);
- return(result);
-}
-
+++ /dev/null
-#include "syslib.h"
-#include <unistd.h>
-#include <string.h>
-
-/*
- * Return a negative error code if the query itself or the processing of the
- * query resulted in an error.
- * Return 1 if there are more processes waiting to be queried.
- * Return 0 if there are no more processes.
- * Note that for the return value of 0 and 1, the 'endpt' is set accordingly.
- */
-int
-vm_query_exit(int *endpt)
-{
- message m;
- int r;
-
- memset(&m, 0, sizeof(m));
- r = _taskcall(VM_PROC_NR, VM_QUERY_EXIT, &m);
- if (r != OK)
- return r;
- if (endpt == NULL)
- return EFAULT;
-
- *endpt = m.m_lsys_vm_query_exit.ret_pt;
- return (m.m_lsys_vm_query_exit.is_more ? 1 : 0);
-}
-
-int
-vm_watch_exit(endpoint_t ep)
-{
- message m;
-
- memset(&m, 0, sizeof(m));
- m.m_lsys_vm_watch_exit.ep = ep;
- return _taskcall(VM_PROC_NR, VM_WATCH_EXIT, &m);
-}
int do_semctl(message *);
int do_semop(message *);
int is_sem_nil(void);
-void sem_process_vm_notify(void);
+void sem_process_event(endpoint_t, int);
/* utility.c */
int check_perm(struct ipc_perm *, endpoint_t, int);
SHM_UNMAP
GETPHYS
GETREF
- QUERY_EXIT
- WATCH_EXIT
;
};
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;
}
printf("IPC: got %d from %d\n", m.m_type, m.m_source);
if (is_ipc_notify(ipc_status)) {
- switch (m.m_source) {
- case VM_PROC_NR:
- /*
- * Currently, only semaphore handling needs
- * to know about processes exiting.
- */
- sem_process_vm_notify();
- break;
- default:
- printf("IPC: ignoring notification from %d\n",
- m.m_source);
- break;
- }
+ 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;
}
if (call_index < __arraycount(call_vec) &&
call_vec[call_index] != NULL) {
- /*
- * If any process does an IPC call, we have to know
- * about it exiting. Tell VM to watch it for us.
- * TODO: this is not true; limit to affected processes.
- */
- if (vm_watch_exit(m.m_source) != OK) {
- printf("IPC: watch failed on %d\n",
- m.m_source);
- }
-
r = call_vec[call_index](&m);
} else
r = ENOSYS;
return (sem_list_nr == 0);
}
+/*
+ * Check whether processes have terminated and/or are about to have a signal
+ * caught, in which case any pending blocking operation must be cancelled.
+ */
void
-sem_process_vm_notify(void)
+sem_process_event(endpoint_t endpt, int has_exited __unused)
{
- endpoint_t endpt;
- int r;
- /* For each endpoint, check whether it is waiting. */
- while ((r = vm_query_exit(&endpt)) >= 0) {
- remove_process(endpt);
-
- if (r == 0)
- break;
- }
- if (r < 0)
- printf("IPC: query exit error (%d)\n", r);
+ /*
+ * As long as we do not support SEM_UNDO, it does not matter whether
+ * the process has exited or has a signal delivered: in both cases, we
+ * need to cancel any blocking semop(2) call.
+ */
+ remove_process(endpt);
}
PROG= pm
SRCS= main.c forkexit.c exec.c time.c alarm.c \
signal.c utility.c table.c trace.c getset.c misc.c \
- profile.c mcontext.c schedule.c
+ profile.c mcontext.c schedule.c event.c
DPADD+= ${LIBSYS} ${LIBTIMERS}
LDADD+= -lsys -ltimers
#define NO_TRACER 0 /* process is not being traced */
+#define NO_EVENTSUB ((char)-1) /* no current process event subscriber */
+
#define MAX_CLOCK_T ((unsigned long) 1 << ((sizeof(clock_t) * 8) - 1))
#define MAX_SECS ( (clock_t) (MAX_CLOCK_T/system_hz) )
/* max.secs for setitimer() ((2^31-1)/HZ) */
--- /dev/null
+/*
+ * 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);
+}
rmc->mp_interval[i] = 0; /* reset timer intervals */
rmc->mp_started = getticks(); /* remember start time, for ps(1) */
+ assert(rmc->mp_eventsub == NO_EVENTSUB);
+
/* Find a free pid for the child and put it in the table. */
new_pid = get_free_pid();
rmc->mp_pid = new_pid; /* assign pid to child */
rmc->mp_interval[i] = 0; /* reset timer intervals */
rmc->mp_started = getticks(); /* remember start time, for ps(1) */
+ assert(rmc->mp_eventsub == NO_EVENTSUB);
+
/* Find a free pid for the child and put it in the table. */
new_pid = get_free_pid();
rmc->mp_pid = new_pid; /* assign pid to child */
/* If the process is not yet stopped, we force a stop here. This means that
* the process may still have a delay call pending. For this reason, the main
* message loop discards requests from exiting processes.
+ *
+ * TODO: make the kernel discard delayed calls upon forced stops for exits,
+ * so that no service needs to deal with this. Right now it appears that the
+ * only thing preventing problems with other services is the fact that
+ * regular messages are prioritized over asynchronous messages.
*/
if (!(rmp->mp_flags & PROC_STOPPED)) {
if ((r = sys_stop(proc_nr_e)) != OK) /* stop the process */
if((r=vm_willexit(proc_nr_e)) != OK) {
panic("exit_proc: vm_willexit failed: %d", r);
}
- vm_notify_sig_wrapper(rmp->mp_endpoint);
+
if (proc_nr_e == INIT_PROC_NR)
{
printf("PM: INIT died with exit status %d; showing stacktrace\n", exit_status);
panic("exit_proc: VFS died: %d", r);
}
- /* Tell VFS about the exiting process. */
+ /* Tell VFS, and after that any matching process event subscribers, about the
+ * exiting process.
+ */
memset(&m, 0, sizeof(m));
m.m_type = dump_core ? VFS_PM_DUMPCORE : VFS_PM_EXIT;
m.VFS_PM_ENDPT = rmp->mp_endpoint;
/*===========================================================================*
* exit_restart *
*===========================================================================*/
-void exit_restart(rmp, dump_core)
-struct mproc *rmp; /* pointer to the process being terminated */
-int dump_core; /* flag indicating whether to dump core */
+void exit_restart(struct mproc *rmp)
{
/* VFS replied to our exit or coredump request. Perform the second half of the
* exit code.
rmp->mp_scheduler = NONE;
/* For core dumps, now is the right time to try to contact the parent. */
- if (dump_core)
+ if (!(rmp->mp_flags & (TRACE_ZOMBIE | ZOMBIE | TOLD_PARENT)))
zombify(rmp);
if (!(rmp->mp_flags & PRIV_PROC))
/* This child meets the pid test and has exited. */
waited_for = tell_parent(rp, addr);
- if (waited_for && !(rp->mp_flags & VFS_CALL))
+ if (waited_for &&
+ !(rp->mp_flags & (VFS_CALL | EVENT_CALL)))
cleanup(rp);
return(SUSPEND);
}
/* The 'try_cleanup' flag merely saves us from having to be really
* careful with statement ordering in exit_proc() and exit_restart().
*/
- if (try_cleanup && !(child->mp_flags & VFS_CALL))
+ if (try_cleanup && !(child->mp_flags & (VFS_CALL | EVENT_CALL)))
cleanup(child);
}
else {
handle_vfs_reply();
result = SUSPEND; /* don't reply */
+ } else if (call_nr == PROC_EVENT_REPLY) {
+ result = do_proc_event_reply();
} else if (IS_PM_CALL(call_nr)) {
/* If the system call number is valid, perform the call. */
call_index = (unsigned int) (call_nr - PM_BASE);
init_timer(&rmp->mp_timer);
rmp->mp_magic = MP_MAGIC;
rmp->mp_sigact = mpsigact[rmp - mproc];
+ rmp->mp_eventsub = NO_EVENTSUB;
}
/* Build the set of signals which cause core dumps, and the set of signals
break;
- case VFS_PM_EXIT_REPLY:
- exit_restart(rmp, FALSE /*dump_core*/);
-
- break;
-
case VFS_PM_CORE_REPLY:
if (m_in.VFS_PM_STATUS == OK)
rmp->mp_sigstatus |= WCOREFLAG;
- exit_restart(rmp, TRUE /*dump_core*/);
+ /* FALLTHROUGH */
+ case VFS_PM_EXIT_REPLY:
+ assert(rmp->mp_flags & EXITING);
- break;
+ /* Publish the exit event. Continue exiting the process after that. */
+ publish_event(rmp);
+
+ return; /* do not take the default action */
case VFS_PM_FORK_REPLY:
/* Schedule the newly created process ... */
/* Process is now unpaused */
rmp->mp_flags |= UNPAUSED;
- break;
+ /* Publish the signal event. Continue with signals only after that. */
+ publish_event(rmp);
+
+ return; /* do not take the default action */
default:
panic("handle_vfs_reply: unknown reply code: %d", call_nr);
EXTERN struct mproc {
char mp_exitstatus; /* storage for status when process exits */
char mp_sigstatus; /* storage for signal # for killed procs */
+ char mp_eventsub; /* process event subscriber, or NO_EVENTSUB */
pid_t mp_pid; /* process id */
endpoint_t mp_endpoint; /* kernel endpoint id */
pid_t mp_procgrp; /* pid of process group (used for signals) */
#define TRACE_ZOMBIE 0x10000 /* waiting for tracer to issue WAIT4 call */
#define DELAY_CALL 0x20000 /* waiting for call before sending signal */
#define TAINTED 0x40000 /* process is 'tainted' */
+#define EVENT_CALL 0x80000 /* waiting for process event subscriber */
#define MP_MAGIC 0xC0FFEE0
void set_alarm(struct mproc *rmp, clock_t ticks);
void check_vtimer(int proc_nr, int sig);
+/* event.c */
+int do_proceventmask(void);
+int do_proc_event_reply(void);
+void publish_event(struct mproc *rmp);
+
/* exec.c */
int do_exec(void);
int do_newexec(void);
int do_srv_fork(void);
int do_exit(void);
void exit_proc(struct mproc *rmp, int exit_status, int dump_core);
-void exit_restart(struct mproc *rmp, int dump_core);
+void exit_restart(struct mproc *rmp);
int do_wait4(void);
int wait_test(struct mproc *rmp, struct mproc *child);
int do_sigsuspend(void);
void check_pending(struct mproc *rmp);
void restart_sigs(struct mproc *rmp);
-void vm_notify_sig_wrapper(endpoint_t ep);
/* time.c */
int do_stime(void);
struct sigaction svec;
struct sigaction *svp;
- assert(!(mp->mp_flags & (PROC_STOPPED | VFS_CALL | UNPAUSED)));
+ assert(!(mp->mp_flags & (PROC_STOPPED | VFS_CALL | UNPAUSED | EVENT_CALL)));
sig_nr = m_in.m_lc_pm_sig.nr;
if (sig_nr == SIGKILL) return(OK);
*===========================================================================*/
int do_sigpending(void)
{
- assert(!(mp->mp_flags & (PROC_STOPPED | VFS_CALL | UNPAUSED)));
+ assert(!(mp->mp_flags & (PROC_STOPPED | VFS_CALL | UNPAUSED | EVENT_CALL)));
mp->mp_reply.m_pm_lc_sigset.set = mp->mp_sigpending;
return OK;
sigset_t set;
int i;
- assert(!(mp->mp_flags & (PROC_STOPPED | VFS_CALL | UNPAUSED)));
+ assert(!(mp->mp_flags & (PROC_STOPPED | VFS_CALL | UNPAUSED | EVENT_CALL)));
set = m_in.m_lc_pm_sigset.set;
mp->mp_reply.m_pm_lc_sigset.set = mp->mp_sigmask;
*===========================================================================*/
int do_sigsuspend(void)
{
- assert(!(mp->mp_flags & (PROC_STOPPED | VFS_CALL | UNPAUSED)));
+ assert(!(mp->mp_flags & (PROC_STOPPED | VFS_CALL | UNPAUSED | EVENT_CALL)));
mp->mp_sigmask2 = mp->mp_sigmask; /* save the old mask */
mp->mp_sigmask = m_in.m_lc_pm_sigset.set;
*/
int r;
- assert(!(mp->mp_flags & (PROC_STOPPED | VFS_CALL | UNPAUSED)));
+ assert(!(mp->mp_flags & (PROC_STOPPED | VFS_CALL | UNPAUSED | EVENT_CALL)));
mp->mp_sigmask = m_in.m_lc_pm_sigset.set;
sigdelset(&mp->mp_sigmask, SIGKILL);
assert(rmp->mp_flags & PROC_STOPPED);
- /* If the process is blocked on a VFS call, do not resume it now. Most likely * it will be unpausing, in which case the process must remain stopped.
- * Otherwise, it will still be resumed once the VFS call returns. If the
- * process has died, do not resume it either.
+ /* If the process is blocked on a VFS call or a process event notification,
+ * do not resume it now. Most likely it will be unpausing, in which case the
+ * process must remain stopped. Otherwise, it will still be resumed once the
+ * VFS or event call is replied to. If the process has died, do not resume
+ * it either.
*/
- if (rmp->mp_flags & (VFS_CALL | EXITING))
+ if (rmp->mp_flags & (VFS_CALL | EVENT_CALL | EXITING))
return;
if ((r = sys_resume(rmp->mp_endpoint)) != OK)
* that case, we must wait with further signal processing until VFS has
* replied. Stop the process.
*/
- if (rmp->mp_flags & VFS_CALL) {
+ if (rmp->mp_flags & (VFS_CALL | EVENT_CALL)) {
stop_proc(rmp, FALSE /*may_delay*/);
return OK;
return;
}
- if (rmp->mp_flags & VFS_CALL) {
+ if (rmp->mp_flags & (VFS_CALL | EVENT_CALL)) {
sigaddset(&rmp->mp_sigpending, signo);
if(ksig)
sigaddset(&rmp->mp_ksigpending, signo);
- /* Process the signal once VFS replies. Stop the process in the
- * meantime, so that it cannot make another call after the VFS reply
- * comes in but before we look at its signals again. Since we always
- * stop the process to deliver signals during a VFS call, the
- * PROC_STOPPED flag doubles as an indicator in restart_sigs() that
- * signals must be rechecked after a VFS reply comes in.
+ /* Process the signal once VFS and process event subscribers reply.
+ * Stop the process in the meantime, so that it cannot make another
+ * call after the VFS reply comes in but before we look at its signals
+ * again. Since we always stop the process to deliver signals during a
+ * VFS or event call, the PROC_STOPPED flag doubles as an indicator in
+ * restart_sigs() that signals must be rechecked after a reply arrives.
*/
if (!(rmp->mp_flags & (PROC_STOPPED | DELAY_CALL))) {
/* If a VFS call is ongoing and the process is not yet stopped,
sigdelset(&rmp->mp_ksigpending, i);
sig_proc(rmp, i, FALSE /*trace*/, ksig);
- if (rmp->mp_flags & VFS_CALL) {
+ if (rmp->mp_flags & (VFS_CALL | EVENT_CALL)) {
/* Signals must be rechecked upon return from the new
* VFS call, unless the process was killed. In both
* cases, the process is stopped.
/* VFS has replied to a request from us; do signal-related work.
*/
- if (rmp->mp_flags & (VFS_CALL | EXITING)) return;
+ if (rmp->mp_flags & (VFS_CALL | EVENT_CALL | EXITING)) return;
if (rmp->mp_flags & TRACE_EXIT) {
/* Tracer requested exit with specific exit value */
*/
message m;
- assert(!(rmp->mp_flags & VFS_CALL));
+ assert(!(rmp->mp_flags & (VFS_CALL | EVENT_CALL)));
/* If the UNPAUSED flag is set, VFS replied to an earlier unpause request. */
if (rmp->mp_flags & UNPAUSED) {
return TRUE;
}
- /* Not paused in PM. Let VFS try to unpause the process. The process needs to
- * be stopped for this. If it is not already stopped, try to stop it now. If
- * that does not succeed immediately, postpone signal delivery.
+ /* Not paused in PM. Let VFS, and after that any matching process event
+ * subscribers, try to unpause the process. The process needs to be stopped
+ * for this. If it is not already stopped, try to stop it now. If that does
+ * not succeed immediately, postpone signal delivery.
*/
if (!(rmp->mp_flags & PROC_STOPPED) && !stop_proc(rmp, TRUE /*may_delay*/))
return FALSE;
tell_vfs(rmp, &m);
- /* Also tell VM. */
- vm_notify_sig_wrapper(rmp->mp_endpoint);
-
return FALSE;
}
return(TRUE);
}
-
-/*===========================================================================*
- * vm_notify_sig_wrapper *
- *===========================================================================*/
-void vm_notify_sig_wrapper(endpoint_t ep)
-{
-/* get IPC's endpoint,
- * the reason that we directly get the endpoint
- * instead of from DS server is that otherwise
- * it will cause deadlock between PM, VM and DS.
- */
- struct mproc *rmp;
- endpoint_t ipc_ep = 0;
-
- for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++) {
- if (!(rmp->mp_flags & IN_USE))
- continue;
- if (!strcmp(rmp->mp_name, "ipc")) {
- ipc_ep = rmp->mp_endpoint;
- vm_notify_sig(ep, ipc_ep);
-
- return;
- }
- }
-}
CALL(PM_REBOOT) = do_reboot, /* reboot(2) */
CALL(PM_SVRCTL) = do_svrctl, /* svrctl(2) */
CALL(PM_SPROF) = do_sprofile, /* sprofile(2) */
+ CALL(PM_PROCEVENTMASK) = do_proceventmask, /* proceventmask(2) */
CALL(PM_SRV_FORK) = do_srv_fork, /* srv_fork(2) */
CALL(PM_SRV_KILL) = do_srv_kill, /* srv_kill(2) */
CALL(PM_EXEC_NEW) = do_newexec,
case T_EXIT: /* exit */
child->mp_flags |= TRACE_EXIT;
- /* Defer the exit if the traced process has an VFS call pending. */
- if (child->mp_flags & VFS_CALL)
+ /* Defer the exit if the traced process has a call pending. */
+ if (child->mp_flags & (VFS_CALL | EVENT_CALL))
child->mp_exitstatus = m_in.m_lc_pm_ptrace.data; /* save it */
else
exit_proc(child, m_in.m_lc_pm_ptrace.data,
*/
int r;
- if (rmp->mp_flags & VFS_CALL)
+ if (rmp->mp_flags & (VFS_CALL | EVENT_CALL))
panic("tell_vfs: not idle: %d", m_ptr->m_type);
r = asynsend3(VFS_PROC_NR, m_ptr, AMF_NOREPLY);
PROG= vm
SRCS= main.c alloc.c utility.c exit.c fork.c break.c \
mmap.c slaballoc.c region.c pagefaults.c pagetable.c \
- rs.c queryexit.c pb.c regionavl.c \
+ rs.c pb.c regionavl.c \
mem_anon.c mem_directphys.c mem_anon_contig.c mem_shared.c \
mem_cache.c cache.c vfs.c mem_file.c fdref.c acl.c
CALLMAP(VM_FORK, do_fork);
CALLMAP(VM_BRK, do_brk);
CALLMAP(VM_WILLEXIT, do_willexit);
- CALLMAP(VM_NOTIFY_SIG, do_notify_sig);
CALLMAP(VM_PROCCTL, do_procctl_notrans);
CALLMAP(VM_SHM_UNMAP, do_munmap);
CALLMAP(VM_GETREF, do_get_refcount);
CALLMAP(VM_INFO, do_info);
- CALLMAP(VM_QUERY_EXIT, do_query_exit);
- CALLMAP(VM_WATCH_EXIT, do_watch_exit);
/* Cache blocks. */
CALLMAP(VM_MAPCACHEPAGE, do_mapcache);
/* getrusage */
CALLMAP(VM_GETRUSAGE, do_getrusage);
- /* Initialize the structures for queryexit */
- init_query_exit();
-
/* Mark VM instances. */
num_vm_instances = 1;
vmproc[VM_PROC_NR].vm_flags |= VMF_VM_INSTANCE;
int do_rs_update(message *m);
int do_rs_memctl(message *m);
-/* queryexit.c */
-int do_query_exit(message *m);
-int do_watch_exit(message *m);
-int do_notify_sig(message *m);
-void init_query_exit(void);
-
/* pb.c */
struct phys_block *pb_new(phys_bytes phys);
void pb_free(struct phys_block *);
+++ /dev/null
-
-#define _SYSTEM 1
-
-#include <minix/callnr.h>
-#include <minix/com.h>
-#include <minix/config.h>
-#include <minix/const.h>
-#include <minix/ds.h>
-#include <minix/endpoint.h>
-#include <minix/minlib.h>
-#include <minix/type.h>
-#include <minix/ipc.h>
-#include <minix/sysutil.h>
-#include <minix/syslib.h>
-#include <minix/safecopies.h>
-#include <minix/bitmap.h>
-#include <minix/vm.h>
-#include <minix/ds.h>
-
-#include <errno.h>
-#include <string.h>
-#include <env.h>
-#include <stdio.h>
-
-#include "glo.h"
-#include "proto.h"
-#include "util.h"
-
-struct query_exit_struct {
- int avail;
- endpoint_t ep;
-};
-static struct query_exit_struct array[NR_PROCS];
-
-/*===========================================================================*
- * do_query_exit *
- *===========================================================================*/
-int do_query_exit(message *m)
-{
- int i, nr;
- endpoint_t ep = NONE;
-
- for (i = 0; i < NR_PROCS; i++) {
- if (!array[i].avail) {
- array[i].avail = 1;
- ep = array[i].ep;
- array[i].ep = 0;
-
- break;
- }
- }
-
- nr = 0;
- for (i = 0; i < NR_PROCS; i++) {
- if (!array[i].avail)
- nr++;
- }
- m->m_lsys_vm_query_exit.ret_pt = ep;
- m->m_lsys_vm_query_exit.is_more = (nr > 0);
-
- return OK;
-}
-
-/*===========================================================================*
- * do_notify_sig *
- *===========================================================================*/
-int do_notify_sig(message *m)
-{
- int i, avails = 0;
- endpoint_t ep = m->VM_NOTIFY_SIG_ENDPOINT;
- endpoint_t ipc_ep = m->VM_NOTIFY_SIG_IPC;
- int r;
- struct vmproc *vmp;
- int pslot;
-
- if(vm_isokendpt(ep, &pslot) != OK) return ESRCH;
- vmp = &vmproc[pslot];
-
- /* Only record the event if we've been asked to report it. */
- if(!(vmp->vm_flags & VMF_WATCHEXIT))
- return OK;
-
- for (i = 0; i < NR_PROCS; i++) {
- /* its signal is already here */
- if (!array[i].avail && array[i].ep == ep)
- goto out;
- if (array[i].avail)
- avails++;
- }
- if (!avails) {
- /* no slot for signals, unlikely */
- printf("VM: no slot for signals!\n");
- return ENOMEM;
- }
-
- for (i = 0; i < NR_PROCS; i++) {
- if (array[i].avail) {
- array[i].avail = 0;
- array[i].ep = ep;
-
- break;
- }
- }
-
-out:
- /* only care when IPC server starts up,
- * and bypass the process to be signal is IPC itself.
- */
- if (ipc_ep != 0 && ep != ipc_ep) {
- r = ipc_notify(ipc_ep);
- if (r != OK)
- printf("VM: ipc_notify error!\n");
- }
- return OK;
-}
-
-/*===========================================================================*
- * do_watch_exit *
- *===========================================================================*/
-int do_watch_exit(message *m)
-{
- endpoint_t e = m->m_lsys_vm_watch_exit.ep;
- struct vmproc *vmp;
- int p;
- if(vm_isokendpt(e, &p) != OK) return ESRCH;
- vmp = &vmproc[p];
- vmp->vm_flags |= VMF_WATCHEXIT;
-
- return OK;
-}
-
-/*===========================================================================*
- * init_query_exit *
- *===========================================================================*/
-void init_query_exit(void)
-{
- int i;
-
- for (i = 0; i < NR_PROCS; i++) {
- array[i].avail = 1;
- array[i].ep = 0;
- }
-}
-
/* Bits for vm_flags */
#define VMF_INUSE 0x001 /* slot contains a process */
#define VMF_EXITING 0x002 /* PM is cleaning up this process */
-#define VMF_WATCHEXIT 0x008 /* Store in queryexit table */
#define VMF_VM_INSTANCE 0x010 /* This is a VM process instance */
#endif