Notification support in the kernel
authorBarret Rhoden <brho@cs.berkeley.edu>
Mon, 29 Mar 2010 23:40:11 +0000 (16:40 -0700)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:35:40 +0000 (17:35 -0700)
The kernel can send notifications to a process.  Will need some merging
work with the glibc branch, especially regarding transition stacks and
userspace's job.  mhello has example code for what low-level user code
needs to do to request and receive notifications.

Note that you cannot notify vcore0 at this point, due to some glibc
issues.

Documentation/process-internals.txt
Documentation/processes.txt
kern/include/process.h
kern/include/ros/notification.h
kern/src/process.c
tests/mhello.c

index f293d63..918fdec 100644 (file)
@@ -528,12 +528,17 @@ preempt-critical locks.
 It is possible that notifications will mix with preemptions or come while a
 process is not running.  Ultimately, the process wants to be notified on a
 given vcore.  Whenever we send an active notification, we set a flag in
-procdata.  If the vcore is offline or is in a preempt-phase, we don't bother
-sending the IPI/notif message.  The kernel will make sure it runs the
-notification handler (as well as restoring the preempt_tf) the next time that
-vcore is restarted.  Note that userspace can toggle this, so they can handle
-the notifications from a different core if it likes, or they can independently
-send a notification.
+procdata (notif_pending).  If the vcore is offline, we don't bother sending the IPI/notif message.  The kernel will make sure it runs
+the notification handler (as well as restoring the preempt_tf) the next time
+that vcore is restarted.  Note that userspace can toggle this, so they can
+handle the notifications from a different core if it likes, or they can
+independently send a notification.
+
+If a vcore has a preempt_pending, we will still send the active notification
+(IPI).  The core ought to get a notification for the preemption anyway, so we
+need to be able to send one.  Additionally, once the vcore is handling that
+preemption notification, it will have notifs disabled, which will prevent us
+from sending any extra notifications anyways.
  
 4.7: Notifs While a Preempt Message is Served
 ---------------------------
@@ -548,10 +553,10 @@ k_msgs work), the IPI will fire and push us right back into the kernel to
 execute the preemption, and the notif handler's context will be saved in the
 preempt_tf (ready to go when the vcore gets started again).
 
-We could try to just set the notif_pending flag and ignore the message, but
-that would involve inspecting the queue for the preempt k_msg.  Additionally,
-a preempt k_msg can arrive anyway.  Finally, it's possible to have another
-message in the queue between the notif and the preempt, and it gets ugly
+We could try to just leave the notif_pending flag set and ignore the message,
+but that would involve inspecting the queue for the preempt k_msg.
+Additionally, a preempt k_msg can arrive anyway.  Finally, it's possible to have
+another message in the queue between the notif and the preempt, and it gets ugly
 quickly trying to determine what to do.
 
 4.8: When a Pcore is "Free"
index a0b11fa..fb1ab8c 100644 (file)
@@ -309,6 +309,8 @@ In procdata there is an array of per-vcore data, holding some
 preempt/notification information and space for two trapframes: one for
 notification and one for preemption.
 
+4.2.1: Overall
+-----------------------------
 When a notification arrives to a process under normal circumstances, the
 kernel places the previous running context in the notification trapframe, and
 returns to userspace at the program entry point (the elf entry point) on the
@@ -326,23 +328,21 @@ turning interrupts on in hardware).  When a core starts up, this flag is off,
 meaning that notifications are disabled by default.  It is the process's
 responsibility to turn on notifications for a given vcore.
 
+4.2.2: Notif Event Details
+-----------------------------
 When the process runs the handler, it is actually starting up at the same
