Initial notification and preemption headers
authorBarret Rhoden <brho@cs.berkeley.edu>
Fri, 5 Mar 2010 03:54:52 +0000 (19:54 -0800)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:35:39 +0000 (17:35 -0700)
Check the documentation if you're curious how this all should work.

Documentation/process-internals.txt
Documentation/processes.txt
kern/arch/i686/ros/trapframe.h [new file with mode: 0644]
kern/arch/i686/trap.c
kern/arch/i686/trap.h
kern/arch/sparc/ros/trapframe.h [new file with mode: 0644]
kern/arch/sparc/trap.h
kern/include/ros/notification.h [new file with mode: 0644]
kern/include/ros/procdata.h

index cec4b5f..4db1bbf 100644 (file)
@@ -1,8 +1,18 @@
+process-internals.txt
+Barret Rhoden
+
 This discusses core issues with process design and implementation.  Most of this
 info is available in the source in the comments (but may not be in the future).
 For now, it's a dumping ground for topics that people ought to understand before
 they muck with how processes work.
 
+Contents:
+1. Reference Counting
+2. When Do We Really Leave "Process Context"?
+3. Leaving the Kernel Stack:
+4. Preemption and Notification Issues:
+5. TBD
+
 1. Reference Counting
 ===========================
 1.1 Basics:
@@ -235,7 +245,7 @@ more sense.  It eventually led to having __proc_unlock_ipi_pending(), which made
 proc_destroy() much cleaner and helped with a general model of dealing with
 these issues.  Win-win.
 
-2 When Do We Really Leave "Process Context"?
+2. When Do We Really Leave "Process Context"?
 ===========================
 2.1 Overview
 ---------------------------
@@ -296,19 +306,307 @@ proc_yield() abandons the core / leaves context.
 
 2.3 Other issues:
 ---------------------------
-Note that it is not clear exactly how we want to deal with interrupting
-processes that are in the kernel.  There is no true process context, so we can't
+Note that dealing with interrupting
+processes that are in the kernel is tricky.  There is no true process context, so we can't
 leave a core until the kernel is in a "safe place", i.e. it's state is bundled