-location in code as it always does, so the kernel will pass it a parameter in
-a register to let it know that it is in a notification.  Additionally, the
-kernel will mask notifications (much like an x86 interrupt gate).  The process
-must check its per-core event queue to see why it was called, and deal with
-all of the events on the queue.  In the case where the event queue overflows,
-the kernel will up a counter so the process can at least be aware things are
-missed.
-
-For missed events, and for events that do not need messages (they have no
-parameters and multiple notifications are irrelevant), the kernel will toggle
-that event's bit in a bitmask.  For the events that don't want messages, we may
-have a flag that userspace sets, meaning they just want to know it happened.
-This might be too much of a pain, so we'll see.  For notification events that
-overflowed the queue, the parameters will be lost, but hopefully the application
-can sort it out.  Again, we'll see.  A specific notif_event should not appear in
-both the event buffers and in the bitmask.
+location in code as it always does.  To determine if it was a notification or
+not, simply check the queue and bitmask.  This has the added benefit of allowing
+a process to notice notifications that it missed previously, or notifs it wanted
+without active notification (IPI).  If we want to bypass this check by having a
+magic register signal, we can add that later.  Additionally, the kernel will
+mask notifications (much like an x86 interrupt gate).  It will also mask
+notifications when starting a core with a fresh trapframe, since the process
+will be executing on its transition stack.  The process must check its per-core
+event queue to see why it was called, and deal with all of the events on the
+queue.  In the case where the event queue overflows, the kernel will up a
+counter so the process can at least be aware things are missed.  At the very
+least, the process will see the notification marked in a bitmask.
 
 These notification events include things such as: an IO is complete, a
 preemption is pending to this core, the process just returned from a
@@ -357,6 +357,47 @@ masked, the process will simply be killed.    It is up to the process to make
 sure the appropriate pages are pinned, which it should do before entering _M
 mode.
 
+4.2.3: Event Overflow and Non-Messages
+-----------------------------
+For missed/overflowed events, and for events that do not need messages (they
+have no parameters and multiple notifications are irrelevant), the kernel will
+toggle that event's bit in a bitmask.  For the events that don't want messages,
+we may have a flag that userspace sets, meaning they just want to know it
+happened.  This might be too much of a pain, so we'll see.  For notification
+events that overflowed the queue, the parameters will be lost, but hopefully the
+application can sort it out.  Again, we'll see.  A specific notif_event should
+not appear in both the event buffers and in the bitmask.
+
+It does not make sense for all events to have messages.  Others, it does not
+make sense to specify a different core on which to run the handler (e.g. page
+faults).  The notification methods that the process expresses via procdata are
+suggestions to the kernel.  When they don't make sense, they will be ignored.
+Some notifications might be unserviceable without messages.  A process needs to
+have a fallback mechanism.  For example, they can read the vcoremap to see who
+was lost, or they can restart a thread to cause it to page fault again.
+
+Event overflow sucks - it leads to a bunch of complications.  Ultimately, what
+we really want is a limitless amount of notification messages (per core), as
+well as a limitless amount of notification types.  And we want these to be
+relayed to userspace without trapping into the kernel. 
+
+We could do this if we had a way to dynamically manage memory in procdata, with
+a distrusted process on one side of the relationship.  We could imagine growing
+procdata dynamically (we plan to, mostly to grow the preempt_data struct as we
+request more vcores), and then run some sort of heap manager / malloc.  Things
+get very tricky since the kernel should never follow pointers that userspace can
+touch.  Additionally, whatever memory management we use becomes a part of the
+kernel interface.  
+
+Even if we had that, dynamic notification *types* is tricky - they are
+identified by a number, not by a specific (list) element.
+
+For now, this all seems like an unnecessary pain in the ass.  We might adjust it
+in the future if we come up with clean, clever ways to deal with the problem,
+which we aren't even sure is a problem yet.
+
+4.2.4: How to Use and Leave a Transition Stack
+-----------------------------
 We considered having the kernel be aware of a process's transition stacks and
 sizes so that it can detect if a vcore is in a notification handler based on
 the stack pointer in the trapframe when a trap or interrupt fires.  While
@@ -382,6 +423,19 @@ notification queue/list is empty before moving back to real code.  Then it
 should jump back to a real stack, unmask notifications, and jump to the newly
 scheduled thread.
 