-enough that it can be recontinued later.  We might end up letting the call
-proceed, but not return to userspace (since that's a good save point).  There's
-some rough comments about this in proc_startcore() (check for a pending
-preempt).
+enough that it can be recontinued later.  Calls of this type are not immediate
+active messages, executed at a convenient time (specifically, before we return
+to userspace in proc_startcore().
 
 This same thing applies to __death messages.  Even though a process is dying, it
 doesn't mean we can just drop whatever the kernel was doing on its behalf.  For
 instance, it might be holding a reference that will never get decreffed if its
-stack gets dropped.  This is a big TODO.
+stack gets dropped.
 
 3. Leaving the Kernel Stack:
 ===========================
-Next painful commit will deal with this a bit more...
+Just because a message comes in saying to kill a process, it does not mean we
+should immediately abandon_core().  The problem is more obvious when there is
+a preempt message, instead of a death message, but either way there is state
+that needs cleaned up (refcnts that need downed, etc).
+
+The solution to this is rather simple: don't abandon right away.  That was
+always somewhat the plan for preemption, but was never done for death.  And
+there are several other cases to worry about too.  To enforce this, we expand
+the active messages into a generic work execution message that can be delayed
+or shipped to another core.  These types of messages will not be executed
+immediately on the receiving pcore - instead they are on the queue for "when
+there's nothing else to do in the kernel", which is checked in smp_idle() and
+before returning to userspace in proc_startcore().  Additionally, these active
+messages can also be queued on an alarm queue, delaying their activation as
+part of a generic kernel alarm facility.
+
+4. Preemption and Notification Issues:
+===========================
+4.1: Message Ordering and Local Calls:
+---------------------------
+Since we go with the model of cores being told what to do, there are issues
+with messages being received in the wrong order.  That is why we have the
+active messages (guaranteed, in-order delivery), with the proc-lock protecting
+the send order.  However, this is not enough for some rare races.
+
+Local calls can also perform the same tasks as messages (calling
+proc_destroy() while a death IPI is on its way). We refer to these calls as
+messing with "local fate" (compared to global state (we're clever).
+Preempting a single vcore doesn't change the process's state).  These calls
+are a little different, because they also involve a check to see if it should
+perform the function or other action (e.g., death just idling and waiting for
+an IPI instead of trying to kill itself), instead of just blindly doing
+something.
+
+4.1.1: Possible Solutions
+----------------
+There are two ways to deal with this.  One (and the better one, I think) is to
+check state, and determine if it should proceed or abort.  This requires that
+all local-fate dependent calls always have enough state, meaning that any
+function that results in sending a directive to a vcore store enough info in
+the proc struct that a local call can determine if it should take action or
+abort.  This might be sufficient.  This works for death already, since you
+aren't supposed to do anything other than die (and restore any invariants
+first, handled in Section 3).  We'll go with this way.
+
+The other way is to send the work (including the checks) in a self-ipi active
+message.  This will guarantee that the message is executed after any existing
+messages (making the a_msg queue the authority for what should happen to a
+core).  The check is also performed later (when the a_msg executes).  There
+are a couple issues with this: if we allow the local core to send itself an
+a_msg that could be out of order (meaning it should not be sent, and is only
+sent due to ignorance of its sealed fate), AND if we return the core to the
+idle-core-list once its fate is sealed, we need to detect that the message is
+for the wrong process and that the process is in the wrong state.  To do this,
+we probably need local versioning on the pcore so it can detect that the
+message is late/wrong.  We might get by with just the proc* (though that is
+tricky with death and proc reuse), so long as we don't allow new startcores
+for a proc until AFTER the preemption is completed.
+
+4.2: Preempt-Served Flag
+----------------
+We want to be able to consider a pcore free once its owning proc has dealt
+with removing it (not necessarily taken from the vcoremap, but at least it is
+a done-deal that the core will go away and the messages are sent).  This
+allows a scheduler-like function to easily take a core and then give it to
+someone else, without waiting for each vcore to respond, saying that the pcore
+is free/idle.
+
+Since we want to keep the pcore in the vcoremap, we need another signal to let
+a process know a message is already on its way.  preempt_pending is a signal
+to userspace that the alarm was set, not that an actual message is on its way
+and that a vcore's fate is sealed.  Since we can't use a pcore's presence in
+the vcoremap to determine that the core should be revoked, we have to check
+the "fate sealed"/preempt-served flag. 
+
+It's a bit of a pain to have this flag, just to resolve this race in the
+kernel, though the local call would have to check the vcoremap anyway,
+incurring a cache miss if we go with using the vcoremap to signal the
+impending message.
+
+The preempt_pending flag is actual a timestamp, with the expiration time of
+the core at which the message will be sent.  We could try to use that, but
+since alarms aren't fired at exactly the time they are scheduled, the message
+might not actually be sent yet (though it will, really soon).  Still, we'll
+just go with the preempt-served flag for now.
+
+4.3: Impending Notifications
+----------------
+It's also possible that there is an impending notification.  There's no change
+in fate (though there could be a fate-changing preempt on its way), just the
+user wants a notification handler to run.  We need a flag anyways for this
+(discussed below), so proc_yield() or whatever other local call we have can
+check this flag as well.  
+
+Though for proc_yield(), it doesn't care if a notification is on its way (can
+be dependent on a flag to yield from userspace, based on the nature of the
+yield (which still needs to be sorted)).  If the yield is in response to a
+preempt_pending, it actually should yield and not receive the notification.
+So it should destroy its vcoreid->pcoreid mapping and abandon_core().  When
+that notification hits, it will be for a proc that isn't current, and will be
+ignored (it will get run the next time that vcore fires up, handled below).
+
+There is a slight chance that the same proc will run on that pcore, but with a
+different vcore.  In the off chance this happens, the new vcore will get a
+spurious notification.  Userspace needs to be able to handle spurious
+notifications anyways, (there are a couple other cases, and in general it's
+not hard to do), so this is not a problem.  Instead of trying to have the
+kernel ignore the notification, we just send a spurious one.  A crappy
+alternative would be to send the vcoreid with the notification, but that would
+mean we can't send a generic message (broadcast) to a bunch of cores, which
+will probably be a problem later.
+
+Note that this specific case is because the "local work message" gets
+processed out of order with respect to the notification.  And we want this in
+that case, since that proc_yield() is more important than the notification.
+
+4.4: Preemption / Allocation Phases and Alarm Delays
+---------------------------
+A per-vcore preemption phase starts when the kernel marks the core's
+preempt_pending flag/counter and can includes the time when an alarm is
+waiting to go off to reclaim the core.  The phase ends when the vcore's pcore
+is reclaimed, either as a result of the kernel taking control, or because a
+process voluntarily yielded.
+
+Specifically, the preempt_pending variable is actually a timestamp for when
+the core will be revoked (this assumes some form of global time, which we need
+anyways).  If its value is 0, then there is no preempt-pending, it is not in a
+phase, and the vcore can be given out again. 
+
+When a preempt alarm goes off, the alarm only means to check a process for
+expired vcores.  If the vcore has been yielded while the alarm was pending,
+the preempt_pending flag will be reset to 0.  To speed up the search for
+vcores to preempt, there's a circular buffer corelist in the proc struct, with
+vcoreids of potential suspects.  Or at least this will exist at some point.
+Also note that the preemption list isn't bound to a specific alarm: you can
+check the list at any time (not necessarily on a specific alarm), and you can
+have spurious alarms (the list is empty, so it'll be a noop).
+
+Likewise, a global preemption phase is when an entire MCP is getting
+gang_prempted, and the global deadline is set.  A function can quickly check
+to see if the process responded, since the list of vcores with preemptions
+pending will be empty.
+
+It seems obvious, but we do not allow allocation of a vcore during its
+preemption phase.  The main reason is that it can potentially break
+assumptions about the vcore->pcore mapping and can result in multiple
+instances of the same vcore on different pcores.  Imagine a preempt message
+sent to a pcore (after the alarm goes off), meanwhile that vcore/pcore yields
+and the vcore reactivates somewhere else.  There is a potential race on the
+preempt_tf state: the new vcore is reading while the old is writing.  This
+issue is sorted naturally: the vcore entry in the vcoremap isn't cleared until
+the vcore/pcore is actually yielded/taken away, so the code looking for a free
+vcoreid slot will not try to use it.
+
+Note that if we didn't design the alarm system to simply check for
+preemptions (perhaps it has a stored list of vcores to preempt), then we
+couldn't end the preempt-phase until the alarm was sorted.  If that is the
+case, we could easily give out a vcore that had been yielded but was still in
+a preempt-phase.  Stopping an alarm would be tricky too, since there could be
+lots of vcores in different states that need to be sorted by the alarm (so
+ripping it out isn't enough).  Setting a flag might not be enough either.
+Vcore version numbers/history (as well as global proc histories) is a pain I'd
+like to avoid too.  So don't change the alarm / delayed preemption system
+without thinking about this.
+
+Also, allowing a vcore to restart while preemptions are pending also mucks
+with keeping the vcore mapping "old" (while the message is in flight).  A
+pcore will want to use that to determine which vcore is running on it.  It
+would be possible to keep a pcoremap for the reverse mapping out of sync, but
+that seems like a bad idea.  In general, having the pcoremap is a good idea
+(whenever we talk about a vcoremap, we're usually talking about both
+directions: "the vcore->pcore mapping").
+
+4.5: Global Preemption Flags
+---------------------------
+If we are trying to preempt an entire process at the same time, instead of
+playing with the circular buffer of vcores pending preemption, we could have a
+global timer as well.  This avoids some O(n) operations, though it means that
+userspace needs to check two "flags" (expiration dates) when grabbing its
+preempt-critical locks.
+
+4.6: Notifications Mixed with Preemption and Sleeping
+---------------------------
+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.
+4.7: Notifs While a Preempt Message is Served
+---------------------------
+It is possible to have the kernel handling a notification a_msg and to have a
+preempt a_msg in the queue (preempt-served flag is set).  Ultimately, what we
+want is for the core to be preempted and the notification handler to run on
+the next execution.  Both messages are in the a_msg queue for "a convenient
+time to leave the kernel" (I'll have a better name for that later).  What we
+do is execute the notification handler and jump to userspace.  Since there is
+still an a_msg in the queue (and we self_ipi'd ourselves, it's part of how
+a_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 a_msg.  Additionally,
+a preempt a_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"
+---------------------------
+There are a couple ways to handle pcores.  One approach would be to not
+consider them free and able to be given to another process until the old
+process is completely removed (abandon_core()).  Another approach is to free
+the core once its fate is sealed (which we do).  This probably gives more
+flexibility in schedule()-like functions (no need to wait to give the core
+out), quicker dispatch latencies, less contention on shared structs (like the
+idle-core-map), etc.
+
+Also, we don't remove the pcore from the vcoremap, even if it is being
+allocated to another core (the same pcore can exist in two vcoremaps, contrary
+to older statements).  Taking the pcore from the vcoremap would mean some
+non-fate related local calls (sys_get_vcoreid()) will fail, since the vcoreid
+is gone!  Additionally, we don't need a vcoreid in the a_msg (we would have if
+we could not use the vcore/pcoremappings).  There should not be any issues
+with the new process sending messages to the pcore before the core is sorted,
+since a_msgs are delivered in order.
+
+4.9: Future Broadcast/Messaging Needs
+---------------------------
+Currently, messaging is serialized.  Broadcast IPIs exist, but the active
+message system is based on adding an a_msg to a list in a pcore's
+per_cpu_info.  Further the sending of these messages is in a loop.  In the
+future, we would like to have broadcast messaging of some sort (literally a
+broadcast, like the IPIs, and if not that, then a communication tree of
+sorts).  
+
+Given those desires, we want to make sure that no message we send needs
+details specific to a pcore (such as the vcoreid running on it, a history
+number, or anything like that).  Thus no a_msg related to process management
+should have anything that cannot apply to the entire process.  At this point,
+most just have a struct proc *.  A pcore ought to be able to figure out what
+is happening based on the pcoremap, information in the struct proc, and in the
+preempt struct in procdata.
+
+4.10: Other Things We Thought of but Don't Like
+---------------------------
+All local fate-related work is sent as a self a_msg, to enforce ordering.
+It doesn't capture the difference between a local call and a remote a_msg.
+The a_msg has already considered state and made its decision.  The local call
+is an attempt.  It is also unnecessary, if we put in enough information to
+make a decision in the proc struct.  Finally, it caused a few other problems
+(like needing to detect arbitrary stale messages).
+
+Overall message history: doesn't work well when you do per-core stuff, since
+it will invalidate other messages for the process.  We then though of a pcore
+history counter to detect stale messages.  Don't like that either.  We'd have
+to send the history in the message, since it's a per-message, per-core
+expiration.  There might be other ways around this, but this doesn't seem
+necessary.
+
+Alarms have pointers to a list of which cores should be preempted when that
+specific alarm goes off (saved with the alarm).  Ugh.  It gets ugly with
+multiple outstanding preemptions and cores getting yielded while the alarms
+sleep (and possibly could get reallocated, though we'd make a rule to prevent
+that).  Like with notifications, being able to handle spurious alarms and
+thinking of an alarm as just a prod to check somewhere is much more flexible
+and simple.  It is similar to generic messages that have the actual important
+information stored somewhere else (as with allowing broadcasts, with different
+receivers performing slightly different operations).
+
+Synchrony for messages (wanting a response to a preempt a_msg, for example)
+sucks.  Just encode the state of impending fate in the proc struct, where it
+belongs.  Additionally, we don't want to hold the proc lock even longer than
+we do now (which is probably too long as it is).  Finally, it breaks a golden
+rule: never wait while holding a lock: you will deadlock the system (e.g. if
+the receiver is already in the kernel spinning on the lock).  We'd have to
+send messages, unlock (which might cause a message to hit the calling pcore,
+as in the case of locally called proc_destroy()), and in the meantime some
+useful invariant might be broken.
+
+We also considered using the transition stack as a signal that a process is in
+a notification handler.  The kernel can inspect the stack pointer to determine
+this.  It's possible, but unnecessary.
+
+5. TBD
+===========================
index e8fba72..a0b11fa 100644 (file)
@@ -1,5 +1,8 @@
+processes.txt
+Barret Rhoden
+
 All things processes!  This explains processes from a high level, especially
-focusing on the user-kernel boundary and transitions to the multi-cored state,
+focusing on the user-kernel boundary and transitions to the many-core state,
 which is the way in which parallel processes run.  This doesn't discuss deep
 details of the ROS kernel's process code.
 
@@ -9,10 +12,14 @@ parallel applications.
 Part 1: Overview
 Part 2: How They Work
 Part 3: Resource Requests
-Part 4: Notification
+Part 4: Preemption and Notification
 Part 5: Old Arguments (mostly for archival purposes))
 Part 6: Parlab app use cases
 
+Revision History:
+2009-10-30 - Initial version
+2010-03-04 - Preemption/Notification, changed to many-core processes
+
 Part 1: World View of Processes
 ==================================
 A process is the lowest level of control, protection, and organization in the
@@ -33,10 +40,10 @@ Features:
   They have a list of resources that are given/leased to them.
 
 None of these are new.  Here's what's new:
-- They can run in a multi-core mode, where its cores run at the same time, and
+- They can run in a many-core mode, where its cores run at the same time, and
   it is aware of changes to these conditions (page faults, preemptions).  It
   can still request more resources (cores, memory, whatever).
-- Every core in a multi-cored process is *not* backed by a kernel
+- Every core in a many-core process (MCP) is *not* backed by a kernel
   thread/kernel stack, unlike with Linux tasks.
        - There are *no* per-core run-queues in the kernel that decide for
          themselves which kernel thread to run.
@@ -147,11 +154,6 @@ would also work.  The vcoreid makes things a little easier, such as when a
 process wants to refer to one of its other cores (not the calling core).  It
 also makes the event notification mechanisms easier to specify and maintain.
 
-The vcoreid only makes sense when in an _M mode.  In _S mode, your vcoreid is
-0.  Vcoreid's only persist when in _M mode.  If you leave _M mode on a non-0
-vcore, that context becomes vcore0.  This can get tricky for userspace's
-stacks (more below).
-
 Processes that care about locality should check what their pcoreid is.  This
 is currently done via sys_getcpuid().  The name will probably change.
 
@@ -176,23 +178,14 @@ coming up at the entry point again (and not starting the program over).  I
 recommend setting a global variable that can be checked from assembly before
 going to _M the first time.
 
-When coming in to the entry point, new cores should grab a stack.  There are a
-few ways to do this.  They ought to be the same stack every time for a
-specific vcore.  They will be the transition stacks (in Lithe terms) that are
+When coming in to the entry point, whether as the result of a startcore or a
+notification, the kernel will set the stack pointer to whatever is requested
+by userspace in procdata.  A process should allocate stacks of whatever size
+it wants for its vcores when it is in _S mode, and write these location to
+procdata.  These stacks are the transition stacks (in Lithe terms) that are
 used as jumping-off points for future function calls.  These stacks need to be
-used in a continuation-passing style.  Start from the top every time.  The
-reason for that is actual contexts may move around, and vcores will come and
-go - and each vcore will always reuse the same stack.  Lithe works this way,
-so it's not an issue.
-
-It's recommended that while in _S mode the process allocates stacks and puts
-them in an array, indexed by vcoreid for the entry point to easily find them.
-Then each core grabs its transition stack, then goes into hart_entry.
-
-Userspace needs to make sure the calling context (which will become vcore0) is
-on a good stack, like USTACKTOP.  This could happen if you leave _M from a core
-other than vcore0, then return to _M mode.  While in _S mode, you were still
-on whatever stack you came from (like vcore3's transition stack).
+used in a continuation-passing style, and each time they are used, they start
+from the top.
 
 2.4.2: To go from _M to _S, a process requests 0 cores
 --------------
@@ -202,8 +195,10 @@ to deal with this.  In general, they will regrab their transition stacks when
 they come back up.  Their other stacks and whatnot (like TBB threads) need to
 be dealt with.
 
-As mentioned above, when the caller next switches to _M, that context
-(including its stack) becomes the new vcore0.
+When the caller next switches to _M, that context (including its stack)
+maintains its old vcore identity.  If vcore3 causes the switch to _S mode, it
+ought to remain vcore3 (lots of things get broken otherwise).
+As of March 2010, the code does not reflect this
 
 2.4.3: Requesting more cores while in _M
 --------------
@@ -213,6 +208,8 @@ from _S to _M: at the entry point.
 
 2.4.4: Yielding
 --------------
+This will get revised soon, to account for different types of yields.
+
 Yielding gives up a core.  In _S mode, it will transition from RUNNING_S to
 RUNNABLE_S.  The context is saved in env_tf.  A yield will *not* transition
 from _M to _S.
@@ -229,70 +226,18 @@ is run again, all cores will come up at the entry point (including vcore0 and
 the calling core).  This isn't implemented yet, and will wait on some work
 with preemption.
 
+We also need a type of yield (or a flag) that says the process is just giving
+up the core temporarily, but actually wants the core and does not want
+resource requests to be readjusted.  For example, in the event of a preemption
+notification, a process may yield (ought to!) so that the kernel does not need
+to waste effort with full preemption.
+
 2.4.5: Others
 --------------
 There are other transitions, mostly self-explanatory.  We don't currently use
 any WAITING states, since we have nothing to block on yet.  DYING is a state
 when the kernel is trying to kill your process, which can take a little while
 to clean up.
-       
-2.5: Preemption
--------------------------------
-None of this is implemented yet, or fully flushed out.
-
-2.5.1: Ideas
---------------
-The rough plan is to notify beforehand, then take action if userspace doesn't
-yield.
-
-When a core is preempted or interrupted for any reason (like a pagefault or
-other trap), the context is saved in a structure in procinfo that contains the
-vcoreid, a code for what happened, and the actual context (probably always
-including FP/SSE registers).
-
-If userspace is keeping the core, like with a page fault or other trap, that
-core will start up at the entry point, and regrab its transition stack.  There
-could be issues with this, if the trap came while operating on that stack.
-There'll probably be some way other way to help userspace know something
-happened.  Recursive (page) faults are also tricky.
-
-If it lost the core, like in a preemption, the context is saved (fully), and
-the process is notified (in the manner it wishes).  See Part 4.
-
-When a process loses all of its cores, it just loses them all.  There is no
-notification interrupt (though there will be a message posted).  When a
-process comes up at the entry point once it is run again, it should check its
-event queue (if it cares).
-
-2.5.2: Issues with preemption, trap redirection, and event notification
---------------
-There are other issues with cascading interrupts (when contexts are still
-running handlers).  Imagine a pagefault, followed by preempting the handler.
-It doesn't make sense to run the preempt context after the page fault.  The
-difficulty is in determining when the context is no longer handling an event.
-We could consider using a register, controllable by userspace, to give the
-kernel this hint.  Maybe have a window of time we won't preempt again (though
-that is really painful.  How long is this window?).
-
-Even worse, consider vcore0 is set up to receive all events.  If events come
-in faster than it can process them, it will both nest too deep and process out
-of order.
-
-Also, we don't have a good way to handle interrupts/events when on the
-transition stack at all (let alone cascading or overflow).  The kernel will
-just start you from the top of the stack, which will eventually clobber
-whatever the core was doing previously.
-
-We can consider using another register to signal some sort of exceptional
-event, and before touching the transition stack, the core can jump to a
-per-core exception stack.  Perhaps to avoid nesting exceptions, when an
-interrupt comes in, the kernel can see userspace is on it's exception stack
-and not post the event, and it's up to userspace to check for any missed
-events before leaving its core.  Ugly.  And can't handle traps.
-
-Pre-Notification issues: how much time does userspace need to clean up and
-yield?  How quickly does the kernel need the core back (for scheduling
-reasons)?
 
 Part 3: Resource Requests
 ===============================
@@ -326,27 +271,251 @@ use them).  A latency of 0 would mean a process wants it instantly, which
 probably means they ought to be already allocated (and billed to) that
 process.  
 
-Part 4: Event Notification
+Part 4: Preemption and Event Notification
 ===============================
+Preemption and Notification are tied together.  Preemption is when the kernel
+takes a resource (specifically, cores).  There are two types core_preempt()
+(one core) and gang_preempt() (all cores).  Notification (discussed below) is
+when the kernel informs a process of an event, usually referring to the act of
+running a function on a core (active notification).
+
+The rough plan for preemption is to notify beforehand, then take action if
+userspace doesn't yield.  This is a notification a process can ignore, though
+it is highly recommended to at least be aware of impending core_preempt()
+events.
+
+4.1: Notification Basics
+-------------------------------
 One of the philosophical goals of ROS is to expose information up to userspace
 (and allow requests based on that information).  There will be a variety of
 events in the system that processes will want to know about.  To handle this,
 we'll eventually build something like the following.
 
 All events will have a number, like an interrupt vector.  Each process will
-have an event queue.  On most architectures, it will be a simple
-producer-consumer ring buffer sitting in the "shared memory" procdata region
-(shared between the kernel and userspace).  The kernel writes a message into
-the buffer with the event number and some other helpful information.  For
-instance, a preemption event will say which vcore was preempted and provide a
-pointer to the context that was preempted.
+have an event queue (per core, described below).  On most architectures, it
+will be a simple producer-consumer ring buffer sitting in the "shared memory"
+procdata region (shared between the kernel and userspace).  The kernel writes
+a message into the buffer with the event number and some other helpful
+information.
 
 Additionally, the process may request to be actively notified of specific
 events.  This is done by having the process write into an event vector table
-(like an IDT) in procdata.  For each event, the process can write a function
-pointer and a vcoreid.  The kernel will execute that function on the given
-core (and pass appropriate arguments, such as a pointer to the message in the
-event queue and a pointer to the context that was just interrupted).
+(like an IDT) in procdata.  For each event, the process writes the vcoreid it
+wants to be notified on.
+
+4.2: Notification Specifics
+-------------------------------
+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.
+
+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
+transition stack.  If a process is already handling a notification on that
+core, the kernel will not interrupt it.  It is the processes's responsibility
+to check for more notifications before returning to its normal work.  The
+process must also unmask notifications (in procdata) before it returns to do
+normal work.  Unmasking notifications is the signal to the kernel to not
+bother sending IPIs, and if an IPI is sent before notifications are masked,
+then the kernel will double-check this flag to make sure interrupts should
+have arrived.
+
+Notification unmasking is done by setting the notif_enabled flag (similar to
+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.
+
+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.
+
+These notification events include things such as: an IO is complete, a
+preemption is pending to this core, the process just returned from a
+preemption, there was a trap (divide by 0, page fault), and many other things.
+We plan to allow this list to grow at runtime (a process can request new event
+notification types).  These messages will often need some form of a timestamp,
+especially ones that will expire in meaning (such as a preempt_pending).
+
+Note that only one notification can be active at a time, including a fault.
+This means that if a process page faults or something while notifications are
+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.
+
+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
+cool, the flag for notif_enabled is much easier and just as capable.
+Userspace needs to be aware of various races, and only enable notifications
+when it is ready to have its transition stack clobbered.  This means that when
+switching from big user-thread to user-thread, the process should temporarily
+disable notifications and reenable them before starting the new thread fully.
+This is analogous to having a kernel that disables interrupts while in process
+context.
+
+A process can fake not being on its transition stack, and even unmapping their
+stack.  At worst, a vcore could recursively page fault (the kernel does not
+know it is in a handler, if they keep enabling notifs before faulting), and
+that would continue til the core is forcibly preempted.  This is not an issue
+for the kernel.
+
+When a process wants to use its transition stack, it ought to check
+preempt_pending, mask notifications, jump to its transition stack, do its work
+(e.g. process notifications, check for new notifications, schedule a new
+thread) periodically checking for a pending preemption, and making sure the
+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.
+
+4.3: Preemption Specifics
+-------------------------------
+When a vcore is preempted, the kernel takes whatever context was running (which
+could be a notification context) and stores it in the preempt trapframe for
+that vcore in procdata.  There is also a flag (actually a counter) that states
+if the context there has been sorted out.  Userspace should set this once it
+has copied out the data and dealt with it accordingly.
+
+The invariant regarding the preemption slot is that there should never be
+anything running on a vcore when there is a valid/not-sorted preempt
+trapframe.  The reason is that a preemption can come in at any time (such as
+right after returning from a preemption).
+
+To maintain this invariant, when the kernel starts a vcore, it will run the
+context that is in the preempt trapframe if the "preempt_tf_sorted" (name will
+change) flag is not set.  A process needs to be careful of a race here.  If
+they are trying to deal with a preempt trapframe (must be from another vcore,
+btw), the kernel could start to run that trapframe (in case it is granting a
+core request / proc_startcore()ing).  When the kernel prepares to use the
+trapframe (which it will do regardless of userspace activities), it will up
+the flag/counter.  If the process notices a change in that flag, it ought to
+abort its operation.  It can up the counter on its own when it no longer wants
+the kernel to run that context (this means it can get clobbered).
+
+4.4: Other trickiness
+-------------------------------
+4.4.1: Preemption -> deadlock
+-------------------------------
+One issue is that a context can be holding a lock that is necessary for the
+userspace scheduler to manage preempted threads, and this context can be
+preempted.  This would deadlock the scheduler.  To assist a process from
+locking itself up, the kernel will toggle a preempt_pending flag in
+procdata for that vcore before sending the actual preemption.  Whenever the
+scheduler is grabbing one of these critical spinlocks, it needs to check that
+flag first, and yield if a preemption is coming in.
+
+Another option we may implement is for the process to be able to signal to the
+kernel that it is in one of these ultra-critical sections by writing a magic
+value to a specific register in the trapframe.  If there kernel sees this, it
+will allow the process to run for a little longer.  The issue with this is
+that the kernel would need to assume processes will always do this (malicious
+ones will) and add this extra wait time to the worst case preemption time.
+
+Finally, a scheduler could try to use non-blocking synchronization (no
+spinlocks), or one of our other long-term research synchronization methods to
+avoid deadlock, though we realize this is a pain for userspace for now.  FWIW,
+there are some OSs out there with only non-blocking synchronization (I think).
+
+4.4.2: Cascading and overflow
+-------------------------------
+There used to be issues with cascading interrupts (when contexts are still
+running handlers).  Imagine a pagefault, followed by preempting the handler.
+It doesn't make sense to run the preempt context after the page fault.
+Earlier designs had issues where it was hard for a vcore to determine the
+order of events and unmixing preemption, notification, and faults.  We deal
+with this by having separate slots for preemption and notification, and by
+treating faults as another form of notification.  Faulting while handling a
+notification just leads to death.  Perhaps there is a better way to do that.
+
+Another thing we considered would be to have two stacks - transition for
+notification and an exception stack for faults.  We'd also need a fault slot
+for the faulting trapframe.  This begins to take up even more memory, and it
+is not clear how to handle mixed faults and notifications.  If you fault while
+on the notification slot, then fine.  But you could fault for other reasons,
+and then receive a notification.  And then if you fault in that handler, we're
+back to where we started - might as well just kill them.
+
+Another issue was overload.  Consider if vcore0 is set up to receive all
+events.  If events come in faster than it can process them, it will both nest
+too deep and process out of order.  To handle this, we only notify once, and
+will not send future active notifications / interrupts until the process
+issues an "end of interrupt" (EOI) for that vcore.  This is modelled after
+hardware interrupts (on x86, at least).
+
+4.4.3: Restarting a Preempted Notification
+-------------------------------
+There will be cases where the trapframe in the preempt_tf slot is actually a
+notification handler, which was running on the transition stack of that
+particular vcore.  Userspace needs to be careful about restarting contexts
+that were on those cores.  They can tell by examining the stack pointer to see
+if it was on a transition stack.  Alternatively, if notifications are masked,
+it is also likely they in a notification handler.  The real concern is the
+transition stack.  If a vcore is processing on the transition stack of another
+vcore, there is a risk that the vcore comes back up and starts clobbering the
+transition stack.
+
+To avoid this, userspace should allocate a new transition stack and switch the
+target vcore to use that new stack (in procdata).  The only time (for now)
+that the kernel cares about a transition stack is when it is popping a tf on a
+new or freshly notified vcore.
+
+This all should be a rare occurance, since the vcore should see the
+preempt_pending when it starts its notification and yield, instead of being
+forced into this weird situation.  This also means that notifications should
+take less time than the kernel will wait before preempting.
+
+This issue ties in with deadlocking in 4.4.1.  If userspace is never in a
+notif handler when it gets preempted, that deadlock will not happen (and we
+also may need only one trapframe slot).  Userspace probably cannot guarantee
+that, so we'll have to deal with it.  Avoiding the deadlock on a spinlock is
+much more reasonable (and we can provide the locking function).
+
+4.4.4: Userspace Yield Races
+-------------------------------
+Imagine a vcore realizes it is getting preempted soon, so it starts to yield.
+However, it is too slow and doesn't make it into the kernel before a preempt
+message takes over.  When that vcore is run again, it will continue where it
+left off and yield its core.  The desired outcome is for yield to fail, since
+the process doesn't really want to yield that core.  To sort this out, yield
+will take a parameter saying that the yield is in response to a pending
+preemption.  If the phase is over (preempted and returned), the call will not
+yield and simply return to userspace.
+
+4.4.5: Userspace m_yield
+-------------------------------
+There are a variety of ways to implement an m_yield (yield the entire MCP).
+We could have a "no niceness" yield - just immediately preempt, but there is a danger of the
+locking business.  We could do the usual delay game, though if userspace is
+requesting its yield, arguably we don't need to give warning. 
+
+Another approach would be to not have an explicit m_yield call.  Instead, we
+can provide a notify_all call, where the notification sent to every vcore is
+to yield.  I imagine we'll have a notify_all (or rather, flags to the notify
+call) anyway, so we can do this for now.
+
+The fastest way will probably be the no niceness way.  One way to make this
+work would be for vcore0 to hold all of the low-level locks (from 4.4.1) and
+manually unlock them when it wakes up.  Yikes!
+
+4.5: Random Other Stuff
+-------------------------------
+Pre-Notification issues: how much time does userspace need to clean up and
+yield?  How quickly does the kernel need the core back (for scheduling
+reasons)?
 
 Part 5: Old Arguments about Processes vs Partitions
 ===============================
@@ -426,7 +595,7 @@ On 2009-09-15 at 22:33 John Kubiatowicz wrote:
 
 I want a process to be aware of it's specific resources, as well as the other
 members of it's partition.  An individual process (which is gang-scheduled in
-multi-core mode) has a specific list of resources.  Its just that the overall
+many-core mode) has a specific list of resources.  Its just that the overall
 'partition of system resources' is separate from the list of specific
 resources of a process, simply because there can be many processes under the
 same partition (collection of resources allocated).
@@ -454,7 +623,7 @@ decides to give it a certain amount of resources, which creates it's partition
 (aka, chunk of resources granted to it's process group, of which it is the
 only member).  The sysadmin can tweak this allocation via procfs.
 
-The process runs on its cores in it's multicore mode.  It is gang scheduled,
+The process runs on its cores in it's many-core mode.  It is gang scheduled,
 and knows how many cores there are.  When the kernel starts the process on
 it's extra cores, it passes control to a known spot in code (the ELF entry
 point), with the virtual core id passed as a parameter.
@@ -509,7 +678,7 @@ on the cores may or may not trust one another.  One solution is to run each
 VM-core in it's own process (like with Linux's KVM, it uses N tasks (part of
 one process) for an N-way SMP VM).  The processes set up the appropriate
 shared memory mapping between themselves early on.  Another approach would be
-to allow a multi-cored process to install specific address spaces on each
+to allow a many-cored process to install specific address spaces on each
 core, and interpose on syscalls, privileged instructions, and page faults.
 This sounds very much like the Cell approach, which may be fine for a VM, but
 not for the general case of a process.
diff --git a/kern/arch/i686/ros/trapframe.h b/kern/arch/i686/ros/trapframe.h
new file mode 100644 (file)
index 0000000..02c5225
--- /dev/null
@@ -0,0 +1,49 @@
+/*  Mostly from JOS.   See COPYRIGHT for copyright information. */
+
+#ifndef ROS_INCLUDE_ARCH_TRAPFRAME_H
+#define ROS_INCLUDE_ARCH_TRAPFRAME_H
+
+typedef struct pushregs {
+       /* registers as pushed by pusha */
+       uint32_t reg_edi;
+       uint32_t reg_esi;
+       uint32_t reg_ebp; uint32_t reg_oesp;            /* Useless */
+       uint32_t reg_ebx;
+       uint32_t reg_edx;
+       uint32_t reg_ecx;
+       uint32_t reg_eax;
+} push_regs_t;
+
+typedef struct trapframe {
+       push_regs_t tf_regs;
+       uint16_t tf_gs;
+       uint16_t tf_padding1;
+       uint16_t tf_fs;
+       uint16_t tf_padding2;
+       uint16_t tf_es;
+       uint16_t tf_padding3;
+       uint16_t tf_ds;
+       uint16_t tf_padding4;
+       uint32_t tf_trapno;
+       /* below here defined by x86 hardware */
+       uint32_t tf_err;
+       uintptr_t tf_eip;
+       uint16_t tf_cs;
+       uint16_t tf_padding5;
+       uint32_t tf_eflags;
+       /* below here only when crossing rings, such as from user to kernel */
+       uintptr_t tf_esp;
+       uint16_t tf_ss;
+       uint16_t tf_padding6;
+} trapframe_t;
+
+/* TODO: consider using a user-space specific trapframe, since they don't need
+ * all of this information.  Will do that eventually, but til then: */
+#define user_trapframe trapframe
+
+/* FP state and whatever else the kernel won't muck with automatically */
+typedef struct ancillary_state {
+       uint32_t silly; // remove this when you actually use this struct
+} ancillary_state_t;
+
+#endif /* !ROS_INCLUDE_ARCH_TRAPFRAME_H */
index 08734ea..4cc609b 100644 (file)
@@ -362,7 +362,7 @@ void sysenter_init(void)
 }
 
 /* This is called from sysenter's asm, with the tf on the kernel stack. */