+This can be really tricky.  When userspace is changing threads, it will need to
+unmask notifs as well as jump to the new thread.  There is a slight race here,
+but it is okay.  The race is that an IPI can arrive after notifs are unmasked,
+but before returning to the real user thread.  Then the code will think the
+notif_tf represents the new user thread, even though it hasn't started (and the
+PC is wrong).  The trick is to make sure that all state required to start the
+new thread, as well as future instructions, are all saved within the "stuff"
+that gets saved in the notif_tf.  When these threading packages change contexts,
+they ought to push the PC on the stack of the new thread, (then enable notifs)
+and then execute a return.  If an IPI arrives before the "function return", then
+when that context gets restarted, it will run the "return" with the appropriate
+value on the stack still.
+
 4.3: Preemption Specifics
 -------------------------------
 When a vcore is preempted, the kernel takes whatever context was running (which
index 3b77871..dacd79d 100644 (file)
@@ -81,6 +81,7 @@ void proc_run(struct proc *SAFE p);
 void proc_restartcore(struct proc *SAFE p, trapframe_t *SAFE tf);
 void proc_destroy(struct proc *SAFE p);
 void proc_yield(struct proc *SAFE p);
+
 /* Exposed for sys_getvcoreid(), til it's unnecessary */
 uint32_t proc_get_vcoreid(struct proc *SAFE p, uint32_t pcoreid);
 
@@ -143,10 +144,9 @@ void __startcore(trapframe_t *tf, uint32_t srcid, struct proc *CT(1) a0,
 void __death(trapframe_t *tf, uint32_t srcid, void *SNT a0, void *SNT a1,
              void *SNT a2);
 #else
-void __startcore(trapframe_t *tf, uint32_t srcid, void * a0, void * a1,
-                 void * a2);
-void __death(trapframe_t *tf, uint32_t srcid, void * a0, void * a1,
-             void * a2);
+void __startcore(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2);
+void __death(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2);
+void __notify(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2);
 #endif
 
 /* Arch Specific */
index 3ec0e3e..3aa2c7e 100644 (file)
@@ -20,8 +20,8 @@ struct notif_method {
 
 /* Notification Flags.  vcore0 stuff might be implemented. */
 #define NOTIF_WANTED                   0x001   /* wanted, process-wide */
-#define NOTIF_NO_IPI                   0x002   /* do not IPI the core */
-#define NOTIF_NO_MSG                   0x004   /* no message, just flip the bit */
+#define NOTIF_IPI                              0x002   /* IPI the core */
+#define NOTIF_MSG                              0x004   /* send a message (notif event) */
 #define NOTIF_VCORE0_IPI               0x008   /* fall back to vcore0 for an IPI */
 #define NOTIF_VCORE0_EVENT             0x010   /* fall back to vcore0 for an event */
 
index 3637852..a519689 100644 (file)
@@ -8,6 +8,7 @@
 #pragma nosharc
 #endif
 
+#include <ros/bcq.h>
 #include <arch/arch.h>
 #include <arch/bitmask.h>
 #include <process.h>
@@ -690,6 +691,63 @@ void proc_yield(struct proc *SAFE p)
        abandon_core();
 }
 
+/* If you expect to notify yourself, cleanup state and process_routine_kmsg() */
+void do_notify(struct proc *p, uint32_t vcoreid, unsigned int notif,
+               struct notif_event *ne)
+{
+       assert(notif < MAX_NR_NOTIF);
+       struct notif_method *nm = &p->procdata->notif_methods[notif];
+       struct preempt_data *vcpd = &p->procdata->vcore_preempt_data[vcoreid];
+
+       /* enqueue notif message or toggle bits */
+       if (ne && nm->flags & NOTIF_MSG) {
+               if (bcq_enqueue(&vcpd->notif_evts, ne, NR_PERCORE_EVENTS, 4)) {
+                       atomic_inc((atomic_t)&vcpd->event_overflows); // careful here
+                       SET_BITMASK_BIT_ATOMIC(vcpd->notif_bmask, notif);
+               }
+       } else {
+               SET_BITMASK_BIT_ATOMIC(vcpd->notif_bmask, notif);
+       }
+
+       /* Active notification */
+       /* TODO: Currently, there is a race for notif_pending, and multiple senders
+        * can send an IPI.  Worst thing is that the process gets interrupted
+        * briefly and the kernel immediately returns back once it realizes notifs
+        * are masked.  To fix it, we'll need atomic_swapb() (right answer), or not
+        * use a bool. (wrong answer). */
+       if (nm->flags & NOTIF_IPI && vcpd->notif_enabled && !vcpd->notif_pending) {
+               vcpd->notif_pending = TRUE;
+               spin_lock_irqsave(&p->proc_lock);
+                       if ((p->state & PROC_RUNNING_M) && // TODO: (VC#) (_S state)
+                                     (p->procinfo->vcoremap[vcoreid].valid)) {
+                               printk("[kernel] sending notif to vcore %d\n", vcoreid);
+                               send_kernel_message(p->procinfo->vcoremap[vcoreid].pcoreid,
+                                                   __notify, p, 0, 0, KMSG_ROUTINE);
+                       } else { // TODO: think about this, fallback, etc
+                               warn("Vcore unmapped, not receiving an active notif");
+                       }
+               spin_unlock_irqsave(&p->proc_lock);
+       }
+}
+
+/* Sends notification number notif to proc p.  Meant for generic notifications /
+ * reference implementation.  do_notify does the real work.  This one mostly
+ * just determines where the notif should be sent, other checks, etc.
+ * Specifically, it handles the parameters of notif_methods.  If you happen to
+ * notify yourself, make sure you process routine kmsgs. */
+void proc_notify(struct proc *p, unsigned int notif)
+{
+       assert(notif < MAX_NR_NOTIF); // notifs start at 0
+       struct notif_method *nm = &p->procdata->notif_methods[notif];
+       struct notif_event ne;
+       
+       ne.ne_type = notif;
+
+       if (!(nm->flags & NOTIF_WANTED))
+               return;
+       do_notify(p, nm->vcoreid, notif, &ne);
+}
+
 /* Global version of the helper, for sys_get_vcoreid (might phase that syscall
  * out). */
 uint32_t proc_get_vcoreid(struct proc *SAFE p, uint32_t pcoreid)
@@ -997,6 +1055,7 @@ void __startcore(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2)
        uint32_t pcoreid = core_id(), vcoreid;
        struct proc *p_to_run = (struct proc *CT(1))a0;
        struct trapframe local_tf, *tf_to_pop;
+       struct preempt_data *vcpd;
 
        assert(p_to_run);
        vcoreid = get_vcoreid(p_to_run, pcoreid);
@@ -1009,6 +1068,9 @@ void __startcore(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2)
                memset(tf_to_pop, 0, sizeof(*tf_to_pop));
                proc_init_trapframe(tf_to_pop, vcoreid, p_to_run->env_entry,
                                    p_to_run->procdata->stack_pointers[vcoreid]);
+               /* Disable/mask active notifications for fresh vcores */
+               vcpd = &p_to_run->procdata->vcore_preempt_data[vcoreid];
+               vcpd->notif_enabled = FALSE;
        } else {
                /* Don't want to accidentally reuse this tf (saves on a for loop in
                 * proc_run, though we check there to be safe for now). */
@@ -1020,6 +1082,42 @@ void __startcore(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2)
        __proc_startcore(p_to_run, tf_to_pop);
 }
 
+/* Bail out if it's the wrong process, or if they no longer want a notif */
+// TODO: think about what TF this is: make sure it's the user one, and not a
+// kernel one (was it interrupted, or proc_kmsgs())
+void __notify(trapframe_t *tf, uint32_t srcid, void *a0, void *a1, void *a2)
+{
+       struct user_trapframe local_tf;
+       struct preempt_data *vcpd;
+       uint32_t vcoreid;
+       struct proc *p = (struct proc*)a0;
+
+       if (p != current)
+               return;
+       // TODO: think about locking here.  document why we don't need this
+       vcoreid = p->procinfo->pcoremap[core_id()].vcoreid;
+       vcpd = &p->procdata->vcore_preempt_data[vcoreid];
+
+       printd("received active notification for proc %d's vcore %d on pcore %d\n",
+              p->procinfo->pid, vcoreid, core_id());
+
+       /* sort signals.  notifs are now masked, like an interrupt gate */
+       if (!vcpd->notif_enabled)
+               return;
+       vcpd->notif_enabled = FALSE;
+       vcpd->notif_pending = FALSE; // no longer pending - it made it here
+       
+       /* save the old tf in the notify slot, build and pop a new one.  Note that
+        * silly state isn't our business for a notification. */        
+       // TODO: this is assuming the struct user_tf is the same as a regular TF
+       vcpd->notif_tf = *tf;
+       memset(&local_tf, 0, sizeof(local_tf));
+       proc_init_trapframe(&local_tf, vcoreid, p->env_entry,
+                           p->procdata->stack_pointers[vcoreid]);
+       __proc_startcore(p, &local_tf);
+
+}
+
 /* Stop running whatever context is on this core, load a known-good cr3, and
  * 'idle'.  Note this leaves no trace of what was running. This "leaves the
  * process's context. */
index e2b5e3f..7f2a476 100644 (file)
@@ -1,6 +1,10 @@
 #include <parlib.h>
 #include <ros/mman.h>
 #include <ros/resource.h>
+#include <ros/procdata.h>
+#include <ros/notification.h>
+#include <ros/bcq.h>
+#include <arch/arch.h>
 #include <stdio.h>
 #include <hart.h>
 
@@ -15,6 +19,23 @@ int main(int argc, char** argv)
 
        hart_barrier_init(&b,hart_max_harts()-1);
 
+/* begin: stuff userspace needs to do before switching to multi-mode */
+
+       /* tell the kernel where and how we want to receive notifications */
+       struct notif_method *nm;
+       for (int i = 1; i < MAX_NR_NOTIF; i++) {
+               nm = &__procdata.notif_methods[i];
+               nm->flags |= NOTIF_WANTED | NOTIF_MSG | NOTIF_IPI;
+               nm->vcoreid = i % 2; // vcore0 or 1, keepin' it fresh.
+       }
+
+       /* don't forget to enable notifs on vcore0 at some point */
+       struct preempt_data *vcpd;
+       vcpd = &__procdata.vcore_preempt_data[0];
+       vcpd->notif_enabled = TRUE;
+       
+/* end: stuff userspace needs to do before switching to multi-mode */
+
        if ((vcoreid = hart_self())) {
                printf("Should never see me! (from vcore %d)\n", vcoreid);
        } else { // core 0
@@ -23,7 +44,8 @@ int main(int argc, char** argv)
                       vcoreid, &temp, temp);
                printf("Multi-Goodbye, world, from PID: %d!\n", sys_getpid());
                //retval = sys_resource_req(RES_CORES, 2, 0);
-               retval = hart_request(hart_max_harts()-2);
+               //retval = hart_request(hart_max_harts()-2);
+               retval = hart_request(2); // doesn't do what you think.  this gives 3.
                //debug("retval = %d\n", retval);
        }
        printf("Vcore %d Done!\n", vcoreid);