-void sysenter_callwrapper(struct Trapframe *tf)
+void sysenter_callwrapper(struct trapframe *tf)
 {
        // save a per-core reference to the tf
        set_current_tf(tf);
index 53c0754..41fcad9 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef ROS_INCLUDE_ARCH_TRAP_H
-#define ROS_INCLUDE_ARCH_TRAP_H
+#ifndef ROS_KERN_ARCH_TRAP_H
+#define ROS_KERN_ARCH_TRAP_H
 
 #define MSR_IA32_SYSENTER_CS 0x174
 #define MSR_IA32_SYSENTER_ESP 0x175
 
 #include <ros/common.h>
 #include <arch/mmu.h>
+#include <ros/arch/trapframe.h>
 
 /* The kernel's interrupt descriptor table */
 extern gatedesc_t idt[];
 extern taskstate_t ts;
 
-typedef struct PushRegs {
-       /* registers as pushed by pusha */
-       uint32_t reg_edi;
-       uint32_t reg_esi;
-       uint32_t reg_ebp;
-       uint32_t reg_oesp;              /* Useless */
-       uint32_t reg_ebx;
-       uint32_t reg_edx;
-       uint32_t reg_ecx;
-       uint32_t reg_eax;
-} push_regs_t;
-
-typedef struct Trapframe {
-       push_regs_t tf_regs;
-       uint16_t tf_gs;
-       uint16_t tf_padding1;
-       uint16_t tf_fs;
-       uint16_t tf_padding2;
-       uint16_t tf_es;
-       uint16_t tf_padding3;
-       uint16_t tf_ds;
-       uint16_t tf_padding4;
-       uint32_t tf_trapno;
-       /* below here defined by x86 hardware */
-       uint32_t tf_err;
-       uintptr_t tf_eip;
-       uint16_t tf_cs;
-       uint16_t tf_padding5;
-       uint32_t tf_eflags;
-       /* below here only when crossing rings, such as from user to kernel */
-       uintptr_t tf_esp;
-       uint16_t tf_ss;
-       uint16_t tf_padding6;
-} trapframe_t;
-
-typedef struct AncillaryState {
-       uint32_t silly; // remove this when you actually use this struct
-} ancillary_state_t;
+/* the struct trapframe and friends are in ros/arch/trapframe.h */
 
 static inline void set_errno(trapframe_t* tf, uint32_t errno)
 {
diff --git a/kern/arch/sparc/ros/trapframe.h b/kern/arch/sparc/ros/trapframe.h
new file mode 100644 (file)
index 0000000..64eabfa
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef ROS_INCLUDE_ARCH_TRAPFRAME_H
+#define ROS_INCLUDE_ARCH_TRAPFRAME_H
+
+typedef struct trapframe
+{
+       uint32_t gpr[32] __attribute__((aligned (8)));
+       uint32_t psr;
+       uint32_t pc;
+       uint32_t npc;
+       uint32_t wim;
+       uint32_t tbr;
+       uint32_t y;
+       uint32_t asr13;
+       uint32_t pc_insn;
+       uint32_t fault_status;
+       uint32_t fault_addr;
+       uint64_t timestamp;
+} trapframe_t;
+
+/* TODO: consider using a user-space specific trapframe, since they don't need
+ * all of this information.  Will do that eventually, but til then: */
+#define user_trapframe trapframe
+
+typedef struct ancillary_state
+{
+       uint32_t fpr[32] __attribute__((aligned (8)));
+       uint32_t fsr;
+} ancillary_state_t;
+
+#endif /* !ROS_INCLUDE_ARCH_TRAPFRAME_H */
index 3aeee38..668f388 100644 (file)
@@ -7,28 +7,9 @@
 #ifndef __ASSEMBLER__
 
 #include <ros/common.h>
+#include <ros/arch/trapframe.h>
 
-typedef struct
-{
-       uint32_t gpr[32] __attribute__((aligned (8)));
-       uint32_t psr;
-       uint32_t pc;
-       uint32_t npc;
-       uint32_t wim;
-       uint32_t tbr;
-       uint32_t y;
-       uint32_t asr13;
-       uint32_t pc_insn;
-       uint32_t fault_status;
-       uint32_t fault_addr;
-       uint64_t timestamp;
-} trapframe_t;
-
-typedef struct
-{
-       uint32_t fpr[32] __attribute__((aligned (8)));
-       uint32_t fsr;
-} ancillary_state_t;
+/* the struct trapframe and friends are in ros/arch/trapframe.h */
 
 void data_access_exception(trapframe_t* state);
 void real_fp_exception(trapframe_t* state, ancillary_state_t* astate);
diff --git a/kern/include/ros/notification.h b/kern/include/ros/notification.h
new file mode 100644 (file)
index 0000000..cf000bc
--- /dev/null
@@ -0,0 +1,90 @@
+/* Copyright (c) 2010 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * Kernel interface for notification delivery and preemption. */
+
+#ifndef ROS_INC_NOTIFICATION_H
+#define ROS_INC_NOTIFICATION_H
+
+#include <ros/common.h>
+#include <ros/arch/trapframe.h>
+// TODO: #include some one-way queue macros for the notif_event queue
+// TODO: move me to an atomic header, and give me some support functions.
+typedef uint8_t seq_ctr_t;
+
+/* How/If a process wants to be notified about an event */
+struct notif_method {
+       uint32_t                                vcoreid;        /* which vcore to notify */
+       int                                             flags;
+};
+
+/* 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_VCORE0_IPI               0x008   /* fall back to vcore0 for an IPI */
+#define NOTIF_VCORE0_EVENT             0x010   /* fall back to vcore0 for an event */
+
+/* Notification Event Types */
+#define NE_NONE                                         0
+#define NE_PREMPT_PENDING               1
+#define NE_GANG_PREMPT_PENDING  2
+#define NE_VCORE_REVOKE                         3
+#define NE_GANG_RETURN                  4
+#define NE_USER_IPI                             5
+#define NE_PAGE_FAULT                   6
+#define NE_ALARM                                7
+#define NE_FREE_APPLE_PIE               8
+#define NE_ETC_ETC_ETC                  9
+#define NR_NOTIF_TYPES                 10 /* keep me last */
+
+/* Will probably have dynamic notifications later */
+#define MAX_NR_DYN_NOTIF               25
+#define MAX_NR_NOTIF                   (NR_NOTIF_TYPES + MAX_NR_DYN_NOTIF)
+
+/* Want to keep this small and generic, but add items as you need them.  One
+ * item some will need is an expiration time, which ought to be put in the 64
+ * bit arg.  Will need tweaking / thought as we come up with events.  These are
+ * what get put on the per-core queue in procdata. */
+struct notif_event {
+       uint16_t                                ne_type;
+       uint16_t                                ne_arg1;
+       uint32_t                                ne_arg2;
+       uint64_t                                ne_arg3;
+       uint64_t                                ne_arg4;
+};
+
+#define NR_PERCORE_EVENTS 10 // whatever
+
+/* Per-core data about preemptions and notifications */
+struct preempt_data {
+       struct user_trapframe   preempt_tf;
+       struct ancillary_state  preempt_anc;
+       struct user_trapframe   notif_tf;
+       void                                    *transition_stack;      /* advertised by the user */
+       // TODO: move to procinfo!
+       uint64_t                                preempt_pending;
+       bool                                    notif_enabled;          /* vcore is willing to receive*/
+       bool                                    notif_pending;          /* notif a_msg on the way */
+       seq_ctr_t                               preempt_tf_valid;
+       uint8_t                                 notif_bmask[(NR_PERCORE_EVENTS - 1) / 8 + 1];
+       struct notif_event              notif_events[NR_PERCORE_EVENTS];
+       unsigned int                    prod_idx;
+       unsigned int                    cons_idx;
+       unsigned int                    event_overflows;
+};
+
+/* Structs for different types of events that need parameters. */
+// TODO: think about this a bit.  And don't want to make them til we need them.
+
+/* Example: want the vcoreid of what was lost. */
+struct ne_vcore_revoke {
+       uint16_t                                ne_type;
+       uint16_t                                ne_pad1;
+       uint32_t                                ne_vcoreid;
+       uint64_t                                ne_pad3;
+       uint64_t                                ne_pad4;
+};
+
+#endif /* ROS_INC_NOTIFICATION_H */
index 1509566..bbea850 100644 (file)
@@ -1,25 +1,33 @@
-/* See COPYRIGHT for copyright information. */
+/* Copyright (c) 2009 The Regents of the University of California
+ * See LICENSE for details.  */
 
 #ifndef ROS_PROCDATA_H
 #define ROS_PROCDATA_H
 
 #include <ros/memlayout.h>
 #include <ros/ring_syscall.h>
-#include <arch/mmu.h>
 #include <ros/arch/arch.h>
+#include <ros/common.h>
+#include <ros/procinfo.h>
+#include <ros/notification.h>
+#include <arch/mmu.h>
 
 typedef struct procdata {
-       // The actual ring buffers for communicating with user space
-       syscall_sring_t  syscallring;  // Per-process ring buffer for async syscalls
-       char padding1[SYSCALLRINGSIZE - sizeof(syscall_sring_t)];
-       sysevent_sring_t syseventring; // Per-process ring buffer for async sysevents
-       char padding2[SYSEVENTRINGSIZE - sizeof(sysevent_sring_t)];
+       syscall_sring_t                 syscallring;
+       char                                    pad1[SYSCALLRINGSIZE - sizeof(syscall_sring_t)];
+       sysevent_sring_t                syseventring;
+       char                                    pad2[SYSEVENTRINGSIZE - sizeof(sysevent_sring_t)];
 #ifdef __i386__
-       segdesc_t *ldt;
+       segdesc_t                               *ldt; // TODO: bug with this.  needs to go
 #endif
-
+       // TODO: will replace these in a later commit
        uintptr_t stack_pointers[MAX_NUM_CPUS];
+       struct notif_method             notif_methods[MAX_NR_NOTIF];
+       /* Long range, would like these to be mapped in lazily, as the vcores are
+        * requested.  Sharing MAX_NUM_CPUS is a bit weird too. */
+       struct preempt_data             vcore_preempt_data[MAX_NUM_CPUS];
 } procdata_t;
+
 #define PROCDATA_NUM_PAGES  ((sizeof(procdata_t)-1)/PGSIZE + 1)
 
 // this is how user programs access the procdata page