@@ -31,12 +53,37 @@ int main(int argc, char** argv)
        hart_barrier_wait(&b,hart_self());
 
        printf("All Cores Done!\n", vcoreid);
+       while(1); // manually kill from the monitor
        return 0;
 }
 
 void hart_entry(void)
 {
        uint32_t vcoreid = hart_self();
+
+/* begin: stuff userspace needs to do to handle notifications */
+
+       struct preempt_data *vcpd;
+       vcpd = &__procdata.vcore_preempt_data[vcoreid];
+       
+       /* here is how you receive a notif_event */
+       struct notif_event ne = {0};
+       bcq_dequeue(&vcpd->notif_evts, &ne, NR_PERCORE_EVENTS);
+       printf("the queue is on vcore %d and has a ne with type %d\n", vcoreid,
+              ne.ne_type);
+       /* it might be in bitmask form too: */
+       //printf("and the bitmask looks like: ");
+       //PRINT_BITMASK(__procdata.vcore_preempt_data[vcoreid].notif_bmask, MAX_NR_NOTIF);
+       /* can see how many messages had to be sent as bits */
+       printf("Number of event overflows: %d\n", vcpd->event_overflows);
+
+       /* unmask notifications once you can let go of the notif_tf and it is okay
+        * to clobber the transition stack.
+        * Check Documentation/processes.txt: 4.2.4 */
+       vcpd->notif_enabled = TRUE;
+       
+/* end: stuff userspace needs to do to handle notifications */
+
        temp = 0xcafebabe;
        printf("Hello from hart_entry in vcore %d with temp addr %p and temp %p\n",
               vcoreid, &temp, temp);